improvement(go.d/nats): add routez metrics (#19264)
Ilya Mashchenko committed
Dec 21, 2024 at 22:32 UTC
08548765920be03daa23126a492ba66f898dfd62
7 files changed
+199
-4
src/go/plugin/go.d/collector/nats/charts.go
+78
-1
@@ -4,6 +4,7 @@ package nats
4
5
import (
6
"fmt"
7
+ "strconv"
8
"strings"
9
10
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -27,6 +28,10 @@ const (
28
prioAccountSubscriptions
29
prioAccountSlowConsumers
30
prioAccountLeafNodes
31
+
32
+ prioRouteTraffic
33
+ prioRouteMessages
34
+ prioRouteSubscriptions
35
)
36
37
var serverCharts = func() module.Charts {
@@ -290,7 +295,79 @@ func (c *Collector) addAccountCharts(acc string) {
295
}
296
297
func (c *Collector) removeAccountCharts(acc string) {
293
- px := fmt.Sprintf("accstatz_acc_%s_", acc)
298
+ px := fmt.Sprintf("account_%s_", acc)
299
+ c.removeCharts(px)
300
+}
301
+
302
+var routeChartsTmpl = module.Charts{
303
+ routeTrafficTmpl.Copy(),
304
+ routeMessagesTmpl.Copy(),
305
+ routeSubscriptionsTmpl.Copy(),
306
+}
307
+
308
+var (
309
+ routeTrafficTmpl = module.Chart{
310
+ ID: "route_%d_traffic",
311
+ Title: "Route Traffic",
312
+ Units: "bytes/s",
313
+ Fam: "route traffic",
314
+ Ctx: "nats.route_traffic",
315
+ Priority: prioRouteTraffic,
316
+ Type: module.Area,
317
+ Dims: module.Dims{
318
+ {ID: "routez_route_id_%d_in_bytes", Name: "in", Algo: module.Incremental},
319
+ {ID: "routez_route_id_%d_out_bytes", Name: "out", Mul: -1, Algo: module.Incremental},
320
+ },
321
+ }
322
+ routeMessagesTmpl = module.Chart{
323
+ ID: "route_%d_messages",
324
+ Title: "Route Messages",
325
+ Units: "messages/s",
326
+ Fam: "route traffic",
327
+ Ctx: "nats.route_messages",
328
+ Priority: prioRouteMessages,
329
+ Type: module.Line,
330
+ Dims: module.Dims{
331
+ {ID: "routez_route_id_%d_in_msgs", Name: "in", Algo: module.Incremental},
332
+ {ID: "routez_route_id_%d_out_msgs", Name: "out", Mul: -1, Algo: module.Incremental},
333
+ },
334
+ }
335
+ routeSubscriptionsTmpl = module.Chart{
336
+ ID: "route_%d_subscriptions",
337
+ Title: "Route Active Subscriptions",
338
+ Units: "subscriptions",
339
+ Fam: "route subscriptions",
340
+ Ctx: "nats.route_subscriptions",
341
+ Priority: prioRouteSubscriptions,
342
+ Type: module.Line,
343
+ Dims: module.Dims{
344
+ {ID: "routez_route_id_%d_num_subs", Name: "active"},
345
+ },
346
+ }
347
+)
348
+
349
+func (c *Collector) addRouteCharts(rid uint64, remoteId string) {
350
+ charts := routeChartsTmpl.Copy()
351
+
352
+ for _, chart := range *charts {
353
+ chart.ID = fmt.Sprintf(chart.ID, rid)
354
+ chart.Labels = []module.Label{
355
+ {Key: "route_id", Value: strconv.FormatUint(rid, 10)},
356
+ {Key: "remote_id", Value: remoteId},
357
+ }
358
+ for _, dim := range chart.Dims {
359
+ dim.ID = fmt.Sprintf(dim.ID, rid)
360
+ }
361
+ }
362
+
363
+ if err := c.Charts().Add(*charts...); err != nil {
364
+ c.Warningf("failed to add charts for route id %d: %s", rid, err)
365
+ }
366
+
367
+}
368
+
369
+func (c *Collector) removeRouteCharts(rid uint64) {
370
+ px := fmt.Sprintf("route_%d_", rid)
371
c.removeCharts(px)
372
}
373
src/go/plugin/go.d/collector/nats/collect.go
+42
@@ -22,6 +22,9 @@ func (c *Collector) collect() (map[string]int64, error) {
22
if err := c.collectAccstatz(mx); err != nil {
23
return mx, err
24
}
25
+ if err := c.collectRoutez(mx); err != nil {
26
+ return mx, err
27
+ }
28
29
return mx, nil
30
}
@@ -137,3 +140,42 @@ func (c *Collector) collectAccstatz(mx map[string]int64) error {
140
141
return nil
142
}
143
+
144
+func (c *Collector) collectRoutez(mx map[string]int64) error {
145
+ req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathRoutez)
146
+ if err != nil {
147
+ return err
148
+ }
149
+
150
+ var resp routezResponse
151
+ if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
152
+ return err
153
+ }
154
+
155
+ seen := make(map[uint64]bool)
156
+
157
+ for _, route := range resp.Routes {
158
+ seen[route.Rid] = true
159
+ if !c.seenRoutes[route.Rid] {
160
+ c.seenRoutes[route.Rid] = true
161
+ c.addRouteCharts(route.Rid, route.RemoteID)
162
+ }
163
+
164
+ px := fmt.Sprintf("routez_route_id_%d_", route.Rid)
165
+
166
+ mx[px+"in_bytes"] = route.InBytes
167
+ mx[px+"out_bytes"] = route.OutBytes
168
+ mx[px+"in_msgs"] = route.InMsgs
169
+ mx[px+"out_msgs"] = route.OutMsgs
170
+ mx[px+"num_subs"] = int64(route.NumSubs)
171
+ }
172
+
173
+ for rid := range c.seenRoutes {
174
+ if !seen[rid] {
175
+ delete(c.seenRoutes, rid)
176
+ c.removeRouteCharts(rid)
177
+ }
178
+ }
179
+
180
+ return nil
181
+}
src/go/plugin/go.d/collector/nats/collector.go
+2
@@ -41,6 +41,7 @@ func New() *Collector {
41
},
42
charts: serverCharts.Copy(),
43
seenAccounts: make(map[string]bool),
44
+ seenRoutes: make(map[uint64]bool),
45
}
46
}
47
@@ -60,6 +61,7 @@ type Collector struct {
61
httpClient *http.Client
62
63
seenAccounts map[string]bool
64
+ seenRoutes map[uint64]bool
65
}
66
67
func (c *Collector) Configuration() any {
src/go/plugin/go.d/collector/nats/collector_test.go
+13
-2
@@ -23,6 +23,7 @@ var (
23
dataVer210HealthzOk, _ = os.ReadFile("testdata/v2.10.24/healthz-ok.json")
24
dataVer210Varz, _ = os.ReadFile("testdata/v2.10.24/varz.json")
25
dataVer210Accstatz, _ = os.ReadFile("testdata/v2.10.24/accstatz.json")
26
+ dataVer210Routez, _ = os.ReadFile("testdata/v2.10.24/routez.json")
27
)
28
29
func Test_testDataIsValid(t *testing.T) {
@@ -32,6 +33,7 @@ func Test_testDataIsValid(t *testing.T) {
33
"dataVer210HealthzOk": dataVer210HealthzOk,
34
"dataVer210Varz": dataVer210Varz,
35
"dataVer210Accstatz": dataVer210Accstatz,
36
+ "dataVer210Routez": dataVer210Routez,
37
} {
38
require.NotNil(t, data, name)
39
}
@@ -126,8 +128,10 @@ func TestCollector_Collect(t *testing.T) {
128
wantMetrics map[string]int64
129
}{
130
"success on valid response": {
129
- prepare: caseOk,
130
- wantNumOfCharts: len(serverCharts) + len(accountChartsTmpl)*3,
131
+ prepare: caseOk,
132
+ wantNumOfCharts: len(serverCharts) +
133
+ len(accountChartsTmpl)*3 +
134
+ len(routeChartsTmpl)*1,
135
wantMetrics: map[string]int64{
136
"accstatz_acc_$G_conns": 0,
137
"accstatz_acc_$G_leaf_nodes": 0,
@@ -156,6 +160,11 @@ func TestCollector_Collect(t *testing.T) {
160
"accstatz_acc_default_sent_msgs": 2546732,
161
"accstatz_acc_default_slow_consumers": 1,
162
"accstatz_acc_default_total_conns": 44,
163
+ "routez_route_id_1_in_bytes": 4,
164
+ "routez_route_id_1_in_msgs": 1,
165
+ "routez_route_id_1_num_subs": 1,
166
+ "routez_route_id_1_out_bytes": 4,
167
+ "routez_route_id_1_out_msgs": 1,
168
"varz_http_endpoint_/_req": 5710,
169
"varz_http_endpoint_/accountz_req": 2201,
170
"varz_http_endpoint_/accstatz_req": 6,
@@ -240,6 +249,8 @@ func caseOk(t *testing.T) (*Collector, func()) {
249
return
250
}
251
_, _ = w.Write(dataVer210Accstatz)
252
+ case urlPathRoutez:
253
+ _, _ = w.Write(dataVer210Routez)
254
default:
255
w.WriteHeader(http.StatusNotFound)
256
}
src/go/plugin/go.d/collector/nats/metadata.yaml
+29
-1
@@ -239,7 +239,7 @@ modules:
239
dimensions:
240
- name: uptime
241
- name: account
242
- description: These metrics refer to [accounts](https://docs.nats.io/running-a-nats-service/nats_admin/monitoring#account-statistics).
242
+ description: These metrics refer to [Accounts](https://docs.nats.io/running-a-nats-service/nats_admin/monitoring#account-statistics).
243
labels:
244
- name: account
245
description: "Account name."
@@ -288,6 +288,34 @@ modules:
288
chart_type: line
289
dimensions:
290
- name: leafnode
291
+ - name: route
292
+ description: These metrics refer to [Routes](https://docs.nats.io/running-a-nats-service/nats_admin/monitoring#route-information).
293
+ labels:
294
+ - name: route_id
295
+ description: "A unique identifier for a route within the NATS cluster."
296
+ - name: remote_id
297
+ description: "he unique identifier of the remote server connected via the route."
298
+ metrics:
299
+ - name: nats.route_traffic
300
+ description: Route Traffic
301
+ unit: bytes/s
302
+ chart_type: area
303
+ dimensions:
304
+ - name: in
305
+ - name: out
306
+ - name: nats.route_messages
307
+ description: Route Messages
308
+ unit: messages/s
309
+ chart_type: line
310
+ dimensions:
311
+ - name: in
312
+ - name: out
313
+ - name: nats.route_subscriptions
314
+ description: Route Active Subscriptions
315
+ unit: subscriptions
316
+ chart_type: line
317
+ dimensions:
318
+ - name: active
319
- name: http endpoint
320
description: These metrics refer to HTTP endpoints.
321
labels:
src/go/plugin/go.d/collector/nats/restapi.go
+15
@@ -17,6 +17,8 @@ const (
17
urlPathHealthz = "/healthz"
18
// https://docs.nats.io/running-a-nats-service/nats_admin/monitoring#account-statistics
19
urlPathAccstatz = "/accstatz"
20
+ // https://docs.nats.io/running-a-nats-service/nats_admin/monitoring#route-information
21
+ urlPathRoutez = "/routez"
22
)
23
24
var (
@@ -96,3 +98,16 @@ type accstatzResponse struct {
98
SlowConsumers int64 `json:"slow_consumers"`
99
} `json:"account_statz"`
100
}
101
+
102
+// https://github.com/nats-io/nats-server/blob/v2.10.24/server/monitor.go#L752
103
+type routezResponse struct {
104
+ Routes []struct {
105
+ Rid uint64 `json:"rid"`
106
+ RemoteID string `json:"remote_id"`
107
+ InMsgs int64 `json:"in_msgs"`
108
+ OutMsgs int64 `json:"out_msgs"`
109
+ InBytes int64 `json:"in_bytes"`
110
+ OutBytes int64 `json:"out_bytes"`
111
+ NumSubs uint32 `json:"subscriptions"`
112
+ } `json:"routes"`
113
+}
src/go/plugin/go.d/collector/nats/testdata/v2.10.24/routez.json
new
+20
@@ -0,0 +1,20 @@
1
+{
2
+ "server_id": "NACDVKFBUW4C4XA24OOT6L4MDP56MW76J5RJDFXG7HLABSB46DCMWCOW",
3
+ "now": "2019-06-24T14:29:16.046656-07:00",
4
+ "num_routes": 1,
5
+ "routes": [
6
+ {
7
+ "rid": 1,
8
+ "remote_id": "de475c0041418afc799bccf0fdd61b47",
9
+ "did_solicit": true,
10
+ "ip": "127.0.0.1",
11
+ "port": 61791,
12
+ "pending_size": 0,
13
+ "in_msgs": 1,
14
+ "out_msgs": 1,
15
+ "in_bytes": 4,
16
+ "out_bytes": 4,
17
+ "subscriptions": 1
18
+ }
19
+ ]
20
+}