Skip to main content

oxilean_runtime/profiler/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::cell::RefCell;
6
7use super::types::{
8    AllocationTracker, AnnotatedTimeline, CallTreeNode, ComprehensiveProfilingReport, CountingStep,
9    EventFilter, FlameGraph, FlameNode, GcCollectionRecord, GcProfiler, HeatMap, Histogram,
10    PerfCounter, ProfileReport, ProfileSample, Profiler, ProfilerConfig, ProfilingEvent,
11    ProfilingMiddleware, ProfilingSession, RealTimeMonitor, SamplingProfiler, StackSnapshot,
12    TacticProfileLog, TacticProfilingEvent, TimelineAnnotation, TimelineView,
13};
14
15thread_local! {
16    #[doc = " Per-thread profiler instance."] static THREAD_PROFILER : RefCell < Profiler
17    > = RefCell::new(Profiler::new());
18}
19/// Enable the thread-local profiler.
20pub fn profiler_enable() {
21    THREAD_PROFILER.with(|p| p.borrow_mut().enable());
22}
23/// Disable the thread-local profiler.
24pub fn profiler_disable() {
25    THREAD_PROFILER.with(|p| p.borrow_mut().disable());
26}
27/// Generate a report from the thread-local profiler.
28pub fn profiler_report() -> ProfileReport {
29    THREAD_PROFILER.with(|p| p.borrow().generate_report())
30}
31/// Record a function entry in the thread-local profiler.
32pub fn profiler_enter(name: &str) {
33    THREAD_PROFILER.with(|p| p.borrow_mut().enter_function(name));
34}
35/// Record a function exit in the thread-local profiler.
36pub fn profiler_exit(name: &str) {
37    THREAD_PROFILER.with(|p| p.borrow_mut().exit_function(name));
38}
39/// Record an allocation in the thread-local profiler.
40pub fn profiler_alloc(size: usize, tag: &str) {
41    THREAD_PROFILER.with(|p| p.borrow_mut().alloc(size, tag));
42}
43/// Record a deallocation in the thread-local profiler.
44pub fn profiler_dealloc(size: usize, tag: &str) {
45    THREAD_PROFILER.with(|p| p.borrow_mut().dealloc(size, tag));
46}
47#[cfg(test)]
48mod tests {
49    use super::*;
50    #[test]
51    fn test_profiler_basic() {
52        let mut p = Profiler::new();
53        p.enter_function("foo");
54        p.exit_function("foo");
55        assert!(p.events.is_empty());
56        p.enable();
57        p.enter_function("bar");
58        p.exit_function("bar");
59        assert_eq!(p.events.len(), 2);
60    }
61    #[test]
62    fn test_profiler_report() {
63        let mut p = Profiler::new();
64        p.enable();
65        p.enter_function("compute");
66        p.exit_function("compute");
67        p.enter_function("compute");
68        p.exit_function("compute");
69        p.alloc(1024, "heap");
70        p.alloc(512, "stack");
71        let report = p.generate_report();
72        assert_eq!(report.total_calls, 2);
73        assert_eq!(report.total_alloc_bytes, 1536);
74        assert_eq!(report.gc_cycles, 0);
75        assert!(!report.hot_functions.is_empty());
76        assert_eq!(report.hot_functions[0].0, "compute");
77    }
78    #[test]
79    fn test_memory_profile() {
80        let mut p = Profiler::new();
81        p.enable();
82        p.alloc(100, "a");
83        p.alloc(200, "b");
84        p.dealloc(100, "a");
85        let mem = p.memory_profile();
86        assert_eq!(mem.total_allocs, 2);
87        assert_eq!(mem.peak_bytes, 300);
88        assert_eq!(mem.current_bytes, 200);
89        let text = mem.to_text();
90        assert!(text.contains("Memory Profile"));
91        assert!(text.contains("300"));
92    }
93    #[test]
94    fn test_profiler_disabled() {
95        let mut p = Profiler::new();
96        p.alloc(1000, "test");
97        p.enter_function("ignored");
98        p.gc_cycle(10, 50);
99        assert!(p.events.is_empty());
100        assert!(p.call_stack.is_empty());
101        p.enable();
102        p.alloc(42, "enabled");
103        assert_eq!(p.events.len(), 1);
104    }
105    #[test]
106    fn test_profiler_json() {
107        let mut p = Profiler::new();
108        p.enable();
109        p.enter_function("alpha");
110        p.exit_function("alpha");
111        p.alloc(256, "buf");
112        let report = p.generate_report();
113        let json = report.to_json();
114        assert!(json.contains("total_calls"));
115        assert!(json.contains("total_alloc_bytes"));
116        assert!(json.contains("gc_cycles"));
117        assert!(json.contains("hot_functions"));
118        assert!(json.starts_with('{'));
119        assert!(json.ends_with('}'));
120    }
121    #[test]
122    fn test_gc_cycle() {
123        let mut p = Profiler::new();
124        p.enable();
125        p.gc_cycle(50, 200);
126        p.gc_cycle(30, 170);
127        let report = p.generate_report();
128        assert_eq!(report.gc_cycles, 2);
129        let text = report.to_text();
130        assert!(text.contains("Profile Report"));
131        assert!(text.contains("GC cycles"));
132    }
133}
134pub(super) fn profiler_now_ns() -> u64 {
135    use std::time::{SystemTime, UNIX_EPOCH};
136    SystemTime::now()
137        .duration_since(UNIX_EPOCH)
138        .map(|d| d.as_nanos() as u64)
139        .unwrap_or(0)
140}
141#[cfg(test)]
142mod profiler_extended_tests {
143    use super::*;
144    #[test]
145    fn test_sampling_profiler_basic() {
146        let mut p = SamplingProfiler::new(1_000_000);
147        p.enable();
148        p.enter("foo");
149        p.enter("bar");
150        p.take_sample(0);
151        assert_eq!(p.sample_count(), 1);
152        let sample = &p.samples[0];
153        assert_eq!(sample.top_function(), Some("bar"));
154        assert_eq!(sample.depth(), 2);
155    }
156    #[test]
157    fn test_flat_profile() {
158        let mut p = SamplingProfiler::new(1_000_000);
159        p.enable();
160        for _ in 0..3 {
161            p.enter("foo");
162            p.take_sample(0);
163            p.leave("foo");
164        }
165        p.enter("bar");
166        p.take_sample(0);
167        p.leave("bar");
168        let flat = p.flat_profile();
169        assert_eq!(flat[0].0, "foo");
170        assert_eq!(flat[0].1, 3);
171        assert_eq!(flat[1].0, "bar");
172        assert_eq!(flat[1].1, 1);
173    }
174    #[test]
175    fn test_cumulative_profile() {
176        let mut p = SamplingProfiler::new(1_000_000);
177        p.enable();
178        p.current_stack = vec!["inner".to_string(), "outer".to_string()];
179        p.take_sample(0);
180        p.take_sample(0);
181        let cum = p.cumulative_profile();
182        let has_inner = cum.iter().any(|(n, _)| n == "inner");
183        let has_outer = cum.iter().any(|(n, _)| n == "outer");
184        assert!(has_inner);
185        assert!(has_outer);
186    }
187    #[test]
188    fn test_flame_graph_basic() {
189        let mut fg = FlameGraph::new();
190        let stack = vec![
191            "main".to_string(),
192            "compute".to_string(),
193            "inner".to_string(),
194        ];
195        fg.add_stack(&stack);
196        fg.add_stack(&stack);
197        assert_eq!(fg.total_samples, 2);
198        let text = fg.render_text();
199        assert!(text.contains("(all)"));
200    }
201    #[test]
202    fn test_flame_node_get_or_create() {
203        let mut node = FlameNode::new("root");
204        {
205            let child = node.get_or_create_child("child1");
206            child.count += 1;
207        }
208        {
209            let child = node.get_or_create_child("child1");
210            child.count += 1;
211        }
212        {
213            node.get_or_create_child("child2");
214        }
215        assert_eq!(node.children.len(), 2);
216        assert_eq!(node.children[0].count, 2);
217    }
218    #[test]
219    fn test_perf_counter() {
220        let mut pc = PerfCounter::new();
221        pc.simulate_instructions(1000);
222        pc.simulate_cache_miss();
223        pc.simulate_branch_misprediction();
224        assert_eq!(pc.instructions_retired, 1000);
225        assert_eq!(pc.cache_misses, 1);
226        assert_eq!(pc.branch_mispredictions, 1);
227        assert!(pc.ipc() > 0.0);
228        let summary = pc.summary();
229        assert!(summary.contains("PerfCounters"));
230        assert!(summary.contains("IPC"));
231    }
232    #[test]
233    fn test_allocation_tracker() {
234        let mut tracker = AllocationTracker::new();
235        tracker.record_alloc("heap", 1024);
236        tracker.record_alloc("heap", 512);
237        tracker.record_alloc("stack", 256);
238        tracker.record_dealloc("heap", 512);
239        let heap_stats = tracker
240            .stats_for("heap")
241            .expect("test operation should succeed");
242        assert_eq!(heap_stats.alloc_count, 2);
243        assert_eq!(heap_stats.total_bytes, 1536);
244        assert_eq!(heap_stats.live_bytes, 1024);
245        assert_eq!(tracker.total_live_bytes(), 1024 + 256);
246        let top = tracker.top_allocators(1);
247        assert_eq!(top[0].0, "heap");
248    }
249    #[test]
250    fn test_tactic_profile_log() {
251        let mut log = TacticProfileLog::new();
252        log.record(TacticProfilingEvent::new("intro", 500, true, 1, 1));
253        log.record(TacticProfilingEvent::new("apply", 1500, true, 1, 0));
254        log.record(TacticProfilingEvent::new("rw", 300, false, 1, 1));
255        assert_eq!(log.total_duration_ns(), 2300);
256        assert_eq!(log.success_count(), 2);
257        let top = log.top_slow(1);
258        assert_eq!(top[0].tactic, "apply");
259        assert!((log.avg_duration_ns() - 2300.0 / 3.0).abs() < 1.0);
260    }
261    #[test]
262    fn test_tactic_profiling_event_goals_eliminated() {
263        let event = TacticProfilingEvent::new("exact", 100, true, 3, 1);
264        assert_eq!(event.goals_eliminated(), 2);
265    }
266    #[test]
267    fn test_stack_snapshot() {
268        let frames = vec!["main".to_string(), "foo".to_string(), "bar".to_string()];
269        let snap = StackSnapshot::new(12345, frames.clone()).with_label("checkpoint");
270        assert_eq!(snap.depth(), 3);
271        assert_eq!(snap.label.as_deref(), Some("checkpoint"));
272        let formatted = snap.format();
273        assert!(formatted.contains("checkpoint"));
274        assert!(formatted.contains("main"));
275        assert!(formatted.contains("bar"));
276    }
277    #[test]
278    fn test_profiler_config_builder() {
279        let cfg = ProfilerConfig::new().enable_all();
280        assert!(cfg.event_profiling);
281        assert!(cfg.sampling_profiling);
282        let cfg2 = ProfilerConfig::default().disable_all();
283        assert!(!cfg2.event_profiling);
284        assert!(!cfg2.sampling_profiling);
285    }
286    #[test]
287    fn test_call_tree_node() {
288        let mut root = CallTreeNode::new("main");
289        root.inclusive_ns = 10_000;
290        root.exclusive_ns = 1_000;
291        root.call_count = 2;
292        let mut child = CallTreeNode::new("compute");
293        child.inclusive_ns = 9_000;
294        child.exclusive_ns = 9_000;
295        child.call_count = 5;
296        root.children.push(child);
297        assert!((root.avg_exclusive_ns() - 500.0).abs() < 1.0);
298        assert!((root.avg_inclusive_ns() - 5000.0).abs() < 1.0);
299        assert!(root.find_child("compute").is_some());
300        assert!(root.find_child("missing").is_none());
301    }
302    #[test]
303    fn test_sampling_profiler_avg_stack_depth() {
304        let mut p = SamplingProfiler::new(1_000_000);
305        p.enabled = true;
306        p.samples
307            .push(ProfileSample::new(0, vec!["a".into(), "b".into()], 0));
308        p.samples.push(ProfileSample::new(
309            1,
310            vec!["a".into(), "b".into(), "c".into()],
311            0,
312        ));
313        assert!((p.avg_stack_depth() - 2.5).abs() < 1e-9);
314    }
315}
316#[cfg(test)]
317mod profiler_extended_tests_2 {
318    use super::*;
319    #[test]
320    fn test_event_filter_timestamp() {
321        let filter = EventFilter {
322            min_timestamp_ns: 100,
323            max_timestamp_ns: 200,
324            ..EventFilter::new()
325        };
326        let e1 = ProfilingEvent::FunctionCall {
327            name: "f".to_string(),
328            depth: 0,
329        };
330        let e2 = ProfilingEvent::FunctionCall {
331            name: "g".to_string(),
332            depth: 0,
333        };
334        assert!(filter.matches(150, &e1));
335        assert!(!filter.matches(50, &e2));
336        assert!(!filter.matches(250, &e2));
337    }
338    #[test]
339    fn test_event_filter_function_name() {
340        let filter = EventFilter {
341            function_names: vec!["foo".to_string()],
342            ..EventFilter::new()
343        };
344        let e_foo = ProfilingEvent::FunctionCall {
345            name: "foo".to_string(),
346            depth: 0,
347        };
348        let e_bar = ProfilingEvent::FunctionCall {
349            name: "bar".to_string(),
350            depth: 0,
351        };
352        assert!(filter.matches(0, &e_foo));
353        assert!(!filter.matches(0, &e_bar));
354    }
355    #[test]
356    fn test_event_filter_alloc_size() {
357        let filter = EventFilter {
358            min_alloc_bytes: 100,
359            ..EventFilter::new()
360        };
361        let small = ProfilingEvent::Allocation {
362            size: 50,
363            tag: "x".to_string(),
364        };
365        let large = ProfilingEvent::Allocation {
366            size: 200,
367            tag: "y".to_string(),
368        };
369        assert!(!filter.matches(0, &small));
370        assert!(filter.matches(0, &large));
371    }
372    #[test]
373    fn test_timeline_view_build() {
374        let mut p = Profiler::new();
375        p.enable();
376        p.enter_function("foo");
377        p.exit_function("foo");
378        p.alloc(1024, "heap");
379        p.gc_cycle(10, 90);
380        let view = TimelineView::build(&p);
381        assert!(!view.entries.is_empty());
382        let func_entries = view.by_category("function");
383        assert!(!func_entries.is_empty());
384    }
385    #[test]
386    fn test_heat_map() {
387        let mut hm = HeatMap::new(10, 1_000_000_000);
388        let base = 0u64;
389        hm.record(0, base);
390        hm.record(100_000_000, base);
391        hm.record(900_000_000, base);
392        assert_eq!(hm.counts[0], 1);
393        let ascii = hm.render_ascii();
394        assert!(ascii.contains('|'));
395    }
396    #[test]
397    fn test_profiling_session() {
398        let mut session = ProfilingSession::new("test_session");
399        assert!(!session.running);
400        session.start();
401        assert!(session.running);
402        session.enter_function("main");
403        session.alloc(512, "buf");
404        session.dealloc(512, "buf");
405        session.exit_function("main");
406        session.stop();
407        assert!(!session.running);
408        let report = session.combined_report();
409        assert!(report.contains("test_session"));
410    }
411    #[test]
412    fn test_real_time_monitor() {
413        let mut mon = RealTimeMonitor::new("metrics", 100);
414        mon.record("cpu", 0.5);
415        mon.record("cpu", 0.7);
416        mon.record("mem", 1024.0);
417        assert!((mon.latest("cpu").expect("test operation should succeed") - 0.7).abs() < 1e-9);
418        assert!((mon.avg("cpu") - 0.6).abs() < 1e-9);
419        assert_eq!(mon.count("cpu"), 2);
420        assert_eq!(mon.count("mem"), 1);
421        assert!(mon.latest("missing").is_none());
422    }
423    #[test]
424    fn test_real_time_monitor_capacity() {
425        let mut mon = RealTimeMonitor::new("capacity_test", 3);
426        for i in 0..5 {
427            mon.record("x", i as f64);
428        }
429        assert_eq!(mon.snapshots.len(), 3);
430    }
431    #[test]
432    fn test_histogram() {
433        let mut h = Histogram::new(5, 0.0, 100.0);
434        h.record(10.0);
435        h.record(10.0);
436        h.record(50.0);
437        h.record(90.0);
438        assert_eq!(h.total, 4);
439        assert!((h.mean() - 40.0).abs() < 1e-9);
440        let mode = h.mode_bucket().expect("test operation should succeed");
441        assert!(mode.count >= 1);
442        let ascii = h.render_ascii();
443        assert!(ascii.contains('['));
444    }
445    #[test]
446    fn test_histogram_edge_case() {
447        let mut h = Histogram::new(3, 0.0, 10.0);
448        h.record(0.0);
449        h.record(5.0);
450        h.record(9.99);
451        h.record(100.0);
452        assert_eq!(h.total, 4);
453    }
454}
455/// A step in the profiling pipeline.
456#[allow(dead_code)]
457pub trait ProfilingStep {
458    /// Process a batch of events.
459    fn process(&mut self, events: &[(u64, ProfilingEvent)]);
460    /// Name of this step.
461    fn name(&self) -> &str;
462}
463#[cfg(test)]
464mod profiler_extended_tests_3 {
465    use super::*;
466    #[test]
467    fn test_gc_collection_record() {
468        let r = GcCollectionRecord::new(1000, 50, 150, 500_000);
469        assert!((r.efficiency() - 50.0 / 200.0).abs() < 1e-9);
470    }
471    #[test]
472    fn test_gc_profiler() {
473        let mut gcp = GcProfiler::new();
474        gcp.record(30, 100, 1_000_000);
475        gcp.record(20, 80, 2_000_000);
476        assert_eq!(gcp.collection_count(), 2);
477        assert_eq!(gcp.total_collected(), 50);
478        assert!((gcp.avg_pause_ns() - 1_500_000.0).abs() < 1.0);
479        assert_eq!(gcp.max_pause_ns(), 2_000_000);
480        let summary = gcp.summary();
481        assert!(summary.contains("2 collections"));
482    }
483    #[test]
484    fn test_counting_step() {
485        let mut step = CountingStep::new("counter");
486        assert_eq!(step.name(), "counter");
487        let events = vec![
488            (
489                0u64,
490                ProfilingEvent::FunctionCall {
491                    name: "f".to_string(),
492                    depth: 0,
493                },
494            ),
495            (
496                1u64,
497                ProfilingEvent::Allocation {
498                    size: 100,
499                    tag: "t".to_string(),
500                },
501            ),
502            (
503                2u64,
504                ProfilingEvent::FunctionCall {
505                    name: "g".to_string(),
506                    depth: 0,
507                },
508            ),
509        ];
510        step.process(&events);
511        assert_eq!(step.counts.get("FunctionCall").copied().unwrap_or(0), 2);
512        assert_eq!(step.counts.get("Allocation").copied().unwrap_or(0), 1);
513    }
514    #[test]
515    fn test_annotated_timeline() {
516        let mut tl = AnnotatedTimeline::new();
517        tl.annotate(TimelineAnnotation::new(100, "start", "checkpoint"));
518        tl.annotate(TimelineAnnotation::new(500, "mid", "checkpoint"));
519        tl.annotate(TimelineAnnotation::new(1000, "end", "checkpoint"));
520        let in_range = tl.annotations_in_range(200, 700);
521        assert_eq!(in_range.len(), 1);
522        assert_eq!(in_range[0].text, "mid");
523    }
524    #[test]
525    fn test_comprehensive_report() {
526        let mut session = ProfilingSession::new("test");
527        session.start();
528        session.enter_function("main");
529        session.alloc(1024, "heap");
530        session.sampler.take_sample(0);
531        session.exit_function("main");
532        session.stop();
533        let report = ComprehensiveProfilingReport::build(&session);
534        let text = report.to_text();
535        assert!(text.contains("Profile Report"));
536    }
537    #[test]
538    fn test_gc_profiler_empty() {
539        let gcp = GcProfiler::new();
540        assert_eq!(gcp.collection_count(), 0);
541        assert_eq!(gcp.total_collected(), 0);
542        assert!((gcp.avg_pause_ns() - 0.0).abs() < 1e-9);
543        assert_eq!(gcp.max_pause_ns(), 0);
544    }
545    #[test]
546    fn test_timeline_annotation_categories() {
547        let mut tl = AnnotatedTimeline::new();
548        tl.annotate(TimelineAnnotation::new(0, "start error", "error"));
549        tl.annotate(TimelineAnnotation::new(0, "start ok", "checkpoint"));
550        let errors: Vec<_> = tl
551            .annotations
552            .iter()
553            .filter(|a| a.category == "error")
554            .collect();
555        assert_eq!(errors.len(), 1);
556    }
557    #[test]
558    fn test_flame_graph_from_profiler() {
559        let mut p = SamplingProfiler::new(1_000_000);
560        p.enabled = true;
561        p.current_stack = vec!["main".into(), "foo".into()];
562        p.take_sample(0);
563        p.take_sample(0);
564        let fg = FlameGraph::from_profiler(&p);
565        assert_eq!(fg.total_samples, 2);
566        let text = fg.render_text();
567        assert!(text.contains("(all)"));
568    }
569}
570#[cfg(test)]
571mod middleware_tests {
572    use super::*;
573    #[test]
574    fn test_middleware_instrument() {
575        let mut mw = ProfilingMiddleware::new();
576        let result = mw.instrument("compute", || 6 * 7);
577        assert_eq!(result, 42);
578        let report = mw.report();
579        assert_eq!(report.total_calls, 1);
580    }
581    #[test]
582    fn test_middleware_inactive() {
583        let mut mw = ProfilingMiddleware::new();
584        mw.active = false;
585        mw.instrument("ignored", || ());
586        let report = mw.report();
587        assert_eq!(report.total_calls, 0);
588    }
589}