wordpress_exporter.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. package main
  2. import (
  3. "net/http"
  4. log "github.com/Sirupsen/logrus"
  5. "github.com/prometheus/client_golang/prometheus/promhttp"
  6. "github.com/prometheus/client_golang/prometheus"
  7. "flag"
  8. "fmt"
  9. "os"
  10. "io/ioutil"
  11. "strings"
  12. "regexp"
  13. "database/sql"
  14. _ "github.com/go-sql-driver/mysql"
  15. )
  16. //This is my collector metrics
  17. type wpCollector struct {
  18. numPostsMetric *prometheus.Desc
  19. numCommentsMetric *prometheus.Desc
  20. numUsersMetric *prometheus.Desc
  21. db_host string
  22. db_name string
  23. db_user string
  24. db_pass string
  25. db_table_prefix string
  26. }
  27. //This is a constructor for my wpCollector struct
  28. func newWordPressCollector(host string, dbname string, username string, pass string, table_prefix string) *wpCollector {
  29. return &wpCollector{
  30. numPostsMetric: prometheus.NewDesc("wp_num_posts_metric",
  31. "Shows the number of total posts in the WordPress site",
  32. nil, nil,
  33. ),
  34. numCommentsMetric: prometheus.NewDesc("wp_num_comments_metric",
  35. "Shows the number of total comments in the WordPress site",
  36. nil, nil,
  37. ),
  38. numUsersMetric: prometheus.NewDesc("wp_num_users_metric",
  39. "Shows the number of registered users in the WordPress site",
  40. nil, nil,
  41. ),
  42. db_host: host,
  43. db_name: dbname,
  44. db_user: username,
  45. db_pass: pass,
  46. db_table_prefix: table_prefix,
  47. }
  48. }
  49. //Describe method is required for a prometheus.Collector type
  50. func (collector *wpCollector) Describe(ch chan<- *prometheus.Desc) {
  51. //We set the metrics
  52. ch <- collector.numPostsMetric
  53. ch <- collector.numCommentsMetric
  54. ch <- collector.numUsersMetric
  55. }
  56. //Collect method is required for a prometheus.Collector type
  57. func (collector *wpCollector) Collect(ch chan<- prometheus.Metric) {
  58. //We run DB queries here to retrieve the metrics we care about
  59. dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", collector.db_user, collector.db_pass, collector.db_host, collector.db_name)
  60. db, err := sql.Open("mysql", dsn)
  61. if(err != nil){
  62. fmt.Fprintf(os.Stderr, "Error connecting to database: %s ...\n", err)
  63. os.Exit(1)
  64. }
  65. defer db.Close()
  66. //select count(*) as num_users from wp_users;
  67. var num_users float64
  68. q1 := fmt.Sprintf("select count(*) as num_users from %susers;", collector.db_table_prefix)
  69. err = db.QueryRow(q1).Scan(&num_users)
  70. if err != nil {
  71. log.Fatal(err)
  72. }
  73. //select count(*) as num_comments from wp_comments;
  74. var num_comments float64
  75. q2 := fmt.Sprintf("select count(*) as num_comments from %scomments;", collector.db_table_prefix)
  76. err = db.QueryRow(q2).Scan(&num_comments)
  77. if err != nil {
  78. log.Fatal(err)
  79. }
  80. //select count(*) as num_posts from wp_posts WHERE post_type='post' AND post_status!='auto-draft';
  81. var num_posts float64
  82. q3 := fmt.Sprintf("select count(*) as num_posts from %sposts WHERE post_type='post' AND post_status!='auto-draft';", collector.db_table_prefix)
  83. err = db.QueryRow(q3).Scan(&num_posts)
  84. if err != nil {
  85. log.Fatal(err)
  86. }
  87. //Write latest value for each metric in the prometheus metric channel.
  88. //Note that you can pass CounterValue, GaugeValue, or UntypedValue types here.
  89. ch <- prometheus.MustNewConstMetric(collector.numPostsMetric, prometheus.CounterValue, num_posts)
  90. ch <- prometheus.MustNewConstMetric(collector.numCommentsMetric, prometheus.CounterValue, num_comments)
  91. ch <- prometheus.MustNewConstMetric(collector.numUsersMetric, prometheus.CounterValue, num_users)
  92. }
  93. func main() {
  94. wpConfPtr := flag.String("wpconfig", "", "Path for wp-config.php file of the WordPress site you wish to monitor")
  95. wpHostPtr := flag.String("host", "127.0.0.1", "Hostname or Address for DB server")
  96. wpPortPtr := flag.String("port", "3306", "DB server port")
  97. wpNamePtr := flag.String("db", "", "DB name")
  98. wpUserPtr := flag.String("user", "", "DB user for connection")
  99. wpPassPtr := flag.String("pass", "", "DB password for connection")
  100. wpTablePrefixPtr := flag.String("tableprefix", "wp_", "Table prefix for WordPress tables")
  101. flag.Parse()
  102. if *wpConfPtr == "" {
  103. db_host := fmt.Sprintf("%s:%s", *wpHostPtr, *wpPortPtr)
  104. db_name := *wpNamePtr
  105. db_user := *wpUserPtr
  106. db_password := *wpPassPtr
  107. table_prefix := *wpTablePrefixPtr
  108. if db_name == "" {
  109. fmt.Fprintf(os.Stderr, "flag -db=dbname required!\n")
  110. os.Exit(1)
  111. }
  112. if db_user == "" {
  113. fmt.Fprintf(os.Stderr, "flag -user=username required!\n")
  114. os.Exit(1)
  115. }
  116. //We create the collector
  117. collector := newWordPressCollector(db_host, db_name, db_user, db_password, table_prefix)
  118. prometheus.MustRegister(collector)
  119. //no path supplied error
  120. //fmt.Fprintf(os.Stderr, "flag -wpconfig=/path/to/wp-config/ required!\n")
  121. //os.Exit(1)
  122. } else{
  123. var wpconfig_file strings.Builder
  124. wpconfig_file.WriteString(*wpConfPtr)
  125. if strings.HasSuffix(*wpConfPtr, "/") {
  126. wpconfig_file.WriteString("wp-config.php")
  127. }else{
  128. wpconfig_file.WriteString("/wp-config.php")
  129. }
  130. //try to read wp-config.php file from path
  131. dat, err := ioutil.ReadFile(wpconfig_file.String())
  132. if(err != nil){
  133. panic(err)
  134. }
  135. fmt.Printf("Read :%v bytes\n", len(dat))
  136. //We must locate with regular expressions the MySQL connection credentials and the table prefix
  137. //define('DB_HOST', 'xxxxxxx');
  138. r, _ := regexp.Compile(`define\(['"]DB_HOST['"].*?,.*?['"](.*?)['"].*?\);`)
  139. res := r.FindStringSubmatch(string(dat[:len(dat)]))
  140. if(res == nil){
  141. fmt.Fprintf(os.Stderr, "Error could not find DB_HOST in wp-config.php ...\n")
  142. os.Exit(1)
  143. }
  144. db_host := res[1]
  145. //define('DB_NAME', 'xxxxxxx');
  146. r, _ = regexp.Compile(`define\(['"]DB_NAME['"].*?,.*?['"](.*?)['"].*?\);`)
  147. res = r.FindStringSubmatch(string(dat[:len(dat)]))
  148. if(res == nil){
  149. fmt.Fprintf(os.Stderr, "Error could not find DB_NAME in wp-config.php ...\n")
  150. os.Exit(1)
  151. }
  152. db_name := res[1]
  153. //define('DB_USER', 'xxxxxxx');
  154. r, _ = regexp.Compile(`define\(['"]DB_USER['"].*?,.*?['"](.*?)['"].*?\);`)
  155. res = r.FindStringSubmatch(string(dat[:len(dat)]))
  156. if(res == nil){
  157. fmt.Fprintf(os.Stderr, "Error could not find DB_USER in wp-config.php ...\n")
  158. os.Exit(1)
  159. }
  160. db_user := res[1]
  161. //define('DB_PASSWORD', 'xxxxxxx');
  162. r, _ = regexp.Compile(`define\(['"]DB_PASSWORD['"].*?,.*?['"](.*?)['"].*?\);`)
  163. res = r.FindStringSubmatch(string(dat[:len(dat)]))
  164. if(res == nil){
  165. fmt.Fprintf(os.Stderr, "Error could not find DB_PASSWORD in wp-config.php ...\n")
  166. os.Exit(1)
  167. }
  168. db_password := res[1]
  169. //$table_prefix = 'wp_';
  170. r, _ = regexp.Compile(`\$table_prefix.*?=.*?['"](.*?)['"];`)
  171. res = r.FindStringSubmatch(string(dat[:len(dat)]))
  172. if(res == nil){
  173. fmt.Fprintf(os.Stderr, "Error could not find $table_prefix in wp-config.php ...\n")
  174. os.Exit(1)
  175. }
  176. table_prefix := res[1]
  177. //We create the collector
  178. collector := newWordPressCollector(db_host, db_name, db_user, db_password, table_prefix)
  179. prometheus.MustRegister(collector)
  180. }
  181. //This section will start the HTTP server and expose
  182. //any metrics on the /metrics endpoint.
  183. http.Handle("/metrics", promhttp.Handler())
  184. log.Info("Beginning to serve on port :8888")
  185. log.Fatal(http.ListenAndServe(":8888", nil))
  186. }