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
}