From: Willy Tarreau Date: Mon, 21 Oct 2024 16:51:55 +0000 (+0200) Subject: MINOR: debug: add "debug dev counters" to list code counters X-Git-Tag: v3.1-dev11~86 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f2c415cec1861322d3745edca959d164955fcfe0;p=thirdparty%2Fhaproxy.git MINOR: debug: add "debug dev counters" to list code counters Issuing "debug dev counters" on the CLI will now scan all existing counters, and report their count, type, location, function name, the condition and an optional comment passed to the macro. The command takes a number of arguments: - "show": this is the default, it will just list the counters - "reset": will reset the matching counters instead of listing them - "all": by default, only non-zero counters are listed. With "all", they are all listed - "bug": restrict the reset or dump to counters of type "BUG" (BUG_ON usually) - "chk": restrict the reset or dump to counters of type "CHK" (CHECK_IF) - "cnt": restrict the reset or dump to counters of type "CNT" (COUNT_IF) The types may be cumulated, and the option entered in any order. Here's an example of the output of "debug dev counters show all bug": Count Type Location function(): "condition" [comment] 0 BUG ring.h:114 ring_dup(): "max > ring_size(dst)" 0 BUG vecpair.h:223 vp_getblk_ofs(): "ofs >= v1->len + v2->len" 0 BUG buf.h:395 b_add(): "b->data + count > b->size" 0 BUG buf.h:106 b_room(): "b->data > b->size" 0 BUG task.h:328 _task_queue(): "(ulong)caller & 1" 0 BUG task.h:324 _task_queue(): "task->tid != tid" 0 BUG task.h:313 _task_queue(): "(ulong)caller & 1" (...) This is expected to be convenient combined with the use and abuse of COUNT_IF() at select locations. --- diff --git a/src/debug.c b/src/debug.c index 17fe80fa32..49049f6dc0 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2103,6 +2103,127 @@ static void debug_release_memstats(struct appctx *appctx) } #endif +#if !defined(USE_OBSOLETE_LINKER) + +/* CLI state for "debug dev counters" */ +struct dev_cnt_ctx { + struct debug_count *start, *stop; /* begin/end of dump */ + int types; /* OR mask of 1<show_all = 1; + continue; + } + else if (strcmp(args[arg], "show") == 0) { + action = 0; + continue; + } + else if (strcmp(args[arg], "bug") == 0) { + ctx->types |= 1 << DBG_BUG; + continue; + } + else if (strcmp(args[arg], "chk") == 0) { + ctx->types |= 1 << DBG_BUG_ONCE; + continue; + } + else if (strcmp(args[arg], "cnt") == 0) { + ctx->types |= 1 << DBG_COUNT_IF; + continue; + } + else + return cli_err(appctx, "Expects an optional action ('reset','show'), optional types ('bug','chk','cnt') and optionally 'all' to even dump null counters.\n"); + } + + if (action == 1) { // reset + struct debug_count *ptr; + + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + + for (ptr = &__start_dbg_cnt; ptr < &__stop_dbg_cnt; ptr++) { + if (ctx->types && !(ctx->types & (1 << ptr->type))) + continue; + _HA_ATOMIC_STORE(&ptr->count, 0); + } + return 1; + } + + /* OK it's a show, let's dump relevant counters */ + ctx->start = &__start_dbg_cnt; + ctx->stop = &__stop_dbg_cnt; + return 0; +} + +/* CLI I/O handler for the "debug dev counters" command using a dev_cnt_ctx + * found in appctx->svcctx. Dumps all mem_stats structs referenced by pointers + * located between ->start and ->stop. Dumps all entries if ->show_all != 0, + * otherwise only non-zero calls. + */ +static int debug_iohandler_counters(struct appctx *appctx) +{ + const char *bug_type[DBG_COUNTER_TYPES] = { + [DBG_BUG] = "BUG", + [DBG_BUG_ONCE] = "CHK", + [DBG_COUNT_IF] = "CNT", + }; + struct dev_cnt_ctx *ctx = appctx->svcctx; + struct debug_count *ptr; + int ret = 1; + + /* we have two inner loops here, one for the proxy, the other one for + * the buffer. + */ + chunk_printf(&trash, "Count Type Location function(): \"condition\" [comment]\n"); + for (ptr = ctx->start; ptr != ctx->stop; ptr++) { + const char *p, *name; + + if (ctx->types && !(ctx->types & (1 << ptr->type))) + continue; + + if (!ptr->count && !ctx->show_all) + continue; + + for (p = name = ptr->file; *p; p++) { + if (*p == '/') + name = p + 1; + } + + if (ptr->type < DBG_COUNTER_TYPES) + chunk_appendf(&trash, "%-10u %3s %s:%d %s(): %s\n", + ptr->count, bug_type[ptr->type], + name, ptr->line, ptr->func, ptr->desc); + + if (applet_putchk(appctx, &trash) == -1) { + ctx->start = ptr; + ret = 0; + goto end; + } + } + + /* we could even dump a summary here if needed, returning ret=0 */ + end: + return ret; +} +#endif /* USE_OBSOLETE_LINKER */ + #ifdef USE_THREAD_DUMP /* handles DEBUGSIG to dump the state of the thread it's working on. This is @@ -2516,6 +2637,9 @@ static struct cli_kw_list cli_kws = {{ },{ {{ "debug", "dev", "bug", NULL }, "debug dev bug : call BUG_ON() and crash", debug_parse_cli_bug, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "check", NULL }, "debug dev check : call CHECK_IF() and possibly crash", debug_parse_cli_check, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "close", NULL }, "debug dev close : close this file descriptor", debug_parse_cli_close, NULL, NULL, NULL, ACCESS_EXPERT }, +#if !defined(USE_OBSOLETE_LINKER) + {{ "debug", "dev", "counters", NULL }, "debug dev counters [all|bug|cnt|chk|?]* : dump/reset rare event counters", debug_parse_cli_counters, debug_iohandler_counters, NULL, NULL, 0 }, +#endif {{ "debug", "dev", "deadlock", NULL }, "debug dev deadlock [nbtask] : deadlock between this number of tasks", debug_parse_cli_deadlock, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "delay", NULL }, "debug dev delay [ms] : sleep this long", debug_parse_cli_delay, NULL, NULL, NULL, ACCESS_EXPERT }, #if defined(DEBUG_DEV)