212 lines
6.7 KiB
Go
212 lines
6.7 KiB
Go
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 '<namespace>_...'")
|
|
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 = "<p>Prometheus exporter path: <a href='" + *promMetricsPath + "'>Metrics</a></p>"
|
|
case influxService:
|
|
servingPath = "<p>InfluxDB push path: <a href='/push'>Push</a></p>"
|
|
}
|
|
|
|
w.Write([]byte(`<html><head><title>Tinode Exporter</title></head><body>
|
|
<h1>Tinode Exporter</h1>
|
|
<p>Server type` + *serveFor + `</p>` + servingPath +
|
|
`<h2>Build</h2>
|
|
<pre>` + version.Info() + ` ` + version.BuildContext() + `</pre>
|
|
</body></html>`))
|
|
})
|
|
|
|
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(`<html><head><title>Tinode Push</title></head><body>
|
|
<h1>Tinode Push</h1>
|
|
<pre>` + msg + `</pre>
|
|
</body></html>`))
|
|
})
|
|
}
|
|
|
|
log.Println("Reading Tinode expvar from", *tinodeAddr)
|
|
log.Printf("Exporter running at %s. Server type %s", *listenAt, serverTypeString)
|
|
log.Fatalln(http.ListenAndServe(*listenAt, nil))
|
|
}
|