1 //
2 // Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
3 // Copyright 2018 Capitar IT Group BV <info@capitar.com>
4 //
5 // This software is supplied under the terms of the MIT License, a
6 // copy of which should be located in the distribution where this
7 // file was obtained (LICENSE.txt). A copy of the license may also be
8 // found online at https://opensource.org/licenses/MIT.
9 //
10
11 #include <stdio.h>
12 #include <string.h>
13
14 #include "core/nng_impl.h"
15
16 typedef struct nng_stat nni_stat;
17
18 struct nng_stat {
19 const nni_stat_info *s_info;
20 const nni_stat_item *s_item; // Used during snapshot collection
21 nni_list s_children;
22 nni_stat * s_parent;
23 nni_list_node s_node;
24 nni_time s_timestamp;
25 union {
26 int sv_id;
27 bool sv_bool;
28 uint64_t sv_value;
29 char * sv_string;
30 } s_val;
31 };
32
33 #ifdef NNG_ENABLE_STATS
34 static nni_stat_item stats_root;
35 static nni_mtx stats_lock;
36 static nni_mtx stats_val_lock;
37 #endif
38
39 void
nni_stat_add(nni_stat_item * parent,nni_stat_item * child)40 nni_stat_add(nni_stat_item *parent, nni_stat_item *child)
41 {
42 #ifdef NNG_ENABLE_STATS
43 // Make sure that the lists for both children and parents
44 // are correctly initialized.
45 if (parent->si_children.ll_head.ln_next == NULL) {
46 NNI_LIST_INIT(&parent->si_children, nni_stat_item, si_node);
47 }
48 if (child->si_children.ll_head.ln_next == NULL) {
49 NNI_LIST_INIT(&child->si_children, nni_stat_item, si_node);
50 }
51 nni_list_append(&parent->si_children, child);
52 #else
53 NNI_ARG_UNUSED(parent);
54 NNI_ARG_UNUSED(child);
55 #endif
56 }
57
58 // nni_stat_register registers a stat tree, acquiring the lock
59 // on the stats structures before doing so.
60 void
nni_stat_register(nni_stat_item * child)61 nni_stat_register(nni_stat_item *child)
62 {
63 #ifdef NNG_ENABLE_STATS
64 nni_mtx_lock(&stats_lock);
65 nni_stat_add(&stats_root, child);
66 nni_mtx_unlock(&stats_lock);
67 #else
68 NNI_ARG_UNUSED(child);
69 #endif
70 }
71
72 #ifdef NNG_ENABLE_STATS
73 void
stat_unregister(nni_stat_item * item)74 stat_unregister(nni_stat_item *item)
75 {
76 nni_stat_item *child;
77 while ((child = nni_list_first(&item->si_children)) != NULL) {
78 stat_unregister(child);
79 }
80 if ((item->si_info->si_alloc) &&
81 (item->si_info->si_type == NNG_STAT_STRING)) {
82 nni_strfree(item->si_u.sv_string);
83 item->si_u.sv_string = NULL;
84 }
85 nni_list_node_remove(&item->si_node);
86 }
87 #endif
88
89 void
nni_stat_unregister(nni_stat_item * item)90 nni_stat_unregister(nni_stat_item *item)
91 {
92 #ifdef NNG_ENABLE_STATS
93 nni_mtx_lock(&stats_lock);
94 stat_unregister(item);
95 nni_mtx_unlock(&stats_lock);
96 #else
97 NNI_ARG_UNUSED(item);
98 #endif
99 }
100
101 void
nni_stat_init(nni_stat_item * item,const nni_stat_info * info)102 nni_stat_init(nni_stat_item *item, const nni_stat_info *info)
103 {
104 #ifdef NNG_ENABLE_STATS
105 memset(item, 0, sizeof(*item));
106 NNI_LIST_INIT(&item->si_children, nni_stat_item, si_node);
107 item->si_info = info;
108 #else
109 NNI_ARG_UNUSED(item);
110 NNI_ARG_UNUSED(info);
111 #endif
112 }
113
114 void
nni_stat_inc(nni_stat_item * item,uint64_t inc)115 nni_stat_inc(nni_stat_item *item, uint64_t inc)
116 {
117 #ifdef NNG_ENABLE_STATS
118 if (item->si_info->si_atomic) {
119 nni_atomic_add64(&item->si_u.sv_atomic, inc);
120 } else {
121 item->si_u.sv_number += inc;
122 }
123 #else
124 NNI_ARG_UNUSED(item);
125 NNI_ARG_UNUSED(inc);
126 #endif
127 }
128
129 void
nni_stat_dec(nni_stat_item * item,uint64_t inc)130 nni_stat_dec(nni_stat_item *item, uint64_t inc)
131 {
132 #ifdef NNG_ENABLE_STATS
133
134 if (item->si_info->si_atomic) {
135 nni_atomic_sub64(&item->si_u.sv_atomic, inc);
136 } else {
137 item->si_u.sv_number -= inc;
138 }
139 #else
140 NNI_ARG_UNUSED(item);
141 NNI_ARG_UNUSED(inc);
142 #endif
143 }
144
145 void
nni_stat_set_id(nni_stat_item * item,int id)146 nni_stat_set_id(nni_stat_item *item, int id)
147 {
148 #ifdef NNG_ENABLE_STATS
149 // IDs don't change, so just set it.
150 item->si_u.sv_id = id;
151 #else
152 NNI_ARG_UNUSED(item);
153 NNI_ARG_UNUSED(id);
154 #endif
155 }
156
157 void
nni_stat_set_bool(nni_stat_item * item,bool b)158 nni_stat_set_bool(nni_stat_item *item, bool b)
159 {
160 #ifdef NNG_ENABLE_STATS
161 // bool is atomic by definitions.
162 item->si_u.sv_bool = b;
163 #else
164 NNI_ARG_UNUSED(item);
165 NNI_ARG_UNUSED(b);
166 #endif
167 }
168
169 void
nni_stat_set_string(nni_stat_item * item,const char * s)170 nni_stat_set_string(nni_stat_item *item, const char *s)
171 {
172 #ifdef NNG_ENABLE_STATS
173 const nni_stat_info *info = item->si_info;
174 char * old = item->si_u.sv_string;
175
176 nni_mtx_lock(&stats_val_lock);
177 if ((s != NULL) && (old != NULL) && (strcmp(s, old) == 0)) {
178 // no change
179 nni_mtx_unlock(&stats_val_lock);
180 return;
181 }
182
183 if (!info->si_alloc) {
184 // no allocation, just set it.
185 item->si_u.sv_string = (char *) s;
186 nni_mtx_unlock(&stats_val_lock);
187 return;
188 }
189
190 item->si_u.sv_string = nni_strdup(s);
191 nni_mtx_unlock(&stats_val_lock);
192
193 nni_strfree(old);
194 #else
195 NNI_ARG_UNUSED(item);
196 NNI_ARG_UNUSED(s);
197 #endif
198 }
199
200 void
nni_stat_set_value(nni_stat_item * item,uint64_t v)201 nni_stat_set_value(nni_stat_item *item, uint64_t v)
202 {
203 #ifdef NNG_ENABLE_STATS
204 if (item->si_info->si_atomic) {
205 nni_atomic_set64(&item->si_u.sv_atomic, v);
206 } else {
207 item->si_u.sv_number = v;
208 }
209 #else
210 NNI_ARG_UNUSED(item);
211 NNI_ARG_UNUSED(v);
212 #endif
213 }
214
215 void
nng_stats_free(nni_stat * st)216 nng_stats_free(nni_stat *st)
217 {
218 #ifdef NNG_ENABLE_STATS
219 nni_stat *child;
220
221 while ((child = nni_list_first(&st->s_children)) != NULL) {
222 nni_list_remove(&st->s_children, child);
223 nng_stats_free(child);
224 }
225 if (st->s_info->si_alloc) {
226 nni_strfree(st->s_val.sv_string);
227 }
228 NNI_FREE_STRUCT(st);
229 #else
230 NNI_ARG_UNUSED(st);
231 #endif
232 }
233
234 #ifdef NNG_ENABLE_STATS
235 static int
stat_make_tree(nni_stat_item * item,nni_stat ** sp)236 stat_make_tree(nni_stat_item *item, nni_stat **sp)
237 {
238 nni_stat * stat;
239 nni_stat_item *child;
240
241 if ((stat = NNI_ALLOC_STRUCT(stat)) == NULL) {
242 return (NNG_ENOMEM);
243 }
244 NNI_LIST_INIT(&stat->s_children, nni_stat, s_node);
245
246 stat->s_info = item->si_info;
247 stat->s_item = item;
248 stat->s_parent = NULL;
249
250 NNI_LIST_FOREACH (&item->si_children, child) {
251 nni_stat *cs;
252 int rv;
253 if ((rv = stat_make_tree(child, &cs)) != 0) {
254 nng_stats_free(stat);
255 return (rv);
256 }
257 nni_list_append(&stat->s_children, cs);
258 cs->s_parent = stat;
259 }
260 *sp = stat;
261 return (0);
262 }
263
264 static void
stat_update(nni_stat * stat)265 stat_update(nni_stat *stat)
266 {
267 const nni_stat_item *item = stat->s_item;
268 const nni_stat_info *info = item->si_info;
269 char * old;
270 char * str;
271
272 switch (info->si_type) {
273 case NNG_STAT_SCOPE:
274 case NNG_STAT_ID:
275 stat->s_val.sv_id = item->si_u.sv_id;
276 break;
277 case NNG_STAT_BOOLEAN:
278 stat->s_val.sv_bool = item->si_u.sv_bool;
279 break;
280 case NNG_STAT_COUNTER:
281 case NNG_STAT_LEVEL:
282 if (info->si_atomic) {
283 stat->s_val.sv_value = nni_atomic_get64(
284 (nni_atomic_u64 *) &item->si_u.sv_atomic);
285 } else {
286 stat->s_val.sv_value = item->si_u.sv_number;
287 }
288 break;
289 case NNG_STAT_STRING:
290 nni_mtx_lock(&stats_val_lock);
291 old = stat->s_val.sv_string;
292 str = item->si_u.sv_string;
293
294 // If we have to allocate a new string, do so. But
295 // only do it if new string is different.
296 if ((info->si_alloc) && (str != NULL) &&
297 ((old == NULL) || (strcmp(str, old) != 0))) {
298
299 stat->s_val.sv_string = nni_strdup(str);
300 nni_strfree(old);
301
302 } else if (info->si_alloc) {
303 nni_strfree(stat->s_val.sv_string);
304 stat->s_val.sv_string = NULL;
305
306 } else {
307 stat->s_val.sv_string = str;
308 }
309 nni_mtx_unlock(&stats_val_lock);
310 break;
311 }
312 stat->s_timestamp = nni_clock();
313 }
314
315 static void
stat_update_tree(nni_stat * stat)316 stat_update_tree(nni_stat *stat)
317 {
318 nni_stat *child;
319 stat_update(stat);
320 NNI_LIST_FOREACH (&stat->s_children, child) {
321 stat_update_tree(child);
322 }
323 }
324
325 int
nni_stat_snapshot(nni_stat ** statp,nni_stat_item * item)326 nni_stat_snapshot(nni_stat **statp, nni_stat_item *item)
327 {
328 int rv;
329 nni_stat *stat;
330
331 if (item == NULL) {
332 item = &stats_root;
333 }
334 nni_mtx_lock(&stats_lock);
335 if ((rv = stat_make_tree(item, &stat)) != 0) {
336 nni_mtx_unlock(&stats_lock);
337 return (rv);
338 }
339 stat_update_tree(stat);
340 nni_mtx_unlock(&stats_lock);
341 *statp = stat;
342 return (0);
343 }
344 #endif
345
346 int
nng_stats_get(nng_stat ** statp)347 nng_stats_get(nng_stat **statp)
348 {
349 #ifdef NNG_ENABLE_STATS
350 int rv;
351 if ((rv = nni_init()) != 0) {
352 return (rv);
353 }
354 return (nni_stat_snapshot(statp, &stats_root));
355 #else
356 NNI_ARG_UNUSED(statp);
357 return (NNG_ENOTSUP);
358 #endif
359 }
360
361 nng_stat *
nng_stat_parent(nng_stat * stat)362 nng_stat_parent(nng_stat *stat)
363 {
364 return (stat->s_parent);
365 }
366
367 nng_stat *
nng_stat_next(nng_stat * stat)368 nng_stat_next(nng_stat *stat)
369 {
370 if (stat->s_parent == NULL) {
371 return (NULL); // Root node, no siblings.
372 }
373 return (nni_list_next(&stat->s_parent->s_children, stat));
374 }
375
376 nng_stat *
nng_stat_child(nng_stat * stat)377 nng_stat_child(nng_stat *stat)
378 {
379 return (nni_list_first(&stat->s_children));
380 }
381
382 const char *
nng_stat_name(nni_stat * stat)383 nng_stat_name(nni_stat *stat)
384 {
385 return (stat->s_info->si_name);
386 }
387
388 uint64_t
nng_stat_value(nni_stat * stat)389 nng_stat_value(nni_stat *stat)
390 {
391 return (stat->s_val.sv_value);
392 }
393
394 bool
nng_stat_bool(nni_stat * stat)395 nng_stat_bool(nni_stat *stat)
396 {
397 return (stat->s_val.sv_bool);
398 }
399
400
401 const char *
nng_stat_string(nng_stat * stat)402 nng_stat_string(nng_stat *stat)
403 {
404 if (stat->s_info->si_type != NNG_STAT_STRING) {
405 return ("");
406 }
407 return (stat->s_val.sv_string);
408 }
409
410 uint64_t
nng_stat_timestamp(nng_stat * stat)411 nng_stat_timestamp(nng_stat *stat)
412 {
413 return ((uint64_t) stat->s_timestamp);
414 }
415
416 int
nng_stat_type(nng_stat * stat)417 nng_stat_type(nng_stat *stat)
418 {
419 return (stat->s_info->si_type);
420 }
421
422 int
nng_stat_unit(nng_stat * stat)423 nng_stat_unit(nng_stat *stat)
424 {
425 return (stat->s_info->si_unit);
426 }
427
428 const char *
nng_stat_desc(nng_stat * stat)429 nng_stat_desc(nng_stat *stat)
430 {
431 return (stat->s_info->si_desc);
432 }
433
434 nng_stat *
nng_stat_find(nng_stat * stat,const char * name)435 nng_stat_find(nng_stat *stat, const char *name)
436 {
437 nng_stat *child;
438 if (stat == NULL) {
439 return (NULL);
440 }
441 if (strcmp(name, stat->s_info->si_name) == 0) {
442 return (stat);
443 }
444 NNI_LIST_FOREACH (&stat->s_children, child) {
445 nng_stat *result;
446 if ((result = nng_stat_find(child, name)) != NULL) {
447 return (result);
448 }
449 }
450 return (NULL);
451 }
452
453 nng_stat *
nng_stat_find_scope(nng_stat * stat,const char * name,int id)454 nng_stat_find_scope(nng_stat *stat, const char *name, int id)
455 {
456 nng_stat *child;
457 if (stat == NULL) {
458 return (NULL);
459 }
460 if ((stat->s_val.sv_id == id) &&
461 (stat->s_info->si_type == NNG_STAT_SCOPE) &&
462 (strcmp(name, stat->s_info->si_name) == 0)) {
463 return (stat);
464 }
465 NNI_LIST_FOREACH (&stat->s_children, child) {
466 nng_stat *result;
467 if ((result = nng_stat_find(child, name)) != NULL) {
468 return (result);
469 }
470 }
471 return (NULL);
472 }
473
474 nng_stat *
nng_stat_find_socket(nng_stat * stat,nng_socket s)475 nng_stat_find_socket(nng_stat *stat, nng_socket s)
476 {
477 return (nng_stat_find_scope(stat, "socket", nng_socket_id(s)));
478 }
479
480 nng_stat *
nng_stat_find_dialer(nng_stat * stat,nng_dialer d)481 nng_stat_find_dialer(nng_stat *stat, nng_dialer d)
482 {
483 return (nng_stat_find_scope(stat, "dialer", nng_dialer_id(d)));
484 }
485
486 nng_stat *
nng_stat_find_listener(nng_stat * stat,nng_listener l)487 nng_stat_find_listener(nng_stat *stat, nng_listener l)
488 {
489 return (nng_stat_find_scope(stat, "listener", nng_listener_id(l)));
490 }
491
492 int
nni_stat_sys_init(void)493 nni_stat_sys_init(void)
494 {
495 #ifdef NNG_ENABLE_STATS
496 static const nni_stat_info root = {
497 .si_name = "",
498 .si_desc = "all statistics",
499 .si_type = NNG_STAT_SCOPE,
500 };
501 nni_mtx_init(&stats_lock);
502 nni_mtx_init(&stats_val_lock);
503 nni_stat_init(&stats_root, &root);
504 #endif
505 return (0);
506 }
507
508 void
nni_stat_sys_fini(void)509 nni_stat_sys_fini(void)
510 {
511 #ifdef NNG_ENABLE_STATS
512 nni_mtx_fini(&stats_lock);
513 nni_mtx_fini(&stats_val_lock);
514 #endif
515 }
516
517 #ifdef NNG_ENABLE_STATS
518 void
stat_sprint_scope(nni_stat * stat,char ** scope,int * lenp)519 stat_sprint_scope(nni_stat *stat, char **scope, int *lenp)
520 {
521 if (stat->s_parent != NULL) {
522 stat_sprint_scope(stat->s_parent, scope, lenp);
523 }
524 if (strlen(stat->s_info->si_name) > 0) {
525 snprintf(*scope, *lenp, "%s#%d.", stat->s_info->si_name,
526 stat->s_val.sv_id);
527 } else {
528 (*scope)[0] = '\0';
529 }
530 *lenp -= (int) strlen(*scope);
531 *scope += strlen(*scope);
532 }
533 #endif
534
535 void
nng_stats_dump(nng_stat * stat)536 nng_stats_dump(nng_stat *stat)
537 {
538 #ifdef NNG_ENABLE_STATS
539 static char buf[128]; // to minimize recursion, not thread safe
540 int len;
541 char * scope;
542 char * indent = " ";
543 unsigned long long val;
544 nni_stat * child;
545
546 switch (nng_stat_type(stat)) {
547 case NNG_STAT_SCOPE:
548 scope = buf;
549 len = sizeof(buf);
550 stat_sprint_scope(stat, &scope, &len);
551 len = (int) strlen(buf);
552 if (len > 0) {
553 if (buf[len - 1] == '.') {
554 buf[--len] = '\0';
555 }
556 }
557 if (len > 0) {
558 nni_plat_printf("\n%s:\n", buf);
559 }
560 break;
561 case NNG_STAT_STRING:
562 nni_plat_printf("%s%-32s\"%s\"\n", indent, nng_stat_name(stat),
563 nng_stat_string(stat));
564 break;
565 case NNG_STAT_BOOLEAN:
566 nni_plat_printf("%s%-32s%s\n", indent, nng_stat_name(stat),
567 nng_stat_bool(stat) ? "true" : "false");
568 break;
569 case NNG_STAT_LEVEL:
570 case NNG_STAT_COUNTER:
571 val = nng_stat_value(stat);
572 nni_plat_printf(
573 "%s%-32s%llu", indent, nng_stat_name(stat), val);
574 switch (nng_stat_unit(stat)) {
575 case NNG_UNIT_BYTES:
576 nni_plat_printf(" bytes\n");
577 break;
578 case NNG_UNIT_MESSAGES:
579 nni_plat_printf(" msgs\n");
580 break;
581 case NNG_UNIT_MILLIS:
582 nni_plat_printf(" ms\n");
583 break;
584 case NNG_UNIT_NONE:
585 case NNG_UNIT_EVENTS:
586 default:
587 nni_plat_printf("\n");
588 break;
589 }
590 break;
591 case NNG_STAT_ID:
592 val = nng_stat_value(stat);
593 nni_plat_printf(
594 "%s%-32s%llu\n", indent, nng_stat_name(stat), val);
595 break;
596 default:
597 nni_plat_printf("%s%-32s<?>\n", indent, nng_stat_name(stat));
598 break;
599 }
600
601 NNI_LIST_FOREACH (&stat->s_children, child) {
602 nng_stats_dump(child);
603 }
604 #else
605 NNI_ARG_UNUSED(stat);
606 #endif
607 }
608