Skip to content

Clear server counters#19

Open
alexanderstephan wants to merge 5 commits into
masterfrom
clear-counters-server
Open

Clear server counters#19
alexanderstephan wants to merge 5 commits into
masterfrom
clear-counters-server

Conversation

@alexanderstephan

Copy link
Copy Markdown
Collaborator

No description provided.

@alexanderstephan
alexanderstephan force-pushed the clear-counters-server branch 4 times, most recently from 8eb86a2 to 604ea0a Compare July 15, 2026 15:14
@alexanderstephan alexanderstephan self-assigned this Jul 17, 2026
proxy_stats_clear_counters() did a blanket memset() on the be_counters /
fe_counters / listener counter structs when clearing all counters. This
zeroed the embedded shared.tg pointer array, which every hot path
dereferences with no NULL check (for example
_HA_ATOMIC_INC(&srv->counters.shared.tg[tgid-1]->cum_lbconn) in
process_srv_queue()). After 'clear counters all' the next request on
that server would segfault.

The memset pattern predates the switch to per-thread-group shared
counters (commit 5495c88 "MEDIUM: counters: Dynamically allocate
per-thread group counters") and wasn't updated when shared.tg gained
pointer-array semantics. Additionally, memset'ing only the outer struct
never actually reset the accumulated counters in the pointed-at
per-tgroup structs, so 'clear counters all' silently failed to match
its documented "same effect as restarting" behaviour for cumulative
counters (bytes, sessions, requests, ...).

Introduce two helpers in src/counters.c:

  void counters_fe_reset(struct fe_counters *counters);
  void counters_be_reset(struct be_counters *counters);

Both iterate shared.tg[0 .. nbtgroups-1] and zero the *contents* of each
per-tgroup struct via memset, then zero the local (non-shared) fields of
the outer struct. The shared.tg pointer array, each shared.tg[it]
pointer, and shared.flags (COUNTERS_SHARED_F_LOCAL is set at boot and
reflects allocation ownership, not counter state) are preserved.

Refactor proxy_stats_clear_counters() to call these helpers instead of
the inline memset. This fixes both the segfault after 'clear counters
all' and the silent no-op on cumulative counters, and provides a shared
primitive for a subsequent 'clear counters server <b>/<s>' command.

In SHM stats-file mode, zeroing the per-tgroup structs affects every
process attached to the same object; this matches the intended
"reset everywhere" semantic of clear counters and is unchanged from
the outer-memset intent.

This bug should be backported wherever the per-thread-group shared
counter refactor is present.
…unters

The plain "clear counters" command (OPER level) resets only the max/peak
gauges of each object's counters, leaving cumulative counters intact,
while "clear counters all" (ADMIN) performs a full reset via the
counters_{fe,be}_reset() helpers introduced earlier in this series.

The max-only reset was open-coded inline in proxy_stats_clear_counters()
with a hand-maintained list of fields, duplicated across the backend,
server and listener branches. This is fragile: a new max gauge added to
struct {fe,be}_counters can silently be forgotten here.

Factor the max-only reset into counters_fe_reset_max() and
counters_be_reset_max(), living next to the existing reset helpers, and
call them from proxy_stats_clear_counters(). Unlike the full-reset
helpers these cannot use a blanket memset because the max fields are
interleaved with live cumulative fields, so the gauges are zeroed
individually.

While consolidating, the two open-coded lists turned out to disagree:
the backend branch cleared conn_max / cps_max / rps_max but not
cur_sess_max, while the server branch cleared cur_sess_max but none of
the former. Both used the same struct be_counters, so these were latent
gaps rather than intentional differences. counters_be_reset_max() now
clears the union of all peak gauges, so plain "clear counters"
consistently resets every max gauge for both backends and servers.

No functional change for the full-reset ("all") path.
…mand

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.
Cover the new 'clear counters server <backend>/<server>' CLI command:

  - error cases:
      * unknown backend
      * unknown server
  - success case: drive two requests pinned to a single server (the
    second server is a backup, so it stays idle), verify via 'show stat'
    that the served server accumulated sessions while the backup stayed
    at zero, clear only the served server's counters, then verify its
    cumulative sessions dropped back to zero while the backup is still
    untouched.
  - the optional 'force' argument is accepted (a no-op override here,
    since these process-local counters are not backed by a shared-memory
    stats file) and the command is discoverable via 'help'.

The success path also implicitly exercises the counters_be_reset()
helper: if the reset were unsafe against the shared.tg pointer, the
vtest run would segfault on the next scheduled scrape or health check.
Add a section for the new 'clear counters server <backend>/<server>
[force]' CLI command in doc/management.txt, alongside 'clear counters'
and 'clear counters all'.

The entry documents the admin-only permission level (it clears
accumulated counters, like 'clear counters all') and the fact that both
accumulated counters and max values are reset, along with the clearable
module-registered extra counters, while the server's runtime state
(address, weight, admin state) and health checks are left untouched. It
describes the optional 'force' argument required to clear counters
stored in a shared-memory stats file, and calls out the Kubernetes
slot-recycle use case that motivated the command (per-entity
attribution after 'set server <b>/<s> name').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant