package main import ( "flag" "fmt" "log" "net/http" "strings" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/version" ) type monitoringService int const ( promService monitoringService = 1 influxService monitoringService = 2 ) const ( // Minimum interval between InfluxDB pushes in seconds. minPushInterval = 10 ) type promHTTPLogger struct{} func (l promHTTPLogger) Println(v ...interface{}) { log.Println(v...) } func parseMetricList(list string) []string { metrics := strings.Split(list, ",") for i, m := range metrics { metrics[i] = strings.TrimSpace(m) } return metrics } // Build version number defined by the compiler: // // -ldflags "-X main.buildstamp=value_to_assign_to_buildstamp" // // Reported to clients in response to {hi} message. // For instance, to define the buildstamp as a timestamp of when the server was built add a // flag to compiler command line: // // -ldflags "-X main.buildstamp=`date -u '+%Y%m%dT%H:%M:%SZ'`" // // or to set it to git tag: // // -ldflags "-X main.buildstamp=`git describe --tags`" var buildstamp = "undef" func main() { log.Printf("Tinode metrics exporter.") var ( serveFor = flag.String("serve_for", "influxdb", "Monitoring service to gather metrics for. Available: influxdb, prometheus.") tinodeAddr = flag.String("tinode_addr", "http://localhost:6060/stats/expvar", "Address of the Tinode instance to scrape.") listenAt = flag.String("listen_at", ":6222", "Host name and port to listen for incoming requests on.") metricList = flag.String("metric_list", "Version,LiveTopics,TotalTopics,LiveSessions,ClusterLeader,TotalClusterNodes,LiveClusterNodes,memstats.Alloc", "Comma-separated list of numeric metrics to scrape and export.") histoMetricList = flag.String("histo_metric_list", "RequestLatency,OutgoingMessageSize", "Comma-separated list of histogram metrics to scrape and export.") instance = flag.String("instance", "exporter", "Exporter instance name.") // Prometheus-specific arguments. promNamespace = flag.String("prom_namespace", "tinode", "Prometheus namespace for metrics '_...'") promMetricsPath = flag.String("prom_metrics_path", "/metrics", "Path under which to expose metrics for Prometheus scrapes.") promTimeout = flag.Int("prom_timeout", 15, "Tinode connection timeout in seconds in response to Prometheus scrapes.") // InfluxDB-specific arguments. influxPushAddr = flag.String("influx_push_addr", "http://localhost:9999/write", "Address of InfluxDB target server where the data gets sent.") influxDBVersion = flag.String("influx_db_version", "1.7", "Version of InfluxDB (only 1.7 and 2.0 are supported).") influxOrganization = flag.String("influx_organization", "test", "InfluxDB organization to push metrics as.") influxBucket = flag.String("influx_bucket", "test", "InfluxDB storage bucket to store data in (used only in InfluxDB 2.0).") influxAuthToken = flag.String("influx_auth_token", "", "InfluxDB authentication token.") influxPushInterval = flag.Int("influx_push_interval", 30, "InfluxDB push interval in seconds.") ) flag.Parse() var service monitoringService if *serveFor == "prometheus" { service = promService } else if *serveFor == "influxdb" { service = influxService } else { log.Fatal("Invalid monitoring service:" + *serveFor + "; must be either \"prometheus\" or \"influxdb\"") } // Validate flags. switch service { case promService: if *promMetricsPath == "/" { log.Fatal("Serving metrics from / is not supported") } case influxService: if *influxOrganization == "" { log.Fatal("Must specify --influx_organization") } if *influxAuthToken == "" { log.Fatal("Must specify --influx_auth_token") } if *influxBucket == "" { log.Fatal("Must specify --influx_bucket") } if *influxDBVersion != "1.7" && *influxDBVersion != "2.0" { log.Fatal("The --influx_db_version must be either 1.7 or 2.0") } if *influxPushInterval > 0 && *influxPushInterval < minPushInterval { *influxPushInterval = minPushInterval log.Println("The --influx_push_interval is too low, reset to", minPushInterval) } } // Index page at web root. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { var servingPath string switch service { case promService: servingPath = "

Prometheus exporter path: Metrics

" case influxService: servingPath = "

InfluxDB push path: Push

" } w.Write([]byte(`Tinode Exporter

Tinode Exporter

Server type` + *serveFor + `

` + servingPath + `

Build

` + version.Info() + ` ` + version.BuildContext() + `
`)) }) metrics := parseMetricList(*metricList) histoMetrics := parseMetricList(*histoMetricList) scraper := Scraper{address: *tinodeAddr, simpleMetrics: metrics, histogramMetrics: histoMetrics} var serverTypeString string // Create exporters. switch service { case promService: serverTypeString = *serveFor promExporter := NewPromExporter(*tinodeAddr, *promNamespace, time.Duration(*promTimeout)*time.Second, &scraper) registry := prometheus.NewRegistry() registry.MustRegister(promExporter) http.Handle(*promMetricsPath, promhttp.InstrumentMetricHandler( registry, promhttp.HandlerFor( registry, promhttp.HandlerOpts{ ErrorLog: &promHTTPLogger{}, Timeout: time.Duration(*promTimeout) * time.Second, }, ), ), ) case influxService: serverTypeString = fmt.Sprintf("%s, version %s", *serveFor, *influxDBVersion) influxDBExporter := NewInfluxDBExporter(*influxDBVersion, *influxPushAddr, *influxOrganization, *influxBucket, *influxAuthToken, *instance, &scraper) if *influxPushInterval > 0 { go func() { interval := time.Duration(*influxPushInterval) * time.Second ch := time.Tick(interval) for { if _, ok := <-ch; ok { if err := influxDBExporter.Push(); err != nil { log.Println("InfluxDB push failed:", err) } } else { return } } }() } else { log.Println("InfluxDB push interval is zero. Will not push data automatically.") } // Forces a data push. http.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) { var msg string if err := influxDBExporter.Push(); err == nil { msg = "HTTP 200 OK" } else { msg = err.Error() } w.Write([]byte(`Tinode Push

Tinode Push

` + msg + `
`)) }) } log.Println("Reading Tinode expvar from", *tinodeAddr) log.Printf("Exporter running at %s. Server type %s", *listenAt, serverTypeString) log.Fatalln(http.ListenAndServe(*listenAt, nil)) }