Skip to main content

SubMsPerfHarness

Struct SubMsPerfHarness 

Source
pub struct SubMsPerfHarness { /* private fields */ }
Expand description

A workload run. Owns raw samples + metadata only. Analysis and serialisation live in crate::bench - call summarize to lift this into a SubMsBenchSummary.

Implementations§

Source§

impl SubMsPerfHarness

Source

pub fn new(workload: &str, lang: &str) -> Self

Examples found in repository?
examples/perf_main.rs (line 27)
26fn main() {
27    let mut h = SubMsPerfHarness::new("subms-self-bench", "rust");
28    h.input("samples_per_stage", "50000");
29    h.add_meta("rust_version", env!("CARGO_PKG_RUST_VERSION"));
30    h.add_meta("crate_version", env!("CARGO_PKG_VERSION"));
31
32    // 1. time_closure: cost of `stage.time(|| {})` with an empty closure.
33    //    This is the per-iteration overhead any user pays when they wrap
34    //    their hot path with `stage.time(...)`.
35    {
36        let s = h.stage("time_closure", 50_000);
37        for _ in 0..50_000 {
38            s.time(|| black_box(()));
39        }
40    }
41
42    // 2. record_ns: cost of `stage.record(ns)` alone. No timer, just the
43    //    Vec push. Strict lower bound on per-sample bookkeeping.
44    {
45        let s = h.stage("record_ns", 50_000);
46        for i in 0..50_000u64 {
47            s.record(black_box(100 + (i & 63)));
48        }
49    }
50
51    // 3. summarize: sort + percentile extraction. Cost of producing one
52    //    SubMsBenchSummary. We use SubMsTimer in a parallel harness so the
53    //    measurement of the measurement isn't recursive.
54    {
55        let summary_target = build_sample_harness(50_000);
56        let s = h.stage("summarize", 100);
57        for _ in 0..100 {
58            s.time(|| {
59                let summary = summarize(black_box(&summary_target));
60                black_box(summary);
61            });
62        }
63    }
64
65    // 4. summary_to_json: serialise to the canonical JSON shape. Cost paid
66    //    by every CI run that uploads results.
67    {
68        let summary = summarize(&build_sample_harness(50_000));
69        let s = h.stage("summary_to_json", 100);
70        for _ in 0..100 {
71            s.time(|| {
72                let mut buf = Cursor::new(Vec::with_capacity(64 * 1024));
73                summary_to_json(black_box(&summary), &mut buf).unwrap();
74                let _ = buf.flush();
75                black_box(buf);
76            });
77        }
78    }
79
80    // 5. diff_summary: regression-diff math between two SubMsBenchSummary
81    //    objects. Cost paid by every PR-time gate.
82    {
83        let base: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
84        let cand: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
85        let s = h.stage("diff_summary", 1_000);
86        for _ in 0..1_000 {
87            s.time(|| {
88                let d = subms::diff_summary(black_box(&base), black_box(&cand));
89                black_box(d);
90            });
91        }
92    }
93
94    // Cross-check: SubMsTimer itself rolls a quick checkpoint walk so the
95    // timer's marking overhead is also visible from this perf JSON's meta.
96    let mut t = SubMsTimer::new("self-bench-wall");
97    t.mark("stages-complete");
98    t.stop("emitting-json");
99    h.add_meta("self_bench_wall_ns", &t.elapsed_ns().to_string());
100
101    let summary = summarize(&h);
102    summary_to_json(&summary, &mut std::io::stdout()).unwrap();
103}
104
105/// Build a SubMsPerfHarness pre-populated with a synthetic stage's worth of
106/// samples - used as fodder for the summarize / json / diff stages above.
107fn build_sample_harness(n: usize) -> SubMsPerfHarness {
108    let mut h = SubMsPerfHarness::new("fixture", "rust");
109    let s = h.stage("fixture-stage", n);
110    for i in 0..n as u64 {
111        // Synthetic log-ish distribution: most samples small, a tail
112        // exercises percentile sort + lookup.
113        let v = if i % 1000 == 0 {
114            10_000 + (i & 1023)
115        } else {
116            100 + (i & 63)
117        };
118        s.record(v);
119    }
120    h
121}
Source

pub fn set_sample_cap(&mut self, cap: usize) -> &mut Self

Max points kept in each stage’s emitted samples_ns timeline. Default 500; crate::benchmark sets it from crate::SubMsBenchParams::sample_cap. Clamped to at least 1.

Source

pub fn sample_cap(&self) -> usize

The configured samples_ns downsample cap (see Self::set_sample_cap).

Source

pub fn input(&mut self, key: &str, value: &str) -> &mut Self

Examples found in repository?
examples/perf_main.rs (line 28)
26fn main() {
27    let mut h = SubMsPerfHarness::new("subms-self-bench", "rust");
28    h.input("samples_per_stage", "50000");
29    h.add_meta("rust_version", env!("CARGO_PKG_RUST_VERSION"));
30    h.add_meta("crate_version", env!("CARGO_PKG_VERSION"));
31
32    // 1. time_closure: cost of `stage.time(|| {})` with an empty closure.
33    //    This is the per-iteration overhead any user pays when they wrap
34    //    their hot path with `stage.time(...)`.
35    {
36        let s = h.stage("time_closure", 50_000);
37        for _ in 0..50_000 {
38            s.time(|| black_box(()));
39        }
40    }
41
42    // 2. record_ns: cost of `stage.record(ns)` alone. No timer, just the
43    //    Vec push. Strict lower bound on per-sample bookkeeping.
44    {
45        let s = h.stage("record_ns", 50_000);
46        for i in 0..50_000u64 {
47            s.record(black_box(100 + (i & 63)));
48        }
49    }
50
51    // 3. summarize: sort + percentile extraction. Cost of producing one
52    //    SubMsBenchSummary. We use SubMsTimer in a parallel harness so the
53    //    measurement of the measurement isn't recursive.
54    {
55        let summary_target = build_sample_harness(50_000);
56        let s = h.stage("summarize", 100);
57        for _ in 0..100 {
58            s.time(|| {
59                let summary = summarize(black_box(&summary_target));
60                black_box(summary);
61            });
62        }
63    }
64
65    // 4. summary_to_json: serialise to the canonical JSON shape. Cost paid
66    //    by every CI run that uploads results.
67    {
68        let summary = summarize(&build_sample_harness(50_000));
69        let s = h.stage("summary_to_json", 100);
70        for _ in 0..100 {
71            s.time(|| {
72                let mut buf = Cursor::new(Vec::with_capacity(64 * 1024));
73                summary_to_json(black_box(&summary), &mut buf).unwrap();
74                let _ = buf.flush();
75                black_box(buf);
76            });
77        }
78    }
79
80    // 5. diff_summary: regression-diff math between two SubMsBenchSummary
81    //    objects. Cost paid by every PR-time gate.
82    {
83        let base: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
84        let cand: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
85        let s = h.stage("diff_summary", 1_000);
86        for _ in 0..1_000 {
87            s.time(|| {
88                let d = subms::diff_summary(black_box(&base), black_box(&cand));
89                black_box(d);
90            });
91        }
92    }
93
94    // Cross-check: SubMsTimer itself rolls a quick checkpoint walk so the
95    // timer's marking overhead is also visible from this perf JSON's meta.
96    let mut t = SubMsTimer::new("self-bench-wall");
97    t.mark("stages-complete");
98    t.stop("emitting-json");
99    h.add_meta("self_bench_wall_ns", &t.elapsed_ns().to_string());
100
101    let summary = summarize(&h);
102    summary_to_json(&summary, &mut std::io::stdout()).unwrap();
103}
Source

pub fn add_meta(&mut self, key: &str, value: &str) -> &mut Self

Set a meta field. Renamed from {@code meta} so the {@link Self::meta} getter can keep its symmetric name with Java’s getter.

Examples found in repository?
examples/perf_main.rs (line 29)
26fn main() {
27    let mut h = SubMsPerfHarness::new("subms-self-bench", "rust");
28    h.input("samples_per_stage", "50000");
29    h.add_meta("rust_version", env!("CARGO_PKG_RUST_VERSION"));
30    h.add_meta("crate_version", env!("CARGO_PKG_VERSION"));
31
32    // 1. time_closure: cost of `stage.time(|| {})` with an empty closure.
33    //    This is the per-iteration overhead any user pays when they wrap
34    //    their hot path with `stage.time(...)`.
35    {
36        let s = h.stage("time_closure", 50_000);
37        for _ in 0..50_000 {
38            s.time(|| black_box(()));
39        }
40    }
41
42    // 2. record_ns: cost of `stage.record(ns)` alone. No timer, just the
43    //    Vec push. Strict lower bound on per-sample bookkeeping.
44    {
45        let s = h.stage("record_ns", 50_000);
46        for i in 0..50_000u64 {
47            s.record(black_box(100 + (i & 63)));
48        }
49    }
50
51    // 3. summarize: sort + percentile extraction. Cost of producing one
52    //    SubMsBenchSummary. We use SubMsTimer in a parallel harness so the
53    //    measurement of the measurement isn't recursive.
54    {
55        let summary_target = build_sample_harness(50_000);
56        let s = h.stage("summarize", 100);
57        for _ in 0..100 {
58            s.time(|| {
59                let summary = summarize(black_box(&summary_target));
60                black_box(summary);
61            });
62        }
63    }
64
65    // 4. summary_to_json: serialise to the canonical JSON shape. Cost paid
66    //    by every CI run that uploads results.
67    {
68        let summary = summarize(&build_sample_harness(50_000));
69        let s = h.stage("summary_to_json", 100);
70        for _ in 0..100 {
71            s.time(|| {
72                let mut buf = Cursor::new(Vec::with_capacity(64 * 1024));
73                summary_to_json(black_box(&summary), &mut buf).unwrap();
74                let _ = buf.flush();
75                black_box(buf);
76            });
77        }
78    }
79
80    // 5. diff_summary: regression-diff math between two SubMsBenchSummary
81    //    objects. Cost paid by every PR-time gate.
82    {
83        let base: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
84        let cand: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
85        let s = h.stage("diff_summary", 1_000);
86        for _ in 0..1_000 {
87            s.time(|| {
88                let d = subms::diff_summary(black_box(&base), black_box(&cand));
89                black_box(d);
90            });
91        }
92    }
93
94    // Cross-check: SubMsTimer itself rolls a quick checkpoint walk so the
95    // timer's marking overhead is also visible from this perf JSON's meta.
96    let mut t = SubMsTimer::new("self-bench-wall");
97    t.mark("stages-complete");
98    t.stop("emitting-json");
99    h.add_meta("self_bench_wall_ns", &t.elapsed_ns().to_string());
100
101    let summary = summarize(&h);
102    summary_to_json(&summary, &mut std::io::stdout()).unwrap();
103}
Source

pub fn stage(&mut self, name: &str, capacity: usize) -> &mut SubMsStage

Create a stage; record samples via SubMsStage::time or SubMsStage::record.

Examples found in repository?
examples/perf_main.rs (line 36)
26fn main() {
27    let mut h = SubMsPerfHarness::new("subms-self-bench", "rust");
28    h.input("samples_per_stage", "50000");
29    h.add_meta("rust_version", env!("CARGO_PKG_RUST_VERSION"));
30    h.add_meta("crate_version", env!("CARGO_PKG_VERSION"));
31
32    // 1. time_closure: cost of `stage.time(|| {})` with an empty closure.
33    //    This is the per-iteration overhead any user pays when they wrap
34    //    their hot path with `stage.time(...)`.
35    {
36        let s = h.stage("time_closure", 50_000);
37        for _ in 0..50_000 {
38            s.time(|| black_box(()));
39        }
40    }
41
42    // 2. record_ns: cost of `stage.record(ns)` alone. No timer, just the
43    //    Vec push. Strict lower bound on per-sample bookkeeping.
44    {
45        let s = h.stage("record_ns", 50_000);
46        for i in 0..50_000u64 {
47            s.record(black_box(100 + (i & 63)));
48        }
49    }
50
51    // 3. summarize: sort + percentile extraction. Cost of producing one
52    //    SubMsBenchSummary. We use SubMsTimer in a parallel harness so the
53    //    measurement of the measurement isn't recursive.
54    {
55        let summary_target = build_sample_harness(50_000);
56        let s = h.stage("summarize", 100);
57        for _ in 0..100 {
58            s.time(|| {
59                let summary = summarize(black_box(&summary_target));
60                black_box(summary);
61            });
62        }
63    }
64
65    // 4. summary_to_json: serialise to the canonical JSON shape. Cost paid
66    //    by every CI run that uploads results.
67    {
68        let summary = summarize(&build_sample_harness(50_000));
69        let s = h.stage("summary_to_json", 100);
70        for _ in 0..100 {
71            s.time(|| {
72                let mut buf = Cursor::new(Vec::with_capacity(64 * 1024));
73                summary_to_json(black_box(&summary), &mut buf).unwrap();
74                let _ = buf.flush();
75                black_box(buf);
76            });
77        }
78    }
79
80    // 5. diff_summary: regression-diff math between two SubMsBenchSummary
81    //    objects. Cost paid by every PR-time gate.
82    {
83        let base: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
84        let cand: SubMsBenchSummary = summarize(&build_sample_harness(50_000));
85        let s = h.stage("diff_summary", 1_000);
86        for _ in 0..1_000 {
87            s.time(|| {
88                let d = subms::diff_summary(black_box(&base), black_box(&cand));
89                black_box(d);
90            });
91        }
92    }
93
94    // Cross-check: SubMsTimer itself rolls a quick checkpoint walk so the
95    // timer's marking overhead is also visible from this perf JSON's meta.
96    let mut t = SubMsTimer::new("self-bench-wall");
97    t.mark("stages-complete");
98    t.stop("emitting-json");
99    h.add_meta("self_bench_wall_ns", &t.elapsed_ns().to_string());
100
101    let summary = summarize(&h);
102    summary_to_json(&summary, &mut std::io::stdout()).unwrap();
103}
104
105/// Build a SubMsPerfHarness pre-populated with a synthetic stage's worth of
106/// samples - used as fodder for the summarize / json / diff stages above.
107fn build_sample_harness(n: usize) -> SubMsPerfHarness {
108    let mut h = SubMsPerfHarness::new("fixture", "rust");
109    let s = h.stage("fixture-stage", n);
110    for i in 0..n as u64 {
111        // Synthetic log-ish distribution: most samples small, a tail
112        // exercises percentile sort + lookup.
113        let v = if i % 1000 == 0 {
114            10_000 + (i & 1023)
115        } else {
116            100 + (i & 63)
117        };
118        s.record(v);
119    }
120    h
121}
Source

pub fn with_observer(self, observer: Arc<dyn SubMsObserver>) -> Self

Register an observer to receive every recorded sample and the post-bench summary. Replaces any existing observer; updates already- created stages so they fire the new observer too. Returns self for builder-style chaining.

Source

pub fn set_observer( &mut self, observer: Option<Arc<dyn SubMsObserver>>, ) -> &mut Self

Mutable setter for late wiring. None clears the observer.

Source

pub fn observer(&self) -> Option<&Arc<dyn SubMsObserver>>

Read the currently-registered observer, if any. Mostly for tests.

Source

pub fn stage_mut(&mut self, name: &str) -> Option<&mut SubMsStage>

Borrow a previously-created stage by name.

Source

pub fn stage_by_name(&self, name: &str) -> Option<&SubMsStage>

Source

pub fn stages(&self) -> &[SubMsStage]

Source

pub fn workload(&self) -> &str

Source

pub fn lang(&self) -> &str

Source

pub fn inputs(&self) -> &BTreeMap<String, String>

Source

pub fn meta(&self) -> &BTreeMap<String, String>

Source

pub fn timestamp(&self) -> String

ISO-8601 seconds-precision timestamp captured at call time. Matches the on-disk JSON’s timestamp field.

Source

pub fn write_json<W: Write>(&self, out: &mut W) -> Result<()>

Back-compat: summarise + emit JSON in the standard subms JSON shape. New code should call summarize then summary_to_json so the analyser is explicit.

Source

pub fn discard_stage(&mut self, name: &str)

Drop a stage if you never recorded into it.

Trait Implementations§

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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, 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.