rsyslog/tests/dynstats-persist.sh
Nelson Yen 18082f70b4 dynstats: add opt-in state persistence; fix worker lifecycle
Operators want dynstats to survive restarts for consistent metrics and
smoother observability in containers and rolling deploys.

Before: dynstats buckets were ephemeral; restarts reset counters.
After: optional on-disk persistence restores counters; worker thread is
started on demand and torn down with the owning rsconf.

Impact: New state files under WorkDirectory (or statefile.directory)
when enabled; slight I/O overhead on configured thresholds. Defaults
preserve previous behavior (persistence off).

This adds two thresholds to trigger persistence:
- persistStateInterval (count-based) and persistStateTimeInterval
  (time-based), both default 0 (disabled). A new statefile.directory
  can override WorkDirectory for dynstats files.
On bucket creation, existing JSON state ("dynstats-state:<bucket>")
is loaded to rehydrate counters. Updates may enqueue async writes to a
lazily-started file-write worker; teardown performs a final sync flush
without holding the bucket lock to avoid I/O-induced deadlocks.
Worker lifecycle is tied to rsconf: init in dynstats_initCnf(),
start on first persistent bucket, stop in dynstats_destroyAllBuckets().
The latter now takes rsconf_t* and is invoked from rsconf destruct,
avoiding prior hangs when loadConf/runConf differed. Per-bucket stats
track flushed bytes/counts/errors; a "file-write-worker" group reports
queue size/enqueues. Docs updated; tests add dynstats-persist(+vg) to
verify restore-after-restart and clean shutdown.

With the help of AI Agents: GitHub Copilot, cubic-dev-ai, ChatGPT codex

Co-authored-by: Rainer Gerhards <rgerhards@adiscon.com>
2026-01-20 10:56:28 +01:00

62 lines
2.2 KiB
Bash
Executable File

#!/bin/bash
# This file is part of the rsyslog project, released under ASL 2.0
#export RSYSLOG_DEBUG="Debug"
. ${srcdir:=.}/diag.sh init
generate_conf
add_conf '
global(workDirectory="'${RSYSLOG_DYNNAME}'.spool")
ruleset(name="stats") {
action(type="omfile" file="'${RSYSLOG_DYNNAME}'.out.stats.log")
}
module(load="../plugins/impstats/.libs/impstats" interval="1" severity="7" resetCounters="on" Ruleset="stats" bracketing="on")
template(name="outfmt" type="string" string="%msg% %$.increment_successful%\n")
dyn_stats(name="msg_stats" resettable="off" persistStateInterval="1" statefile.directory="'${RSYSLOG_DYNNAME}'.spool")
set $.msg_prefix = field($msg, 32, 1);
if (re_match($.msg_prefix, "foo|bar|baz|quux|corge|grault")) then {
set $.increment_successful = dyn_inc("msg_stats", $.msg_prefix);
} else {
set $.increment_successful = -1;
}
action(type="omfile" file=`echo $RSYSLOG_OUT_LOG` template="outfmt")
'
startup
wait_for_stats_flush ${RSYSLOG_DYNNAME}.out.stats.log
injectmsg_file $srcdir/testsuites/dynstats_input_more_0
wait_queueempty
rst_msleep 1100 # wait for stats flush
echo doing shutdown
shutdown_when_empty
echo wait on shutdown
wait_shutdown
custom_content_check 'foo=4' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'baz=2' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'bar=1' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'corge=1' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'quux=2' "${RSYSLOG_DYNNAME}.out.stats.log"
# check above counts stats have been persisted.
ls -l ${RSYSLOG_DYNNAME}.spool
echo restarting rsyslog
startup
echo restarted rsyslog, continuing with test
injectmsg_file $srcdir/testsuites/dynstats_input_more_2
wait_queueempty
wait_for_stats_flush ${RSYSLOG_DYNNAME}.out.stats.log
echo doing shutdown
shutdown_when_empty
echo wait on shutdown
wait_shutdown
custom_content_check 'foo=5' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'baz=2' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'bar=1' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'corge=3' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'quux=3' "${RSYSLOG_DYNNAME}.out.stats.log"
custom_content_check 'grault=1' "${RSYSLOG_DYNNAME}.out.stats.log"
exit_test