## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/blis/profiler.rs

    Line
    Count
    Source
    1
    //! BLIS Profiler Integration
    2
    //!
    3
    //! Performance tracking for BLIS operations at multiple granularity levels.
    4
    //! Supports Kaizen (continuous improvement) methodology.
    5
    //!
    6
    //! # Philosophy
    7
    //!
    8
    //! Kaizen (改善) means "continuous improvement." By tracking performance metrics
    9
    //! at each level of the BLIS hierarchy, we can identify bottlenecks and measure
    10
    //! the impact of optimizations.
    11
    //!
    12
    //! # Profiling Levels
    13
    //!
    14
    //! - **Macro**: L3 cache blocking level (NC x KC tiles)
    15
    //! - **Midi**: L2 cache blocking level (MC x KC tiles)
    16
    //! - **Micro**: Microkernel level (MR x NR tiles)
    17
    //! - **Pack**: Data packing operations
    18
    //!
    19
    //! # Usage
    20
    //!
    21
    //! ```
    22
    //! use trueno::blis::profiler::{BlisProfiler, BlisProfileLevel};
    23
    //!
    24
    //! let mut profiler = BlisProfiler::enabled();
    25
    //! profiler.record(BlisProfileLevel::Micro, 1000, 384);
    26
    //! println!("{}", profiler.summary());
    27
    //! ```
    28
    29
    // ============================================================================
    30
    // Kaizen (Continuous Improvement) - Performance Tracking
    31
    // ============================================================================
    32
    33
    /// Kaizen metrics for tracking improvement
    34
    #[derive(Debug, Clone, Default)]
    35
    pub struct KaizenMetrics {
    36
        /// Total FLOP count
    37
        pub flops: u64,
    38
        /// Total time in nanoseconds
    39
        pub time_ns: u64,
    40
        /// Number of measurements
    41
        pub samples: usize,
    42
    }
    43
    44
    impl KaizenMetrics {
    45
        /// Record a GEMM operation
    46
    0
        pub fn record(&mut self, m: usize, n: usize, k: usize, duration: std::time::Duration) {
    47
    0
            self.flops += 2 * m as u64 * n as u64 * k as u64;
    48
    0
            self.time_ns += duration.as_nanos() as u64;
    49
    0
            self.samples += 1;
    50
    0
        }
    51
    52
        /// Get achieved GFLOP/s
    53
    0
        pub fn gflops(&self) -> f64 {
    54
    0
            if self.time_ns == 0 {
    55
    0
                return 0.0;
    56
    0
            }
    57
    0
            self.flops as f64 / self.time_ns as f64
    58
    0
        }
    59
    60
        /// Reset metrics
    61
    0
        pub fn reset(&mut self) {
    62
    0
            *self = Self::default();
    63
    0
        }
    64
    }
    65
    66
    // ============================================================================
    67
    // BLIS Profiler Integration
    68
    // ============================================================================
    69
    70
    /// Profiling level for BLIS operations
    71
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    72
    pub enum BlisProfileLevel {
    73
        /// L3 block level (NC x KC tiles)
    74
        Macro,
    75
        /// L2 block level (MC x KC tiles)
    76
        Midi,
    77
        /// Microkernel level (MR x NR tiles)
    78
        Micro,
    79
        /// Packing operations
    80
        Pack,
    81
    }
    82
    83
    /// Statistics for a profiling level
    84
    #[derive(Debug, Clone, Default)]
    85
    pub struct BlisLevelStats {
    86
        /// Total time in nanoseconds
    87
        pub total_ns: u64,
    88
        /// Number of invocations
    89
        pub count: u64,
    90
        /// Total FLOPs at this level
    91
        pub flops: u64,
    92
    }
    93
    94
    impl BlisLevelStats {
    95
        /// Record a timing
    96
    0
        pub fn record(&mut self, duration_ns: u64, flops: u64) {
    97
    0
            self.total_ns += duration_ns;
    98
    0
            self.count += 1;
    99
    0
            self.flops += flops;
    100
    0
        }
    101
    102
        /// Get average time in microseconds
    103
    0
        pub fn avg_us(&self) -> f64 {
    104
    0
            if self.count == 0 {
    105
    0
                return 0.0;
    106
    0
            }
    107
    0
            self.total_ns as f64 / self.count as f64 / 1000.0
    108
    0
        }
    109
    110
        /// Get GFLOP/s
    111
    0
        pub fn gflops(&self) -> f64 {
    112
    0
            if self.total_ns == 0 {
    113
    0
                return 0.0;
    114
    0
            }
    115
    0
            self.flops as f64 / self.total_ns as f64
    116
    0
        }
    117
    }
    118
    119
    /// BLIS-aware profiler
    120
    #[derive(Debug, Clone, Default)]
    121
    pub struct BlisProfiler {
    122
        /// Per-level statistics
    123
        pub macro_stats: BlisLevelStats,
    124
        pub midi_stats: BlisLevelStats,
    125
        pub micro_stats: BlisLevelStats,
    126
        pub pack_stats: BlisLevelStats,
    127
        /// Whether profiling is enabled
    128
        pub enabled: bool,
    129
    }
    130
    131
    impl BlisProfiler {
    132
        /// Create a new profiler (disabled by default)
    133
    0
        pub fn new() -> Self {
    134
    0
            Self::default()
    135
    0
        }
    136
    137
        /// Create an enabled profiler
    138
    0
        pub fn enabled() -> Self {
    139
    0
            Self {
    140
    0
                enabled: true,
    141
    0
                ..Self::default()
    142
    0
            }
    143
    0
        }
    144
    145
        /// Record timing for a level
    146
    0
        pub fn record(&mut self, level: BlisProfileLevel, duration_ns: u64, flops: u64) {
    147
    0
            if !self.enabled {
    148
    0
                return;
    149
    0
            }
    150
    0
            match level {
    151
    0
                BlisProfileLevel::Macro => self.macro_stats.record(duration_ns, flops),
    152
    0
                BlisProfileLevel::Midi => self.midi_stats.record(duration_ns, flops),
    153
    0
                BlisProfileLevel::Micro => self.micro_stats.record(duration_ns, flops),
    154
    0
                BlisProfileLevel::Pack => self.pack_stats.record(duration_ns, 0),
    155
            }
    156
    0
        }
    157
    158
        /// Get total GFLOP/s
    159
    0
        pub fn total_gflops(&self) -> f64 {
    160
    0
            let total_ns = self.macro_stats.total_ns;
    161
    0
            let total_flops = self.macro_stats.flops;
    162
    0
            if total_ns == 0 {
    163
    0
                return 0.0;
    164
    0
            }
    165
    0
            total_flops as f64 / total_ns as f64
    166
    0
        }
    167
    168
        /// Generate summary report
    169
    0
        pub fn summary(&self) -> String {
    170
    0
            let mut s = String::new();
    171
    0
            s.push_str("BLIS Profiler Summary\n");
    172
    0
            s.push_str("=====================\n");
    173
    0
            s.push_str(&format!(
    174
    0
                "Macro: {:.1}us avg, {:.1} GFLOP/s, {} calls\n",
    175
    0
                self.macro_stats.avg_us(),
    176
    0
                self.macro_stats.gflops(),
    177
    0
                self.macro_stats.count
    178
    0
            ));
    179
    0
            s.push_str(&format!(
    180
    0
                "Midi:  {:.1}us avg, {:.1} GFLOP/s, {} calls\n",
    181
    0
                self.midi_stats.avg_us(),
    182
    0
                self.midi_stats.gflops(),
    183
    0
                self.midi_stats.count
    184
    0
            ));
    185
    0
            s.push_str(&format!(
    186
    0
                "Micro: {:.1}us avg, {:.1} GFLOP/s, {} calls\n",
    187
    0
                self.micro_stats.avg_us(),
    188
    0
                self.micro_stats.gflops(),
    189
    0
                self.micro_stats.count
    190
    0
            ));
    191
    0
            s.push_str(&format!(
    192
    0
                "Pack:  {:.1}us avg, {} calls\n",
    193
    0
                self.pack_stats.avg_us(),
    194
    0
                self.pack_stats.count
    195
    0
            ));
    196
    0
            s.push_str(&format!("Total: {:.1} GFLOP/s\n", self.total_gflops()));
    197
    0
            s
    198
    0
        }
    199
    200
        /// Reset all statistics
    201
    0
        pub fn reset(&mut self) {
    202
    0
            self.macro_stats = BlisLevelStats::default();
    203
    0
            self.midi_stats = BlisLevelStats::default();
    204
    0
            self.micro_stats = BlisLevelStats::default();
    205
    0
            self.pack_stats = BlisLevelStats::default();
    206
    0
        }
    207
    }
    208
    209
    #[cfg(test)]
    210
    mod tests {
    211
        use super::*;
    212
        use std::time::Duration;
    213
    214
        #[test]
    215
        fn test_kaizen_metrics_default() {
    216
            let m = KaizenMetrics::default();
    217
            assert_eq!(m.flops, 0);
    218
            assert_eq!(m.time_ns, 0);
    219
            assert_eq!(m.samples, 0);
    220
        }
    221
    222
        #[test]
    223
        fn test_kaizen_metrics_record() {
    224
            let mut m = KaizenMetrics::default();
    225
            m.record(2, 3, 4, Duration::from_nanos(100));
    226
            assert_eq!(m.flops, 48); // 2 * 2 * 3 * 4
    227
            assert_eq!(m.time_ns, 100);
    228
            assert_eq!(m.samples, 1);
    229
        }
    230
    231
        #[test]
    232
        fn test_kaizen_metrics_gflops() {
    233
            let mut m = KaizenMetrics::default();
    234
            m.flops = 1_000_000_000;
    235
            m.time_ns = 1_000_000_000;
    236
            assert!((m.gflops() - 1.0).abs() < 1e-10);
    237
        }
    238
    239
        #[test]
    240
        fn test_kaizen_metrics_gflops_zero_time() {
    241
            let m = KaizenMetrics::default();
    242
            assert!((m.gflops() - 0.0).abs() < 1e-10);
    243
        }
    244
    245
        #[test]
    246
        fn test_kaizen_metrics_reset() {
    247
            let mut m = KaizenMetrics::default();
    248
            m.record(2, 3, 4, Duration::from_nanos(100));
    249
            m.reset();
    250
            assert_eq!(m.flops, 0);
    251
            assert_eq!(m.time_ns, 0);
    252
            assert_eq!(m.samples, 0);
    253
        }
    254
    255
        #[test]
    256
        fn test_blis_level_stats_default() {
    257
            let s = BlisLevelStats::default();
    258
            assert_eq!(s.total_ns, 0);
    259
            assert_eq!(s.count, 0);
    260
            assert_eq!(s.flops, 0);
    261
        }
    262
    263
        #[test]
    264
        fn test_blis_level_stats_record() {
    265
            let mut s = BlisLevelStats::default();
    266
            s.record(1000, 500);
    267
            assert_eq!(s.total_ns, 1000);
    268
            assert_eq!(s.count, 1);
    269
            assert_eq!(s.flops, 500);
    270
        }
    271
    272
        #[test]
    273
        fn test_blis_level_stats_avg_us() {
    274
            let mut s = BlisLevelStats::default();
    275
            s.record(2000, 0);
    276
            s.record(4000, 0);
    277
            assert!((s.avg_us() - 3.0).abs() < 1e-10);
    278
        }
    279
    280
        #[test]
    281
        fn test_blis_level_stats_avg_us_zero_count() {
    282
            let s = BlisLevelStats::default();
    283
            assert!((s.avg_us() - 0.0).abs() < 1e-10);
    284
        }
    285
    286
        #[test]
    287
        fn test_blis_level_stats_gflops() {
    288
            let mut s = BlisLevelStats::default();
    289
            s.total_ns = 1_000_000_000;
    290
            s.flops = 1_000_000_000;
    291
            assert!((s.gflops() - 1.0).abs() < 1e-10);
    292
        }
    293
    294
        #[test]
    295
        fn test_blis_level_stats_gflops_zero_time() {
    296
            let s = BlisLevelStats::default();
    297
            assert!((s.gflops() - 0.0).abs() < 1e-10);
    298
        }
    299
    300
        #[test]
    301
        fn test_blis_profiler_new() {
    302
            let p = BlisProfiler::new();
    303
            assert!(!p.enabled);
    304
        }
    305
    306
        #[test]
    307
        fn test_blis_profiler_enabled() {
    308
            let p = BlisProfiler::enabled();
    309
            assert!(p.enabled);
    310
        }
    311
    312
        #[test]
    313
        fn test_blis_profiler_record_disabled() {
    314
            let mut p = BlisProfiler::new();
    315
            p.record(BlisProfileLevel::Micro, 1000, 500);
    316
            assert_eq!(p.micro_stats.count, 0);
    317
        }
    318
    319
        #[test]
    320
        fn test_blis_profiler_record_enabled() {
    321
            let mut p = BlisProfiler::enabled();
    322
            p.record(BlisProfileLevel::Micro, 1000, 500);
    323
            assert_eq!(p.micro_stats.count, 1);
    324
            assert_eq!(p.micro_stats.total_ns, 1000);
    325
            assert_eq!(p.micro_stats.flops, 500);
    326
        }
    327
    328
        #[test]
    329
        fn test_blis_profiler_record_all_levels() {
    330
            let mut p = BlisProfiler::enabled();
    331
            p.record(BlisProfileLevel::Macro, 1000, 100);
    332
            p.record(BlisProfileLevel::Midi, 2000, 200);
    333
            p.record(BlisProfileLevel::Micro, 3000, 300);
    334
            p.record(BlisProfileLevel::Pack, 4000, 400);
    335
    336
            assert_eq!(p.macro_stats.count, 1);
    337
            assert_eq!(p.midi_stats.count, 1);
    338
            assert_eq!(p.micro_stats.count, 1);
    339
            assert_eq!(p.pack_stats.count, 1);
    340
            assert_eq!(p.pack_stats.flops, 0); // Pack doesn't track flops
    341
        }
    342
    343
        #[test]
    344
        fn test_blis_profiler_total_gflops() {
    345
            let mut p = BlisProfiler::enabled();
    346
            p.macro_stats.total_ns = 1_000_000_000;
    347
            p.macro_stats.flops = 1_000_000_000;
    348
            assert!((p.total_gflops() - 1.0).abs() < 1e-10);
    349
        }
    350
    351
        #[test]
    352
        fn test_blis_profiler_total_gflops_zero_time() {
    353
            let p = BlisProfiler::enabled();
    354
            assert!((p.total_gflops() - 0.0).abs() < 1e-10);
    355
        }
    356
    357
        #[test]
    358
        fn test_blis_profiler_summary() {
    359
            let p = BlisProfiler::enabled();
    360
            let summary = p.summary();
    361
            assert!(summary.contains("BLIS Profiler Summary"));
    362
            assert!(summary.contains("Macro:"));
    363
            assert!(summary.contains("Midi:"));
    364
            assert!(summary.contains("Micro:"));
    365
            assert!(summary.contains("Pack:"));
    366
            assert!(summary.contains("Total:"));
    367
        }
    368
    369
        #[test]
    370
        fn test_blis_profiler_reset() {
    371
            let mut p = BlisProfiler::enabled();
    372
            p.record(BlisProfileLevel::Micro, 1000, 500);
    373
            p.reset();
    374
            assert_eq!(p.micro_stats.count, 0);
    375
        }
    376
    377
        #[test]
    378
        fn test_blis_profile_level_debug() {
    379
            assert_eq!(format!("{:?}", BlisProfileLevel::Macro), "Macro");
    380
            assert_eq!(format!("{:?}", BlisProfileLevel::Midi), "Midi");
    381
            assert_eq!(format!("{:?}", BlisProfileLevel::Micro), "Micro");
    382
            assert_eq!(format!("{:?}", BlisProfileLevel::Pack), "Pack");
    383
        }
    384
    385
        #[test]
    386
        fn test_blis_profile_level_eq() {
    387
            assert_eq!(BlisProfileLevel::Macro, BlisProfileLevel::Macro);
    388
            assert_ne!(BlisProfileLevel::Macro, BlisProfileLevel::Micro);
    389
        }
    390
    391
        #[test]
    392
        fn test_blis_profile_level_hash() {
    393
            use std::collections::HashSet;
    394
            let mut set = HashSet::new();
    395
            set.insert(BlisProfileLevel::Macro);
    396
            set.insert(BlisProfileLevel::Micro);
    397
            assert_eq!(set.len(), 2);
    398
            assert!(set.contains(&BlisProfileLevel::Macro));
    399
        }
    400
    }