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
28 int
lws_metrics_tag_add(lws_dll2_owner_t * owner,const char * name,const char * val)29 lws_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
68 int
lws_metrics_tag_wsi_add(struct lws * wsi,const char * name,const char * val)69 lws_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)
77 int
lws_metrics_tag_ss_add(struct lws_ss_handle * ss,const char * name,const char * val)78 lws_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)
84 int
lws_metrics_tag_sspc_add(struct lws_sspc_handle * sspc,const char * name,const char * val)85 lws_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
94 void
lws_metrics_tags_destroy(lws_dll2_owner_t * owner)95 lws_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
108 size_t
lws_metrics_tags_serialize(lws_dll2_owner_t * owner,char * buf,size_t len)109 lws_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
130 const char *
lws_metrics_tag_get(lws_dll2_owner_t * owner,const char * name)131 lws_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
146 static int
147 lws_metrics_dump_cb(lws_metric_pub_t *pub, void *user);
148
149 static void
lws_metrics_report_and_maybe_clear(struct lws_context * ctx,lws_metric_pub_t * pub)150 lws_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
158 static void
lws_metrics_periodic_cb(lws_sorted_usec_list_t * sul)159 lws_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
200 lws_metric_policy_dyn_t *
lws_metrics_policy_dyn_create(struct lws_context * ctx,const lws_metric_policy_t * po)201 lws_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
225 lws_metric_policy_dyn_t *
lws_metrics_policy_get_dyn(struct lws_context * ctx,const lws_metric_policy_t * po)226 lws_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
247 static int
lws_metrics_check_in_policy(const char * polstring,const char * name)248 lws_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
278 static const lws_metric_policy_t *
lws_metrics_find_policy(struct lws_context * ctx,const char * name)279 lws_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
309 lws_metric_t *
lws_metric_create(struct lws_context * ctx,uint8_t flags,const char * name)310 lws_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
385 const lws_metric_policy_t *
lws_metric_get_policy(lws_metric_t * mt)386 lws_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
410 void
lws_metric_rebind_policies(struct lws_context * ctx)411 lws_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
436 int
lws_metric_destroy(lws_metric_t ** pmt,int keep)437 lws_metric_destroy(lws_metric_t **pmt, int keep)
438 {
439 lws_metric_t *mt = *pmt;
440 lws_metric_pub_t *pub = lws_metrics_priv_to_pub(mt);
441
442 if (!mt)
443 return 0;
444
445 lws_dll2_remove(&mt->list);
446
447 if (keep) {
448 lws_dll2_add_tail(&mt->list, &mt->ctx->owner_mtr_no_pol);
449
450 return 0;
451 }
452
453 if (pub->flags & LWSMTFL_REPORT_HIST) {
454 lws_metric_bucket_t *b = pub->u.hist.head, *b1;
455
456 pub->u.hist.head = NULL;
457
458 while (b) {
459 b1 = b->next;
460 lws_free(b);
461 b = b1;
462 }
463 }
464
465 lws_free(mt);
466 *pmt = NULL;
467
468 return 0;
469 }
470
471 /*
472 * Allow an existing metric to have its reporting policy changed at runtime
473 */
474
475 int
lws_metric_switch_policy(lws_metric_t * mt,const char * polname)476 lws_metric_switch_policy(lws_metric_t *mt, const char *polname)
477 {
478 const lws_metric_policy_t *po;
479 lws_metric_policy_dyn_t *dmp;
480
481 po = lws_metrics_find_policy(mt->ctx, polname);
482 if (!po)
483 return 1;
484
485 dmp = lws_metrics_policy_get_dyn(mt->ctx, po);
486 if (!dmp)
487 return 1;
488
489 lws_dll2_remove(&mt->list);
490 lws_dll2_add_tail(&mt->list, &dmp->owner);
491
492 return 0;
493 }
494
495 /*
496 * If keep is set, don't destroy existing metrics objects, just detach them
497 * from the policy being deleted and keep track of them on ctx->
498 * owner_mtr_no_pol
499 */
500
501 void
lws_metric_policy_dyn_destroy(lws_metric_policy_dyn_t * dm,int keep)502 lws_metric_policy_dyn_destroy(lws_metric_policy_dyn_t *dm, int keep)
503 {
504 lws_sul_cancel(&dm->sul);
505
506 lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, dm->owner.head) {
507 lws_metric_t *m = lws_container_of(d, lws_metric_t, list);
508
509 lws_metric_destroy(&m, keep);
510
511 } lws_end_foreach_dll_safe(d, d1);
512
513 lws_sul_cancel(&dm->sul);
514
515 lws_dll2_remove(&dm->list);
516 lws_free(dm);
517 }
518
519 /*
520 * Destroy all dynamic metrics policies, deinit any metrics still using them
521 */
522
523 void
lws_metrics_destroy(struct lws_context * ctx)524 lws_metrics_destroy(struct lws_context *ctx)
525 {
526 lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
527 ctx->owner_mtr_dynpol.head) {
528 lws_metric_policy_dyn_t *dm =
529 lws_container_of(d, lws_metric_policy_dyn_t, list);
530
531 lws_metric_policy_dyn_destroy(dm, 0); /* don't keep */
532
533 } lws_end_foreach_dll_safe(d, d1);
534
535 /* destroy metrics with no current policy too... */
536
537 lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
538 ctx->owner_mtr_no_pol.head) {
539 lws_metric_t *mt = lws_container_of(d, lws_metric_t, list);
540
541 lws_metric_destroy(&mt, 0); /* don't keep */
542
543 } lws_end_foreach_dll_safe(d, d1);
544
545 /* ... that's the whole allocated metrics footprint gone... */
546 }
547
548 int
lws_metrics_hist_bump_(lws_metric_pub_t * pub,const char * name)549 lws_metrics_hist_bump_(lws_metric_pub_t *pub, const char *name)
550 {
551 lws_metric_bucket_t *buck = pub->u.hist.head;
552 size_t nl = strlen(name);
553 char *nm;
554
555 if (!(pub->flags & LWSMTFL_REPORT_HIST)) {
556 lwsl_err("%s: %s not histogram: flags %d\n", __func__,
557 pub->name, pub->flags);
558 assert(0);
559 }
560 assert(nl < 255);
561
562 pub->us_last = lws_now_usecs();
563 if (!pub->us_first)
564 pub->us_first = pub->us_last;
565
566 while (buck) {
567 if (lws_metric_bucket_name_len(buck) == nl &&
568 !strcmp(name, lws_metric_bucket_name(buck))) {
569 buck->count++;
570 goto happy;
571 }
572 buck = buck->next;
573 }
574
575 buck = lws_malloc(sizeof(*buck) + nl + 2, __func__);
576 if (!buck)
577 return 1;
578
579 nm = (char *)buck + sizeof(*buck);
580 /* length byte at beginning of name, avoid struct alignment overhead */
581 *nm = (char)nl;
582 memcpy(nm + 1, name, nl + 1);
583
584 buck->next = pub->u.hist.head;
585 pub->u.hist.head = buck;
586 buck->count = 1;
587 pub->u.hist.list_size++;
588
589 happy:
590 pub->u.hist.total_count++;
591
592 return 0;
593 }
594
595 int
lws_metrics_hist_bump_describe_wsi(struct lws * wsi,lws_metric_pub_t * pub,const char * name)596 lws_metrics_hist_bump_describe_wsi(struct lws *wsi, lws_metric_pub_t *pub,
597 const char *name)
598 {
599 char desc[192], d1[48], *p = desc, *end = desc + sizeof(desc);
600
601 #if defined(LWS_WITH_SECURE_STREAMS)
602 #if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
603 if (wsi->client_bound_sspc) {
604 lws_sspc_handle_t *h = (lws_sspc_handle_t *)wsi->a.opaque_user_data;
605 if (h)
606 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "ss=\"%s\",",
607 h->ssi.streamtype);
608 } else
609 if (wsi->client_proxy_onward) {
610 lws_ss_handle_t *h = (lws_ss_handle_t *)wsi->a.opaque_user_data;
611 struct conn *conn = h->conn_if_sspc_onw;
612
613 if (conn && conn->ss)
614 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
615 "ss=\"%s\",",
616 conn->ss->info.streamtype);
617 } else
618 #endif
619 if (wsi->for_ss) {
620 lws_ss_handle_t *h = (lws_ss_handle_t *)wsi->a.opaque_user_data;
621 if (h)
622 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "ss=\"%s\",",
623 h->info.streamtype);
624 }
625 #endif
626
627 #if defined(LWS_WITH_CLIENT)
628 if (wsi->stash && wsi->stash->cis[CIS_HOST])
629 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "hostname=\"%s\",",
630 wsi->stash->cis[CIS_HOST]);
631 #endif
632
633 lws_sa46_write_numeric_address(&wsi->sa46_peer, d1, sizeof(d1));
634 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "peer=\"%s\",", d1);
635
636 p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "%s", name);
637
638 lws_metrics_hist_bump_(pub, desc);
639
640 return 0;
641 }
642
643 int
lws_metrics_foreach(struct lws_context * ctx,void * user,int (* cb)(lws_metric_pub_t * pub,void * user))644 lws_metrics_foreach(struct lws_context *ctx, void *user,
645 int (*cb)(lws_metric_pub_t *pub, void *user))
646 {
647 int n;
648
649 lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
650 ctx->owner_mtr_no_pol.head) {
651 lws_metric_t *mt = lws_container_of(d, lws_metric_t, list);
652
653 n = cb(lws_metrics_priv_to_pub(mt), user);
654 if (n)
655 return n;
656
657 } lws_end_foreach_dll_safe(d, d1);
658
659 lws_start_foreach_dll_safe(struct lws_dll2 *, d2, d3,
660 ctx->owner_mtr_dynpol.head) {
661 lws_metric_policy_dyn_t *dm =
662 lws_container_of(d2, lws_metric_policy_dyn_t, list);
663
664 lws_start_foreach_dll_safe(struct lws_dll2 *, e, e1,
665 dm->owner.head) {
666
667 lws_metric_t *mt = lws_container_of(e, lws_metric_t, list);
668
669 n = cb(lws_metrics_priv_to_pub(mt), user);
670 if (n)
671 return n;
672
673 } lws_end_foreach_dll_safe(e, e1);
674
675 } lws_end_foreach_dll_safe(d2, d3);
676
677 return 0;
678 }
679
680 static int
lws_metrics_dump_cb(lws_metric_pub_t * pub,void * user)681 lws_metrics_dump_cb(lws_metric_pub_t *pub, void *user)
682 {
683 struct lws_context *ctx = (struct lws_context *)user;
684 int n;
685
686 if (!ctx->system_ops || !ctx->system_ops->metric_report)
687 return 0;
688
689 /*
690 * return nonzero to reset stats
691 */
692
693 n = ctx->system_ops->metric_report(pub);
694
695 /* track when we dumped it... */
696
697 pub->us_first = pub->us_dumped = lws_now_usecs();
698 pub->us_last = 0;
699
700 if (!n)
701 return 0;
702
703 /* ... and clear it back to 0 */
704
705 if (pub->flags & LWSMTFL_REPORT_HIST) {
706 lws_metric_bucket_t *b = pub->u.hist.head, *b1;
707 pub->u.hist.head = NULL;
708
709 while (b) {
710 b1 = b->next;
711 lws_free(b);
712 b = b1;
713 }
714 pub->u.hist.total_count = 0;
715 pub->u.hist.list_size = 0;
716 } else
717 memset(&pub->u.agg, 0, sizeof(pub->u.agg));
718
719 return 0;
720 }
721
722 void
lws_metrics_dump(struct lws_context * ctx)723 lws_metrics_dump(struct lws_context *ctx)
724 {
725 lws_metrics_foreach(ctx, ctx, lws_metrics_dump_cb);
726 }
727
728 static int
_lws_metrics_format(lws_metric_pub_t * pub,lws_usec_t now,int gng,char * buf,size_t len)729 _lws_metrics_format(lws_metric_pub_t *pub, lws_usec_t now, int gng,
730 char *buf, size_t len)
731 {
732 const lws_humanize_unit_t *schema = humanize_schema_si;
733 char *end = buf + len - 1, *obuf = buf;
734
735 if (pub->flags & LWSMTFL_REPORT_DUTY_WALLCLOCK_US)
736 schema = humanize_schema_us;
737
738 if (!(pub->flags & LWSMTFL_REPORT_MEAN)) {
739 /* only the sum is meaningful */
740 if (pub->flags & LWSMTFL_REPORT_DUTY_WALLCLOCK_US) {
741
742 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), " %u, ",
743 (unsigned int)pub->u.agg.count[gng]);
744
745 buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
746 (uint64_t)pub->u.agg.sum[gng],
747 humanize_schema_us);
748
749 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), " / ");
750
751 buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
752 (uint64_t)(now - pub->us_first),
753 humanize_schema_us);
754
755 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
756 " (%d%%)", (int)((100 * pub->u.agg.sum[gng]) /
757 (unsigned long)(now - pub->us_first)));
758 } else {
759 /* it's a monotonic ordinal, like total tx */
760 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "(%u) ",
761 (unsigned int)pub->u.agg.count[gng]);
762 buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
763 (uint64_t)pub->u.agg.sum[gng],
764 humanize_schema_si);
765 }
766
767 } else {
768 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%u, mean: ", (unsigned int)pub->u.agg.count[gng]);
769 /* the average over the period is meaningful */
770 buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf),
771 (uint64_t)(pub->u.agg.count[gng] ?
772 pub->u.agg.sum[gng] / pub->u.agg.count[gng] : 0),
773 schema);
774 }
775
776 return lws_ptr_diff(buf, obuf);
777 }
778
779 int
lws_metrics_format(lws_metric_pub_t * pub,lws_metric_bucket_t ** sub,char * buf,size_t len)780 lws_metrics_format(lws_metric_pub_t *pub, lws_metric_bucket_t **sub, char *buf, size_t len)
781 {
782 char *end = buf + len - 1, *obuf = buf;
783 lws_usec_t t = lws_now_usecs();
784 const lws_humanize_unit_t *schema = humanize_schema_si;
785
786 if (pub->flags & LWSMTFL_REPORT_DUTY_WALLCLOCK_US)
787 schema = humanize_schema_us;
788
789 if (pub->flags & LWSMTFL_REPORT_HIST) {
790
791 if (*sub == NULL)
792 return 0;
793
794 if (*sub) {
795 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
796 "%s{%s} %llu", pub->name,
797 lws_metric_bucket_name(*sub),
798 (unsigned long long)(*sub)->count);
799
800 *sub = (*sub)->next;
801 }
802
803 goto happy;
804 }
805
806 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s: ",
807 pub->name);
808
809 if (!pub->u.agg.count[METRES_GO] && !pub->u.agg.count[METRES_NOGO])
810 return 0;
811
812 if (pub->u.agg.count[METRES_GO]) {
813 if (!(pub->flags & LWSMTFL_REPORT_ONLY_GO))
814 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
815 "Go: ");
816 buf += _lws_metrics_format(pub, t, METRES_GO, buf,
817 lws_ptr_diff_size_t(end, buf));
818 }
819
820 if (!(pub->flags & LWSMTFL_REPORT_ONLY_GO) && pub->u.agg.count[METRES_NOGO]) {
821 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), ", NoGo: ");
822 buf += _lws_metrics_format(pub, t, METRES_NOGO, buf,
823 lws_ptr_diff_size_t(end, buf));
824 }
825
826 if (pub->flags & LWSMTFL_REPORT_MEAN) {
827 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), ", min: ");
828 buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf), pub->u.agg.min,
829 schema);
830 buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), ", max: ");
831 buf += lws_humanize(buf, lws_ptr_diff_size_t(end, buf), pub->u.agg.max,
832 schema);
833 }
834
835 happy:
836 if (pub->flags & LWSMTFL_REPORT_HIST)
837 return 1;
838
839 *sub = NULL;
840
841 return lws_ptr_diff(buf, obuf);
842 }
843
844 /*
845 * We want to, at least internally, record an event... depending on the policy,
846 * that might cause us to call through to the lws_system apis, or just update
847 * our local stats about it and dump at the next periodic chance (also set by
848 * the policy)
849 */
850
851 void
lws_metric_event(lws_metric_t * mt,char go_nogo,u_mt_t val)852 lws_metric_event(lws_metric_t *mt, char go_nogo, u_mt_t val)
853 {
854 lws_metric_pub_t *pub;
855
856 assert((go_nogo & 0xfe) == 0);
857
858 if (!mt)
859 return;
860
861 pub = lws_metrics_priv_to_pub(mt);
862 assert(!(pub->flags & LWSMTFL_REPORT_HIST));
863
864 pub->us_last = lws_now_usecs();
865 if (!pub->us_first)
866 pub->us_first = pub->us_last;
867 pub->u.agg.count[(int)go_nogo]++;
868 pub->u.agg.sum[(int)go_nogo] += val;
869 if (val > pub->u.agg.max)
870 pub->u.agg.max = val;
871 if (val < pub->u.agg.min)
872 pub->u.agg.min = val;
873
874 if (pub->flags & LWSMTFL_REPORT_OOB)
875 lws_metrics_report_and_maybe_clear(mt->ctx, pub);
876 }
877
878
879 void
lws_metrics_hist_bump_priv_tagged(lws_metric_pub_t * mt,lws_dll2_owner_t * tow,lws_dll2_owner_t * tow2)880 lws_metrics_hist_bump_priv_tagged(lws_metric_pub_t *mt, lws_dll2_owner_t *tow,
881 lws_dll2_owner_t *tow2)
882 {
883 char qual[192];
884 size_t p;
885
886 p = lws_metrics_tags_serialize(tow, qual, sizeof(qual));
887 if (tow2)
888 lws_metrics_tags_serialize(tow2, qual + p,
889 sizeof(qual) - p);
890
891 lws_metrics_hist_bump(mt, qual);
892 }
893