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