From: Alexander Stephan Date: Mon, 6 Jul 2026 09:20:48 +0000 (+0000) Subject: MINOR: server: add 'clear counters server /' CLI command X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=8b2d5dbdd00db32f5b01994dfe7cd34fdea89f8a;p=thirdparty%2Fhaproxy.git MINOR: server: add 'clear counters server /' CLI command Add a CLI command to reset the statistics counters of a single server: clear counters server / [force] Motivation: 'clear counters all' resets counters across every proxy and server in the process, which is too blunt for common operational needs. In particular, when a server slot is being reused to represent a different logical entity (e.g. a different Kubernetes pod occupying the same slot after 'set server / name'), the operator needs per-slot counter attribution and cannot afford to wipe the entire process' stats. The command requires ACCESS_LVL_ADMIN, like 'clear counters all'. It resets cumulative counters (not just the max gauges cleared by the base 'clear counters'), so restricting it to admin prevents an operator-level socket from hiding accumulated activity. Like 'clear counters' / 'clear counters all', it is not gated by the server's administrative state: the reset only zeroes counter values and does not touch the server object or its runtime state, so it is safe to issue on a live server (a concurrent counter increment races on a value exactly as it already does for 'clear counters all'). When the server's counters are registered in a shared-memory stats file object (COUNTERS_SHARED_F_LOCAL not set), clearing them breaks the monotonicity that monitoring tools consuming the shared stats rely on and affects every process attached to the object. Such a clear is therefore refused unless the optional 'force' argument is given. The reset covers both the native server counters, via counters_be_reset() introduced earlier in this series (which safely zeroes the per-tgroup accumulated counter contents while preserving the shared.tg pointer array), and the module-registered extra counters, via the new srv_stats_clear_extra_counters() helper. Note on the dispatch: 'clear counters' is a two-word CLI keyword, and cli_find_kw() matches the first keyword whose tokens are all consumed, so a separate three-word 'clear counters server' keyword would be shadowed (or would shadow) depending on registration order. To avoid that ambiguity the new sub-command is dispatched from within cli_parse_clear_counters() when args[2] == "server", mirroring how the existing "all" argument is handled. The per-server worker lives in server.c (cli_clear_counters_server()) where the server lookup and lock helpers are available. --- diff --git a/include/haproxy/server.h b/include/haproxy/server.h index 17764aff9..2ef7e0e62 100644 --- a/include/haproxy/server.h +++ b/include/haproxy/server.h @@ -77,6 +77,7 @@ int srv_set_addr_via_libc(struct server *srv, int *err_code); int srv_postinit(struct server *srv); int srv_init_addr(void); struct server *cli_find_server(struct appctx *appctx, char *arg); +int cli_clear_counters_server(struct appctx *appctx, char *arg, int force); struct server *new_server(struct proxy *proxy); void srv_take(struct server *srv); void srv_drop(struct server *srv); diff --git a/include/haproxy/stats-proxy.h b/include/haproxy/stats-proxy.h index 06060d7fa..e21de0ffc 100644 --- a/include/haproxy/stats-proxy.h +++ b/include/haproxy/stats-proxy.h @@ -8,11 +8,13 @@ struct htx; struct stconn; struct uri_auth; struct proxy; +struct server; int stats_dump_proxies(struct stconn *sc, struct buffer *buf, struct htx *htx); void proxy_stats_clear_counters(int clrall, struct list *stat_modules); int stats_proxy_in_scope(const struct proxy *px, const struct uri_auth *uri, const struct proxy *http_px); +void srv_stats_clear_extra_counters(struct server *sv); #endif /* _HAPROXY_STATS_PROXY_H */ diff --git a/src/server.c b/src/server.c index 348679537..494fa5e60 100644 --- a/src/server.c +++ b/src/server.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -6782,6 +6783,58 @@ out: return 1; } +/* Reset the statistics counters of a single server, invoked from the + * "clear counters server / [force]" CLI command (dispatched + * by cli_parse_clear_counters() in stats.c, since "clear counters" is a + * two-word keyword that would otherwise shadow a three-word variant). + * + * The command is not gated by the server's administrative state: like + * "clear counters" / "clear counters all", it only zeroes counter values + * and does not touch the server object or its runtime state, so it is safe + * to issue on a live server (a concurrent counter increment races on a + * value exactly as it already does for "clear counters all"). This is + * useful when a server slot is being reused to represent a different + * logical entity (e.g. a different Kubernetes pod occupying the same slot + * after a rename) and per-entity counter attribution is required. + * + * When the server's counters are registered in a shared-memory stats file + * object (COUNTERS_SHARED_F_LOCAL not set), clearing them breaks the + * monotonicity that monitoring tools consuming the shared stats rely on, + * and affects every process attached to the object. Such a clear is + * therefore refused unless is set. + * + * is the "/" argument. Always returns 1 (the CLI + * parser convention for "message emitted, stop"); success or error is + * reported to . + */ +int cli_clear_counters_server(struct appctx *appctx, char *arg, int force) +{ + struct server *sv; + + sv = cli_find_server(appctx, arg); + if (!sv) + return 1; + + if (!force && !(sv->counters.shared.flags & COUNTERS_SHARED_F_LOCAL)) { + cli_err(appctx, + "Server counters are stored in a shared-memory stats " + "file; clearing them breaks monotonicity for monitoring " + "tools and affects all attached processes. Append 'force' " + "to clear anyway.\n"); + return 1; + } + + HA_SPIN_LOCK(SERVER_LOCK, &sv->lock); + + counters_be_reset(&sv->counters); + srv_stats_clear_extra_counters(sv); + + HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock); + + cli_msg(appctx, LOG_NOTICE, "Server counters cleared.\n"); + return 1; +} + /* register cli keywords */ static struct cli_kw_list cli_kws = {{ },{ { { "disable", "agent", NULL }, "disable agent : disable agent checks", cli_parse_disable_agent, NULL }, diff --git a/src/stats-proxy.c b/src/stats-proxy.c index f9aa3dcaa..862c79319 100644 --- a/src/stats-proxy.c +++ b/src/stats-proxy.c @@ -1734,3 +1734,32 @@ void proxy_stats_clear_counters(int clrall, struct list *stat_modules) } } } + +/* Reset the module-registered extra counters of a single server , as done + * by "clear counters" for every server. Only modules flagged clearable are + * reset, matching the OPER-level "clear counters" semantics (the ADMIN-only + * "clear counters all" resets non-clearable modules too, but there is no + * per-server equivalent of "all"). The native counters are handled separately + * by the caller via counters_be_reset(). + */ +void srv_stats_clear_extra_counters(struct server *sv) +{ + struct list *stat_modules = &stats_module_list[STATS_DOMAIN_PROXY]; + struct stats_module *mod; + + list_for_each_entry(mod, stat_modules, list) { + enum stats_domain_px_cap mod_cap = stats_px_get_cap(mod->domain_flags); + + if (!mod->clearable) + continue; + if (!(mod_cap & STATS_PX_CAP_SRV)) + continue; + if (!sv->extra_counters) + continue; + + EXTRA_COUNTERS_INIT(sv->extra_counters, + mod, + mod->counters, + mod->counters_size); + } +} diff --git a/src/stats.c b/src/stats.c index f0737df0e..9306cb151 100644 --- a/src/stats.c +++ b/src/stats.c @@ -890,6 +890,21 @@ static int cli_parse_clear_counters(char **args, char *payload, struct appctx *a { int clrall = 0; + /* "clear counters server / [force]" resets the counters of a + * single server. Handled here rather than via a dedicated three-word + * CLI keyword because "clear counters" is a two-word keyword that would + * shadow any three-word variant during keyword lookup. Requires ADMIN + * level like "clear counters all": it clears cumulative counters, and + * an OPER-only user must not be able to wipe those to hide activity. + */ + if (strcmp(args[2], "server") == 0) { + int force = (strcmp(args[4], "force") == 0); + + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + return cli_clear_counters_server(appctx, args[3], force); + } + if (strcmp(args[2], "all") == 0) clrall = 1; @@ -1355,7 +1370,7 @@ REGISTER_PER_THREAD_FREE(free_trash_counters); /* register cli keywords */ static struct cli_kw_list cli_kws = {{ },{ - { { "clear", "counters", NULL }, "clear counters [all] : clear max statistics counters (or all counters)", cli_parse_clear_counters, NULL, NULL }, + { { "clear", "counters", NULL }, "clear counters [all|server / [force]] : clear max statistics counters (or all, or one server's)", cli_parse_clear_counters, NULL, NULL }, { { "show", "info", NULL }, "show info [desc|json|typed|float]* : report information about the running process", cli_parse_show_info, cli_io_handler_dump_info, NULL }, { { "show", "stat", NULL }, "show stat [desc|json|no-maint|typed|up]*: report counters for each proxy and server", cli_parse_show_stat, cli_io_handler_dump_stat, cli_io_handler_release_stat }, { { "show", "schema", "json", NULL }, "show schema json : report schema used for stats", cli_parse_show_schema_json, cli_io_handler_dump_json_schema, NULL },