master
go 161 lines 3.64 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package vernemq
4
5 import (
6 "errors"
7 "strings"
8
9 "github.com/prometheus/common/model"
10 "github.com/prometheus/prometheus/model/labels"
11
12 "github.com/netdata/netdata/go/plugins/pkg/prometheus"
13 )
14
15 func (c *Collector) collect() (map[string]int64, error) {
16 mfs, err := c.prom.Scrape()
17 if err != nil {
18 return nil, err
19 }
20
21 if !c.namespace.found {
22 name, err := c.getMetricNamespace(mfs)
23 if err != nil {
24 return nil, err
25 }
26 c.namespace.found = true
27 c.namespace.name = name
28 }
29
30 mx := make(map[string]int64)
31
32 c.collectMetrics(mx, mfs)
33
34 return mx, nil
35 }
36
37 func (c *Collector) collectMetrics(mx map[string]int64, mfs prometheus.MetricFamilies) {
38 nodes := c.getNodesStats(mfs)
39
40 for node, st := range nodes {
41 if !c.seenNodes[node] {
42 c.seenNodes[node] = true
43 c.addNodeCharts(node, st)
44 }
45
46 st.stats["open_sockets"] = st.stats[metricSocketOpen] - st.stats[metricSocketClose]
47 st.stats["netsplit_unresolved"] = st.stats[metricNetSplitDetected] - st.stats[metricNetSplitResolved]
48 // https://github.com/vernemq/vernemq/blob/a55ada8dfb6051362fcc468d888194bdcd6eb346/apps/vmq_server/priv/static/js/status.js#L167
49 queued := st.stats[metricQueueMessageIn] - (st.stats[metricQueueMessageOut] + st.stats[metricQueueMessageDrop] + st.stats[metricQueueMessageUnhandled])
50 st.stats["queued_messages"] = max(0, queued)
51
52 px := join("node", node)
53
54 for k, val := range st.stats {
55 mx[join(px, k)] = val
56 }
57 for k, val := range st.mqtt4 {
58 mx[join(px, "mqtt4", k)] = val
59 }
60 for k, val := range st.mqtt5 {
61 mx[join(px, "mqtt5", k)] = val
62 }
63 }
64
65 for node := range c.seenNodes {
66 if _, ok := nodes[node]; !ok {
67 delete(c.seenNodes, node)
68 c.removeNodeCharts(node)
69 }
70 }
71
72 }
73
74 func (c *Collector) getNodesStats(mfs prometheus.MetricFamilies) map[string]*nodeStats {
75 nodes := make(map[string]*nodeStats)
76
77 for _, mf := range mfs {
78 name, _ := strings.CutPrefix(mf.Name(), c.namespace.name+"_")
79 if isSchedulerUtilizationMetric(name) {
80 continue
81 }
82
83 for _, m := range mf.Metrics() {
84 var value float64
85
86 switch mf.Type() {
87 case model.MetricTypeGauge:
88 value = m.Gauge().Value()
89 case model.MetricTypeCounter:
90 value = m.Counter().Value()
91 default:
92 continue
93 }
94
95 node := m.Labels().Get("node")
96 if node == "" {
97 continue
98 }
99
100 if _, ok := nodes[node]; !ok {
101 nodes[node] = newNodeStats()
102 }
103
104 nst := nodes[node]
105
106 if len(m.Labels()) == 1 {
107 nst.stats[name] += int64(value)
108 continue
109 }
110
111 if !strings.HasPrefix(name, "mqtt_") && name != metricClientKeepaliveExpired {
112 continue
113 }
114
115 switch m.Labels().Get("mqtt_version") {
116 case "4":
117 nst.mqtt4[name] += int64(value)
118 m.Labels().Range(func(l labels.Label) {
119 if l.Name == "return_code" {
120 nst.mqtt4[join(name, l.Name, l.Value)] += int64(value)
121 }
122 })
123 case "5":
124 nst.mqtt5[name] += int64(value)
125 m.Labels().Range(func(l labels.Label) {
126 if l.Name == "reason_code" {
127 nst.mqtt5[join(name, l.Name, l.Value)] += int64(value)
128 }
129 })
130 }
131 }
132 }
133
134 return nodes
135 }
136
137 func (c *Collector) getMetricNamespace(mfs prometheus.MetricFamilies) (string, error) {
138 want := metricPUBLISHError
139 for _, mf := range mfs {
140 if before, ok := strings.CutSuffix(mf.Name(), want); ok {
141 s := before
142 s = strings.TrimSuffix(s, "_")
143 return s, nil
144 }
145 }
146
147 return "", errors.New("unexpected response: not VerneMQ metrics")
148 }
149
150 func isSchedulerUtilizationMetric(name string) bool {
151 return strings.HasPrefix(name, "system_utilization_scheduler_")
152 }
153
154 func join(a, b string, rest ...string) string {
155 var s strings.Builder
156 s.WriteString(a + "_" + b)
157 for _, v := range rest {
158 s.WriteString("_" + v)
159 }
160 return s.String()
161 }