ktstr 0.4.21

Test harness for Linux process schedulers
[workspace]
members = [".", "ktstr-macros", "scx-ktstr"]

[workspace.package]
edition = "2024"
rust-version = "1.94.1"
license = "GPL-2.0-only"
repository = "https://github.com/likewhatevs/ktstr"

[package]
name = "ktstr"
version = "0.4.21"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Test harness for Linux process schedulers"
readme = "README.md"
keywords = ["sched_ext", "scheduler", "testing", "bpf", "linux"]
categories = ["development-tools::testing"]
documentation = "https://likewhatevs.github.io/ktstr/api/ktstr/"
exclude = [".build-cache/", "doc/guide/book/", ".github/", ".githooks/", ".config/"]

[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
clap_complete = "4"
crc32fast = "1.5"
# Binary serialization for bulk-port TLV payloads (AssertResult,
# PayloadMetrics, RawPayloadOutput). Replaces the prior JSON-over-TLV
# encoding so the host parses test verdicts and per-payload metrics
# without serde_json's text-form overhead. `serde` feature pipes the
# existing `#[derive(Serialize, Deserialize)]` types through bincode's
# `bincode::serde::encode_to_vec` / `decode_from_slice` entry points
# (`features/serde/ser.rs` + `de_owned.rs`); the wire format pin uses
# `bincode::config::standard()` (little-endian, variable-int) for both
# guest and host so layout never diverges. Pre-1.0 — old sidecar /
# baseline data is disposable per project policy.
bincode = { version = "2", features = ["serde"] }
itoa = "1"
gix = { version = "0.81", default-features = false, features = ["sha1", "blocking-network-client", "blocking-http-transport-reqwest-rust-tls", "worktree-mutation", "status", "revision"] }
reqwest = { version = "0.13", features = ["blocking"] }
tar = "0.4"
tempfile = "3"
xz2 = { version = "0.1", features = ["static"] }
opendal = { version = "0.55", default-features = false, features = ["services-ghac"] }
tokio = { version = "1", features = ["rt"] }
zstd = { version = "0.13", features = ["zstdmt"] }
lz4_flex = { version = "0.12", features = ["frame"] }
flate2 = "1"
glob = "0.3"
ctor = "0.8"
libc = "0.2"
nix = { version = "0.31", features = ["event", "feature", "fs", "inotify", "mount", "poll", "process", "ptrace", "reboot", "sched", "signal", "term", "uio"] }
blazesym = { version = "0.2", features = ["dwarf"] }
rand = "0.10"
rmesg = { version = "1", default-features = false, features = ["sync"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
btf-rs = "1.1.1"
kvm-bindings = { version = "0.14", features = ["fam-wrappers"] }
kvm-ioctls = "0.24"
libbpf-rs = { version = "0.26", features = ["vendored"] }
libbpf-sys = { version = "1.6", features = ["vendored"] }
linux-loader = { version = "0.13", features = ["bzimage", "elf", "pe"] }
vm-fdt = "0.3"
vm-memory = { version = "=0.17.1", features = ["backend-mmap"] }
vmm-sys-util = "0.15"
cpio = "0.4.1"
virtio-bindings = "0.2.7"
virtio-queue = "0.17"
vm-superio = "0.8.1"
zerocopy = { version = "0.8.48", features = ["derive"] }
polars = { version = "0.53.0", default-features = false, features = ["lazy", "describe"] }
linkme = "0.3"
ktstr-macros = { version = "0.4.8", path = "ktstr-macros" }
goblin = { version = "0.10.5", features = ["elf64"] }
# Memory-map /proc/self/exe in `try_flush_profraw` instead of reading
# the whole binary into a Vec<u8>. Coverage-instrumented test binaries
# are large (hundreds of MiB to ~1 GiB), and the read+copy cost on the
# guest's coverage-flush path was a measurable share of teardown
# latency. Already in the dep tree transitively (blazesym, gix); pinned
# here so `src/test_support/profraw.rs` doesn't depend on either crate's
# re-export surface.
memmap2 = "0.9"
# DWARF parser for resolving struct-member offsets in jemalloc's tsd_t
# at probe time (jemalloc-probe binary). Already present transitively
# via blazesym; pinned as a direct dep so the probe's offset-recovery
# path doesn't depend on blazesym's re-export surface.
gimli = "0.33"
rayon = "1"
# ELF reader/writer. `features = ["build"]` pulls in the full
# `object::build::elf::Builder` surface used by `elf_strip.rs` and
# `cache.rs` for SHT_NOBITS rewriting. `cargo tree --duplicates` also
# lists `object v0.37.3` reached via `ar_archive_writer` (build-dep of
# `psm` / `stacker` / `polars`) with only the `memchr` feature — that
# second instance is a build-script compilation artifact and does not
# link into any shipped binary. The dupe is an artifact of resolver v3
# keeping build-dep and regular-dep feature graphs separate; unifying
# would require dropping `polars`, which is not a proportionate fix.
object = { version = "0.39", default-features = false, features = ["build"] }
walkdir = "2"
indicatif = "0.18"
# Table rendering for stats and verifier CLI output. Replaces hand-rolled
# `format!("{:<30}...")` layouts plus `"-".repeat()` separators with a
# builder API that measures column widths from actual content, so labels
# like `scenario/topology/work_type` stop spilling into adjacent columns
# when topology names exceed the previously hardcoded 30-column width.
# Default features pull in crossterm for `Cell::fg(Color::Red/Green)` in
# the compare_runs VERDICT column; stdout TTY detection is handled by
# comfy-table itself (see `crate::cli::new_table`).
comfy-table = "7.2"
# Stable hash for cache keys — std::hash::DefaultHasher is not
# guaranteed stable across toolchain versions; siphasher::sip::SipHasher13
# pins the algorithm so cached artifacts survive Rust updates.
siphasher = "1"
# SIMD-accelerated byte-pattern search for IKCONFIG extraction from
# stripped vmlinux images (tens of MB each). `windows().position()`
# with an 8-byte needle is O(n) in naive byte compares; memmem uses
# a two-way algorithm with x86_64 AVX2 / aarch64 Neon when available.
memchr = "2"
# C parser for extracting struct definitions from scx BPF source files.
# Used by cargo-ktstr's BTF type anchor generator to force clang to
# retain struct types that would otherwise be eliminated by inlining +
# dead-code elimination. tree-sitter-c parses any C formatting without
# regex fragility.
tree-sitter = "0.24"
tree-sitter-c = "0.23"
# Human-readable duration parsing for the `--watch <interval>` flag on
# `ktstr locks`. Accepts "100ms", "1s", "5m", "1h" forms per rev 5 §6.
# Tiny crate (~2 modules, zero non-std deps).
humantime = "2"
# Safe wrappers around direct syscalls — replaces ad-hoc
# `libc::uname`/`libc::flock`/`libc::shm_open` sites that each reinvent
# fd ownership and errno handling. Keeps the libc dep around because
# not every call we make has a rustix equivalent we consume yet (mmap,
# some signal paths), but the flock+uname+shm surface migrates cleanly.
rustix = { version = "1", features = ["fs", "shm", "system"] }
# Derive macros for enum-level operations. `VariantNames` auto-generates
# the list of variant names so `WorkType::ALL_NAMES` stays in lock-step
# with the enum definition without a hand-maintained parallel table.
strum = { version = "0.27", features = ["derive"] }
# Levenshtein edit distance for the "did you mean?" suggestion in
# `cli::show_thresholds`'s unknown-test-name error path. Already
# present transitively via clap — promoting it to a direct dep makes
# the intent explicit and survives a future clap version that drops
# the transitive exposure.
strsim = "0.11"
# Base64 encoder for `cargo ktstr export`'s self-extracting .run files
# — embeds a gzip tarball after a `__ARCHIVE__` shell-script marker
# so the bash preamble can `base64 -d | tar xz` the binaries on the
# target host. Lightweight crate, well-audited, used only at export
# time (not on the test hot path).
base64 = "0.22"
# jemalloc as global allocator for the ktstr/cargo-ktstr binaries.
# binary-only — the library stays allocator-agnostic so downstream
# consumers pick their own allocator. `stats` feature compiles
# jemalloc with internal per-allocation bookkeeping so
# `tikv-jemalloc-ctl`'s `stats.{allocated,active,resident,mapped}`
# mallctl reads return populated values instead of failing with
# EPERM; `host_heap::collect` depends on this.
tikv-jemallocator = { version = "0.6", features = ["stats"] }
# Safe wrappers for the jemalloc mallctl introspection surface. Used
# by `host_heap` to capture the running binary's heap footprint
# (active / allocated / resident / mapped bytes, arena count) as part
# of the sidecar's HostContext. Reads return `Err` when the linked
# allocator is not jemalloc, so downstream consumers that do NOT
# enable `tikv-jemallocator` land the heap state as `None` cleanly.
# `stats` feature gates the `stats::*` submodule (allocated/active/
# resident/mapped); without it the submodule is absent at compile
# time. `arenas::narenas` is exposed unconditionally.
tikv-jemalloc-ctl = { version = "0.6", features = ["stats"] }
# Regex synthesis from a list of literal strings — used by
# `ctprof_compare` to render human-readable display labels for
# pattern-aggregated thread-name groups (e.g. 4 threads named
# `tokio-runtime-worker-0..3` collapse to a regex showing the
# shared prefix + alternation). The internal join key stays as the
# deterministic strip-trailing-digit prefix; grex is invoked at
# render time only, on bucket-member literals (≥2 entries), to
# produce a regex string operators can read or paste into tooling.
# `default-features = false` drops the binary CLI surface (clap,
# colored, indoc) so only the library trait is pulled in.
grex = { version = "1.4", default-features = false }
# SHA-1 listed as a direct dep solely to enable `asm` (SHA-NI on
# x86_64) via Cargo feature unification on the transitive instance
# pulled in by gix → gix-hash → sha1-checked → sha1. No call sites
# in ktstr itself — remove this entry if gix ever exposes a
# passthrough feature.
sha1 = { version = "0.10", features = ["asm"] }
# SHA-256 for verifying downloaded model-cache artifacts. `asm`
# enables `sha2-asm` — hand-tuned SHA-NI assembly on x86_64,
# ARMv8 SHA-2 crypto extensions on aarch64.
sha2 = { version = "0.10", features = ["asm"] }
# Lowercase hex encoding for SHA-256 digest comparisons in the
# model-cache verify path.
hex = "0.4"
# Local inference backend for `OutputFormat::LlmExtract`. CPU-only —
# `openmp` enables OpenMP-parallel matmul (no GPU offload). The GGUF
# carries its own tokenizer surface, so no separate tokenizer crate is
# needed. The `sampler` feature enables the `LlamaSampler` builder API
# (`greedy()`, `accept()`, `sample()`) used by `invoke_with_model`.
# Build dep on cmake — the linked llama.cpp C++ sources are built from
# source via `llama-cpp-sys-2`'s build script.
llama-cpp-2 = { version = "0.1.145", default-features = false, features = ["openmp", "sampler"] }
# Stateful UTF-8 decoder required by `LlamaModel::token_to_piece` —
# a single token may carry a partial multi-byte UTF-8 sequence and
# only a stateful decoder can stitch consecutive tokens into valid
# UTF-8 without dropping bytes. Already a transitive dep of
# `llama-cpp-2`; pinned as a direct dep so the call site's intent is
# explicit and survives a future llama-cpp-2 reorganization that
# changes its re-export surface.
encoding_rs = "0.8"
# Error-enum derives for the `InferenceError` type that
# `load_inference` and `invoke_with_model` return. Already a
# transitive dep via `llama-cpp-2`'s own thiserror-derived error
# types; promoted to a direct dep so the migration's
# `#[derive(thiserror::Error)]` site is explicit. Pinned at the v2
# series to match what `llama-cpp-2` already pulls in (v2.0.18);
# v2's `#[source]` attribute matches the structured-error
# pattern this crate's error-handling design favors.
thiserror = "2"

# Compiled regex patterns for the token-based pattern_key
# normalizer in `ctprof_compare.rs`. Used to express the
# spec rules in `classify_token` (`^\d+$`, `^[0-9a-f]+$`,
# `^[A-Za-z]+\d+$`, `^\d+[A-Za-z]+$`) literally rather than as
# hand-rolled char loops, so the rules read identically to the
# spec. Already a transitive dep via `fancy-regex` and others;
# promoted to a direct dep so the import sites are explicit.
regex = "1"

# Netlink stack for the taskstats genetlink capture path
# (`src/taskstats.rs`). The three crates layer cleanly:
#   `netlink-sys`             — AF_NETLINK socket + sendto/recv.
#   `netlink-packet-core`     — `NetlinkMessage<T>` envelope, ALIGN handling,
#                                multipart-reply assembly.
#   `netlink-packet-generic`  — generic-netlink (genetlink): `GenlMessage<T>`
#                                wrapper, CTRL_CMD_GETFAMILY family-id lookup.
# Hand-rolled genetlink against raw libc would be ~300 lines of
# protocol parsing (NLA alignment, multipart NLM_F_MULTI handshake,
# CTRL family lookup) — established crate stack avoids that
# correctness risk.
netlink-sys = "0.8"
netlink-packet-core = "0.7"
netlink-packet-generic = "0.3"

# Bindgen-generated bindings for the `perf_event_open(2)` syscall and
# its `perf_event_attr` struct. The Linux libc and the Rust libc crate
# both deliberately omit these — perf-event-open-sys provides the
# bitfield setters (`set_exclude_host`, `set_disabled`) and the
# kernel-version-tolerant struct layout (per its top-of-crate docs:
# old programs run on new kernels and vice versa). Used by
# `src/monitor/perf_counters.rs` to open one fd per (vCPU, hardware
# counter) with `exclude_host=1` so the PMU counts only what executes
# inside the guest. Disambiguates host-thread idle/spin/productive
# work without any in-guest overhead.
perf-event-open-sys = "6"
ahash = "0.8"

[profile.release]
lto = "thin"
panic = "abort"

[features]
integration = []

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)', 'cfg(coverage_nightly)'] }

[dev-dependencies]
assert_cmd = "2"
insta = "1"
predicates = "3"
proptest = "1"
# Grok-style structured log matcher. Used by the watchdog timing
# precision test to extract the sched_ext stall duration from the
# kernel's SCX_EXIT_ERROR_STALL message ("…failed to run for
# N.NNNs"). `default-features = false` disables the default `onig`
# C-regex engine (which would drag in a C library build). The
# `fancy-regex` engine is the minimum that supports the lookbehind
# and atomic-group constructs grok's default `BASE10NUM` / `NUMBER`
# patterns use; the pure-`regex` backend rejects those patterns
# at compile time.
grok = { version = "2", default-features = false, features = ["fancy-regex"] }
# In-test tracing subscriber that captures `tracing::warn!` /
# `tracing::info!` events into a per-test buffer, queryable via
# `logs_contain(&str)`. Used by the `set_rt_priority_warns_without_cap`
# unit test to pin that the CAP_SYS_NICE-absent path emits an
# actionable warn event — converted from a pure `eprintln!` that no
# test could observe. Default feature set filters to the current
# crate's tracing events, which matches the unit-test use case
# where the event originates from ktstr itself.
tracing-test = "0.2"
trybuild = "1"
mockito = "1"
# `test-utils` feature on the existing virtio-queue dep — gates the
# upstream `mock` module (`MockSplitQueue`, `desc_chain` builders)
# used by virtio-blk descriptor-chain tests. Pinned as a separate
# dev-dep entry rather than via `features = [...]` on the main
# `[dependencies]` line so the mock infrastructure does NOT leak
# into shipped binaries.
virtio-queue = { version = "0.17", features = ["test-utils"] }

[build-dependencies]
cc = "1"
flate2 = "1"
gix = { version = "0.81", default-features = false, features = ["sha1", "blocking-network-client", "blocking-http-transport-reqwest-rust-tls", "worktree-mutation"] }
libbpf-cargo = "0.26"
reqwest = { version = "0.13", features = ["blocking"] }
# Stable hash for BTF content drift detection in build.rs.
# Reusing the same crate ktstr uses for sidecar_variant_hash keeps
# the dep graph narrow and pins one algorithm across every hashing
# site in the project. See build.rs `siphash_13` for the call site.
siphasher = "1"
tar = "0.4"

[[bin]]
name = "ktstr"
path = "src/bin/ktstr.rs"

[[bin]]
name = "cargo-ktstr"
path = "src/bin/cargo-ktstr.rs"

[[bin]]
name = "ktstr-jemalloc-probe"
path = "src/bin/jemalloc_probe.rs"

[[bin]]
name = "ktstr-jemalloc-alloc-worker"
path = "src/bin/jemalloc_alloc_worker.rs"

[[test]]
name = "ktstr_sched_tests"

[[test]]
name = "failure_dump_e2e"

[[test]]
name = "cast_analysis_e2e"

[[test]]
name = "scenario_coverage"

[[test]]
name = "assert_gate_matrix"

[[test]]
name = "cargo_ktstr_cli"

[[test]]
name = "verifier_pipeline"

[[test]]
name = "compile_fail"

[[test]]
name = "jemalloc_probe_tests"

[[test]]
name = "live_host_pipeline_e2e"