]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: server: add 'clear counters server <backend>/<server>' CLI command
authorAlexander Stephan <alexander.stephan@sap.com>
Mon, 6 Jul 2026 09:20:48 +0000 (09:20 +0000)
committerWilly Tarreau <w@1wt.eu>
Thu, 30 Jul 2026 17:00:47 +0000 (19:00 +0200)
Add a CLI command to reset the statistics counters of a single server:

  clear counters server <backend>/<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 <b>/<s> 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.

include/haproxy/server.h
include/haproxy/stats-proxy.h
src/server.c
src/stats-proxy.c
src/stats.c

index 17764aff9169bc2e68183b616bf26a0e1a190427..2ef7e0e62ed50dec54107a4b9102153f552a14ab 100644 (file)
@@ -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);
index 06060d7fa758c5412d8fcf37e5d312e2f9a23897..e21de0ffcc9782b3455f53203dce2db345727dce 100644 (file)
@@ -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 */
index 348679537833fffa6097661da0b44b0e4522b0fd..494fa5e6010195840add2255872787ca5f6ef612 100644 (file)
@@ -47,6 +47,7 @@
 #include <haproxy/sample.h>
 #include <haproxy/sc_strm.h>
 #include <haproxy/server.h>
+#include <haproxy/stats-proxy.h>
 #include <haproxy/stats.h>
 #include <haproxy/ssl_sock.h>
 #include <haproxy/stconn.h>
@@ -6782,6 +6783,58 @@ out:
        return 1;
 }
 
+/* Reset the statistics counters of a single server, invoked from the
+ * "clear counters server <backend>/<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 <force> is set.
+ *
+ * <arg> is the "<backend>/<server>" argument. Always returns 1 (the CLI
+ * parser convention for "message emitted, stop"); success or error is
+ * reported to <appctx>.
+ */
+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 },
index f9aa3dcaa1931cadbe2493bbad5bdd86f49cc2f5..862c7931975f3c4f456abcdd675ec056dbe35a1f 100644 (file)
@@ -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 <sv>, 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);
+       }
+}
index f0737df0e2f0f679cb38c8a2148154d1bc8afafa..9306cb1512a7b5d1c11163aa377d49f3480a4731 100644 (file)
@@ -890,6 +890,21 @@ static int cli_parse_clear_counters(char **args, char *payload, struct appctx *a
 {
        int clrall = 0;
 
+       /* "clear counters server <b>/<s> [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 <bk>/<srv> [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 },