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