WorkSpec

Struct WorkSpec 

Source
pub struct WorkSpec {
Show 13 fields pub work_type: WorkType, pub sched_policy: SchedPolicy, pub num_workers: Option<usize>, pub affinity: AffinityIntent, pub mem_policy: MemPolicy, pub mpol_flags: MpolFlags, pub nice: Option<i32>, pub comm: Option<Cow<'static, str>>, pub pcomm: Option<Cow<'static, str>>, pub uid: Option<u32>, pub gid: Option<u32>, pub numa_node: Option<u32>, pub workers_pct: Option<f64>,
}

Fields§

§work_type: WorkType

What each worker does.

§sched_policy: SchedPolicy

Linux scheduling policy.

§num_workers: Option<usize>

Number of workers. None means use Ctx::workers_per_cgroup.

Composition-sensitive: different work groups within the same cgroup commonly want different worker counts (e.g. an antagonist with 4 spinners alongside a victim with 1 SCHED_FIFO worker). For that reason CgroupDef does NOT expose a cgroup-level default for num_workers — multi-group cgroups set the count per-WorkSpec here.

Type asymmetry with crate::workload::WorkloadConfig::num_workers (usize, no Option) is deliberate. WorkSpec is the declarative spec layer where None is a meaningful “inherit the cgroup-level default” sentinel; resolve_num_workers coalesces it to a concrete usize against the Ctx before WorkloadConfig::for_scenario_engine constructs the spawn-time config. The coalesce happens at the resolution boundary, not silently inside any builder.

§affinity: AffinityIntent

Per-worker affinity intent. Resolved to ResolvedAffinity at runtime via resolve_affinity_for_cgroup().

§mem_policy: MemPolicy

NUMA memory placement policy. Applied via set_mempolicy(2) after fork, before the work loop.

Validated against the resolved cpuset per-WorkSpec at apply-setup time. Because validation is per-group, a cgroup-level default would mask per-group failures with confusing diagnostics — CgroupDef deliberately does not expose a cgroup-level default for mem_policy; multi-group cgroups set it per-WorkSpec here.

§mpol_flags: MpolFlags

Optional mode flags for set_mempolicy(2).

§nice: Option<i32>

Per-worker nice value applied via setpriority(2) after fork, before the work loop. See crate::workload::WorkloadConfig::nice for range, None-vs-Some(n) semantics, and CAP_SYS_NICE rules.

To inherit a cgroup-level default stored at CgroupDef::default_nice, leave this None. Some(0) opts out of the cgroup-level merge — see crate::workload::WorkloadConfig::nice for the underlying setpriority(PRIO_PROCESS, 0, 0) semantics.

§comm: Option<Cow<'static, str>>

Per-worker comm set via prctl(PR_SET_NAME) at thread creation time. The setter rejects > 15 bytes (TASK_COMM_LEN-1) at construction so the operator sees the cap at the call site instead of debugging a kernel-truncated comm — see validate_task_comm_string. None inherits the binary name. Useful for scheduler matchers that filter on task->comm (e.g. layered’s CommPrefix). The comm is applied once per worker; it is NOT live-propagated after the worker enters its work loop.

§pcomm: Option<Cow<'static, str>>

The thread-group leader’s comm — what schedulers read as task->group_leader->comm. When set, apply_setup coalesces every WorkSpec sharing this pcomm value (within one CgroupDef) into ONE forked thread-group leader. The leader’s task->comm is set via prctl(PR_SET_NAME); the setter rejects > 15 bytes (TASK_COMM_LEN-1) at construction so the task->group_leader->comm == pcomm invariant every worker thread observes for the leader’s lifetime matches the requested string exactly (no silent kernel truncation). WorkSpecs with pcomm = None (or empty pcomm string, treated as None) spawn via the conventional fork path — one process per worker.

Dispatch is apply_setup-only. The WorkSpec::pcomm setter itself always accepts a valid value (subject to the existing 15-byte / NUL / empty-string checks). The bail fires later at WorkloadConfig dispatch-construction time — direct calls to crate::workload::WorkloadHandle::spawn (composed entries) and the scenario-engine spawn-dispatch sites (crate::scenario::ops::Op::Spawn / apply_setup non-pcomm path) all reject a pcomm-bearing WorkSpec when they synthesize the per-spawn WorkloadConfig. Those paths always fork one process per worker (fork mode), so task->group_leader->comm would be left at the parent’s task->comm at fork time (the scenario runner’s binary name) and scheduler matchers filtering on the leader’s comm would see zero matches. The bail surfaces the misuse at the call site instead of producing a workload that silently fails to match its fixture. To drive the pcomm container path without going through CgroupDef, callers may invoke crate::workload::WorkloadHandle::spawn_pcomm_cgroup directly with a &[WorkSpec] slice.

This is the AUTHORITATIVE source for the pcomm dispatch: apply_setup reads it directly from each WorkSpec. crate::scenario::ops::types::CgroupDef::pcomm is a convenience method that writes the same value into every WorkSpec at builder time; there is no separate cgroup-level pcomm field.

Per-thread comm goes through Self::comm and the worker’s own prctl(PR_SET_NAME) at thread creation time. Models real workloads like chrome (pcomm) hosting ThreadPoolForeg and GPU Process worker threads (per-thread comm), or java (pcomm) hosting GC Thread and C2 CompilerThre worker threads.

Declarative-only field — absent from crate::workload::WorkloadConfig by design (same shape as Self::num_workers / Self::workers_pct). pcomm is the operator-facing intent that drives the apply_setup pcomm- aware coalesce path; by the time a WorkloadConfig is constructed for spawn, the per-WorkSpec pcomm has either routed through spawn_pcomm_cgroup (then no longer needed at the WorkloadConfig layer) or hit the dispatch-construction bail at WorkloadConfig::for_scenario_engine.

§uid: Option<u32>

Effective UID set via setresuid(uid, uid, uid) after fork. None inherits the parent’s euid. Useful for scheduler matchers that filter on task->real_cred->euid (e.g. layered’s UIDEquals).

§gid: Option<u32>

Effective GID set via setresgid(gid, gid, gid) after fork. None inherits the parent’s egid.

§numa_node: Option<u32>

Restrict worker affinity to the CPUs of this NUMA node. Applied via sched_setaffinity after fork. Useful for scheduler matchers that check bpf_cpumask_subset(cpus_ptr, node_cpumask) (e.g. layered’s NumaNode).

§workers_pct: Option<f64>

Optional fraction-of-cpuset worker count. When Some(p), the dispatch site computes ceil(cpuset_cpus * p) and writes the result into num_workers. The denominator is the cgroup’s currently-recorded cpuset at dispatch time:

  • apply_setup dispatch: the cgroup was just created and its cpuset just resolved via CpusetSpec::resolve(ctx) (or inherited from ctx.topo.usable_cpuset() when the CgroupDef has no .cpuset(...)), so the denominator matches the declared CpusetSpec.
  • Op::Spawn(SpawnPlacement::Cgroup) dispatch: the denominator is whatever cpuset is currently recorded for the cgroup. A prior Op::SetCpuset that narrowed the cgroup will narrow the denominator too. Workers already spawned by a prior apply_setup are not re-counted.

Cannot coexist with num_workers = Some(_) — validation rejects that combination because it’s ambiguous which source wins. Values > 1.0 are accepted as deliberate oversubscription (e.g. workers_pct(2.0) on a 10-CPU cpuset produces 20 workers). NaN/Inf/negative are rejected at construction time.

Declarative-only field — absent from crate::workload::WorkloadConfig by design. The same asymmetry as Self::num_workers: WorkSpec is the operator-facing declarative spec where workers_pct(p) is a meaningful “scale with the cpuset” intent; Self::resolve_workers_pct (called at apply_setup and at each dispatch site) computes the concrete worker count against the dispatch-time cpuset and writes the result into the WorkSpec’s num_workers field, which then flows through the standard resolve_num_workerscrate::workload::WorkloadConfig::num_workers migration boundary. There is no workers_pct field on WorkloadConfig because by spawn time the scale-with-cpuset intent has already collapsed to a concrete count.

Implementations§

Source§

impl WorkSpec

Source

pub fn workers(self, n: usize) -> Self

Set the number of workers.

Source

pub fn workers_pct(self, pct: f64) -> Self

Set the worker count as a fraction of the resolved cpuset CPU count. Apply-setup computes ceil(cpuset_cpus * pct) and writes the result into num_workers. Use this when the worker count should scale with the cpuset rather than hardcoding a per-topology constant.

Setting BOTH workers(n) and workers_pct(p) on the same WorkSpec is rejected at apply-setup time because the two sources would silently fight; pick one. Values > 1.0 are accepted as deliberate oversubscription; NaN, infinite, and non-positive values are rejected here at construction time via an assertion.

§Panics

Panics when pct is NaN, infinite, or <= 0.0. The builder returns Self, so the construction-time gate uses assert! rather than a fallible Result. Negative or zero fractions would resolve to zero workers — caught at apply-setup time by resolve_num_workers’s zero-workers rejection anyway, but the construction-time message is more actionable.

Extreme finite values (e.g. 1e100) pass the construction gate and saturate to usize::MAX via the as cast in resolve_workers_pct (RFC 2484 / Rust 1.45+). Attempting to spawn that many workers would OOM the host. The framework imposes no upper cap; as a rule of thumb keep pct near the intended oversubscription factor (e.g. 1.0, 2.0, 4.0).

Source

pub fn work_type(self, wt: WorkType) -> Self

Set the work type.

Source

pub fn sched_policy(self, p: SchedPolicy) -> Self

Set the Linux scheduling policy.

Source

pub fn affinity(self, a: AffinityIntent) -> Self

Set the per-worker affinity intent.

Source

pub fn mem_policy(self, p: MemPolicy) -> Self

Set the NUMA memory placement policy.

Source

pub fn mpol_flags(self, f: MpolFlags) -> Self

Set the NUMA memory policy mode flags.

Source

pub fn nice(self, n: i32) -> Self

Set the per-worker nice value applied via setpriority(2).

Stores Some(n) on the spec; the spawn pipeline calls setpriority(PRIO_PROCESS, 0, n) unconditionally (including n == 0). The “skip the syscall, inherit the parent’s nice” state is the type-level default None — leave the builder unchained for inherit semantics. Values below the calling task’s current nice require CAP_SYS_NICE; see crate::workload::WorkloadConfig::nice for the full can_nice rule.

Source

pub fn comm(self, name: impl Into<Cow<'static, str>>) -> Self

Set the worker process name via prctl(PR_SET_NAME).

§Panics

Panics on programmer-error inputs — same three cases as Self::pcomm:

  • Empty string (silent kernel-comm clobber).
  • Interior NUL byte (prctl C-string truncation).
  • More than 15 bytes (TASK_COMM_LEN - 1__set_task_comm truncates at 15 so the framework rejects at construction to keep the kernel-observed comm equal to the requested value).

See validate_task_comm_string for the centralized rationale; name.len() is the BYTE length (UTF-8 multi-byte chars count as their byte width, not their codepoint count).

Source

pub fn uid(self, uid: u32) -> Self

Set the worker’s effective UID via setresuid.

Source

pub fn gid(self, gid: u32) -> Self

Set the worker’s effective GID via setresgid.

Source

pub fn numa_node(self, node: u32) -> Self

Restrict worker affinity to a NUMA node’s CPU set.

Source

pub fn pcomm(self, name: impl Into<Cow<'static, str>>) -> Self

Set the thread-group leader’s comm. Triggers fork-then-thread spawn through apply_setup (or via crate::workload::WorkloadHandle::spawn_pcomm_cgroup for the direct entry point): one forked leader process whose task->comm is name, threads spawned inside it. Each thread additionally sets its own task->comm via Self::comm at thread creation time.

§Panics

Panics on programmer-error inputs:

  • Empty string — the empty pcomm has no observable effect (kernel sets task->comm to “”), so it’s a no-op disguised as configuration. apply_setup treats empty as None to keep the dispatch contract unambiguous, but accepting the builder call would silently drop user intent. Reject up front.
  • Interior NUL byte — prctl(PR_SET_NAME) takes a C string; any embedded NUL truncates the kernel-side comm at the first NUL silently, producing a comm value the caller didn’t ask for. Reject so the operator sees the error immediately instead of debugging a truncated comm.
  • More than 15 bytes — __set_task_comm writes min(strlen(buf), sizeof(tsk->comm) - 1) bytes and TASK_COMM_LEN = 16, so the 16th byte (and beyond) is silently dropped. Rejecting at construction time means the task->group_leader->comm == pcomm invariant the rest of the framework relies on holds exactly, and the operator sees the cap at the call site instead of debugging a truncated comm.

Trait Implementations§

Source§

impl Clone for WorkSpec

Source§

fn clone(&self) -> WorkSpec

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for WorkSpec

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for WorkSpec

Source§

fn default() -> Self

Single SpinWait worker under the kernel’s default scheduling class — the framework’s no-customization baseline. Every other field is None / inherit so a test that needs a specific knob (affinity, mem_policy, nice, etc.) sets only that one via the corresponding WorkSpec::with_* builder. num_workers = None defers count selection to CgroupDef’s merged-works contract (the cgroup-level default applies; see CgroupDef::workers / CgroupDef::merged_works). The workers_pct mutex with num_workers only fires when BOTH are Some(_) — at default neither is set, so the WorkSpec::resolve_workers_pct arm that emits the WorkSpec sets BOTH workers(...) and workers_pct(...) bail does not trigger.

Source§

impl<'de> Deserialize<'de> for WorkSpec

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for WorkSpec

Source§

fn eq(&self, other: &WorkSpec) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for WorkSpec

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for WorkSpec

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> MaybeSend for T
where T: Send,

§

impl<T> MaybeSend for T
where T: Send,