Skip to main content

symbiotic/
lib.rs

1//! Performance profiling for Polars benchmarks
2//!
3//! This crate collects process-specific performance metrics:
4//! - Process CPU time and usage
5//! - Process memory (RSS, virtual, heap, stack)
6//! - Page faults and allocator statistics
7//! - Thread pool metrics
8//! - Hardware performance counters (optional)
9
10use tracing_subscriber::{layer::SubscriberExt, Registry};
11
12pub mod layer;
13pub mod collectors;
14pub mod metrics;
15pub mod display;
16pub mod bench_integration;
17
18// eBPF kernel-side intelligence (real implementation with aya)
19pub mod ebpf;
20pub mod pmu;
21#[allow(unused)]
22pub mod correlation;
23pub mod analysis;
24pub mod report;
25#[cfg(feature = "tui")]
26pub mod tui;
27
28// Region profiling engine (per-function hardware counter attribution)
29#[cfg(feature = "profiling")]
30pub mod regions;
31
32// Per-line hardware profiling (IP sampling → DWARF → .symbiot trace)
33#[cfg(feature = "line-profiler")]
34pub mod line_profiler;
35
36// Source registry — compile-time function source capture for multi-level view
37pub mod source_registry;
38
39// Sensory system — complete /proc/self self-awareness
40pub mod senses;
41
42// Export formats (JSON, Chrome Trace, Flamegraph, HTML)
43pub mod export;
44
45// Unified timeline (merges BPF + region + PMU events)
46#[cfg(feature = "profiling")]
47pub mod timeline;
48
49// Differential profiling (compare two runs)
50pub mod diff;
51
52// Query server (HTTP/JSON, gRPC, Unix socket)
53#[cfg(feature = "server")]
54pub mod server;
55
56// VS Code-like profiling dashboard (offline HTML + live web UI)
57#[cfg(feature = "dashboard")]
58pub mod dashboard;
59
60// Allocation tracking (can be used independently or with region profiling)
61#[cfg(feature = "alloc-track")]
62pub mod alloc_tracker;
63
64// Re-export the procedural macros
65pub use symbiotic_macros::profile_bench;
66pub use symbiotic_macros::profile;
67
68// Re-export key types
69pub use layer::ProfilingLayer;
70pub use metrics::{MetricsSnapshot, ProfileReport, CollectorData};
71pub use bench_integration::BenchProfiler;
72
73// Re-export region profiling types
74#[cfg(feature = "profiling")]
75pub use regions::{RegionGuard, RegionRegistry, RegionCounters, RegionNode};
76
77/// Profile a named code block with hardware counters.
78///
79/// When the `profiling` feature is enabled, wraps the body in a `RegionGuard`
80/// that measures cycles, instructions, cache misses, etc. When disabled,
81/// compiles to a zero-cost pass-through.
82///
83/// # Example
84///
85/// ```rust,no_run
86/// # use symbiotic::region;
87/// fn process(data: &mut [f64]) -> f64 {
88///     region!("sort", {
89///         data.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
90///     });
91///     region!("sum", {
92///         data.iter().sum()
93///     })
94/// }
95/// ```
96#[cfg(feature = "profiling")]
97#[macro_export]
98macro_rules! region {
99    ($name:expr, $body:expr) => {{
100        let _guard = $crate::RegionGuard::new($name);
101        $body
102    }};
103}
104
105/// No-op version when profiling is disabled.
106#[cfg(not(feature = "profiling"))]
107#[macro_export]
108macro_rules! region {
109    ($name:expr, $body:expr) => {
110        $body
111    };
112}
113
114/// Awaken the organism's nervous system.
115///
116/// Call this once at the start of `main()`. It:
117/// 1. Loads the BPF sense_hub (59 tracepoints → 96 kernel counters)
118/// 2. Enables sensory deltas in every `region!()` / `#[profile]` guard
119///
120/// After this call, every `RegionGuard` automatically captures kernel events
121/// (page faults, context switches, I/O, lock contention) alongside PMU counters.
122/// Cost: ~8 volatile loads per region entry+exit = ~10 cycles.
123///
124/// If BPF is unavailable (no `ebpf` feature, no permissions), this is a no-op.
125/// Regions still capture PMU counters and wall time.
126///
127/// # Example
128///
129/// ```rust,ignore
130/// fn main() {
131///     symbiotic::init();  // ← one call, everything instrumented
132///
133///     region!("my_work", {
134///         do_stuff();
135///     });
136///
137///     // Report is shown automatically via BenchProfiler::report()
138/// }
139/// ```
140pub fn init() {
141    #[cfg(feature = "ebpf")]
142    {
143        senses::init_zero_copy();
144    }
145}
146
147/// Initialize the profiling subscriber and return a handle to get the report
148pub fn init_profiling() -> ProfilerHandle {
149    let profiling_layer = ProfilingLayer::new();
150    let handle = ProfilerHandle {
151        layer: profiling_layer.clone(),
152    };
153
154    let subscriber = Registry::default()
155        .with(profiling_layer);
156
157    tracing::subscriber::set_global_default(subscriber)
158        .expect("Failed to set global tracing subscriber");
159
160    handle
161}
162
163/// Initialize profiling with custom configuration
164pub fn init_with_config(config: ProfilerConfig) -> ProfilerHandle {
165    let profiling_layer = ProfilingLayer::with_config(config);
166    let handle = ProfilerHandle {
167        layer: profiling_layer.clone(),
168    };
169
170    let subscriber = Registry::default()
171        .with(profiling_layer);
172
173    tracing::subscriber::set_global_default(subscriber)
174        .expect("Failed to set global tracing subscriber");
175
176    handle
177}
178
179/// Configuration for the profiler
180#[derive(Clone, Debug)]
181pub struct ProfilerConfig {
182    /// Sampling interval in milliseconds
183    pub sample_interval_ms: u64,
184    /// Enable hardware performance counters
185    pub enable_perf: bool,
186    /// Enable jemalloc statistics
187    pub enable_jemalloc: bool,
188    /// Enable thread pool monitoring
189    pub enable_threads: bool,
190    /// Maximum number of samples to keep in memory
191    pub max_samples: usize,
192    /// Benchmark name
193    pub benchmark_name: String,
194    /// Query server TCP port (None = disabled). Requires `server` feature.
195    /// Also reads SYMBIOT_PORT env var at startup.
196    pub server_port: Option<u16>,
197    /// Unix domain socket path (None = disabled). Requires `server-uds` feature.
198    /// Also reads SYMBIOT_SOCK env var at startup.
199    pub server_socket: Option<std::path::PathBuf>,
200    /// gRPC server port (None = disabled). Requires `server-grpc` feature.
201    /// Also reads SYMBIOT_GRPC_PORT env var at startup.
202    pub server_grpc_port: Option<u16>,
203}
204
205impl Default for ProfilerConfig {
206    fn default() -> Self {
207        // Check env vars for server config.
208        // When `dashboard` feature is enabled, default to port 9882 so the
209        // web UI is available without requiring SYMBIOT_PORT to be set.
210        let server_port = std::env::var("SYMBIOT_PORT").ok()
211            .and_then(|s| s.parse::<u16>().ok())
212            .or({
213                #[cfg(feature = "dashboard")]
214                { Some(9882) }
215                #[cfg(not(feature = "dashboard"))]
216                { None }
217            });
218        let server_socket = std::env::var("SYMBIOT_SOCK").ok()
219            .map(std::path::PathBuf::from);
220        let server_grpc_port = std::env::var("SYMBIOT_GRPC_PORT").ok()
221            .and_then(|s| s.parse::<u16>().ok());
222
223        Self {
224            sample_interval_ms: 1,    // 1ms = 1000Hz sampling
225            enable_perf: true,         // Enable all hardware counters
226            enable_jemalloc: true,     // Track allocations via jemalloc-ctl
227            enable_threads: true,      // Monitor all threads
228            max_samples: 1000000,      // Maximum samples for high frequency
229            benchmark_name: "benchmark".to_string(),
230            server_port,
231            server_socket,
232            server_grpc_port,
233        }
234    }
235}
236
237// No alternative configurations - always use the default aggressive settings
238
239/// Handle to the profiler for getting reports
240pub struct ProfilerHandle {
241    layer: ProfilingLayer,
242}
243
244impl ProfilerHandle {
245    /// Get the final profiling report
246    pub fn report(&self) -> ProfileReport {
247        self.layer.get_report()
248    }
249
250    /// Stop profiling and get the final report
251    pub fn finish(self) -> ProfileReport {
252        self.layer.stop();
253        self.layer.get_report()
254    }
255
256    /// Report bytes processed by the benchmark
257    pub fn report_bytes(&self, bytes: u64) {
258        self.layer.report_bytes(bytes);
259    }
260
261    /// Report rows processed by the benchmark
262    pub fn report_rows(&self, rows: u64) {
263        self.layer.report_rows(rows);
264    }
265
266    /// Report both bytes and rows processed
267    pub fn report_throughput(&self, bytes: u64, rows: u64) {
268        self.layer.report_bytes(bytes);
269        self.layer.report_rows(rows);
270    }
271
272    /// Dump a heap profile in pprof format (requires jemalloc feature)
273    ///
274    /// Returns gzipped protobuf data that can be analyzed with pprof:
275    /// ```ignore
276    /// let pprof_data = profiler.dump_heap_profile().await?;
277    /// std::fs::write("heap.pb.gz", pprof_data)?;
278    /// // Then analyze with: pprof -http=:8080 heap.pb.gz
279    /// ```
280    #[cfg(feature = "jemalloc")]
281    pub async fn dump_heap_profile(&self) -> Result<Vec<u8>, String> {
282        self.layer.dump_heap_profile().await
283    }
284
285    /// Dump a heap flamegraph in SVG format (requires jemalloc feature)
286    ///
287    /// Returns an interactive SVG that can be viewed in a browser:
288    /// ```ignore
289    /// let svg = profiler.dump_heap_flamegraph().await?;
290    /// std::fs::write("heap_flamegraph.svg", svg)?;
291    /// ```
292    #[cfg(feature = "jemalloc")]
293    pub async fn dump_heap_flamegraph(&self) -> Result<Vec<u8>, String> {
294        self.layer.dump_heap_flamegraph().await
295    }
296}
297