1/*
2 * lws Generic Metrics
3 *
4 * Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25#include "private-lib-core.h"
26#include <assert.h>
27
28int
29lws_metrics_tag_add(lws_dll2_owner_t *owner, const char *name, const char *val)
30{
31	size_t vl = strlen(val);
32	lws_metrics_tag_t *tag;
33
34	// lwsl_notice("%s: adding %s=%s\n", __func__, name, val);
35
36	/*
37	 * Remove (in order to replace) any existing tag of same name
38	 */
39
40	lws_start_foreach_dll(struct lws_dll2 *, d, owner->head) {
41		tag = lws_container_of(d, lws_metrics_tag_t, list);
42
43		if (!strcmp(name, tag->name)) {
44			lws_dll2_remove(&tag->list);
45			lws_free(tag);
46			break;
47		}
48
49	} lws_end_foreach_dll(d);
50
51	/*
52	 * Create the new tag
53	 */
54
55	tag = lws_malloc(sizeof(*tag) + vl + 1, __func__);
56	if (!tag)
57		return 1;
58
59	lws_dll2_clear(&tag->list);
60	tag->name = name;
61	memcpy(&tag[1], val, vl + 1);
62
63	lws_dll2_add_tail(&tag->list, owner);
64
65	return 0;
66}
67
68int
69lws_metrics_tag_wsi_add(struct lws *wsi, const char *name, const char *val)
70{
71	__lws_lc_tag(wsi->a.context, NULL, &wsi->lc, "|%s", val);
72
73	return lws_metrics_tag_add(&wsi->cal_conn.mtags_owner, name, val);
74}
75
76#if defined(LWS_WITH_SECURE_STREAMS)
77int
78lws_metrics_tag_ss_add(struct lws_ss_handle *ss, const char *name, const char *val)
79{
80	__lws_lc_tag(ss->context, NULL, &ss->lc, "|%s", val);
81	return lws_metrics_tag_add(&ss->cal_txn.mtags_owner, name, val);
82}
83#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
84int
85lws_metrics_tag_sspc_add(struct lws_sspc_handle *sspc, const char *name,
86			 const char *val)
87{
88	__lws_lc_tag(sspc->context, NULL, &sspc->lc, "|%s", val);
89	return lws_metrics_tag_add(&sspc->cal_txn.mtags_owner, name, val);
90}
91#endif
92#endif
93
94void
95lws_metrics_tags_destroy(lws_dll2_owner_t *owner)
96{
97	lws_metrics_tag_t *t;
98
99	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, owner->head) {
100		t = lws_container_of(d, lws_metrics_tag_t, list);
101
102		lws_dll2_remove(&t->list);
103		lws_free(t);
104
105	} lws_end_foreach_dll_safe(d, d1);
106}
107
108size_t
109lws_metrics_tags_serialize(lws_dll2_owner_t *owner, char *buf, size_t len)
110{
111	char *end = buf + len - 1, *p = buf;
112	lws_metrics_tag_t *t;
113
114	lws_start_foreach_dll(struct lws_dll2 *, d, owner->head) {
115		t = lws_container_of(d, lws_metrics_tag_t, list);
116
117		p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
118				  "%s=\"%s\"", t->name, (const char *)&t[1]);
119
120		if (d->next && p + 2 < end)
121			*p++ = ',';
122
123	} lws_end_foreach_dll(d);
124
125	*p = '\0';
126
127	return lws_ptr_diff_size_t(p, buf);
128}
129
130const char *
131lws_metrics_tag_get(lws_dll2_owner_t *owner, const char *name)
132{
133	lws_metrics_tag_t *t;
134
135	lws_start_foreach_dll(struct lws_dll2 *, d, owner->head) {
136		t = lws_container_of(d, lws_metrics_tag_t, list);
137
138		if (!strcmp(name, t->name))
139			return (const char *)&t[1];
140
141	} lws_end_foreach_dll(d);
142
143	return NULL;
144}
145
146static int
147lws_metrics_dump_cb(lws_metric_pub_t *pub, void *user);
148
149static void
150lws_metrics_report_and_maybe_clear(struct lws_context *ctx, lws_metric_pub_t *pub)
151{
152	if (!pub->us_first || pub->us_last == pub->us_dumped)
153		return;
154
155	lws_metrics_dump_cb(pub, ctx);
156}
157
158static void
159lws_metrics_periodic_cb(lws_sorted_usec_list_t *sul)
160{
161	lws_metric_policy_dyn_t *dmp = lws_container_of(sul,
162						lws_metric_policy_dyn_t, sul);
163	struct lws_context *ctx = lws_container_of(dmp->list.owner,
164					struct lws_context, owner_mtr_dynpol);
165
166	if (!ctx->system_ops || !ctx->system_ops->metric_report)
167		return;
168
169	lws_start_foreach_dll(struct lws_dll2 *, d, dmp->owner.head) {
170		lws_metric_t *mt = lws_container_of(d, lws_metric_t, list);
171		lws_metric_pub_t *pub = lws_metrics_priv_to_pub(mt);
172
173		lws_metrics_report_and_maybe_clear(ctx, pub);
174
175	} lws_end_foreach_dll(d);
176
177#if defined(LWS_WITH_SYS_SMD) && defined(LWS_WITH_SECURE_STREAMS)
178	(void)lws_smd_msg_printf(ctx, LWSSMDCL_METRICS,
179				 "{\"dump\":\"%s\",\"ts\":%lu}",
180				   dmp->policy->name,
181				   (long)ctx->last_policy);
182#endif
183
184	if (dmp->policy->us_schedule)
185		lws_sul_schedule(ctx, 0, &dmp->sul,
186				 lws_metrics_periodic_cb,
187				 (lws_usec_t)dmp->policy->us_schedule);
188}
189
190/*
191 * Policies are in two pieces, a const policy and a dynamic part that contains
192 * lists and sul timers for the policy etc.  This creates a dynmic part
193 * corresponding to the static part.
194 *
195 * Metrics can exist detached from being bound to any policy about how to
196 * report them, these are collected but not reported unless they later become
197 * bound to a reporting policy dynamically.
198 */
199
200lws_metric_policy_dyn_t *
201lws_metrics_policy_dyn_create(struct lws_context *ctx,
202			      const lws_metric_policy_t *po)
203{
204	lws_metric_policy_dyn_t *dmet;
205
206	dmet = lws_zalloc(sizeof(*dmet), __func__);
207	if (!dmet)
208		return NULL;
209
210	dmet->policy = po;
211	lws_dll2_add_tail(&dmet->list, &ctx->owner_mtr_dynpol);
212
213	if (po->us_schedule)
214		lws_sul_schedule(ctx, 0, &dmet->sul,
215				 lws_metrics_periodic_cb,
216				 (lws_usec_t)po->us_schedule);
217
218	return dmet;
219}
220
221/*
222 * Get a dynamic metrics policy from the const one, may return NULL if OOM
223 */
224
225lws_metric_policy_dyn_t *
226lws_metrics_policy_get_dyn(struct lws_context *ctx,
227			   const lws_metric_policy_t *po)
228{
229	lws_start_foreach_dll(struct lws_dll2 *, d, ctx->owner_mtr_dynpol.head) {
230		lws_metric_policy_dyn_t *dm =
231			lws_container_of(d, lws_metric_policy_dyn_t, list);
232
233		if (dm->policy == po)
234			return dm;
235
236	} lws_end_foreach_dll(d);
237
238	/*
239	 * no dyn policy part for this const policy --> create one
240	 *
241	 * We want a dynamic part for listing metrics that bound to the policy
242	 */
243
244	return lws_metrics_policy_dyn_create(ctx, po);
245}
246
247static int
248lws_metrics_check_in_policy(const char *polstring, const char *name)
249{
250	struct lws_tokenize ts;
251
252	memset(&ts, 0, sizeof(ts));
253
254	ts.start = polstring;
255	ts.len = strlen(polstring);
256	ts.flags = (uint16_t)(LWS_TOKENIZE_F_MINUS_NONTERM |
257			      LWS_TOKENIZE_F_ASTERISK_NONTERM |
258			      LWS_TOKENIZE_F_COMMA_SEP_LIST |
259			      LWS_TOKENIZE_F_NO_FLOATS |
260			      LWS_TOKENIZE_F_DOT_NONTERM);
261
262	do {
263		ts.e = (int8_t)lws_tokenize(&ts);
264
265		if (ts.e == LWS_TOKZE_TOKEN) {
266			if (!lws_strcmp_wildcard(ts.token, ts.token_len, name,
267						 strlen(name)))
268				/* yes, we are mentioned in this guy's policy */
269				return 0;
270		}
271	} while (ts.e > 0);
272
273	/* no, this policy doesn't apply to a metric with our name */
274
275	return 1;
276}
277
278static const lws_metric_policy_t *
279lws_metrics_find_policy(struct lws_context *ctx, const char *name)
280{
281	const lws_metric_policy_t *mp = ctx->metrics_policies;
282
283	if (!mp) {
284#if defined(LWS_WITH_SECURE_STREAMS)
285		if (ctx->pss_policies)
286			mp = ctx->pss_policies->metrics;
287#endif
288		if (!mp)
289			return NULL;
290	}
291
292	while (mp) {
293		if (mp->report && !lws_metrics_check_in_policy(mp->report, name))
294			return mp;
295
296		mp = mp->next;
297	}
298
299	return NULL;
300}
301
302/*
303 * Create a lws_metric_t, bind to a named policy if possible (or add to the
304 * context list of unbound metrics) and set its lws_system
305 * idx.  The metrics objects themselves are typically composed into other
306 * objects and are well-known composed members of them.
307 */
308
309lws_metric_t *
310lws_metric_create(struct lws_context *ctx, uint8_t flags, const char *name)
311{
312	const lws_metric_policy_t *po;
313	lws_metric_policy_dyn_t *dmp;
314	lws_metric_pub_t *pub;
315	lws_metric_t *mt;
316	char pname[32];
317	size_t nl;
318
319	if (ctx->metrics_prefix) {
320
321		/*
322		 * In multi-process case, we want to prefix metrics from this
323		 * process / context with a string distinguishing which
324		 * application they came from
325		 */
326
327		nl = (size_t)lws_snprintf(pname, sizeof(pname) - 1, "%s.%s",
328				  ctx->metrics_prefix, name);
329		name = pname;
330	} else
331		nl = strlen(name);
332
333	mt = (lws_metric_t *)lws_zalloc(sizeof(*mt) /* private */ +
334					sizeof(lws_metric_pub_t) +
335					nl + 1 /* copy of metric name */,
336					__func__);
337	if (!mt)
338		return NULL;
339
340	pub = lws_metrics_priv_to_pub(mt);
341	pub->name = (char *)pub + sizeof(lws_metric_pub_t);
342	memcpy((char *)pub->name, name, nl + 1);
343	pub->flags = flags;
344
345	/* after these common members, we have to use the right type */
346
347	if (!(flags & LWSMTFL_REPORT_HIST)) {
348		/* anything is smaller or equal to this */
349		pub->u.agg.min = ~(u_mt_t)0;
350		pub->us_first = lws_now_usecs();
351	}
352
353	mt->ctx = ctx;
354
355	/*
356	 * Let's see if we can bind to a reporting policy straight away
357	 */
358
359	po = lws_metrics_find_policy(ctx, name);
360	if (po) {
361		dmp = lws_metrics_policy_get_dyn(ctx, po);
362		if (dmp) {
363			lwsl_notice("%s: metpol %s\n", __func__, name);
364			lws_dll2_add_tail(&mt->list, &dmp->owner);
365
366			return 0;
367		}
368	}
369
370	/*
371	 * If not, well, let's go on without and maybe later at runtime, he'll
372	 * get interested in us and apply a reporting policy
373	 */
374
375	lws_dll2_add_tail(&mt->list, &ctx->owner_mtr_no_pol);
376
377	return mt;
378}
379
380/*
381 * If our metric is bound to a reporting policy, return a pointer to it,
382 * otherwise NULL
383 */
384
385const lws_metric_policy_t *
386lws_metric_get_policy(lws_metric_t *mt)
387{
388	lws_metric_policy_dyn_t *dp;
389
390	/*
391	 * Our metric must either be on the "no policy" context list or
392	 * listed by the dynamic part of the policy it is bound to
393	 */
394	assert(mt->list.owner);
395
396	if ((char *)mt->list.owner >= (char *)mt->ctx &&
397	    (char *)mt->list.owner < (char *)mt->ctx + sizeof(struct lws_context))
398		/* we are on the "no policy" context list */
399		return NULL;
400
401	/* we are listed by a dynamic policy owner */
402
403	dp = lws_container_of(mt->list.owner, lws_metric_policy_dyn_t, owner);
404
405	/* return the const policy the dynamic policy represents */
406
407	return dp->policy;
408}
409
410void
411lws_metric_rebind_policies(struct lws_context *ctx)
412{
413	const lws_metric_policy_t *po;
414	lws_metric_policy_dyn_t *dmp;
415
416	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
417				   ctx->owner_mtr_no_pol.head) {
418		lws_metric_t *mt = lws_container_of(d, lws_metric_t, list);
419		lws_metric_pub_t *pub = lws_metrics_priv_to_pub(mt);
420
421		po = lws_metrics_find_policy(ctx, pub->name);
422		if (po) {
423			dmp = lws_metrics_policy_get_dyn(ctx, po);
424			if (dmp) {
425				lwsl_info("%s: %s <- pol %s\n", __func__,
426						pub->name, po->name);
427				lws_dll2_remove(&mt->list);
428				lws_dll2_add_tail(&mt->list, &dmp->owner);
429			}
430		} else
431			lwsl_debug("%s: no pol for %s\n", __func__, pub->name);
432
433	} lws_end_foreach_dll_safe(d, d1);
434}
435
436int
437lws_metric_destroy(lws_metric_t **pmt, int keep)
438{
439	lws_metric_t *mt = *pmt;
440	lws_metric_pub_t *pub;
441
442	if (!mt)
443		return 0;
444
445	pub = lws_metrics_priv_to_pub(mt);
446
447	lws_dll2_remove(&mt->list);
448
449	if (keep) {
450		lws_dll2_add_tail(&mt->list, &mt->ctx->owner_mtr_no_pol);
451
452		return 0;
453	}
454
455	if (pub->flags & LWSMTFL_REPORT_HIST) {
456		lws_metric_bucket_t *b = pub->u.hist.head, *b1;
457
458		pub->u.hist.head = NULL;
459
460		while (b) {
461			b1 = b->next;
462			lws_free(b);
463			b = b1;
464		}
465	}
466
467	lws_free(mt);
468	*pmt = NULL;
469
470	return 0;
471}
472
473/*
474 * Allow an existing metric to have its reporting policy changed at runtime
475 */
476
477int
478lws_metric_switch_policy(lws_metric_t *mt, const char *polname)
479{
480	const lws_metric_policy_t *po;
481	lws_metric_policy_dyn_t *dmp;
482
483	po = lws_metrics_find_policy(mt->ctx, polname);
484	if (!po)
485		return 1;
486
487	dmp = lws_metrics_policy_get_dyn(mt->ctx, po);
488	if (!dmp)
489		return 1;
490
491	lws_dll2_remove(&mt->list);
492	lws_dll2_add_tail(&mt->list, &dmp->owner);
493
494	return 0;
495}
496
497/*
498 * If keep is set, don't destroy existing metrics objects, just detach them
499 * from the policy being deleted and keep track of them on ctx->
500 * owner_mtr_no_pol
501 */
502
503void
504lws_metric_policy_dyn_destroy(lws_metric_policy_dyn_t *dm, int keep)
505{
506	lws_sul_cancel(&dm->sul);
507
508	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, dm->owner.head) {
509		lws_metric_t *m = lws_container_of(d, lws_metric_t, list);
510
511		lws_metric_destroy(&m, keep);
512
513	} lws_end_foreach_dll_safe(d, d1);
514
515	lws_sul_cancel(&dm->sul);
516
517	lws_dll2_remove(&dm->list);
518	lws_free(dm);
519}
520
521/*
522 * Destroy all dynamic metrics policies, deinit any metrics still using them
523 */
524
525void
526lws_metrics_destroy(struct lws_context *ctx)
527{
528	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
529				   ctx->owner_mtr_dynpol.head) {
530		lws_metric_policy_dyn_t *dm =
531			lws_container_of(d, lws_metric_policy_dyn_t, list);
532
533		lws_metric_policy_dyn_destroy(dm, 0); /* don't keep */
534
535	} lws_end_foreach_dll_safe(d, d1);
536
537	/* destroy metrics with no current policy too... */
538
539	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
540				   ctx->owner_mtr_no_pol.head) {
541		lws_metric_t *mt = lws_container_of(d, lws_metric_t, list);
542
543		lws_metric_destroy(&mt, 0); /* don't keep */
544
545	} lws_end_foreach_dll_safe(d, d1);
546
547	/* ... that's the whole allocated metrics footprint gone... */
548}
549
550int
551lws_metrics_hist_bump_(lws_metric_pub_t *pub, const char *name)
552{
553	lws_metric_bucket_t *buck = pub->u.hist.head;
554	size_t nl = strlen(name);
555	char *nm;
556
557	if (!(pub->flags & LWSMTFL_REPORT_HIST)) {
558		lwsl_err("%s: %s not histogram: flags %d\n", __func__,
559				pub->name, pub->flags);
560		assert(0);
561	}
562	assert(nl < 255);
563
564	pub->us_last = lws_now_usecs();
565	if (!pub->us_first)
566		pub->us_first = pub->us_last;
567
568	while (buck) {
569		if (lws_metric_bucket_name_len(buck) == nl &&
570		    !strcmp(name, lws_metric_bucket_name(buck))) {
571			buck->count++;
572			goto happy;
573		}
574		buck = buck->next;
575	}
576
577	buck = lws_malloc(sizeof(*buck) + nl + 2, __func__);
578	if (!buck)
579		return 1;
580
581	nm = (char *)buck + sizeof(*buck);
582	/* length byte at beginning of name, avoid struct alignment overhead */
583	*nm = (char)nl;
584	memcpy(nm + 1, name, nl + 1);
585
586	buck->next = pub->u.hist.head;
587	pub->u.hist.head = buck;
588	buck->count = 1;
589	pub->u.hist.list_size++;
590
591happy:
592	pub->u.hist.total_count++;
593
594	return 0;
595}
596
597int
598lws_metrics_hist_bump_describe_wsi(struct lws *wsi, lws_metric_pub_t *pub,
599				   const char *name)
600{
601	char desc[192], d1[48], *p = desc, *end = desc + sizeof(desc);
602
603#if defined(LWS_WITH_SECURE_STREAMS)
604#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
605	if (wsi->client_bound_sspc) {
606		lws_sspc_handle_t *h = (lws_sspc_handle_t *)wsi->a.opaque_user_data;
607		if (h)
608			p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "ss=\"%s\",",
609				  h->ssi.streamtype);
610	} else
611		if (wsi->client_proxy_onward) {
612			lws_ss_handle_t *h = (lws_ss_handle_t *)wsi->a.opaque_user_data;
613			struct conn *conn = h->conn_if_sspc_onw;
614
615			if (conn && conn->ss)
616				p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
617						  "ss=\"%s\",",
618						  conn->ss->info.streamtype);
619		} else
620#endif
621	if (wsi->for_ss) {
622		lws_ss_handle_t *h = (lws_ss_handle_t *)wsi->a.opaque_user_data;
623		if (h)
624			p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "ss=\"%s\",",
625				  h->info.streamtype);
626	}
627#endif
628
629#if defined(LWS_WITH_CLIENT)
630	if (wsi->stash && wsi->stash->cis[CIS_HOST])
631		p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "hostname=\"%s\",",
632				wsi->stash->cis[CIS_HOST]);
633#endif
634
635	lws_sa46_write_numeric_address(&wsi->sa46_peer, d1, sizeof(d1));
636	p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "peer=\"%s\",", d1);
637
638	p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "%s", name);
639
640	lws_metrics_hist_bump_(pub, desc);
641
642	return 0;
643}
644
645int
646lws_metrics_foreach(struct lws_context *ctx, void *user,
647		    int (*cb)(lws_metric_pub_t *pub, void *user))
648{
649	int n;
650
651	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
652				   ctx->owner_mtr_no_pol.head) {
653		lws_metric_t *mt = lws_container_of(d, lws_metric_t, list);
654
655		n = cb(lws_metrics_priv_to_pub(mt), user);
656		if (n)
657			return n;
658
659	} lws_end_foreach_dll_safe(d, d1);
660
661	lws_start_foreach_dll_safe(struct lws_dll2 *, d2, d3,
662				   ctx->owner_mtr_dynpol.head) {
663		lws_metric_policy_dyn_t *dm =
664			lws_container_of(d2, lws_metric_policy_dyn_t, list);
665
666		lws_start_foreach_dll_safe(struct lws_dll2 *, e, e1,
667					   dm->owner.head) {
668
669			lws_metric_t *mt = lws_container_of(e, lws_metric_t, list);
670
671			n = cb(lws_metrics_priv_to_pub(mt), user);
672			if (n)
673				return n;
674
675		} lws_end_foreach_dll_safe(e, e1);
676
677	} lws_end_foreach_dll_safe(d2, d3);
678
679	return 0;
680}
681
682static int
683lws_metrics_dump_cb(lws_metric_pub_t *pub, void *user)
684{
685	struct lws_context *ctx = (struct lws_context *)user;
686	int n;
687
688	if (!ctx->system_ops || !ctx->system_ops->metric_report)
689		return 0;
690
691	/*
692	 * return nonzero to reset stats
693	 */
694
695	n = ctx->system_ops->metric_report(pub);
696
697	/* track when we dumped it... */
698
699	pub->us_first = pub->us_dumped = lws_now_usecs();
700	pub->us_last = 0;
701
702	if (!n)
703		return 0;
704
705	/* ... and clear it back to 0 */
706
707	if (pub->flags & LWSMTFL_REPORT_HIST) {
708		lws_metric_bucket_t *b = pub->u.hist.head, *b1;
709		pub->u.hist.head = NULL;
710
711		while (b) {
712			b1 = b->next;
713			lws_free(b);
714			b = b1;
715		}
716		pub->u.hist.total_count = 0;
717		pub->u.hist.list_size = 0;
718	} else
719		memset(&pub->u.agg, 0, sizeof(pub->u.agg));
720
721	return 0;
722}
723
724void
725lws_metrics_dump(struct lws_context *ctx)
726{
727	lws_metrics_foreach(ctx, ctx, lws_metrics_dump_cb);
728}
729
730static int
731_lws_metrics_format(lws_metric_pub_t *pub, lws_usec_t now, int gng,
732		    char *buf, size_t len)
733{
734	const lws_humanize_unit_t *schema = humanize_schema_si;
735	char *end = buf + len - 1, *obuf = buf;
736
737	if (pub->flags & LWSMTFL_REPORT_DUTY_WALLCLOCK_US)
738		schema = humanize_schema_us;
739
740	if (!(pub->flags & LWSMTFL_REPORT_MEAN)) {
741		/* only the sum is meaningful */
742		if (pub->flags & LWSMTFL_REPORT_DUTY_WALLCLOCK_US) {
743
744			buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), " %u, ",
745						(unsigned int)pub->u.agg.count[gng]);
746
747			buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
748					    (uint64_t)pub->u.agg.sum[gng],
749					    humanize_schema_us);
750
751			buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), " / ");
752
753			buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
754					    (uint64_t)(now - pub->us_first),
755					    humanize_schema_us);
756
757			buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
758					    " (%d%%)", (int)((100 * pub->u.agg.sum[gng]) /
759						(unsigned long)(now - pub->us_first)));
760		} else {
761			/* it's a monotonic ordinal, like total tx */
762			buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "(%u) ",
763					(unsigned int)pub->u.agg.count[gng]);
764			buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
765					    (uint64_t)pub->u.agg.sum[gng],
766					    humanize_schema_si);
767		}
768
769	} else {
770		buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%u, mean: ", (unsigned int)pub->u.agg.count[gng]);
771		/* the average over the period is meaningful */
772		buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
773				    (uint64_t)(pub->u.agg.count[gng] ?
774					 pub->u.agg.sum[gng] / pub->u.agg.count[gng] : 0),
775				    schema);
776	}
777
778	return lws_ptr_diff(buf, obuf);
779}
780
781int
782lws_metrics_format(lws_metric_pub_t *pub, lws_metric_bucket_t **sub, char *buf, size_t len)
783{
784	char *end = buf + len - 1, *obuf = buf;
785	lws_usec_t t = lws_now_usecs();
786	const lws_humanize_unit_t *schema = humanize_schema_si;
787
788	if (pub->flags & LWSMTFL_REPORT_DUTY_WALLCLOCK_US)
789		schema = humanize_schema_us;
790
791	if (pub->flags & LWSMTFL_REPORT_HIST) {
792
793		if (*sub == NULL)
794			return 0;
795
796		if (*sub) {
797			buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
798					    "%s{%s} %llu", pub->name,
799					    lws_metric_bucket_name(*sub),
800					    (unsigned long long)(*sub)->count);
801
802			*sub = (*sub)->next;
803		}
804
805		goto happy;
806	}
807
808	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s: ",
809				pub->name);
810
811	if (!pub->u.agg.count[METRES_GO] && !pub->u.agg.count[METRES_NOGO])
812		return 0;
813
814	if (pub->u.agg.count[METRES_GO]) {
815		if (!(pub->flags & LWSMTFL_REPORT_ONLY_GO))
816			buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
817					    "Go: ");
818		buf += _lws_metrics_format(pub, t, METRES_GO, buf,
819					   lws_ptr_diff_size_t(end, buf));
820	}
821
822	if (!(pub->flags & LWSMTFL_REPORT_ONLY_GO) && pub->u.agg.count[METRES_NOGO]) {
823		buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), ", NoGo: ");
824		buf += _lws_metrics_format(pub, t, METRES_NOGO, buf,
825					   lws_ptr_diff_size_t(end, buf));
826	}
827
828	if (pub->flags & LWSMTFL_REPORT_MEAN) {
829		buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), ", min: ");
830		buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf), pub->u.agg.min,
831				    schema);
832		buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), ", max: ");
833		buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf), pub->u.agg.max,
834				    schema);
835	}
836
837happy:
838	if (pub->flags & LWSMTFL_REPORT_HIST)
839		return 1;
840
841	*sub = NULL;
842
843	return lws_ptr_diff(buf, obuf);
844}
845
846/*
847 * We want to, at least internally, record an event... depending on the policy,
848 * that might cause us to call through to the lws_system apis, or just update
849 * our local stats about it and dump at the next periodic chance (also set by
850 * the policy)
851 */
852
853void
854lws_metric_event(lws_metric_t *mt, char go_nogo, u_mt_t val)
855{
856	lws_metric_pub_t *pub;
857
858	assert((go_nogo & 0xfe) == 0);
859
860	if (!mt)
861		return;
862
863	pub = lws_metrics_priv_to_pub(mt);
864	assert(!(pub->flags & LWSMTFL_REPORT_HIST));
865
866	pub->us_last = lws_now_usecs();
867	if (!pub->us_first)
868		pub->us_first = pub->us_last;
869	pub->u.agg.count[(int)go_nogo]++;
870	pub->u.agg.sum[(int)go_nogo] += val;
871	if (val > pub->u.agg.max)
872		pub->u.agg.max = val;
873	if (val < pub->u.agg.min)
874		pub->u.agg.min = val;
875
876	if (pub->flags & LWSMTFL_REPORT_OOB)
877		lws_metrics_report_and_maybe_clear(mt->ctx, pub);
878}
879
880
881void
882lws_metrics_hist_bump_priv_tagged(lws_metric_pub_t *mt, lws_dll2_owner_t *tow,
883				  lws_dll2_owner_t *tow2)
884{
885	char qual[192];
886	size_t p;
887
888	p = lws_metrics_tags_serialize(tow, qual, sizeof(qual));
889	if (tow2)
890		lws_metrics_tags_serialize(tow2, qual + p,
891				sizeof(qual) - p);
892
893	lws_metrics_hist_bump(mt, qual);
894}
895