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