master
go 340 lines 8.53 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nats
4
5 import (
6 "fmt"
7 "net/http"
8 "strconv"
9 "strings"
10 "time"
11
12 "github.com/netdata/netdata/go/plugins/pkg/web"
13 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/oldmetrix"
14 )
15
16 func (c *Collector) collect() (map[string]int64, error) {
17 if c.srvMeta.id == "" {
18 if err := c.getServerMeta(); err != nil {
19 return nil, err
20 }
21 }
22
23 mx := make(map[string]int64)
24
25 c.cache.resetUpdated()
26
27 if err := c.collectHealthz(mx); err != nil {
28 return nil, err
29 }
30 if err := c.collectVarz(mx); err != nil {
31 return mx, err
32 }
33 if err := c.collectAccstatz(mx); err != nil {
34 return mx, err
35 }
36 if err := c.collectRoutez(mx); err != nil {
37 return mx, err
38 }
39 if err := c.collectGatewayz(mx); err != nil {
40 return mx, err
41 }
42 if err := c.collectLeafz(mx); err != nil {
43 return mx, err
44 }
45 if err := c.collectJsz(mx); err != nil {
46 return mx, err
47 }
48
49 c.updateCharts()
50
51 return mx, nil
52 }
53
54 func (c *Collector) getServerMeta() error {
55 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathVarz)
56 if err != nil {
57 return err
58 }
59
60 var resp struct {
61 ID string `json:"server_id"`
62 Name string `json:"server_name"`
63 Cluster struct {
64 Name string `json:"name"`
65 } `json:"cluster"`
66 }
67
68 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
69 return err
70 }
71
72 c.srvMeta.id = resp.ID
73 c.srvMeta.name = resp.Name
74 c.srvMeta.clusterName = resp.Cluster.Name
75
76 return nil
77 }
78
79 func (c *Collector) collectHealthz(mx map[string]int64) error {
80 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathHealthz)
81 if err != nil {
82 return err
83 }
84
85 switch c.HealthzCheck {
86 case "js-enabled-only":
87 req.URL.RawQuery = urlQueryHealthzJsEnabledOnly
88 case "js-server-only":
89 req.URL.RawQuery = urlQueryHealthzJsServerOnly
90 }
91
92 var resp healthzResponse
93 client := web.DoHTTP(c.httpClient).OnNokCode(func(resp *http.Response) (bool, error) { return true, nil })
94 if err := client.RequestJSON(req, &resp); err != nil {
95 return err
96 }
97 if resp.Status == nil {
98 return fmt.Errorf("healthz response missing status")
99 }
100
101 mx["varz_srv_healthz_status_ok"] = oldmetrix.Bool(*resp.Status == "ok")
102 mx["varz_srv_healthz_status_error"] = oldmetrix.Bool(*resp.Status != "ok")
103
104 return nil
105 }
106
107 func (c *Collector) collectVarz(mx map[string]int64) error {
108 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathVarz)
109 if err != nil {
110 return err
111 }
112
113 var resp varzResponse
114 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
115 return err
116 }
117
118 uptime, _ := parseUptime(resp.Uptime)
119 mx["varz_srv_uptime"] = int64(uptime.Seconds())
120 mx["varz_srv_in_msgs"] = resp.InMsgs
121 mx["varz_srv_out_msgs"] = resp.OutMsgs
122 mx["varz_srv_in_bytes"] = resp.InBytes
123 mx["varz_srv_out_bytes"] = resp.OutBytes
124 mx["varz_srv_slow_consumers"] = resp.SlowConsumers
125 mx["varz_srv_subscriptions"] = int64(resp.Subscriptions)
126 mx["varz_srv_connections"] = int64(resp.Connections)
127 mx["varz_srv_total_connections"] = int64(resp.TotalConnections)
128 mx["varz_srv_routes"] = int64(resp.Routes)
129 mx["varz_srv_remotes"] = int64(resp.Remotes)
130 mx["varz_srv_cpu"] = int64(resp.CPU)
131 mx["varz_srv_mem"] = resp.Mem
132
133 for _, path := range httpEndpoints {
134 v := resp.HTTPReqStats[path]
135 mx[fmt.Sprintf("varz_http_endpoint_%s_req", path)] = int64(v)
136 }
137
138 return nil
139 }
140
141 func (c *Collector) collectAccstatz(mx map[string]int64) error {
142 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathAccstatz)
143 if err != nil {
144 return err
145 }
146
147 req.URL.RawQuery = urlQueryAccstatz
148
149 var resp accstatzResponse
150 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
151 return err
152 }
153
154 for _, acc := range resp.AccStats {
155 c.cache.accounts.put(acc)
156
157 px := fmt.Sprintf("accstatz_acc_%s_", acc.Account)
158
159 mx[px+"conns"] = int64(acc.Conns)
160 mx[px+"total_conns"] = int64(acc.TotalConns)
161 mx[px+"num_subs"] = int64(acc.NumSubs)
162 mx[px+"leaf_nodes"] = int64(acc.LeafNodes)
163 mx[px+"slow_consumers"] = acc.SlowConsumers
164 mx[px+"received_bytes"] = acc.Received.Bytes
165 mx[px+"received_msgs"] = acc.Received.Msgs
166 mx[px+"sent_bytes"] = acc.Sent.Bytes
167 mx[px+"sent_msgs"] = acc.Sent.Msgs
168 }
169
170 return nil
171 }
172
173 func (c *Collector) collectRoutez(mx map[string]int64) error {
174 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathRoutez)
175 if err != nil {
176 return err
177 }
178
179 var resp routezResponse
180 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
181 return err
182 }
183
184 for _, route := range resp.Routes {
185 c.cache.routes.put(route)
186
187 px := fmt.Sprintf("routez_route_id_%d_", route.Rid)
188
189 mx[px+"in_bytes"] = route.InBytes
190 mx[px+"out_bytes"] = route.OutBytes
191 mx[px+"in_msgs"] = route.InMsgs
192 mx[px+"out_msgs"] = route.OutMsgs
193 mx[px+"num_subs"] = int64(route.NumSubs)
194 }
195
196 return nil
197 }
198
199 func (c *Collector) collectGatewayz(mx map[string]int64) error {
200 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathGatewayz)
201 if err != nil {
202 return err
203 }
204
205 var resp gatewayzResponse
206 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
207 return err
208 }
209
210 for name, ogw := range resp.OutboundGateways {
211 c.cache.outGateways.put(resp.Name, name, ogw)
212
213 px := fmt.Sprintf("gatewayz_outbound_gw_%s_cid_%d_", name, ogw.Connection.Cid)
214
215 mx[px+"in_bytes"] = ogw.Connection.InBytes
216 mx[px+"out_bytes"] = ogw.Connection.OutBytes
217 mx[px+"in_msgs"] = ogw.Connection.InMsgs
218 mx[px+"out_msgs"] = ogw.Connection.OutMsgs
219 mx[px+"num_subs"] = int64(ogw.Connection.NumSubs)
220 uptime, _ := parseUptime(ogw.Connection.Uptime)
221 mx[px+"uptime"] = int64(uptime.Seconds())
222 }
223
224 for name, igws := range resp.InboundGateways {
225 for _, igw := range igws {
226 c.cache.inGateways.put(resp.Name, name, igw)
227
228 px := fmt.Sprintf("gatewayz_inbound_gw_%s_cid_%d_", name, igw.Connection.Cid)
229
230 mx[px+"in_bytes"] = igw.Connection.InBytes
231 mx[px+"out_bytes"] = igw.Connection.OutBytes
232 mx[px+"in_msgs"] = igw.Connection.InMsgs
233 mx[px+"out_msgs"] = igw.Connection.OutMsgs
234 mx[px+"num_subs"] = int64(igw.Connection.NumSubs)
235 uptime, _ := parseUptime(igw.Connection.Uptime)
236 mx[px+"uptime"] = int64(uptime.Seconds())
237 }
238 }
239
240 return nil
241 }
242
243 func (c *Collector) collectLeafz(mx map[string]int64) error {
244 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathLeafz)
245 if err != nil {
246 return err
247 }
248
249 var resp leafzResponse
250 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
251 return err
252 }
253
254 for _, leaf := range resp.Leafs {
255 c.cache.leafs.put(leaf)
256 px := fmt.Sprintf("leafz_leaf_%s_%s_%s_%d_", leaf.Name, leaf.Account, leaf.IP, leaf.Port)
257
258 mx[px+"in_bytes"] = leaf.InBytes
259 mx[px+"out_bytes"] = leaf.OutBytes
260 mx[px+"in_msgs"] = leaf.InMsgs
261 mx[px+"out_msgs"] = leaf.OutMsgs
262 mx[px+"num_subs"] = int64(leaf.NumSubs)
263 rtt, _ := time.ParseDuration(leaf.RTT)
264 mx[px+"rtt"] = rtt.Microseconds()
265 }
266
267 return nil
268 }
269
270 func (c *Collector) collectJsz(mx map[string]int64) error {
271 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPathJsz)
272 if err != nil {
273 return err
274 }
275
276 var resp jszResponse
277 if err := web.DoHTTP(c.httpClient).RequestJSON(req, &resp); err != nil {
278 return err
279 }
280
281 mx["jsz_disabled"] = oldmetrix.Bool(resp.Disabled)
282 mx["jsz_enabled"] = oldmetrix.Bool(!resp.Disabled)
283 mx["jsz_streams"] = int64(resp.Streams)
284 mx["jsz_consumers"] = int64(resp.Consumers)
285 mx["jsz_bytes"] = int64(resp.Bytes)
286 mx["jsz_messages"] = int64(resp.Messages)
287 mx["jsz_memory_used"] = int64(resp.Memory)
288 mx["jsz_store_used"] = int64(resp.Store)
289 mx["jsz_api_total"] = int64(resp.Api.Total)
290 mx["jsz_api_errors"] = int64(resp.Api.Errors)
291 mx["jsz_api_inflight"] = int64(resp.Api.Inflight)
292
293 return nil
294 }
295
296 func parseUptime(uptime string) (time.Duration, error) {
297 // https://github.com/nats-io/nats-server/blob/v2.10.24/server/monitor.go#L1354
298
299 var duration time.Duration
300 var num strings.Builder
301
302 for i := 0; i < len(uptime); i++ {
303 ch := uptime[i]
304 if ch >= '0' && ch <= '9' {
305 num.WriteByte(ch)
306 continue
307 }
308
309 if num.Len() == 0 {
310 return 0, fmt.Errorf("invalid format: unit '%c' without number", ch)
311 }
312
313 n, err := strconv.Atoi(num.String())
314 if err != nil {
315 return 0, fmt.Errorf("invalid number in duration: %s", num.String())
316 }
317
318 switch ch {
319 case 'y':
320 duration += time.Duration(n) * 365 * 24 * time.Hour
321 case 'd':
322 duration += time.Duration(n) * 24 * time.Hour
323 case 'h':
324 duration += time.Duration(n) * time.Hour
325 case 'm':
326 duration += time.Duration(n) * time.Minute
327 case 's':
328 duration += time.Duration(n) * time.Second
329 default:
330 return 0, fmt.Errorf("invalid unit in duration: %c", ch)
331 }
332 num.Reset()
333 }
334
335 if num.Len() > 0 {
336 return 0, fmt.Errorf("invalid format: number without unit at end")
337 }
338
339 return duration, nil
340 }