Files
im_server/monitoring/exporter/prom_exporter.go
T
2026-08-11 15:59:57 +08:00

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
}