331 lines
10 KiB
Go
331 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
// PromExporter collects metrics in Prometheus format from a Tinode server.
|
|
type PromExporter struct {
|
|
address string
|
|
timeout time.Duration
|
|
namespace string
|
|
|
|
scraper *Scraper
|
|
|
|
up *prometheus.Desc
|
|
version *prometheus.Desc
|
|
topicsLive *prometheus.Desc
|
|
topicsTotal *prometheus.Desc
|
|
sessionsLive *prometheus.Desc
|
|
sessionsTotal *prometheus.Desc
|
|
|
|
numGoroutines *prometheus.Desc
|
|
|
|
incomingMessagesWebsockTotal *prometheus.Desc
|
|
outgoingMessagesWebsockTotal *prometheus.Desc
|
|
|
|
incomingMessagesLongpollTotal *prometheus.Desc
|
|
outgoingMessagesLongpollTotal *prometheus.Desc
|
|
|
|
incomingMessagesGrpcTotal *prometheus.Desc
|
|
outgoingMessagesGrpcTotal *prometheus.Desc
|
|
|
|
fileDownloadsTotal *prometheus.Desc
|
|
fileUploadsTotal *prometheus.Desc
|
|
|
|
ctrlCodesTotal2xx *prometheus.Desc
|
|
ctrlCodesTotal3xx *prometheus.Desc
|
|
ctrlCodesTotal4xx *prometheus.Desc
|
|
ctrlCodesTotal5xx *prometheus.Desc
|
|
|
|
clusterLeader *prometheus.Desc
|
|
clusterSize *prometheus.Desc
|
|
clusterNodesLive *prometheus.Desc
|
|
malloced *prometheus.Desc
|
|
requestLatencyMsCount *prometheus.Desc
|
|
outgoingMessageBytesCount *prometheus.Desc
|
|
}
|
|
|
|
// NewPromExporter returns an initialized Prometheus exporter.
|
|
func NewPromExporter(server, namespace string, timeout time.Duration, scraper *Scraper) *PromExporter {
|
|
return &PromExporter{
|
|
address: server,
|
|
timeout: timeout,
|
|
namespace: namespace,
|
|
scraper: scraper,
|
|
up: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "up"),
|
|
"If tinode instance is reachable.",
|
|
nil,
|
|
nil,
|
|
),
|
|
version: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "version"),
|
|
"The version of this tinode instance.",
|
|
nil,
|
|
nil,
|
|
),
|
|
topicsLive: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "topics_live_count"),
|
|
"Number of currently active topics.",
|
|
nil,
|
|
nil,
|
|
),
|
|
topicsTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "topics_total"),
|
|
"Total number of topics used during instance lifetime.",
|
|
nil,
|
|
nil,
|
|
),
|
|
sessionsLive: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "sessions_live_count"),
|
|
"Number of currently active sessions.",
|
|
nil,
|
|
nil,
|
|
),
|
|
sessionsTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "sessions_total"),
|
|
"Total number of sessions since instance start.",
|
|
nil,
|
|
nil,
|
|
),
|
|
numGoroutines: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "num_goroutines"),
|
|
"Number of currently spawned goroutines.",
|
|
nil,
|
|
nil,
|
|
),
|
|
incomingMessagesWebsockTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "incoming_messages_websock_total"),
|
|
"Total number of incoming messages via websocket.",
|
|
nil,
|
|
nil,
|
|
),
|
|
outgoingMessagesWebsockTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "outgoing_messages_websock_total"),
|
|
"Total number of outgoiing messages via websocket.",
|
|
nil,
|
|
nil,
|
|
),
|
|
incomingMessagesLongpollTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "incoming_messages_longpoll_total"),
|
|
"Total number of incoming messages via longpoll.",
|
|
nil,
|
|
nil,
|
|
),
|
|
outgoingMessagesLongpollTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "outgoing_messages_longpoll_total"),
|
|
"Total number of outgoiing messages via longpoll.",
|
|
nil,
|
|
nil,
|
|
),
|
|
incomingMessagesGrpcTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "incoming_messages_grpc_total"),
|
|
"Total number of incoming messages via grpc.",
|
|
nil,
|
|
nil,
|
|
),
|
|
outgoingMessagesGrpcTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "outgoing_messages_grpc_total"),
|
|
"Total number of outgoiing messages via grpc.",
|
|
nil,
|
|
nil,
|
|
),
|
|
fileDownloadsTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "file_downloads_total"),
|
|
"Total number of large file downloads.",
|
|
nil,
|
|
nil,
|
|
),
|
|
fileUploadsTotal: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "file_uploads_total"),
|
|
"Total number of large file uploads.",
|
|
nil,
|
|
nil,
|
|
),
|
|
ctrlCodesTotal2xx: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "ctrl_codes_total_2xx"),
|
|
"Total number of 2xx ctrl response codes.",
|
|
nil,
|
|
nil,
|
|
),
|
|
ctrlCodesTotal3xx: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "ctrl_codes_total_3xx"),
|
|
"Total number of 3xx ctrl response codes.",
|
|
nil,
|
|
nil,
|
|
),
|
|
ctrlCodesTotal4xx: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "ctrl_codes_total_4xx"),
|
|
"Total number of 4xx ctrl response codes.",
|
|
nil,
|
|
nil,
|
|
),
|
|
ctrlCodesTotal5xx: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "ctrl_codes_total_5xx"),
|
|
"Total number of 5xx ctrl response codes.",
|
|
nil,
|
|
nil,
|
|
),
|
|
clusterLeader: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "cluster_leader"),
|
|
"If this cluster node is the cluster leader.",
|
|
nil,
|
|
nil,
|
|
),
|
|
clusterSize: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "cluster_size"),
|
|
"Configured number of cluster nodes.",
|
|
nil,
|
|
nil,
|
|
),
|
|
clusterNodesLive: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "cluster_nodes_live"),
|
|
"Number of cluster nodes believed to be live by the current node.",
|
|
nil,
|
|
nil,
|
|
),
|
|
malloced: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "malloced_bytes"),
|
|
"Number of bytes of memory allocated and in use.",
|
|
nil,
|
|
nil,
|
|
),
|
|
requestLatencyMsCount: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "request_latency_ms_count"),
|
|
"Request latency histogram (in ms).",
|
|
nil,
|
|
nil,
|
|
),
|
|
outgoingMessageBytesCount: prometheus.NewDesc(
|
|
prometheus.BuildFQName(namespace, "", "outgoing_message_bytes"),
|
|
"Response size histogram (in bytes).",
|
|
nil,
|
|
nil,
|
|
),
|
|
}
|
|
}
|
|
|
|
// Describe describes all the metrics exported by the memcached exporter. It
|
|
// implements prometheus.Collector.
|
|
func (e *PromExporter) Describe(ch chan<- *prometheus.Desc) {
|
|
ch <- e.up
|
|
ch <- e.version
|
|
ch <- e.topicsLive
|
|
ch <- e.topicsTotal
|
|
ch <- e.sessionsLive
|
|
ch <- e.sessionsTotal
|
|
ch <- e.numGoroutines
|
|
|
|
ch <- e.incomingMessagesWebsockTotal
|
|
ch <- e.outgoingMessagesWebsockTotal
|
|
|
|
ch <- e.incomingMessagesLongpollTotal
|
|
ch <- e.outgoingMessagesLongpollTotal
|
|
|
|
ch <- e.incomingMessagesGrpcTotal
|
|
ch <- e.outgoingMessagesGrpcTotal
|
|
|
|
ch <- e.fileDownloadsTotal
|
|
ch <- e.fileUploadsTotal
|
|
|
|
ch <- e.ctrlCodesTotal2xx
|
|
ch <- e.ctrlCodesTotal3xx
|
|
ch <- e.ctrlCodesTotal4xx
|
|
ch <- e.ctrlCodesTotal5xx
|
|
|
|
ch <- e.clusterLeader
|
|
ch <- e.clusterSize
|
|
ch <- e.clusterNodesLive
|
|
ch <- e.malloced
|
|
|
|
ch <- e.requestLatencyMsCount
|
|
ch <- e.outgoingMessageBytesCount
|
|
}
|
|
|
|
// Collect fetches statistics from the configured Tinode instance, and
|
|
// delivers them as Prometheus metrics. It implements prometheus.Collector.
|
|
func (e *PromExporter) Collect(ch chan<- prometheus.Metric) {
|
|
up := float64(1)
|
|
if stats, err := e.scraper.Scrape(); err != nil {
|
|
log.Println("Failed to fetch or parse response", err)
|
|
up = 0
|
|
} else {
|
|
if err := e.parseStats(ch, stats); err != nil {
|
|
up = 0
|
|
}
|
|
}
|
|
|
|
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, up)
|
|
}
|
|
|
|
func (e *PromExporter) parseStats(ch chan<- prometheus.Metric, stats map[string]interface{}) error {
|
|
err := firstError(
|
|
e.parseAndUpdate(ch, e.version, prometheus.GaugeValue, stats, "Version"),
|
|
e.parseAndUpdate(ch, e.topicsLive, prometheus.GaugeValue, stats, "LiveTopics"),
|
|
e.parseAndUpdate(ch, e.topicsTotal, prometheus.CounterValue, stats, "TotalTopics"),
|
|
e.parseAndUpdate(ch, e.sessionsLive, prometheus.GaugeValue, stats, "LiveSessions"),
|
|
e.parseAndUpdate(ch, e.sessionsTotal, prometheus.CounterValue, stats, "TotalSessions"),
|
|
e.parseAndUpdate(ch, e.numGoroutines, prometheus.GaugeValue, stats, "NumGoroutines"),
|
|
|
|
e.parseAndUpdate(ch, e.incomingMessagesWebsockTotal, prometheus.CounterValue, stats, "IncomingMessagesWebsockTotal"),
|
|
e.parseAndUpdate(ch, e.outgoingMessagesWebsockTotal, prometheus.CounterValue, stats, "OutgoingMessagesWebsockTotal"),
|
|
|
|
e.parseAndUpdate(ch, e.incomingMessagesLongpollTotal, prometheus.CounterValue, stats, "IncomingMessagesLongpollTotal"),
|
|
e.parseAndUpdate(ch, e.outgoingMessagesLongpollTotal, prometheus.CounterValue, stats, "OutgoingMessagesLongpollTotal"),
|
|
|
|
e.parseAndUpdate(ch, e.incomingMessagesGrpcTotal, prometheus.CounterValue, stats, "IncomingMessagesGrpcTotal"),
|
|
e.parseAndUpdate(ch, e.outgoingMessagesGrpcTotal, prometheus.CounterValue, stats, "OutgoingMessagesGrpcTotal"),
|
|
|
|
e.parseAndUpdate(ch, e.fileDownloadsTotal, prometheus.CounterValue, stats, "FileDownloadsTotal"),
|
|
e.parseAndUpdate(ch, e.fileUploadsTotal, prometheus.CounterValue, stats, "FileUploadsTotal"),
|
|
|
|
e.parseAndUpdate(ch, e.ctrlCodesTotal2xx, prometheus.CounterValue, stats, "CtrlCodesTotal2xx"),
|
|
e.parseAndUpdate(ch, e.ctrlCodesTotal3xx, prometheus.CounterValue, stats, "CtrlCodesTotal3xx"),
|
|
e.parseAndUpdate(ch, e.ctrlCodesTotal4xx, prometheus.CounterValue, stats, "CtrlCodesTotal4xx"),
|
|
e.parseAndUpdate(ch, e.ctrlCodesTotal5xx, prometheus.CounterValue, stats, "CtrlCodesTotal5xx"),
|
|
|
|
e.parseAndUpdate(ch, e.clusterLeader, prometheus.GaugeValue, stats, "ClusterLeader"),
|
|
e.parseAndUpdate(ch, e.clusterSize, prometheus.GaugeValue, stats, "TotalClusterNodes"),
|
|
e.parseAndUpdate(ch, e.clusterNodesLive, prometheus.GaugeValue, stats, "LiveClusterNodes"),
|
|
e.parseAndUpdate(ch, e.malloced, prometheus.GaugeValue, stats, "memstats.Alloc"),
|
|
|
|
e.parseAndUpdateHisto(ch, e.requestLatencyMsCount, stats, "RequestLatency"),
|
|
e.parseAndUpdateHisto(ch, e.outgoingMessageBytesCount, stats, "OutgoingMessageSize"),
|
|
)
|
|
|
|
return err
|
|
}
|
|
|
|
func (e *PromExporter) parseAndUpdate(ch chan<- prometheus.Metric, desc *prometheus.Desc, valueType prometheus.ValueType,
|
|
stats map[string]interface{}, key string) error {
|
|
v, err := parseNumeric(stats, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ch <- prometheus.MustNewConstMetric(desc, valueType, v)
|
|
return nil
|
|
}
|
|
|
|
func (e *PromExporter) parseAndUpdateHisto(ch chan<- prometheus.Metric, desc *prometheus.Desc,
|
|
stats map[string]interface{}, key string) error {
|
|
h, err := parseHisto(stats, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ch <- prometheus.MustNewConstHistogram(desc, h.count, h.sum, h.buckets)
|
|
return nil
|
|
}
|
|
|
|
func firstError(errs ...error) error {
|
|
for _, v := range errs {
|
|
if v != nil {
|
|
return v
|
|
}
|
|
}
|
|
return nil
|
|
}
|