use std::fs::{self, File};
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::time::Duration;
use criterion::{
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput,
};
use stackpulse::bench_support::{
self, BenchSpoolSample, LivePerfSampleFixture, SparseKernelSymbolsFixture, CURRENT_SPOOL_MAGIC,
};
use stackpulse::profile::{
is_python_runtime_basename as is_python_module, LocationInfo, NativeFrame, PythonFrame,
ResolvedFrame,
};
use stackpulse::record::{SampleErrorKind, SampleErrorStats};
use stackpulse::spool::{FrameMode, FrameRecord, ModulePath, ModuleRecord, PythonRuntimeRecord};
use stackpulse::{Snapshot, Symbolizer, SymbolizerBuilder};
const FIXTURE_VERSION: u32 = 10;
const OPEN_BATCH: u64 = 8;
const BORROWED_ITERATE_BATCH: u64 = 64;
const EXPANDED_ITERATE_BATCH: u64 = 16;
const METADATA_BATCH: u64 = 4096;
const WRITE_BATCH: u64 = 4;
const LIVE_PARSE_BATCH: u64 = 64;
const LIVE_RING_BATCH: u64 = 16;
const LIVE_RECORD_BATCH: u64 = 4;
const SPOOL_SYMBOLIZE_BATCH: u64 = 8;
const ADDRESS_CACHE_BATCH: u64 = 512;
const PERF_MAP_BATCH: u64 = 256;
const SPARSE_KERNEL_SYMBOL_BATCH: u64 = 8;
const NATIVE_ELF_FRAMES: usize = 32;
const HELPER_BATCH: u64 = 512;
const ERROR_STATS_BATCH: u64 = 128;
const SAMPLES: usize = 31;
const WARMUP_TIME: Duration = Duration::from_secs(2);
const MEASUREMENT_TIME: Duration = Duration::from_secs(10);
#[derive(Clone, Copy)]
struct ScenarioSpec {
name: &'static str,
processes: usize,
modules_per_process: usize,
samples: usize,
unique_stacks: usize,
stack_depth: usize,
include_kernel: bool,
include_python: bool,
include_python_runtime_records: bool,
}
const SPOOL_SCENARIOS: &[ScenarioSpec] = &[
ScenarioSpec {
name: "hot_stack_reuse",
processes: 1,
modules_per_process: 4,
samples: 16_384,
unique_stacks: 16,
stack_depth: 16,
include_kernel: false,
include_python: false,
include_python_runtime_records: false,
},
ScenarioSpec {
name: "many_unique_stacks",
processes: 2,
modules_per_process: 6,
samples: 4_096,
unique_stacks: 4_096,
stack_depth: 32,
include_kernel: false,
include_python: false,
include_python_runtime_records: true,
},
ScenarioSpec {
name: "deep_native_stacks",
processes: 1,
modules_per_process: 5,
samples: 2_048,
unique_stacks: 64,
stack_depth: 96,
include_kernel: false,
include_python: false,
include_python_runtime_records: false,
},
ScenarioSpec {
name: "python_kernel_mix",
processes: 3,
modules_per_process: 6,
samples: 4_096,
unique_stacks: 256,
stack_depth: 48,
include_kernel: true,
include_python: true,
include_python_runtime_records: true,
},
];
fn criterion_config() -> Criterion {
Criterion::default()
.sample_size(SAMPLES)
.warm_up_time(WARMUP_TIME)
.measurement_time(MEASUREMENT_TIME)
.noise_threshold(0.02)
}
criterion_group! {
name = benches;
config = criterion_config();
targets =
bench_spool_open,
bench_spool_iteration,
bench_spool_write,
bench_symbolization,
bench_helpers,
bench_live_perf_events
}
criterion_main!(benches);
fn bench_spool_open(c: &mut Criterion) {
let mut group = c.benchmark_group("stackpulse_cpu/spool/open");
group.sampling_mode(SamplingMode::Flat);
for spec in SPOOL_SCENARIOS {
let path = ensure_spool_fixture(*spec);
let bytes = fs::metadata(&path).expect("synthetic spool metadata").len();
group.throughput(Throughput::Bytes(bytes * OPEN_BATCH));
group.bench_function(BenchmarkId::from_parameter(spec.name), |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..OPEN_BATCH {
let reader = Snapshot::open(black_box(&path)).expect("open synthetic spool");
checksum = checksum
.wrapping_add(reader.modules().len())
.wrapping_add(reader.samples().len())
.wrapping_add(reader.python_runtime_records().len());
}
black_box(checksum)
});
});
}
group.finish();
}
fn bench_spool_iteration(c: &mut Criterion) {
let readers: Vec<_> = SPOOL_SCENARIOS
.iter()
.map(|spec| {
(
*spec,
Snapshot::open(ensure_spool_fixture(*spec)).expect("open synthetic spool"),
)
})
.collect();
let borrowed_frame_count = readers
.iter()
.map(|(spec, _)| spec.samples as u64 * spec.stack_depth as u64)
.sum::<u64>()
* BORROWED_ITERATE_BATCH;
let mut borrowed = c.benchmark_group("stackpulse_cpu/spool/iterate/borrowed_stack_frames");
borrowed.sampling_mode(SamplingMode::Flat);
borrowed.throughput(Throughput::Elements(borrowed_frame_count));
borrowed.bench_function("all_scenarios", |b| {
b.iter(|| {
let mut checksum = 0usize;
let mut frames = 0usize;
for _ in 0..BORROWED_ITERATE_BATCH {
for (_, reader) in &readers {
for stack in reader.stacks() {
for frame in stack.frames() {
frames += 1;
checksum = checksum.wrapping_add(raw_frame_score(frame));
}
}
}
}
black_box(checksum ^ frames)
});
});
borrowed.finish();
let mut context_group =
c.benchmark_group("stackpulse_cpu/spool/iterate/borrowed_stack_contexts");
context_group.sampling_mode(SamplingMode::Flat);
context_group.throughput(Throughput::Elements(borrowed_frame_count));
context_group.bench_function("all_scenarios", |b| {
b.iter(|| {
let mut checksum = 0usize;
let mut frames = 0usize;
for _ in 0..BORROWED_ITERATE_BATCH {
for (_, reader) in &readers {
for stack in reader.stacks() {
for context in stack.contexts() {
frames += 1;
checksum = checksum
.wrapping_add(raw_frame_score(context.frame))
.wrapping_add(context.module.map_or(0, |module| {
module.module.id() as usize ^ module.file_relative_ip as usize
}));
}
}
}
}
black_box(checksum ^ frames)
});
});
context_group.finish();
let mut expanded_group =
c.benchmark_group("stackpulse_cpu/spool/iterate/expanded_stack_frames");
expanded_group.sampling_mode(SamplingMode::Flat);
for (spec, reader) in &readers {
let expanded_frame_count =
spec.samples as u64 * spec.stack_depth as u64 * EXPANDED_ITERATE_BATCH;
expanded_group.throughput(Throughput::Elements(expanded_frame_count));
expanded_group.bench_function(BenchmarkId::from_parameter(spec.name), |b| {
b.iter(|| {
let mut expanded = Vec::with_capacity(spec.stack_depth);
let mut checksum = 0usize;
let mut frames = 0usize;
for _ in 0..EXPANDED_ITERATE_BATCH {
for stack in reader.stacks() {
expanded.clear();
expanded.extend(stack.frames().copied());
frames += expanded.len();
checksum = checksum.wrapping_add(raw_frames_score(&expanded));
}
}
black_box(checksum ^ frames)
});
});
}
expanded_group.finish();
let metadata_elements = readers
.iter()
.map(|(_, reader)| reader.samples().len() as u64)
.sum::<u64>()
* METADATA_BATCH;
let mut metadata = c.benchmark_group("stackpulse_cpu/spool/iterate/sample_metadata");
metadata.sampling_mode(SamplingMode::Flat);
metadata.throughput(Throughput::Elements(metadata_elements));
metadata.bench_function("all_scenarios", |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..METADATA_BATCH {
for (_, reader) in &readers {
for sample in reader.samples() {
checksum = checksum
.wrapping_add(reader.timestamp_us(sample).unwrap_or(0) as usize)
.wrapping_add(sample.process_id.get() as usize)
.wrapping_add(sample.thread_id.get() as usize);
}
for module in reader.modules() {
let path = module.path().as_str();
checksum = checksum
.wrapping_add(path.len())
.wrapping_add(usize::from(is_python_module(basename(path))));
}
}
}
black_box(checksum)
});
});
metadata.finish();
}
fn bench_spool_write(c: &mut Criterion) {
let cases: Vec<_> = SPOOL_SCENARIOS
.iter()
.map(|spec| {
let bytes = fs::metadata(ensure_spool_fixture(*spec))
.expect("synthetic spool metadata")
.len();
(*spec, bytes as usize, bytes, materialize_spool_case(*spec))
})
.collect();
let bytes = cases.iter().map(|(_, _, bytes, _)| *bytes).sum::<u64>();
let mut group = c.benchmark_group("stackpulse_cpu/spool/write_memory");
group.sampling_mode(SamplingMode::Flat);
group.throughput(Throughput::Bytes(bytes * WRITE_BATCH));
group.bench_function("all_scenarios", |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..WRITE_BATCH {
for (_, capacity, _, case) in &cases {
checksum = checksum.wrapping_add(
bench_support::write_spool_samples_to_memory(
black_box(&case.modules),
black_box(&case.python_runtime_records),
black_box(&case.samples),
*capacity,
)
.expect("write synthetic samples through real spool writer"),
);
}
}
black_box(checksum)
});
});
group.finish();
}
fn bench_live_perf_events(c: &mut Criterion) {
let fixture = LivePerfSampleFixture::new();
let mut perf_event = c.benchmark_group("stackpulse_self/perf_event");
perf_event.sampling_mode(SamplingMode::Flat);
perf_event.throughput(Throughput::Bytes(fixture.event_bytes() * LIVE_PARSE_BATCH));
perf_event.bench_function("parse_synthetic_sample_records", |b| {
b.iter(|| {
black_box(bench_support::parse_live_perf_samples(
black_box(&fixture),
LIVE_PARSE_BATCH,
))
});
});
perf_event.finish();
let mut ring_lifecycle = c.benchmark_group("stackpulse_self/ring_lifecycle");
ring_lifecycle.sampling_mode(SamplingMode::Flat);
ring_lifecycle.throughput(Throughput::Elements(
fixture.sample_count() * LIVE_RING_BATCH,
));
for ring_count in [1, 4, 64] {
ring_lifecycle.bench_with_input(
BenchmarkId::new("construct_publish_consume_drop", ring_count),
&ring_count,
|b, &ring_count| {
b.iter(|| {
black_box(
bench_support::consume_perf_ring_records(
black_box(&fixture),
ring_count,
LIVE_RING_BATCH,
)
.expect("consume synthetic records through perf mmap ring"),
)
});
},
);
}
ring_lifecycle.finish();
let mut recorder = c.benchmark_group("stackpulse_self/ring_replay");
recorder.sampling_mode(SamplingMode::Flat);
recorder.throughput(Throughput::Elements(
fixture.sample_count() * LIVE_RECORD_BATCH,
));
recorder.bench_function("zero_copy_ring_records_to_spool", |b| {
b.iter(|| {
black_box(
bench_support::replay_live_perf_ring_records_to_spool(
black_box(&fixture),
LIVE_RECORD_BATCH,
)
.expect("replay synthetic ring records through recorder hot path"),
)
});
});
recorder.finish();
}
fn bench_symbolization(c: &mut Criterion) {
let address_stacks = address_only_stacks(256, 32, FrameMode::User, 0x7000_0000);
let mut address_group = c.benchmark_group("stackpulse_cpu/symbolize/address_only");
address_group.sampling_mode(SamplingMode::Flat);
address_group.throughput(Throughput::Elements(total_frames(&address_stacks) as u64));
address_group.bench_function("unique_stacks", |b| {
b.iter(|| {
let mut symbolizer = SymbolizerBuilder::for_modules(&[])
.disable_perf_maps()
.build()
.expect("build symbolizer");
let mut checksum = 0usize;
for frames in &address_stacks {
checksum =
checksum.wrapping_add(score_resolved_frame_slice(&mut symbolizer, 42, frames));
}
black_box(checksum)
});
});
let mut warm_symbolizer = SymbolizerBuilder::for_modules(&[])
.disable_perf_maps()
.build()
.expect("build symbolizer");
for frames in &address_stacks {
let _ = score_resolved_frame_slice(&mut warm_symbolizer, 42, frames);
}
address_group.throughput(Throughput::Elements(
total_frames(&address_stacks) as u64 * ADDRESS_CACHE_BATCH,
));
address_group.bench_function("warm_frame_cache", |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..ADDRESS_CACHE_BATCH {
for frames in &address_stacks {
checksum = checksum.wrapping_add(score_resolved_frame_slice(
&mut warm_symbolizer,
42,
frames,
));
}
}
black_box(checksum)
});
});
address_group.finish();
let mut spool_symbolizers: Vec<_> =
[SPOOL_SCENARIOS[0], SPOOL_SCENARIOS[1], SPOOL_SCENARIOS[3]]
.into_iter()
.map(|spec| {
let reader =
Snapshot::open(ensure_spool_fixture(spec)).expect("open synthetic spool");
let mut symbolizer = reader
.symbolizer()
.disable_perf_maps()
.build()
.expect("build symbolizer");
let _ = symbolize_reader(&reader, &mut symbolizer);
(spec, reader, symbolizer)
})
.collect();
let spool_symbolize_frames = spool_symbolizers
.iter()
.map(|(spec, _, _)| spec.samples as u64 * spec.stack_depth as u64)
.sum::<u64>()
* SPOOL_SYMBOLIZE_BATCH;
let mut spool_group =
c.benchmark_group("stackpulse_cpu/symbolize/spool_samples_warm_frame_cache");
spool_group.sampling_mode(SamplingMode::Flat);
spool_group.throughput(Throughput::Elements(spool_symbolize_frames));
spool_group.bench_function("all_scenarios", |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..SPOOL_SYMBOLIZE_BATCH {
for (_, reader, symbolizer) in &mut spool_symbolizers {
checksum = checksum.wrapping_add(symbolize_reader(reader, symbolizer));
}
}
black_box(checksum)
});
});
spool_group.finish();
let perf_map = PerfMapFixture::new(512);
let mut perf_map_group = c.benchmark_group("stackpulse_cpu/symbolize/python_perf_map");
perf_map_group.sampling_mode(SamplingMode::Flat);
perf_map_group.throughput(Throughput::Elements(
perf_map.frames.len() as u64 * PERF_MAP_BATCH,
));
perf_map_group.bench_function("python_perf_map", |b| {
b.iter(|| {
let mut symbolizer = SymbolizerBuilder::for_modules(&[])
.build()
.expect("build symbolizer");
let mut checksum = 0usize;
for stack_id in 0..PERF_MAP_BATCH {
checksum = checksum.wrapping_add(stack_id as usize).wrapping_add(
score_resolved_frame_slice(
&mut symbolizer,
perf_map.process_id,
&perf_map.frames,
),
);
}
black_box(checksum)
});
});
perf_map_group.finish();
if let Some((modules, frames)) = current_exe_symbolization_fixture() {
let mut native_group = c.benchmark_group("stackpulse_cpu/symbolize/native_elf");
native_group.sampling_mode(SamplingMode::Flat);
native_group.throughput(Throughput::Elements(frames.len() as u64));
native_group.bench_function("cold_current_exe_batch", |b| {
b.iter(|| {
let mut symbolizer = SymbolizerBuilder::for_modules(&modules)
.disable_perf_maps()
.build()
.expect("build symbolizer");
black_box(score_resolved_frame_slice(
&mut symbolizer,
std::process::id() as i32,
&frames,
))
});
});
native_group.finish();
}
let kernel_symbols = SparseKernelSymbolsFixture::new(65_536, 1_024);
let mut kernel_group = c.benchmark_group("stackpulse_cpu/symbolize/kernel_symbols");
kernel_group.sampling_mode(SamplingMode::Flat);
kernel_group.throughput(Throughput::Bytes(
kernel_symbols.bytes() * SPARSE_KERNEL_SYMBOL_BATCH,
));
kernel_group.bench_function("parse_sparse_sorted_kallsyms", |b| {
b.iter(|| {
black_box(bench_support::parse_sparse_kernel_symbols(
black_box(&kernel_symbols),
SPARSE_KERNEL_SYMBOL_BATCH,
))
});
});
kernel_group.finish();
}
fn bench_helpers(c: &mut Criterion) {
let module_names = [
"python",
"python3",
"python3.12",
"python3.13t",
"Python3.12",
"libpython3.12.so",
"libpython3.12.so.1.0",
"libpython3.13t.so",
"libpython3.12.dylib",
"pypy3",
"python3.12-config",
"libpythonx.so",
"libnotpython3.12.so",
];
let paths = [
PathBuf::from("/usr/bin/python3.12"),
PathBuf::from("/opt/stackpulse/lib/libworker.so"),
PathBuf::from("[kernel.kallsyms]"),
PathBuf::from("/tmp/a path/deleted.so"),
PathBuf::from("relative-name"),
];
let basename_inputs = [
"/tmp/app.py",
"/very/long/path/with/many/segments/module.py",
"no/slash/after/first",
"filename_only",
"/usr/lib/x86_64-linux-gnu/libpython3.12.so.1.0",
];
let frames = resolved_frame_matrix();
let bases = module_image_base_inputs();
let mut group = c.benchmark_group("stackpulse_cpu/helpers");
group.sampling_mode(SamplingMode::Flat);
group.throughput(Throughput::Elements(HELPER_BATCH));
group.bench_function("profile_and_path_helpers", |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..HELPER_BATCH {
for name in module_names {
checksum = checksum.wrapping_add(usize::from(is_python_module(name)));
}
for path in &paths {
checksum = checksum.wrapping_add(bench_support::path_name(path).len());
}
for input in basename_inputs {
checksum = checksum.wrapping_add(bench_support::basename_start(input));
}
for frame in &frames {
checksum = checksum.wrapping_add(frame.display_name().len());
}
for &(base_avma, base_svma, avma) in &bases {
let (relative, svma) =
bench_support::translate_module_address(base_avma, base_svma, avma)
.expect("valid AVMA");
checksum = checksum
.wrapping_add(relative as usize)
.wrapping_add(svma as usize);
}
}
black_box(checksum)
});
});
group.throughput(Throughput::Elements(ERROR_STATS_BATCH));
group.bench_function("sample_error_stats", |b| {
b.iter(|| {
let mut checksum = 0usize;
for _ in 0..ERROR_STATS_BATCH {
let stats = dense_error_stats();
stats.record(SampleErrorKind::NativeStackRead);
checksum = checksum.wrapping_add(
stats.total() as usize
^ stats
.nonzero_counts()
.map(|(_, count)| count as usize)
.sum::<usize>(),
);
}
black_box(checksum)
});
});
group.finish();
}
fn ensure_spool_fixture(spec: ScenarioSpec) -> PathBuf {
let dir = fixture_dir();
fs::create_dir_all(&dir).expect("create synthetic fixture directory");
let path = dir.join(format!("{}-v{FIXTURE_VERSION}.spool", spec.name));
if fixture_has_current_magic(&path) {
return path;
}
let _ = fs::remove_file(&path);
let tmp_path = dir.join(format!(
"{}-v{FIXTURE_VERSION}.{}.tmp",
spec.name,
std::process::id()
));
write_synthetic_spool(&tmp_path, spec).expect("write synthetic spool fixture");
assert!(
fixture_has_current_magic(&tmp_path),
"synthetic fixture was not written in the current spool format"
);
fs::rename(&tmp_path, &path).expect("install immutable synthetic spool fixture");
path
}
fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("stackpulse-bench-fixtures")
}
fn fixture_has_current_magic(path: &Path) -> bool {
let Ok(mut file) = File::open(path) else {
return false;
};
let mut magic = [0; CURRENT_SPOOL_MAGIC.len()];
file.read_exact(&mut magic).is_ok() && magic == *CURRENT_SPOOL_MAGIC
}
fn write_synthetic_spool(path: &Path, spec: ScenarioSpec) -> io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let case = materialize_spool_case(spec);
Ok(bench_support::write_spool_samples_to_path(
path,
&case.modules,
&case.python_runtime_records,
&case.samples,
)?)
}
struct SpoolBenchCase {
modules: Vec<ModuleRecord>,
python_runtime_records: Vec<PythonRuntimeRecord>,
samples: Vec<BenchSpoolSample>,
}
fn materialize_spool_case(spec: ScenarioSpec) -> SpoolBenchCase {
let modules = synthetic_modules(spec);
let python_runtime_records = if spec.include_python_runtime_records {
(0..spec.processes)
.map(|process| PythonRuntimeRecord {
timestamp_ns: 10_000 + process as u64,
process_id: stackpulse::Pid::try_from(process_id(process))
.expect("valid benchmark pid"),
is_python_runtime: spec.include_python && process % 2 == 0,
})
.collect()
} else {
Vec::new()
};
let kernel_module_id = modules
.iter()
.find(|module| module.is_kernel())
.map(ModuleRecord::id);
let mut stack = Vec::with_capacity(spec.stack_depth);
let mut samples = Vec::with_capacity(spec.samples);
for sample_idx in 0..spec.samples {
let process_idx = sample_idx % spec.processes;
let process_id = process_id(process_idx);
let variant = sample_idx % spec.unique_stacks;
stack.clear();
for depth in 0..spec.stack_depth {
let use_kernel = kernel_module_id.is_some() && depth + 1 == spec.stack_depth;
let frame = if use_kernel && sample_idx % 3 == 0 {
let module = &modules[kernel_module_id.expect("kernel module id") as usize];
frame_in_module(module, variant, depth, FrameMode::Kernel)
} else {
let module_offset = (variant + depth * 3) % spec.modules_per_process;
let module_idx = process_idx * spec.modules_per_process + module_offset;
frame_in_module(&modules[module_idx], variant, depth, FrameMode::User)
};
stack.push(frame);
}
samples.push(BenchSpoolSample {
timestamp_ns: 1_000_000 + sample_idx as u64 * 1_000,
process_id,
thread_id: thread_id(process_idx, sample_idx),
frames: stack.clone(),
});
}
SpoolBenchCase {
modules,
python_runtime_records,
samples,
}
}
fn synthetic_modules(spec: ScenarioSpec) -> Vec<ModuleRecord> {
let mut modules = Vec::with_capacity(
spec.processes * spec.modules_per_process + usize::from(spec.include_kernel),
);
for process in 0..spec.processes {
let process_id = process_id(process);
let process_base = 0x1000_0000_0000 + process as u64 * 0x1000_0000;
for index in 0..spec.modules_per_process {
let id = modules.len() as u32;
let start = process_base + index as u64 * 0x0010_0000;
modules.push(
ModuleRecord::new(
id,
stackpulse::Pid::try_from(process_id).expect("valid benchmark pid"),
start..start + 0x000c_0000,
(index as u64 % 4) * 0x1000,
module_path(spec, process, index),
)
.expect("valid benchmark module")
.file_identity(0, 0, 100_000 + id as u64, 0),
);
}
}
if spec.include_kernel {
let id = modules.len() as u32;
modules.push(
ModuleRecord::kernel(
id,
0xffff_ffff_8000_0000..0xffff_ffff_9000_0000,
ModulePath::from("[kernel.kallsyms]"),
)
.expect("valid benchmark kernel module"),
);
}
modules
}
fn module_path(spec: ScenarioSpec, process: usize, index: usize) -> ModulePath {
if spec.include_python {
match index {
0 => return ModulePath::from(format!("/opt/python/process-{process}/python3.12")),
1 => return ModulePath::from("[anon:python-code]"),
2 => return ModulePath::from(format!("/tmp/stackpulse-app-{process}.py")),
_ => {}
}
}
ModulePath::from(format!("/opt/stackpulse/lib/libbench-{process}-{index}.so"))
}
fn frame_in_module(
module: &ModuleRecord,
variant: usize,
depth: usize,
fallback_mode: FrameMode,
) -> FrameRecord {
let addresses = module.address_range();
let span = addresses.end - addresses.start;
let offset = ((variant as u64 * 131) + (depth as u64 * 67)) % span.saturating_sub(0x100);
let file_relative_ip = module.file_offset() + offset;
let abs_ip = addresses.start + offset;
FrameRecord {
module_id: Some(module.id()),
file_relative_ip,
abs_ip,
mode: if module.is_kernel() {
FrameMode::Kernel
} else {
fallback_mode
},
}
}
fn process_id(index: usize) -> i32 {
10_000 + index as i32
}
fn thread_id(process_idx: usize, sample_idx: usize) -> u64 {
process_id(process_idx) as u64 * 10 + (sample_idx % 8) as u64
}
fn basename(path: &str) -> &str {
path.rsplit('/').next().unwrap_or(path)
}
fn address_only_stacks(
stacks: usize,
depth: usize,
mode: FrameMode,
base: u64,
) -> Vec<Vec<FrameRecord>> {
(0..stacks)
.map(|stack_id| {
(0..depth)
.map(|depth| {
let abs_ip = base + stack_id as u64 * 0x1000 + depth as u64 * 0x30 + 8;
FrameRecord {
module_id: None,
file_relative_ip: abs_ip,
abs_ip,
mode,
}
})
.collect()
})
.collect()
}
fn total_frames(stacks: &[Vec<FrameRecord>]) -> usize {
stacks.iter().map(Vec::len).sum()
}
fn symbolize_reader(reader: &Snapshot, symbolizer: &mut Symbolizer) -> usize {
let mut checksum = 0usize;
for stack in reader.stacks() {
let mut sample_score = 0usize;
let resolved = symbolizer.resolve(stack).expect("symbolize stack");
let frames = resolved.len();
for frame in resolved {
sample_score = sample_score.wrapping_add(resolved_frame_score(frame));
}
checksum = checksum.wrapping_add(sample_score).wrapping_add(frames);
}
checksum
}
fn score_resolved_frame_slice(
symbolizer: &mut Symbolizer,
process_id: i32,
frames: &[FrameRecord],
) -> usize {
let mut score = 0usize;
let resolved = symbolizer
.resolve_raw(
stackpulse::Pid::try_from(process_id).expect("valid benchmark pid"),
frames,
)
.expect("symbolize frames");
let count = resolved.len();
for frame in resolved {
score = score.wrapping_add(resolved_frame_score(frame));
}
score.wrapping_add(count)
}
fn raw_frames_score(frames: &[FrameRecord]) -> usize {
frames.iter().fold(0usize, |score, frame| {
score.wrapping_add(raw_frame_score(frame))
})
}
fn raw_frame_score(frame: &FrameRecord) -> usize {
frame
.abs_ip
.wrapping_add(frame.file_relative_ip)
.wrapping_add(u64::from(frame.module_id.unwrap_or(u32::MAX))) as usize
}
fn resolved_frame_score(frame: &ResolvedFrame) -> usize {
match frame {
ResolvedFrame::Python(frame) => frame
.file_name()
.len()
.wrapping_add(frame.func_name.len())
.wrapping_add(frame.location.lineno as usize),
ResolvedFrame::Native(frame) => {
let symbol_score = frame.symbol.as_ref().map_or(0usize, |symbol| {
symbol
.name()
.len()
.wrapping_add(symbol.module.len())
.wrapping_add(symbol.offset as usize)
});
(frame.pc as usize).wrapping_add(symbol_score)
}
}
}
fn resolved_frame_matrix() -> Vec<ResolvedFrame> {
vec![
ResolvedFrame::Native(NativeFrame::from_address(0x1000)),
ResolvedFrame::Native(NativeFrame::from_address(0x1010)),
ResolvedFrame::Python(PythonFrame::new(
"/tmp/stackpulse/app.py",
LocationInfo {
lineno: 42,
end_lineno: 43,
column: 1,
end_column: 8,
},
"stackpulse_busy_leaf",
None,
false,
)),
ResolvedFrame::Python(PythonFrame::new(
"/tmp/stackpulse/app.py",
LocationInfo::default(),
"stackpulse_busy_middle",
Some(2),
false,
)),
]
}
fn module_image_base_inputs() -> Vec<(u64, u64, u64)> {
(0..128)
.map(|index| {
let avma = 0x7fff_0000_0000 + index * 0x20_000;
let svma = 0x1000 + index * 0x10;
(avma, svma, avma + 0x1234)
})
.collect()
}
fn dense_error_stats() -> SampleErrorStats {
let stats = SampleErrorStats::new();
for (index, kind) in SampleErrorKind::ALL.iter().enumerate() {
for _ in 0..(index + 1) {
stats.record(*kind);
}
}
stats
}
fn current_exe_symbolization_fixture() -> Option<(Vec<ModuleRecord>, Vec<FrameRecord>)> {
let exe = std::env::current_exe().ok()?;
let exe = fs::canonicalize(&exe).unwrap_or(exe);
let abs_ip = native_symbol_probe_addr();
let maps = fs::read_to_string("/proc/self/maps").ok()?;
let (start, end, file_offset, inode) = find_current_exe_mapping(&maps, &exe, abs_ip)?;
let file_relative_ip = file_offset + abs_ip.saturating_sub(start);
let module = ModuleRecord::new(
0,
stackpulse::Pid::try_from(std::process::id()).ok()?,
start..end,
file_offset,
exe.to_string_lossy().into_owned(),
)
.ok()?
.file_identity(0, 0, inode, 0);
let frames = current_exe_frame_batch(start, end, file_offset, abs_ip);
let frames = if frames.is_empty() {
vec![FrameRecord {
module_id: Some(0),
file_relative_ip,
abs_ip,
mode: FrameMode::User,
}]
} else {
frames
};
Some((vec![module], frames))
}
fn current_exe_frame_batch(
start: u64,
end: u64,
file_offset: u64,
center: u64,
) -> Vec<FrameRecord> {
if start >= end {
return Vec::new();
}
let last = end - 1;
let half = NATIVE_ELF_FRAMES as i64 / 2;
(0..NATIVE_ELF_FRAMES)
.map(|index| {
let delta = (index as i64 - half) * 8;
let abs_ip = if delta < 0 {
center.saturating_sub((-delta) as u64)
} else {
center.saturating_add(delta as u64)
}
.clamp(start, last);
FrameRecord {
module_id: Some(0),
file_relative_ip: file_offset + abs_ip.saturating_sub(start),
abs_ip,
mode: FrameMode::User,
}
})
.collect()
}
fn find_current_exe_mapping(maps: &str, exe: &Path, abs_ip: u64) -> Option<(u64, u64, u64, u64)> {
maps.lines().find_map(|line| {
let mut fields = line.split_whitespace();
let range = fields.next()?;
let perms = fields.next()?;
let file_offset = u64::from_str_radix(fields.next()?, 16).ok()?;
let _dev = fields.next()?;
let inode = fields.next()?.parse().ok()?;
let path = fields.collect::<Vec<_>>().join(" ");
if perms.as_bytes().get(2).is_none_or(|b| *b != b'x') {
return None;
}
if !path_matches_current_exe(&path, exe) {
return None;
}
let (start, end) = range.split_once('-')?;
let start = u64::from_str_radix(start, 16).ok()?;
let end = u64::from_str_radix(end, 16).ok()?;
(start <= abs_ip && abs_ip < end).then_some((start, end, file_offset, inode))
})
}
fn path_matches_current_exe(path: &str, exe: &Path) -> bool {
let path = Path::new(path);
path == exe || fs::canonicalize(path).is_ok_and(|canonical| canonical == exe)
}
#[inline(never)]
fn native_symbol_probe_addr() -> u64 {
native_symbol_probe_addr as *const () as usize as u64
}
struct PerfMapFixture {
process_id: i32,
path: PathBuf,
frames: Vec<FrameRecord>,
}
impl PerfMapFixture {
fn new(symbols: usize) -> Self {
let process_id = i32::try_from(std::process::id())
.expect("Linux process IDs fit in i32")
.checked_add(20_000)
.expect("benchmark process ID fits in i32");
let path = PathBuf::from(format!("/tmp/perf-{process_id}.map"));
let mut text = String::new();
let mut frames = Vec::with_capacity(symbols);
let base = 0x5000_0000;
for index in 0..symbols {
let start = base + index as u64 * 0x40;
text.push_str(&format!(
"{start:x} 40 py::bench_func_{index}:/tmp/stackpulse_bench.py\n"
));
frames.push(FrameRecord {
module_id: None,
file_relative_ip: start + 8,
abs_ip: start + 8,
mode: FrameMode::User,
});
}
fs::write(&path, text).expect("write synthetic perf map");
Self {
process_id,
path,
frames,
}
}
}
impl Drop for PerfMapFixture {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}