pub struct SchbenchConfig {
pub message_threads: usize,
pub worker_threads: usize,
pub cache_footprint_kib: usize,
pub operations: usize,
pub sleep_usec: u64,
pub skip_locking: bool,
pub requests_per_sec: usize,
pub auto_rps: usize,
pub split_percent: Option<usize>,
pub pipe_transfer_bytes: usize,
}Expand description
User-facing config for the Schbench workload.
Declarative config for the Schbench
workload. Construct via SchbenchConfig::default (schbench’s own
defaults) plus the chainable setters, e.g.
SchbenchConfig::default().message_threads(2).worker_threads(4). Derives
Clone/Debug/PartialEq/Eq/Hash/serde; the builder shape follows
WorkloadConfig, but Eq+Hash (which
WorkloadConfig and WorkSpec omit because of their transitive f64) are
available here since every field is integer/bool – the ktstr f64-free
leaf-config convention.
§schbench(8) CLI parity
This port re-expresses schbench’s default (matrix-work) mode natively, so
its tunables are config fields and topology rather than CLI flags. The
mapping to schbench’s option table (schbench.c:138-187):
| schbench flag | ktstr |
|---|---|
-m message-threads | message_threads |
-t threads | worker_threads (workers per message thread; 0 = ceil(cpuset_cpus / message_threads), see below) |
-F cache_footprint | cache_footprint_kib |
-n operations | operations |
-s sleep_usec | sleep_usec |
-L no-locking | skip_locking |
-R rps | requests_per_sec |
-A auto-rps | auto_rps |
--split (long-only) | split_percent (None = no split, all-private) |
-p pipe (also --pipe) | pipe_transfer_bytes (0 = off; memory-transfer mode, no matrix work) |
-r runtime | in-VM: the scenario engine’s run window (the engine runs until stop); host-side: the run_secs argument to run_standalone |
§Set by ktstr topology, not a flag
-tdefault: withworker_threads = 0, ktstr matches schbench’s-t0-default – it divides the CPU count across the message threads,ceil(cpus / message_threads)per thread (schbench.c:1849-1852), so the total worker count stays near the CPU count. ktstr scopes “cpus” to the allocated guest cpuset (the worker’ssched_getaffinitymask, set by the scenario’s topology /CgroupDef) rather than schbench’sget_nprocs, so the total is ≈ the cpuset’s CPU count. An explicit non-zeroworker_threadsis workers-per-message-thread in both.-M(message-cpus) /-W(worker-cpus) thread pinning: ktstr places threads through its affinity / cpuset layer, so there is deliberately no per-thread-pin knob.
§Observability flags -> the metric API
schbench’s -w (warmuptime), -i (intervaltime), -z (zerotime), -j
(json), and -J (jobname) shape its streaming stderr/JSON report. ktstr’s
numbers flow through the metric API instead – per-phase attribution and the
sidecar – so these have no flag equivalent. ktstr-schbench-validate
reproduces schbench’s stderr-table shape for a side-by-side comparison.
§Split mode (--split)
Some(p) partitions cache_footprint_kib into a per-thread private matrix
(p%) and ONE process-global shared matrix (100-p%) that every worker
multiplies into concurrently, reproducing schbench’s cross-core
shared-working-set cache contention (schbench.c:1390-1404, :1858-1863).
ktstr models the shared matrix with AtomicU64 Relaxed accesses. Like
schbench’s emitted code, the shared kernel keeps the running sum in a register
and STORES it to each shared C cell on every inner (k) iteration – C is
write-only in the loop (A and B are loaded each k, C is never reloaded), and
that per-k store is what generates the contention. Both gcc and clang keep the
per-k store: do_some_math reads m1/m2/m3 as offsets into one base
pointer, so neither can prove the m3 store doesn’t alias the next k’s
m1/m2 loads. On x86-64 a Relaxed load/store lowers to a plain MOV (no
LOCK), so the contention is identical to schbench’s plain shared-memory race
– but sound (atomics, no data race), with zero unsafe. None (default) is
the legacy all-private single matrix.
§Pipe mode (-p)
pipe_transfer_bytes > 0 REPLACES the matrix workload with schbench’s
memory-transfer simulation (schbench.c:177, pipe_test). It rides the
message-handshake path: the message thread memsets each woken worker’s
per-thread page to 1 (schbench.c:980-981) and the worker memsets its own
page to 2 before blocking (schbench.c:1003-1004), pipe_transfer_bytes
bytes each per handshake cycle (clamped to 1 MiB, PIPE_TRANSFER_BUFFER).
do_work and the think-sleep are skipped (schbench.c:1448), so the only
per-cycle work is the wakeup handshake + the two memsets; the report is the
PER-WORKER memory-transfer throughput (avg worker transfer = the aggregate
rate divided by the worker count, schbench.c:1697,1942-1943,1979) alongside
the wakeup-latency table, not request latency.
ktstr does NOT compose -p with -R: in pipe mode it always runs the
message-handshake waker (so BOTH pipe memsets fire — a full transfer) and never
starts the RPS injector. schbench instead COMPOSES them, half-broken: it has no
precedence (-R alone picks the waker, schbench.c:1594), so -p -R runs the
RPS injector while the worker-side memset still fires unconditionally
(schbench.c:1003-1004) but the waker-side memset — which lives only in
xlist_wake_all (schbench.c:980-981) — does not, yielding a degenerate
half-pipe. ktstr’s full pipe is the more faithful -p behavior; the realistic
use is -p without -R. schbench also zeroes warmuptime in pipe mode
(schbench.c:296); ktstr has no warmuptime concept, so that is a no-op here.
§Modes not ported
-C(calibrate): a tuning aid that times schbench’s own work loop and forces-L(schbench.c:166,:389). Intentionally out of scope – ktstr measures through the metric path.
Fields§
§message_threads: usizeNumber of message threads (schbench.c -m, default 1).
worker_threads: usizeWorker threads per message thread (schbench.c -t). 0 resolves to
ceil(cpuset_cpus / message_threads) – the CPU count of the allocated
guest cpuset (the worker’s sched_getaffinity mask, per ruling) divided
across the message threads, matching schbench’s 0-default
(schbench.c:1849-1852) scoped to the cpuset rather than get_nprocs.
See resolve_worker_count and the CLI-parity section above.
cache_footprint_kib: usizePer-worker matrix cache footprint in KiB (schbench.c -F, default
256); sets the matrix dimension.
operations: usizeMatrix multiplications per work cycle (schbench.c -n, default 5).
sleep_usec: u64Think-time sleep before the matrix work, microseconds (schbench.c
-s, default 100); simulates networking. 0 disables.
skip_locking: boolSkip the per-CPU lock around the matrix work (schbench.c -L,
default false: locking on).
requests_per_sec: usizeFixed request rate, requests/second (schbench.c -R, default 0 = off).
0 selects the default message-handshake mode (each worker is woken by its
message thread); non-zero switches to the RPS-injector mode, where a
dedicated thread enqueues requests_per_sec requests/second round-robin
across the workers (schbench.c run_rps_thread, :1258).
auto_rps: usizeAuto-RPS target CPU-busy percentage (schbench.c -A, default 0 = off).
Non-zero turns on closed-loop rate control: a once-per-second control
thread grows/shrinks the live request rate toward this host-busy% target
(schbench.c auto_scale_rps, :1180). Setting it seeds the rate to 10
when requests_per_sec is 0 (schbench.c:286), so auto-RPS starts low
and climbs.
split_percent: Option<usize>Percent of the cache footprint that is PRIVATE per worker thread
(schbench.c --split, long-only, 0-100). None = no split:
schbench’s legacy all-private single matrix (schbench.c:1405-1408,
:1879-1880). Some(p) partitions cache_footprint_kib into a
per-thread private matrix (p%) and ONE process-global shared matrix
(100-p%) that every worker multiplies into concurrently, reproducing
schbench’s cross-core shared-working-set cache contention
(schbench.c:1390-1404, :1858-1863). Some(0) = all shared,
Some(100) = all private (same matrix sizes as None, but routed
through the split branch, matching schbench’s split_specified path).
Out-of-range Some(p > 100) panics when the engine consumes it
(schbench.c:362-365 exits on the same); the builder also debug-asserts
the bound.
pipe_transfer_bytes: usizePipe-mode transfer size in bytes (schbench.c -p/--pipe, default 0 =
off, clamped to 1 MiB PIPE_TRANSFER_BUFFER). Non-zero REPLACES the
matrix workload with schbench’s memory-transfer simulation: the message
thread memsets each woken worker’s per-thread page to 1 and the worker
memsets its own page to 2 (schbench.c:980-981/:1003-1004),
pipe_transfer_bytes bytes each per cycle, while do_work + the
think-sleep are skipped (schbench.c:1448). Reports PER-WORKER
memory-transfer throughput (avg worker transfer) rather than request
latency.
Implementations§
Source§impl SchbenchConfig
impl SchbenchConfig
Sourcepub fn message_threads(self, n: usize) -> Self
pub fn message_threads(self, n: usize) -> Self
Set the number of message threads (schbench -m).
Sourcepub fn worker_threads(self, n: usize) -> Self
pub fn worker_threads(self, n: usize) -> Self
Set worker threads per message thread (schbench -t); 0 = one per
allocated CPU.
Sourcepub fn cache_footprint_kib(self, kib: usize) -> Self
pub fn cache_footprint_kib(self, kib: usize) -> Self
Set the per-worker matrix cache footprint in KiB (schbench -F).
Sourcepub fn operations(self, n: usize) -> Self
pub fn operations(self, n: usize) -> Self
Set the matrix multiplications per work cycle (schbench -n).
Sourcepub fn sleep_usec(self, usec: u64) -> Self
pub fn sleep_usec(self, usec: u64) -> Self
Set the think-time sleep in microseconds (schbench -s); 0 disables.
Sourcepub fn skip_locking(self, skip: bool) -> Self
pub fn skip_locking(self, skip: bool) -> Self
Skip the per-CPU lock around the matrix work (schbench -L).
Sourcepub fn requests_per_sec(self, rps: usize) -> Self
pub fn requests_per_sec(self, rps: usize) -> Self
Set the fixed request rate in requests/second (schbench -R); 0 selects
the default message-handshake mode, non-zero the RPS-injector mode.
Sourcepub fn auto_rps(self, target_pct: usize) -> Self
pub fn auto_rps(self, target_pct: usize) -> Self
Set the auto-RPS target host-busy percentage (schbench -A); 0 disables
auto-scaling. Non-zero seeds the rate to 10 when requests_per_sec is 0.
Sourcepub fn split_percent(self, percent: Option<usize>) -> Self
pub fn split_percent(self, percent: Option<usize>) -> Self
Set the private/shared cache-footprint split percentage (schbench
--split); None (default) = no split, all-private single matrix.
Some(p) requires p <= 100: the builder debug-asserts it for early
feedback, and the engine hard-panics on an out-of-range value at the
consumption boundary in run – the analog of schbench exiting on a bad
--split (schbench.c:362-365).
Sourcepub fn pipe_transfer_bytes(self, bytes: usize) -> Self
pub fn pipe_transfer_bytes(self, bytes: usize) -> Self
Set the pipe-mode transfer size in bytes (schbench -p/--pipe); 0
(default) = off (the matrix workload). Non-zero switches to schbench’s
memory-transfer simulation; values above 1 MiB (PIPE_TRANSFER_BUFFER)
are clamped when the engine consumes it (schbench clamps the same,
schbench.c:291-294).
Trait Implementations§
Source§impl Clone for SchbenchConfig
impl Clone for SchbenchConfig
Source§fn clone(&self) -> SchbenchConfig
fn clone(&self) -> SchbenchConfig
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SchbenchConfig
impl Debug for SchbenchConfig
Source§impl Default for SchbenchConfig
impl Default for SchbenchConfig
Source§impl<'de> Deserialize<'de> for SchbenchConfig
impl<'de> Deserialize<'de> for SchbenchConfig
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Hash for SchbenchConfig
impl Hash for SchbenchConfig
Source§impl PartialEq for SchbenchConfig
impl PartialEq for SchbenchConfig
Source§impl Serialize for SchbenchConfig
impl Serialize for SchbenchConfig
impl Eq for SchbenchConfig
impl StructuralPartialEq for SchbenchConfig
Auto Trait Implementations§
impl Freeze for SchbenchConfig
impl RefUnwindSafe for SchbenchConfig
impl Send for SchbenchConfig
impl Sync for SchbenchConfig
impl Unpin for SchbenchConfig
impl UnwindSafe for SchbenchConfig
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more