## 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/brick/profiling.rs

    Line
    Count
    Source
    1
    //! High-Performance Profiling Patterns
    2
    //!
    3
    //! CPU cycle counters, cached time service, and page fault detection.
    4
    //! Based on Phase 11: E.9 patterns.
    5
    6
    use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
    7
    use std::time::Instant;
    8
    9
    // ============================================================================
    10
    // CPU Cycle Counters
    11
    // ============================================================================
    12
    13
    /// CPU cycle counter using RDTSCP (x86_64) or CNTVCT_EL0 (ARM64).
    14
    ///
    15
    /// Returns actual CPU cycles for frequency-invariant performance analysis.
    16
    /// Use with `elapsed_ns` to calculate IPC (Instructions Per Cycle).
    17
    ///
    18
    /// # Example
    19
    /// ```rust,ignore
    20
    /// let start_cycles = cpu_cycles();
    21
    /// // ... operation ...
    22
    /// let end_cycles = cpu_cycles();
    23
    /// let cycles_per_element = (end_cycles - start_cycles) / num_elements;
    24
    /// ```
    25
    #[cfg(target_arch = "x86_64")]
    26
    #[inline]
    27
    0
    pub fn cpu_cycles() -> u64 {
    28
        unsafe {
    29
    0
            let mut _aux: u32 = 0;
    30
    0
            core::arch::x86_64::__rdtscp(&mut _aux)
    31
        }
    32
    0
    }
    33
    34
    /// CPU cycle counter for ARM64 using CNTVCT_EL0 register.
    35
    #[cfg(target_arch = "aarch64")]
    36
    #[inline]
    37
    pub fn cpu_cycles() -> u64 {
    38
        let cycles: u64;
    39
        unsafe {
    40
            core::arch::asm!("mrs {}, cntvct_el0", out(reg) cycles);
    41
        }
    42
        cycles
    43
    }
    44
    45
    /// Fallback for unsupported architectures (returns 0).
    46
    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
    47
    #[inline]
    48
    pub fn cpu_cycles() -> u64 {
    49
        0
    50
    }
    51
    52
    // ============================================================================
    53
    // Cached Time Service (Pattern 2 from actix-web)
    54
    // ============================================================================
    55
    56
    /// Global cached instant in nanoseconds, updated by background thread.
    57
    static CACHED_NANOS: AtomicU64 = AtomicU64::new(0);
    58
    59
    /// Epoch instant for cached time calculation.
    60
    static EPOCH: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
    61
    62
    /// Flag to track if time service is initialized.
    63
    static TIME_SERVICE_INIT: AtomicBool = AtomicBool::new(false);
    64
    65
    /// Initialize the cached time service (call once at startup).
    66
    ///
    67
    /// Spawns a background thread that updates cached time every 100µs.
    68
    /// This avoids syscall overhead when profiling high-frequency operations.
    69
    ///
    70
    /// # Example
    71
    /// ```rust,ignore
    72
    /// trueno::brick::init_time_service();
    73
    /// // Later...
    74
    /// let ns = trueno::brick::cached_nanos();
    75
    /// ```
    76
    0
    pub fn init_time_service() {
    77
    0
        if TIME_SERVICE_INIT.swap(true, Ordering::SeqCst) {
    78
    0
            return; // Already initialized
    79
    0
        }
    80
    81
    0
        let epoch = *EPOCH.get_or_init(Instant::now);
    82
    0
        CACHED_NANOS.store(0, Ordering::Relaxed);
    83
    84
    0
        std::thread::Builder::new()
    85
    0
            .name("trueno-time-service".into())
    86
    0
            .spawn(move || loop {
    87
    0
                std::thread::sleep(std::time::Duration::from_micros(100)); // 100µs precision
    88
    0
                let elapsed = epoch.elapsed().as_nanos() as u64;
    89
    0
                CACHED_NANOS.store(elapsed, Ordering::Relaxed);
    90
    0
            })
    91
    0
            .expect("Failed to spawn time service thread");
    92
    0
    }
    93
    94
    /// Get cached time in nanoseconds since epoch (NO SYSCALL, ~1ns overhead).
    95
    ///
    96
    /// Returns 0 if time service is not initialized. For accurate timing,
    97
    /// call `init_time_service()` at application startup.
    98
    #[inline]
    99
    0
    pub fn cached_nanos() -> u64 {
    100
    0
        CACHED_NANOS.load(Ordering::Relaxed)
    101
    0
    }
    102
    103
    /// Get cached time or fall back to Instant::now() if service not initialized.
    104
    #[inline]
    105
    0
    pub fn cached_nanos_or_now() -> u64 {
    106
    0
        let cached = CACHED_NANOS.load(Ordering::Relaxed);
    107
    0
        if cached == 0 && !TIME_SERVICE_INIT.load(Ordering::Relaxed) {
    108
            // Fall back to syscall if time service not initialized
    109
    0
            EPOCH
    110
    0
                .get_or_init(Instant::now)
    111
    0
                .elapsed()
    112
    0
                .as_nanos() as u64
    113
        } else {
    114
    0
            cached
    115
        }
    116
    0
    }
    117
    118
    // ============================================================================
    119
    // Page Fault Detection (Pattern from B4 Investigation)
    120
    // ============================================================================
    121
    122
    /// Get current minor and major page fault counts (Linux only).
    123
    ///
    124
    /// Returns (minor_faults, major_faults).
    125
    /// - Minor faults: Page in memory but not mapped (soft fault)
    126
    /// - Major faults: Page on disk, requires I/O (hard fault)
    127
    #[cfg(target_os = "linux")]
    128
    0
    pub fn get_page_faults() -> (u64, u64) {
    129
        use std::fs;
    130
    0
        let stat = fs::read_to_string("/proc/self/stat").unwrap_or_default();
    131
    0
        let fields: Vec<&str> = stat.split_whitespace().collect();
    132
    0
        if fields.len() > 12 {
    133
    0
            let minor = fields[9].parse().unwrap_or(0);
    134
    0
            let major = fields[11].parse().unwrap_or(0);
    135
    0
            (minor, major)
    136
        } else {
    137
    0
            (0, 0)
    138
        }
    139
    0
    }
    140
    141
    /// Fallback for non-Linux platforms.
    142
    #[cfg(not(target_os = "linux"))]
    143
    pub fn get_page_faults() -> (u64, u64) {
    144
        (0, 0)
    145
    }
    146
    147
    /// Execute a closure while tracking page faults.
    148
    ///
    149
    /// Logs a warning if more than 1000 minor faults or any major faults occur.
    150
    ///
    151
    /// # Example
    152
    /// ```rust,ignore
    153
    /// let result = with_page_fault_tracking("mmap_copy", || {
    154
    ///     data.copy_from_slice(&mmap_region);
    155
    /// });
    156
    /// ```
    157
    0
    pub fn with_page_fault_tracking<T, F: FnOnce() -> T>(name: &str, f: F) -> (T, u64, u64) {
    158
    0
        let (minor_before, major_before) = get_page_faults();
    159
    0
        let result = f();
    160
    0
        let (minor_after, major_after) = get_page_faults();
    161
    162
    0
        let minor_delta = minor_after.saturating_sub(minor_before);
    163
    0
        let major_delta = major_after.saturating_sub(major_before);
    164
    165
        #[cfg(feature = "tracing")]
    166
        if minor_delta > 1000 || major_delta > 0 {
    167
            tracing::warn!(
    168
                operation = name,
    169
                minor_faults = minor_delta,
    170
                major_faults = major_delta,
    171
                "High page fault count detected"
    172
            );
    173
        }
    174
    175
    0
        let _ = name; // Suppress unused warning when tracing disabled
    176
    0
        (result, minor_delta, major_delta)
    177
    0
    }
    178
    179
    #[cfg(test)]
    180
    mod tests {
    181
        use super::*;
    182
    183
        #[test]
    184
        fn test_cpu_cycles_returns_value() {
    185
            let cycles = cpu_cycles();
    186
            // On x86_64/aarch64, should return non-zero
    187
            // On other architectures, returns 0
    188
            #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
    189
            assert!(cycles > 0);
    190
            #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
    191
            assert_eq!(cycles, 0);
    192
        }
    193
    194
        #[test]
    195
        fn test_cached_nanos_or_now_returns_value() {
    196
            let nanos = cached_nanos_or_now();
    197
            // Should return a valid nanosecond count (non-zero on most systems)
    198
            // Note: u64 is always >= 0, so just verify we got a value
    199
            let _ = nanos; // Value is always valid for u64
    200
        }
    201
    202
        #[test]
    203
        fn test_page_fault_tracking() {
    204
            let (result, minor, major) = with_page_fault_tracking("test", || 42);
    205
            assert_eq!(result, 42);
    206
            // Page faults are u64, always non-negative by type
    207
            let _ = (minor, major); // Values are always valid for u64
    208
        }
    209
    210
        #[test]
    211
        fn test_get_page_faults() {
    212
            let (minor, major) = get_page_faults();
    213
            // Page faults are u64, always non-negative by type (may be 0 on non-Linux)
    214
            let _ = (minor, major); // Values are always valid for u64
    215
        }
    216
    }