1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
// Copyright 2026 Ryan Daum
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Pluggable measurement backends.
//!
//! # Why a trait
//!
//! Historically `micromeasure` had exactly two measurement paths hard-wired
//! into the runner:
//!
//! 1. On Linux: a perf-event group around the closure, filling CPU PMU
//! counters (cycles, instructions, branches, cache misses, frontend/backend
//! stalls) into [`crate::bench::Results`].
//! 2. Off Linux: a wall-clock span with no counters.
//!
//! That works well for CPU microbenchmarks but is misleading for GPU work,
//! as documented in `book/src/gpu-sharp-edges.md`. A GPU benchmark
//! needs CUDA event elapsed time recorded on the device, host-vs-device
//! latency split, and domain-specific metrics (`cuda_event_ms`, `tflops`,
//! `tensor_tflops`, ...) that do not fit the fixed `Results` shape.
//!
//! The [`MeasurementBackend`] trait lets a benchmark plug in a backend that
//! owns the measurement window without forcing CUDA (or any other device
//! API) to become a dependency of normal `micromeasure` builds. The crate
//! ships a wall-clock fallback and an optional
//! [`CudaEventBackend`](crate::CudaEventBackend) behind the `cuda` feature.
//!
//! # Trait shape
//!
//! The trait is object-safe so a runner can hold either a generic
//! `B: MeasurementBackend` or a `Box<dyn MeasurementBackend>`. The runner is
//! responsible for the outer wall-clock span (via `Instant`) and for invoking
//! the bench closure that returns the operation count; the backend is
//! responsible for any domain-specific counters it records around that same
//! window, plus any derived metrics only it can produce.
//!
//! # Integration status
//!
//! The trait is wired into the runner. The runner calls
//! [`begin`](MeasurementBackend::begin) / [`end`](MeasurementBackend::end)
//! / [`collect`](MeasurementBackend::collect) per sample for all standard
//! (single-threaded) benchmarks, replacing the historic
//! `execute_standard_sample` / `execute_timing_only` split.
//!
//! The platform default backend is [`LinuxPerfBackend`](crate::LinuxPerfBackend)
//! on Linux (preserving the historic perf-event group + individual-counter
//! fallback chain) and [`WallClockBackend`] elsewhere.
//!
//! Custom backends are supplied via a benchmark group's `backend` method and
//! take precedence over the platform default. Concurrent groups may opt into
//! a backend whose window spans the coordinated worker start through all
//! worker joins.
use crateResults;
use ;
use Duration;
/// How a [`MetricValue`] should be formatted in the custom metrics table.
///
/// Most metrics are continuous numbers (latency in ms, throughput in
/// TFLOP/s) and render with the adaptive `format_metric_value` helper.
/// Some are categorical or integer-valued (algorithm IDs, workspace sizes
/// in bytes, selected device index) and should never appear in scientific
/// notation or with decimal places.
/// What a measured operation reports in addition to the standard `Results`.
///
/// This is the per-sample extension point called for by Phase 2 of
/// `book/src/gpu-sharp-edges.md` ("No Per-Sample Custom Metrics").
/// Backends push names and values here as they collect them; the runner
/// aggregates them across samples (mean, median, p95, min, max) once the
/// `MetricValue` aggregation path is wired up.
///
/// Names are `&'static str` so backends can use string literals without
/// allocation, matching [`crate::bench::CounterValue`]. Units are also
/// `&'static str` for the same reason; rendering scales them with the
/// existing `Throughput::format_rate` helper when appropriate.
///
/// ## Display name
///
/// By default the metric `name` is used as the label in the custom metrics
/// table. When the machine-friendly name is not human-friendly (e.g.
/// `gpu_gib_s`), call [`with_display_name`](Self::with_display_name) to
/// set a human-readable label (e.g. `"GPU bandwidth"`). The aggregation
/// key remains the `name` field; the display name is purely cosmetic.
/// Per-sample result returned by a benchmark function that wants to report
/// custom metrics in addition to the standard timing/throughput/PMU pipeline.
///
/// This is the Phase 2 extension point called for by
/// `book/src/gpu-sharp-edges.md` ("Fixed `fn(&mut T, usize, usize)`
/// Shape Is Awkward For Rich Results" and "No Per-Sample Custom Metrics").
///
/// A bench function with the richer signature
/// `fn(&mut T, usize, usize) -> BenchSampleResult` returns one of these per
/// sample. The runner:
///
/// 1. Reads `operations` and routes it through the existing
/// throughput/latency aggregation (`Results.iterations`).
/// 2. Collects `metrics` per sample and aggregates them into
/// [`crate::session::MetricSummary`] (mean, median, p95, min, max) once
/// all samples are in.
/// 3. Persists the aggregated summaries in `BenchmarkStats.metrics` (JSON
/// via the existing serde derive) and renders them in a `custom metrics:`
/// table beneath the standard stats table.
///
/// ## When to use `bench_sample` vs `bench`
///
/// - Use [`crate::BenchmarkGroup::bench`] for tight CPU loops where the
/// framework-derived numbers (latency, throughput, PMU counters) tell the
/// whole story. Operation count is implicit (`chunk_size` or
/// `BenchContext::operations_per_chunk()`).
/// - Use [`crate::BenchmarkGroup::bench_sample`] when the measured closure
/// knows facts only available after execution: selected cuBLASLt algorithm,
/// CUDA event elapsed time, TFLOP/s derived from a shape-dependent FLOP
/// count, workspace size, validation codes, etc.
///
/// ## Construction
///
/// `BenchSampleResult` is constructible ergonomically:
///
/// ```
/// use micromeasure::bench::backend::{BenchSampleResult, MetricValue};
///
/// let r = BenchSampleResult::operations(1024)
/// .with_metric("cuda_event_ms", 1.23, "ms")
/// .with_metric("tflops", 12.5, "TFLOP/s");
/// assert_eq!(r.operations, 1024);
/// assert_eq!(r.metrics.len(), 2);
/// ```
///
/// or via `From<u64>` for one-liners that report operations only:
///
/// ```
/// use micromeasure::bench::backend::BenchSampleResult;
/// let r: BenchSampleResult = 1024_u64.into();
/// assert_eq!(r.operations, 1024);
/// ```
/// Coarse classification of what a benchmark measures.
///
/// Used by the diagnostics path to suppress or relabel CPU-PMU bottleneck
/// messages when the measured operation is not CPU work (see "CPU PMU
/// Diagnostics Are Misleading For GPU Kernels" in the sharp-edges doc).
///
/// The value is stored on the benchmark/group and flows into
/// [`BenchmarkStats`] via the runner; the trait itself does not Consult it
/// directly, but a backend implementation can use it to control how much
/// PMU data it records and how it labels the result.
///
/// [`BenchmarkStats`]: crate::session::BenchmarkStats
/// Pluggable measurement window for one sample of one benchmark.
///
/// A backend owns whatever domain-specific state it needs across a single
/// measurement window (one perf-event group, one pair of CUDA events, one
/// ROCm profiler range, ...). The runner calls [`begin`](Self::begin)
/// immediately before invoking the bench closure and
/// [`end`](Self::end) immediately after it returns the operation count,
/// then calls [`collect`](Self::collect) to materialize whatever it
/// observed into the shared [`Results`] struct and a list of
/// [`MetricValue`]s for any derived per-sample metrics.
///
/// ## Responsibilities
///
/// - The **runner** owns the outer wall-clock span (via `Instant`) and the
/// bench closure invocation. It passes the resulting host elapsed time and
/// operation count into `collect`.
/// - The **backend** owns its own counters and timing sources where
/// applicable (e.g. perf event `time_enabled`/`time_running` for PMU
/// multiplexing, or CUDA event recorded/elapsed timestamps). It writes
/// whatever subset of `Results` fields it actually populated and leaves
/// the rest at their defaults (`false` / `0`).
///
/// ## Object safety
///
/// The trait is object-safe on purpose. Consuming code may use it as
/// `B: MeasurementBackend` for zero-cost dispatch, or as
/// `Box<dyn MeasurementBackend>` when the backend is selected at runtime
/// (e.g. from a feature flag or a CLI argument).
///
/// ## Failure modes
///
/// Backends report failure by leaving `has_*` flags false in `Results` and
/// not pushing metrics. The existing renderer already handles the
/// "timing only" case gracefully when no PMU fields are populated; the same
/// path covers a GPU backend that could not record CUDA events. A
/// dedicated backend-issues channel (analogous to the current
/// `record_perf_issue` global) can be layered on top without changing the
/// trait shape.
///
/// ## Per-sample correlation
///
/// `collect` receives `chunk_index: usize` so a backend can correlate the
/// current sample with state it captured in [`begin`](Self::begin) /
/// [`end`](Self::end) (e.g. sampling the GPU clock at sample N for
/// thermal-state tracking, or skipping CUDA-event recording during the
/// warm-up phase). Backends that have nothing to correlate ignore it.
///
/// ## Example: a CUDA event backend
///
/// This is sketch code showing the shape of a CUDA event backend. For CUDA
/// default-stream benchmarks, enable `micromeasure`'s `cuda` feature and use
/// [`crate::CudaEventBackend`] instead of writing this adapter by hand.
///
/// ```ignore
/// use micromeasure::bench::backend::{MeasurementBackend, MetricValue, Results};
/// use std::time::Duration;
///
/// pub struct CudaEventBackend {
/// start: cuda::Event,
/// stop: cuda::Event,
/// stream: cuda::Stream,
/// elapsed_ms: f64,
/// }
///
/// impl CudaEventBackend {
/// pub fn new() -> Self {
/// let stream = cuda::Stream::new();
/// Self {
/// start: cuda::Event::new(),
/// stop: cuda::Event::new(),
/// stream,
/// elapsed_ms: 0.0,
/// }
/// }
/// }
///
/// impl MeasurementBackend for CudaEventBackend {
/// fn begin(&mut self) {
/// self.start.record(&self.stream);
/// }
///
/// fn end(&mut self) {
/// self.stop.record(&self.stream);
/// self.stop.synchronize();
/// self.elapsed_ms = self.start.elapsed_ms(&self.stop).unwrap_or(0.0);
/// }
///
/// fn collect(
/// &mut self,
/// host_elapsed: Duration,
/// ops: u64,
/// chunk_index: usize,
/// results: &mut Results,
/// metrics: &mut Vec<MetricValue>,
/// ) {
/// results.duration = host_elapsed;
/// results.iterations = ops;
/// results.chunks_executed = 1;
/// // Intentionally leave has_cycles / has_instructions / ... false:
/// // CPU PMU data on this thread is host-orchestration noise for a
/// // GPU benchmark, not the primary bottleneck.
/// metrics.push(MetricValue::new("cuda_event_ms", self.elapsed_ms, "ms"));
/// let overhead_ms =
/// host_elapsed.as_secs_f64() * 1_000.0 - self.elapsed_ms;
/// metrics.push(MetricValue::new(
/// "host_overhead_ms",
/// overhead_ms.max(0.0),
/// "ms",
/// ));
/// // chunk_index would be used here to skip recording during the
/// // warmup phase, or to log per-sample clock samples.
/// let _ = chunk_index;
/// }
///
/// fn measurement_label(&self) -> &'static str {
/// "timing + CUDA events"
/// }
/// }
/// ```
///
/// # Integration status
///
/// The trait is wired into the standard path and, when explicitly configured
/// on a concurrent group, the whole concurrent sample. Concurrent worker PMU
/// measurements remain available separately in worker summaries.
/// Minimal backend that records only wall-clock time around the closure.
///
/// This is the in-tree fallback. It mirrors the current non-Linux
/// `execute_timing_only` path: no PMU fields, no metrics, just duration and
/// iteration count. It is also the shape a CUDA adapter would start from
/// before adding device-event recording.
;
// LinuxPerfBackend is implemented in `perf.rs` (behind `cfg(target_os =
// "linux")`) and re-exported from `bench.rs`. It preserves the historic
// `run_with_perf_group` / `run_with_individual_counters` fallback chain,
// including `record_perf_issue` and `scale_multiplexed_count`.