7 Commits
Author SHA1 Message Date
Michael Kolupaev a8a23a8a35 Setup progress indication 2026-07-21 15:04:31 -07:00
Michael Kolupaev 3b76453a55 Faster cleanup 2026-07-21 14:29:17 -07:00
Michael KolupaevandClaude Fable 5 8d32787374 keeper-bench: keep_count option for create generators
`keep_count: <n>` (or `auto`) on a create generator keeps its output path
set at a roughly constant size, so the dataset doesn't drift over a long
benchmark: each operation chooses between Create and Remove with a
probability based on the current shard size — 0.5 at the target,
approaching 0/1 as the size deviates (logistic curve, 5% of the target as
the scale). The last path in a shard is never removed, so a set can only
be empty if it started empty. `auto` snapshots the size at the end of
setup as the target. Mutually exclusive with `remove_factor`.

Sizes and targets of dynamic path sets are now printed with each periodic
report in both the generated and storage modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:47:02 -07:00
Michael KolupaevandClaude Fable 5 1298720094 keeper-bench: dynamic path sets
Path sets that a `create` generator outputs to are now dynamic: created
nodes are added to the set on success and `remove_factor` removes take
nodes out of it (before sending; put back unless the outcome shows the
node is gone). The output set is the explicit `tag` from the create
generator's config, the `children_of` set of its fixed parent, or an
anonymous set when only `remove_factor` needs one; it is only populated
if some generator reads it. This allows pointing e.g. a `set` generator
at nodes produced by a `create` generator, and replaces the old
per-generator `paths_created` bookkeeping (along with the name
uniqueness enforcement, which never worked across threads anyway).

Dynamic sets are sharded by worker thread, so each thread reads and
updates only its own shard (shard mutexes still guard against response
callbacks running on client/commit threads). Since a dynamic set may lag
behind the real state, requests drawing paths from one report `ZNONODE`/
`ZNODEEXISTS` results as a separate `ignored_errors` counter instead of
failing, even without `continue_on_errors`. Generators whose input set
is currently empty decline, and another generator is picked instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:42:46 -07:00
Michael KolupaevandClaude Fable 5 8aab8b2efe keeper-bench: introduce PathSet and draw all generator paths from shared sets
Each `PathGetter` now draws from exactly one `PathSet` — a literal path
list, one `tagged` reference, or one `children_of` reference (mixing
sources in one `path` is no longer supported). Sets are registered in
`NodesSetup` while parsing the setup and generator configs, so generators
referencing the same tag or parent share one in-memory copy of the paths
instead of each generator merging its own copy.

The initialization sequence is reordered accordingly: parse generators
(registering sets), create the setup tree (populating tag sets on the
fly), resolve `children_of` sets by listing each distinct parent once,
then validate. Tag sets nobody reads are not populated at all.

The setup tree now stores `repeat` counts unexpanded, mirroring the
config; instances are expanded during the creation traversal (root names
are recorded for cleanup). For repeated subtrees this removes the
per-instance child pointers from memory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:33:06 -07:00
Michael KolupaevandClaude Fable 5 3e7bb4bd71 keeper-bench: share one immutable Generator across worker threads
Generators used to be constructed per worker thread, each re-parsing the
config and building its own copy of every path list (including the results
of `children_of` listings), multiplying memory usage by the concurrency.
Now a single `Generator` is built once on the main thread and shared; the
only per-call mutable state is the rng, passed in via `GenerateContext`
(`{rng, thread_idx}`) by each thread.

This also deletes the `setSeed`/`setSeedImpl` cascade with its per-component
seed offsets: with a fixed `generator.seed`, each thread's rng is simply
seeded with `seed + thread_idx`. `children_of` parents are now listed once
instead of once per thread, and the workload description is printed once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:25:04 -07:00
Michael KolupaevandClaude Fable 5 15255aef45 Split keeper-bench Runner into GeneratedRunner and LogRunner
- `GeneratedRunner` (generated mode) and `LogRunner` (replay and
  setup-nodes-collection modes) replace the two-mode `Runner`.
- `--input-request-log` now always selects replay mode, taking precedence
  over the config's `generator` section instead of being silently ignored.
- `BenchmarkContext` is renamed to `NodesSetup` and moved to its own file;
  the setup tree walk now emits `Create` requests into a callback, which
  `StorageRunner` reuses instead of a hand-rolled tree walk.
- Shared helpers are extracted to `RunnerCommon`: `getOption` (command
  line > config > default), `ConnectionFactory`, `BenchmarkOutput`.
  `StorageRunner` uses `getOption` too.
- Generators are initialized sequentially up front in `GeneratedRunner`,
  removing `generator_init_barrier`.
- SIGINT is now blocked process-wide at program entry, so Ctrl+C is always
  seen by the `InterruptListener` polls instead of sometimes terminating
  the process via a global-thread-pool thread that didn't block it.
  `StorageRunner` and `LogRunner` now also shut down gracefully on SIGINT
  and print the final report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:43:40 -07:00