#![allow(missing_docs)]
#![allow(unsafe_code)]
use bytes::Bytes;
use serde_json::{json, Value};
use std::alloc::{GlobalAlloc, Layout, System};
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
use webui::streaming::{ChunkPool, StreamingWriter};
use webui::{build, BuildOptions, CssStrategy, Protocol, ResponseWriter, WebUIHandler};
use webui_handler::RenderOptions;
struct CountingAlloc;
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static ALLOC_BYTES: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
ALLOC_BYTES.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
ALLOC_BYTES.fetch_add(layout.size(), Ordering::Relaxed);
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if new_size > layout.size() {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
ALLOC_BYTES.fetch_add(new_size - layout.size(), Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn alloc_snapshot() -> (usize, usize) {
(
ALLOC_COUNT.load(Ordering::Relaxed),
ALLOC_BYTES.load(Ordering::Relaxed),
)
}
#[derive(Copy, Clone)]
struct ProcessUsage {
user_cpu: Duration,
sys_cpu: Duration,
max_rss_bytes: i64,
}
impl ProcessUsage {
#[cfg(unix)]
fn now() -> Self {
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
let rc = unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut usage) };
assert_eq!(rc, 0, "getrusage failed");
Self {
user_cpu: timeval_to_duration(usage.ru_utime),
sys_cpu: timeval_to_duration(usage.ru_stime),
max_rss_bytes: unix_rss_to_bytes(usage.ru_maxrss as i64),
}
}
#[cfg(windows)]
fn now() -> Self {
use windows_sys::Win32::Foundation::FILETIME;
use windows_sys::Win32::System::ProcessStatus::{
K32GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, GetProcessTimes};
let process = unsafe { GetCurrentProcess() };
let mut creation_time = FILETIME::default();
let mut exit_time = FILETIME::default();
let mut kernel_time = FILETIME::default();
let mut user_time = FILETIME::default();
let times_ok = unsafe {
GetProcessTimes(
process,
&mut creation_time,
&mut exit_time,
&mut kernel_time,
&mut user_time,
)
};
assert_ne!(times_ok, 0, "GetProcessTimes failed");
let counters_size = process_memory_counters_size();
let mut counters: PROCESS_MEMORY_COUNTERS = unsafe { std::mem::zeroed() };
counters.cb = counters_size;
let memory_ok = unsafe { K32GetProcessMemoryInfo(process, &mut counters, counters_size) };
assert_ne!(memory_ok, 0, "GetProcessMemoryInfo failed");
Self {
user_cpu: filetime_to_duration(user_time),
sys_cpu: filetime_to_duration(kernel_time),
max_rss_bytes: usize_to_i64_saturating(counters.PeakWorkingSetSize),
}
}
#[cfg(not(any(unix, windows)))]
fn now() -> Self {
Self {
user_cpu: Duration::ZERO,
sys_cpu: Duration::ZERO,
max_rss_bytes: -1,
}
}
fn max_rss_bytes(&self) -> i64 {
self.max_rss_bytes
}
}
#[cfg(unix)]
fn timeval_to_duration(tv: libc::timeval) -> Duration {
let secs = tv.tv_sec as u64;
let usecs = tv.tv_usec as u32;
Duration::new(secs, usecs * 1_000)
}
#[cfg(unix)]
fn unix_rss_to_bytes(raw: i64) -> i64 {
if cfg!(target_os = "macos") {
raw
} else {
raw.saturating_mul(1024)
}
}
#[cfg(windows)]
fn filetime_to_duration(filetime: windows_sys::Win32::Foundation::FILETIME) -> Duration {
let ticks = (u64::from(filetime.dwHighDateTime) << 32) | u64::from(filetime.dwLowDateTime);
let secs = ticks / 10_000_000;
let nanos = match u32::try_from((ticks % 10_000_000) * 100) {
Ok(value) => value,
Err(_) => panic!("FILETIME nanosecond remainder must fit in u32"),
};
Duration::new(secs, nanos)
}
#[cfg(windows)]
fn usize_to_i64_saturating(value: usize) -> i64 {
match i64::try_from(value) {
Ok(value) => value,
Err(_) => i64::MAX,
}
}
#[cfg(windows)]
fn process_memory_counters_size() -> u32 {
match u32::try_from(std::mem::size_of::<
windows_sys::Win32::System::ProcessStatus::PROCESS_MEMORY_COUNTERS,
>()) {
Ok(value) => value,
Err(_) => panic!("PROCESS_MEMORY_COUNTERS size must fit in u32"),
}
}
#[derive(Copy, Clone)]
struct ResourceDelta {
iters: usize,
allocs: usize,
bytes: usize,
user_cpu: Duration,
sys_cpu: Duration,
wall_time: Duration,
rss_high_water_bytes: i64,
}
impl ResourceDelta {
fn per_iter(&self) -> PerIter {
let n = self.iters as f64;
PerIter {
allocs: self.allocs as f64 / n,
bytes: self.bytes as f64 / n,
user_cpu_us: self.user_cpu.as_secs_f64() * 1_000_000.0 / n,
sys_cpu_us: self.sys_cpu.as_secs_f64() * 1_000_000.0 / n,
wall_us: self.wall_time.as_secs_f64() * 1_000_000.0 / n,
rss_bytes: self.rss_high_water_bytes,
}
}
}
struct PerIter {
allocs: f64,
bytes: f64,
user_cpu_us: f64,
sys_cpu_us: f64,
wall_us: f64,
rss_bytes: i64,
}
const FIRST_NAMES: &[&str] = &[
"Sarah", "Marcus", "Yuki", "Priya", "James", "Amara", "Luis", "Emma", "David", "Fatima",
];
const LAST_NAMES: &[&str] = &[
"Chen",
"Johnson",
"Tanaka",
"Sharma",
"O'Brien",
"Okafor",
"Ramirez",
"Lindström",
"Kim",
"Al-Hassan",
];
const GROUPS: &[&str] = &["Family", "Work", "Friends", "Other"];
fn generate_contact(idx: usize) -> Value {
let first = FIRST_NAMES[idx % FIRST_NAMES.len()];
let last = LAST_NAMES[idx % LAST_NAMES.len()];
json!({
"id": (idx + 1).to_string(),
"firstName": first,
"lastName": last,
"email": format!("{}.{}@example.com", first.to_lowercase(), last.to_lowercase()),
"phone": format!("+1 (555) {:03}-{:04}", (idx * 111) % 1000, (idx * 1234) % 10000),
"company": "Contoso Ltd",
"group": GROUPS[idx % GROUPS.len()],
"favorite": idx.is_multiple_of(3),
"initials": format!("{}{}", &first[..1], &last[..1]),
"avatarColor": "#4A90D9",
"notes": String::new(),
"address": format!("{} St, Seattle, WA", (idx + 1) * 100),
})
}
fn build_state(count: usize) -> Value {
let contacts: Vec<Value> = (0..count).map(generate_contact).collect();
let recent: Vec<Value> = contacts[count.saturating_sub(5)..].to_vec();
json!({
"page": "dashboard",
"searchQuery": "",
"activeGroup": "all",
"groups": GROUPS,
"totalContacts": count,
"totalFavorites": 0,
"totalGroups": GROUPS.len(),
"contacts": contacts.clone(),
"filteredContacts": contacts,
"recentContacts": recent,
"favoriteContacts": Vec::<Value>::new(),
"selectedContact": null,
})
}
fn build_protocol() -> Protocol {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let app_dir = manifest
.join("..")
.join("..")
.join("examples")
.join("app")
.join("contact-book-manager")
.join("src");
let document = build(BuildOptions {
app_dir,
entry: "index.html".to_string(),
css: CssStrategy::Style,
..BuildOptions::default()
})
.expect("failed to build contact-book-manager protocol")
.protocol;
Protocol::new(document)
}
const HEAD_INJECT: &str = r#"<link rel="preload" as="image" href="/img/hero.jpg" fetchpriority="high"><link rel="preload" as="image" href="/img/p1.jpg"><link rel="preload" as="image" href="/img/p2.jpg">"#;
const BODY_INJECT: &str = r#"<script>(function(){var e=new EventSource('/__webui/livereload');e.addEventListener('reload',function(){location.reload()})})();</script>"#;
struct StringWriter {
buf: String,
}
impl StringWriter {
fn with_capacity(cap: usize) -> Self {
Self {
buf: String::with_capacity(cap),
}
}
}
impl ResponseWriter for StringWriter {
fn write(&mut self, content: &str) -> webui_handler::Result<()> {
self.buf.push_str(content);
Ok(())
}
fn end(&mut self) -> webui_handler::Result<()> {
Ok(())
}
}
fn drain_total(mut rx: mpsc::Receiver<Bytes>) -> usize {
let mut total = 0;
while let Some(chunk) = rx.blocking_recv() {
total += chunk.len();
}
total
}
fn post_inject(html: &str, script: &str) -> String {
if let Some(idx) = html
.as_bytes()
.windows(7)
.position(|w| w.eq_ignore_ascii_case(b"</body>"))
{
let mut out = String::with_capacity(html.len() + script.len() + 2);
out.push_str(&html[..idx]);
out.push_str(script);
out.push_str(&html[idx..]);
out
} else {
let mut out = String::with_capacity(html.len() + script.len());
out.push_str(html);
out.push_str(script);
out
}
}
fn run_string(protocol: &Protocol, state: &Value, output_size: usize) -> usize {
let h = WebUIHandler::new();
let mut w = StringWriter::with_capacity(output_size);
h.render(
protocol,
state,
&RenderOptions::new("index.html", "/"),
&mut w,
)
.expect("render");
w.buf.len()
}
fn run_streaming(protocol: &Protocol, state: &Value, output_size: usize) -> usize {
let h = WebUIHandler::new();
let cap = (output_size / StreamingWriter::CHUNK_TARGET) + 4;
let (tx, rx) = mpsc::channel::<Bytes>(cap);
let mut w = StreamingWriter::new(tx);
h.render(
protocol,
state,
&RenderOptions::new("index.html", "/"),
&mut w,
)
.expect("render");
drop(w);
drain_total(rx)
}
fn run_streaming_with_inject(protocol: &Protocol, state: &Value, output_size: usize) -> usize {
let h = WebUIHandler::new();
let cap = (output_size / StreamingWriter::CHUNK_TARGET) + 4;
let (tx, rx) = mpsc::channel::<Bytes>(cap);
let mut w = StreamingWriter::new(tx);
let opts = RenderOptions::new("index.html", "/")
.with_head_inject(HEAD_INJECT)
.with_body_inject(BODY_INJECT);
h.render(protocol, state, &opts, &mut w).expect("render");
drop(w);
drain_total(rx)
}
fn run_streaming_pooled_with_inject(
protocol: &Protocol,
state: &Value,
output_size: usize,
pool: &Arc<ChunkPool>,
) -> usize {
let h = WebUIHandler::new();
let cap = (output_size / StreamingWriter::CHUNK_TARGET) + 4;
let (tx, rx) = mpsc::channel::<Bytes>(cap);
let mut w = StreamingWriter::new_pooled(tx, Arc::clone(pool));
let opts = RenderOptions::new("index.html", "/")
.with_head_inject(HEAD_INJECT)
.with_body_inject(BODY_INJECT);
h.render(protocol, state, &opts, &mut w).expect("render");
drop(w);
drain_total(rx)
}
fn run_string_postinject(protocol: &Protocol, state: &Value, output_size: usize) -> usize {
let h = WebUIHandler::new();
let mut w = StringWriter::with_capacity(output_size);
h.render(
protocol,
state,
&RenderOptions::new("index.html", "/"),
&mut w,
)
.expect("render");
let merged = post_inject(&w.buf, BODY_INJECT);
merged.len()
}
fn measure<F>(iters: usize, mut f: F) -> ResourceDelta
where
F: FnMut(),
{
for _ in 0..3 {
f();
}
let (a0, b0) = alloc_snapshot();
let r0 = ProcessUsage::now();
let t0 = Instant::now();
for _ in 0..iters {
f();
}
let wall = t0.elapsed();
let r1 = ProcessUsage::now();
let (a1, b1) = alloc_snapshot();
ResourceDelta {
iters,
allocs: a1.saturating_sub(a0),
bytes: b1.saturating_sub(b0),
user_cpu: r1.user_cpu.saturating_sub(r0.user_cpu),
sys_cpu: r1.sys_cpu.saturating_sub(r0.sys_cpu),
wall_time: wall,
rss_high_water_bytes: r1.max_rss_bytes(),
}
}
fn print_header() {
println!();
println!(
"| {:<26} | {:>7} | {:>10} | {:>13} | {:>9} | {:>11} | {:>10} | {:>14} |",
"path/scale (output bytes)",
"iters",
"allocs/run",
"bytes/run",
"wall µs",
"user µs/run",
"sys µs/run",
"process RSS",
);
println!(
"|{:-<28}|{:->9}|{:->12}|{:->15}|{:->11}|{:->13}|{:->12}|{:->16}|",
"", "", "", "", "", "", "", ""
);
}
fn print_row(label: &str, delta: ResourceDelta) {
let pi = delta.per_iter();
println!(
"| {:<26} | {:>7} | {:>10.2} | {:>13} | {:>9.2} | {:>11.2} | {:>10.2} | {:>14} |",
label,
delta.iters,
pi.allocs,
format_bytes_per_run(pi.bytes),
pi.wall_us,
pi.user_cpu_us,
pi.sys_cpu_us,
format_total_rss(pi.rss_bytes),
);
}
fn format_bytes_per_run(bytes: f64) -> String {
if bytes < 1024.0 {
format!("{bytes:.0} B")
} else if bytes < 1024.0 * 1024.0 {
format!("{:.1} KiB", bytes / 1024.0)
} else {
format!("{:.2} MiB", bytes / (1024.0 * 1024.0))
}
}
fn format_total_rss(bytes: i64) -> String {
if bytes < 0 {
return "n/a".to_string();
}
if bytes < 1024 * 1024 {
format!("{:.1} KiB", bytes as f64 / 1024.0)
} else {
format!("{:.2} MiB", bytes as f64 / (1024.0 * 1024.0))
}
}
fn warmup_output_size(protocol: &Protocol, state: &Value) -> usize {
let h = WebUIHandler::new();
let mut w = StringWriter::with_capacity(128 * 1024);
h.render(
protocol,
state,
&RenderOptions::new("index.html", "/"),
&mut w,
)
.expect("warmup");
w.buf.len()
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SnapshotRow {
label: String,
iters: usize,
allocs_per_run: f64,
bytes_per_run: f64,
user_cpu_us_per_run: f64,
sys_cpu_us_per_run: f64,
wall_us_per_run: f64,
rss_high_water_bytes: i64,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct Snapshot {
schema: u32,
name: String,
timestamp_unix: u64,
rows: Vec<SnapshotRow>,
}
const SNAPSHOT_SCHEMA: u32 = 1;
fn snapshot_path(name: &str) -> std::path::PathBuf {
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest
.join("..")
.join("..")
.join("target")
.join("bench-baselines")
.join(format!("resource-{name}.json"))
}
fn save_snapshot(name: &str, rows: &[SnapshotRow]) {
let path = snapshot_path(name);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let snap = Snapshot {
schema: SNAPSHOT_SCHEMA,
name: name.to_string(),
timestamp_unix: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
rows: rows.iter().map(SnapshotRow::clone_data).collect(),
};
let json = match serde_json::to_string_pretty(&snap) {
Ok(s) => s,
Err(e) => {
eprintln!("snapshot: serialize failed: {e}");
return;
}
};
if let Err(e) = std::fs::write(&path, json) {
eprintln!("snapshot: write {} failed: {e}", path.display());
return;
}
println!();
println!("✔ Baseline saved to {}", path.display());
}
fn load_snapshot(name: &str) -> Option<Snapshot> {
let path = snapshot_path(name);
let bytes = match std::fs::read(&path) {
Ok(b) => b,
Err(_) => {
eprintln!(
"compare: baseline '{name}' not found at {} — run with --save {name} first",
path.display()
);
return None;
}
};
match serde_json::from_slice::<Snapshot>(&bytes) {
Ok(s) if s.schema == SNAPSHOT_SCHEMA => Some(s),
Ok(s) => {
eprintln!(
"compare: baseline '{name}' has schema {} (expected {SNAPSHOT_SCHEMA}); regenerate with --save",
s.schema
);
None
}
Err(e) => {
eprintln!("compare: parse {} failed: {e}", path.display());
None
}
}
}
impl SnapshotRow {
fn clone_data(&self) -> SnapshotRow {
SnapshotRow {
label: self.label.clone(),
iters: self.iters,
allocs_per_run: self.allocs_per_run,
bytes_per_run: self.bytes_per_run,
user_cpu_us_per_run: self.user_cpu_us_per_run,
sys_cpu_us_per_run: self.sys_cpu_us_per_run,
wall_us_per_run: self.wall_us_per_run,
rss_high_water_bytes: self.rss_high_water_bytes,
}
}
}
fn print_diff(current: &[SnapshotRow], baseline: &Snapshot) {
println!();
println!(
"Diff vs baseline '{}' (saved {} ago)",
baseline.name,
format_age(baseline.timestamp_unix)
);
println!(
"| {:<42} | {:>14} | {:>14} | {:>14} |",
"row", "allocs Δ%", "bytes Δ%", "user_cpu Δ%"
);
println!("|{:-<44}|{:->16}|{:->16}|{:->16}|", "", "", "", "");
for cur in current {
let base = baseline.rows.iter().find(|b| b.label == cur.label);
let (a, b, c) = match base {
Some(base) => (
pct_change(base.allocs_per_run, cur.allocs_per_run),
pct_change(base.bytes_per_run, cur.bytes_per_run),
pct_change(base.user_cpu_us_per_run, cur.user_cpu_us_per_run),
),
None => {
println!(
"| {:<42} | {:>14} | {:>14} | {:>14} |",
cur.label, "(new row)", "—", "—"
);
continue;
}
};
println!(
"| {:<42} | {:>13.1}% | {:>13.1}% | {:>13.1}% |",
cur.label, a, b, c
);
}
println!();
println!("Negative Δ% = improvement; positive = regression. Threshold for action: ±5%.");
println!();
}
fn pct_change(base: f64, current: f64) -> f64 {
if base == 0.0 {
return 0.0;
}
((current - base) / base) * 100.0
}
fn format_age(then_unix: u64) -> String {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let secs = now.saturating_sub(then_unix);
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m", secs / 60)
} else if secs < 86400 {
format!("{}h", secs / 3600)
} else {
format!("{}d", secs / 86400)
}
}
enum Mode {
Print,
Save(String),
Compare(String),
}
fn parse_args() -> Mode {
let args: Vec<String> = std::env::args().skip(1).collect();
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--save" => {
return iter.next().map(Mode::Save).unwrap_or_else(|| {
eprintln!("--save requires a baseline name");
std::process::exit(2);
});
}
"--compare" => {
return iter.next().map(Mode::Compare).unwrap_or_else(|| {
eprintln!("--compare requires a baseline name");
std::process::exit(2);
});
}
"--help" | "-h" => {
println!(
"Usage: streaming_resource_bench [--save NAME] [--compare NAME]\n\n\
With no args: prints the table.\n\
--save NAME: write current results to target/bench-baselines/resource-NAME.json\n\
--compare NAME: print results AND a Δ%-table vs the saved baseline"
);
std::process::exit(0);
}
other => {
eprintln!("unknown arg: {other}");
std::process::exit(2);
}
}
}
Mode::Print
}
fn main() {
let mode = parse_args();
let scales = [10usize, 100, 1000];
let iters_per_scale = 2_000;
println!("WebUI streaming resource benchmark");
println!("==================================");
println!(
"Build: {} | iterations per row: {}",
if cfg!(debug_assertions) {
"DEBUG (numbers will be misleading; rebuild with --release)"
} else {
"release"
},
iters_per_scale
);
println!(
"RSS column = process-wide high-water mark observed at end of phase \
(cumulative across all phases, only meaningful as a peak)."
);
print_header();
let protocol = build_protocol();
let pool = Arc::new(ChunkPool::new(256, StreamingWriter::CHUNK_TARGET + 1024));
let paths: &[(&str, fn(&Protocol, &Value, usize) -> usize)] = &[
(
"string",
run_string as fn(&Protocol, &Value, usize) -> usize,
),
("streaming", run_streaming),
("streaming+inject(opts)", run_streaming_with_inject),
("string+postinject", run_string_postinject),
];
let mut snapshot_rows: Vec<SnapshotRow> = Vec::new();
for &scale in &scales {
let state = build_state(scale);
let output_size = warmup_output_size(&protocol, &state);
for (label, f) in paths {
let delta = measure(iters_per_scale, || {
std::hint::black_box(f(&protocol, &state, output_size));
});
let row_label = format!("{label}/{scale}");
print_row(&format!("{row_label} ({output_size}B)"), delta);
snapshot_rows.push(delta_to_row(&row_label, delta));
}
let delta = measure(iters_per_scale, || {
std::hint::black_box(run_streaming_pooled_with_inject(
&protocol,
&state,
output_size,
&pool,
));
});
let row_label = format!("streaming+inject(opts) POOLED/{scale}");
print_row(&format!("{row_label} ({output_size}B)"), delta);
snapshot_rows.push(delta_to_row(&row_label, delta));
println!(
"|{:-<28}|{:->9}|{:->12}|{:->15}|{:->11}|{:->13}|{:->12}|{:->16}|",
"", "", "", "", "", "", "", ""
);
}
println!();
println!("Notes:");
println!(" * `allocs/run` and `bytes/run` are exact (custom GlobalAlloc).");
if cfg!(windows) {
println!(" * `user µs/run` is `GetProcessTimes` user time delta / iters.");
println!(" * `process RSS` is `PeakWorkingSetSize` for the process at");
} else {
println!(" * `user µs/run` is `getrusage(RUSAGE_SELF).ru_utime` delta / iters.");
println!(" * `process RSS` is the high-water mark for the whole process at");
}
println!(" phase end. Per-iteration RSS is not directly observable; use");
println!(" `bytes/run` to compare per-render heap pressure across paths.");
match mode {
Mode::Print => {}
Mode::Save(name) => save_snapshot(&name, &snapshot_rows),
Mode::Compare(name) => {
if let Some(baseline) = load_snapshot(&name) {
print_diff(&snapshot_rows, &baseline);
}
}
}
}
fn delta_to_row(label: &str, delta: ResourceDelta) -> SnapshotRow {
let pi = delta.per_iter();
SnapshotRow {
label: label.to_string(),
iters: delta.iters,
allocs_per_run: pi.allocs,
bytes_per_run: pi.bytes,
user_cpu_us_per_run: pi.user_cpu_us,
sys_cpu_us_per_run: pi.sys_cpu_us,
wall_us_per_run: pi.wall_us,
rss_high_water_bytes: pi.rss_bytes,
}
}