Skip to main content

trustformers_debug/
flame_graph_profiler.rs

1//! Advanced flame graph profiling implementation for TrustformeRS Debug
2// reason: debug/profiling scaffolding — structs are constructed and their fields/methods
3// are retained for the data model, serialization completeness, and future consumers that
4// do not yet read every member. Consolidated from many item-level #[allow(dead_code)].
5#![allow(dead_code)]
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::Path;
11use std::time::{Instant, SystemTime};
12
13use crate::profiler::{ProfileEvent, Profiler};
14
15/// Flame graph node representing a stack frame
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct FlameGraphNode {
18    pub name: String,
19    pub value: u64,
20    pub delta: Option<i64>, // For differential analysis
21    pub children: HashMap<String, FlameGraphNode>,
22    pub total_value: u64,
23    pub self_value: u64,
24    pub percentage: f64,
25    pub color: Option<String>,
26    pub metadata: HashMap<String, String>,
27}
28
29/// Stack frame for flame graph construction
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub struct StackFrame {
32    pub function_name: String,
33    pub module_name: Option<String>,
34    pub file_name: Option<String>,
35    pub line_number: Option<u32>,
36    pub address: Option<u64>,
37}
38
39/// Sample data for flame graph
40#[derive(Debug, Clone)]
41pub struct FlameGraphSample {
42    pub stack: Vec<StackFrame>,
43    pub duration_ns: u64,
44    pub timestamp: u64,
45    pub thread_id: u64,
46    pub cpu_id: Option<u32>,
47    pub memory_usage: Option<usize>,
48    pub gpu_kernel: Option<String>,
49    pub metadata: HashMap<String, String>,
50}
51
52/// Configuration for flame graph generation
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct FlameGraphConfig {
55    pub sampling_rate: u32, // Samples per second
56    pub min_width: f64,     // Minimum width for node visibility
57    pub color_scheme: FlameGraphColorScheme,
58    pub direction: FlameGraphDirection,
59    pub title: String,
60    pub subtitle: Option<String>,
61    pub include_memory: bool,
62    pub include_gpu: bool,
63    pub differential_mode: bool,
64    pub merge_similar_stacks: bool,
65    pub filter_noise: bool,
66    pub noise_threshold: f64,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum FlameGraphColorScheme {
71    Hot,          // Red-orange gradient
72    Cool,         // Blue-purple gradient
73    Java,         // Java-specific colors
74    Memory,       // Memory-aware coloring
75    Differential, // Differential analysis colors
76    Random,       // Random but consistent colors
77    Custom(HashMap<String, String>),
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub enum FlameGraphDirection {
82    TopDown,  // Traditional flame graph
83    BottomUp, // Icicle graph
84}
85
86/// Export format for flame graphs
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub enum FlameGraphExportFormat {
89    SVG,
90    InteractiveHTML,
91    JSON,
92    Speedscope,
93    D3,
94    Folded,
95}
96
97/// Advanced flame graph profiler
98#[derive(Debug)]
99pub struct FlameGraphProfiler {
100    config: FlameGraphConfig,
101    samples: Vec<FlameGraphSample>,
102    sampling_timer: Option<Instant>,
103    root_node: Option<FlameGraphNode>,
104    baseline_samples: Option<Vec<FlameGraphSample>>, // For differential analysis
105    metadata: HashMap<String, String>,
106    current_cpu_usage: f64,
107    current_memory_usage: usize,
108    performance_counters: HashMap<String, u64>,
109}
110
111impl FlameGraphProfiler {
112    /// Create a new flame graph profiler
113    pub fn new(config: FlameGraphConfig) -> Self {
114        Self {
115            config,
116            samples: Vec::new(),
117            sampling_timer: None,
118            root_node: None,
119            baseline_samples: None,
120            metadata: HashMap::new(),
121            current_cpu_usage: 0.0,
122            current_memory_usage: 0,
123            performance_counters: HashMap::new(),
124        }
125    }
126
127    /// Start profiling with sampling
128    pub fn start_sampling(&mut self) -> Result<()> {
129        tracing::info!(
130            "Starting flame graph sampling at {} Hz",
131            self.config.sampling_rate
132        );
133        self.sampling_timer = Some(Instant::now());
134        self.samples.clear();
135        self.root_node = None;
136
137        // Initialize performance counters
138        self.performance_counters.insert("samples_collected".to_string(), 0);
139        self.performance_counters.insert("stack_depth_max".to_string(), 0);
140        self.performance_counters.insert("unique_functions".to_string(), 0);
141
142        Ok(())
143    }
144
145    /// Stop profiling and build flame graph
146    pub fn stop_sampling(&mut self) -> Result<()> {
147        tracing::info!(
148            "Stopping flame graph sampling, collected {} samples",
149            self.samples.len()
150        );
151        self.sampling_timer = None;
152        self.build_flame_graph()?;
153        Ok(())
154    }
155
156    /// Add a sample to the profiler
157    pub fn add_sample(&mut self, sample: FlameGraphSample) {
158        // Update performance counters
159        if let Some(counter) = self.performance_counters.get_mut("samples_collected") {
160            *counter += 1;
161        }
162
163        let stack_depth = sample.stack.len() as u64;
164        if let Some(max_depth) = self.performance_counters.get_mut("stack_depth_max") {
165            if stack_depth > *max_depth {
166                *max_depth = stack_depth;
167            }
168        }
169
170        self.samples.push(sample);
171    }
172
173    /// Add a sample from current stack trace
174    pub fn sample_current_stack(&mut self, duration_ns: u64) -> Result<()> {
175        let stack = self.capture_stack_trace()?;
176        let sample = FlameGraphSample {
177            stack,
178            duration_ns,
179            timestamp: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_nanos() as u64,
180            thread_id: self.get_current_thread_id(),
181            cpu_id: self.get_current_cpu_id(),
182            memory_usage: Some(self.current_memory_usage),
183            gpu_kernel: None,
184            metadata: HashMap::new(),
185        };
186
187        self.add_sample(sample);
188        Ok(())
189    }
190
191    /// Add GPU kernel sample
192    pub fn sample_gpu_kernel(&mut self, kernel_name: &str, duration_ns: u64) {
193        let stack = vec![StackFrame {
194            function_name: format!("GPU::{}", kernel_name),
195            module_name: Some("GPU".to_string()),
196            file_name: None,
197            line_number: None,
198            address: None,
199        }];
200
201        let sample = FlameGraphSample {
202            stack,
203            duration_ns,
204            timestamp: SystemTime::now()
205                .duration_since(SystemTime::UNIX_EPOCH)
206                .unwrap_or_default()
207                .as_nanos() as u64,
208            thread_id: 0, // GPU operations on virtual thread
209            cpu_id: None,
210            memory_usage: None,
211            gpu_kernel: Some(kernel_name.to_string()),
212            metadata: [("type".to_string(), "gpu".to_string())].into_iter().collect(),
213        };
214
215        self.add_sample(sample);
216    }
217
218    /// Set baseline for differential analysis
219    pub fn set_baseline(&mut self) {
220        self.baseline_samples = Some(self.samples.clone());
221        tracing::info!("Set baseline with {} samples", self.samples.len());
222    }
223
224    /// Build flame graph from collected samples
225    pub fn build_flame_graph(&mut self) -> Result<()> {
226        if self.samples.is_empty() {
227            return Err(anyhow::anyhow!("No samples collected"));
228        }
229
230        let mut root = FlameGraphNode {
231            name: "root".to_string(),
232            value: 0,
233            delta: None,
234            children: HashMap::new(),
235            total_value: 0,
236            self_value: 0,
237            percentage: 100.0,
238            color: None,
239            metadata: HashMap::new(),
240        };
241
242        // Merge samples into tree structure
243        for sample in &self.samples {
244            self.merge_sample_into_tree(&mut root, sample);
245        }
246
247        // Calculate totals and percentages
248        self.calculate_node_metrics(&mut root);
249
250        // Apply differential analysis if baseline exists
251        if self.config.differential_mode && self.baseline_samples.is_some() {
252            self.apply_differential_analysis(&mut root)?;
253        }
254
255        // Filter noise if enabled
256        if self.config.filter_noise {
257            self.filter_noise_nodes(&mut root);
258        }
259
260        // Update performance counters
261        let unique_functions = self.count_unique_functions(&root);
262        if let Some(counter) = self.performance_counters.get_mut("unique_functions") {
263            *counter = unique_functions;
264        }
265
266        self.root_node = Some(root);
267        tracing::info!(
268            "Built flame graph with {} unique functions",
269            unique_functions
270        );
271        Ok(())
272    }
273
274    /// Export flame graph to various formats
275    pub async fn export(&self, format: FlameGraphExportFormat, output_path: &Path) -> Result<()> {
276        let root = self
277            .root_node
278            .as_ref()
279            .ok_or_else(|| anyhow::anyhow!("Flame graph not built yet"))?;
280
281        match format {
282            FlameGraphExportFormat::SVG => self.export_svg(root, output_path).await,
283            FlameGraphExportFormat::InteractiveHTML => {
284                self.export_interactive_html(root, output_path).await
285            },
286            FlameGraphExportFormat::JSON => self.export_json(root, output_path).await,
287            FlameGraphExportFormat::Speedscope => self.export_speedscope(root, output_path).await,
288            FlameGraphExportFormat::D3 => self.export_d3(root, output_path).await,
289            FlameGraphExportFormat::Folded => self.export_folded(output_path).await,
290        }
291    }
292
293    /// Export as SVG flame graph
294    async fn export_svg(&self, root: &FlameGraphNode, output_path: &Path) -> Result<()> {
295        let mut svg_content = String::new();
296
297        // SVG header
298        svg_content.push_str(&format!(
299            r##"<?xml version="1.0" encoding="UTF-8"?>
300<svg width="1200" height="800" xmlns="http://www.w3.org/2000/svg">
301<defs>
302    <linearGradient id="background" x1="0%" y1="0%" x2="0%" y2="100%">
303        <stop offset="0%" style="stop-color:#eeeeee"/>
304        <stop offset="100%" style="stop-color:#eeeeb0"/>
305    </linearGradient>
306</defs>
307<rect width="100%" height="100%" fill="url(#background)"/>
308<text x="600" y="24" text-anchor="middle" font-size="17" font-family="Verdana">{}</text>
309<text x="600" y="44" text-anchor="middle" font-size="12" font-family="Verdana" fill="#999">
310    {} samples, {} functions
311</text>
312"##,
313            self.config.title,
314            self.samples.len(),
315            self.count_unique_functions(root)
316        ));
317
318        // Render flame graph rectangles
319        self.render_svg_node(&mut svg_content, root, 0, 0, 1200, 0)?;
320
321        svg_content.push_str("</svg>");
322
323        tokio::fs::write(output_path, svg_content).await?;
324        tracing::info!("Exported SVG flame graph to {:?}", output_path);
325        Ok(())
326    }
327
328    /// Export as interactive HTML flame graph
329    async fn export_interactive_html(
330        &self,
331        root: &FlameGraphNode,
332        output_path: &Path,
333    ) -> Result<()> {
334        let json_data = serde_json::to_string(root)?;
335
336        let html_content = format!(
337            r#"<!DOCTYPE html>
338<html>
339<head>
340    <title>{}</title>
341    <meta charset="utf-8">
342    <style>
343        body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; }}
344        .flame-graph {{ width: 100%; height: 600px; border: 1px solid #ccc; }}
345        .tooltip {{ position: absolute; background: rgba(0,0,0,0.8); color: white;
346                   padding: 10px; border-radius: 4px; pointer-events: none; z-index: 1000; }}
347        .controls {{ margin-bottom: 20px; }}
348        .info {{ margin-top: 20px; font-size: 14px; color: #666; }}
349    </style>
350    <script src="https://d3js.org/d3.v7.min.js"></script>
351</head>
352<body>
353    <h1>{}</h1>
354    <div class="controls">
355        <button onclick="resetZoom()">Reset Zoom</button>
356        <button onclick="searchFunction()">Search</button>
357        <input type="text" id="searchInput" placeholder="Function name...">
358    </div>
359    <div id="flame-graph" class="flame-graph"></div>
360    <div class="info">
361        <p>Samples: {} | Functions: {} | Total Time: {:.2}ms</p>
362        <p>Click to zoom, double-click to reset. Hover for details.</p>
363    </div>
364    <div id="tooltip" class="tooltip" style="display: none;"></div>
365
366    <script>
367        const data = {};
368        // Interactive flame graph implementation would go here
369        // This is a simplified version - full implementation would include D3.js visualization
370        console.log('Flame graph data loaded:', data);
371    </script>
372</body>
373</html>"#,
374            self.config.title,
375            self.config.title,
376            self.samples.len(),
377            self.count_unique_functions(root),
378            root.total_value as f64 / 1_000_000.0, // Convert ns to ms
379            json_data
380        );
381
382        tokio::fs::write(output_path, html_content).await?;
383        tracing::info!("Exported interactive HTML flame graph to {:?}", output_path);
384        Ok(())
385    }
386
387    /// Export as JSON
388    async fn export_json(&self, root: &FlameGraphNode, output_path: &Path) -> Result<()> {
389        let json_data = serde_json::to_string_pretty(root)?;
390        tokio::fs::write(output_path, json_data).await?;
391        tracing::info!("Exported JSON flame graph to {:?}", output_path);
392        Ok(())
393    }
394
395    /// Export as Speedscope format
396    async fn export_speedscope(&self, root: &FlameGraphNode, output_path: &Path) -> Result<()> {
397        let speedscope_data = self.convert_to_speedscope_format(root)?;
398        let json_data = serde_json::to_string_pretty(&speedscope_data)?;
399        tokio::fs::write(output_path, json_data).await?;
400        tracing::info!("Exported Speedscope format to {:?}", output_path);
401        Ok(())
402    }
403
404    /// Export as D3.js compatible format
405    async fn export_d3(&self, root: &FlameGraphNode, output_path: &Path) -> Result<()> {
406        let d3_data = self.convert_to_d3_format(root)?;
407        let json_data = serde_json::to_string_pretty(&d3_data)?;
408        tokio::fs::write(output_path, json_data).await?;
409        tracing::info!("Exported D3 format to {:?}", output_path);
410        Ok(())
411    }
412
413    /// Export as folded stack format
414    async fn export_folded(&self, output_path: &Path) -> Result<()> {
415        let mut folded_content = String::new();
416
417        for sample in &self.samples {
418            let stack_str: Vec<String> =
419                sample.stack.iter().map(|frame| frame.function_name.clone()).collect();
420            folded_content.push_str(&format!("{} {}\n", stack_str.join(";"), sample.duration_ns));
421        }
422
423        tokio::fs::write(output_path, folded_content).await?;
424        tracing::info!("Exported folded format to {:?}", output_path);
425        Ok(())
426    }
427
428    /// Get flame graph analysis report
429    pub fn get_analysis_report(&self) -> FlameGraphAnalysisReport {
430        let root = self.root_node.as_ref();
431
432        FlameGraphAnalysisReport {
433            total_samples: self.samples.len(),
434            total_duration_ns: self.samples.iter().map(|s| s.duration_ns).sum(),
435            unique_functions: root.map(|r| self.count_unique_functions(r)).unwrap_or(0),
436            max_stack_depth: self.performance_counters.get("stack_depth_max").copied().unwrap_or(0),
437            hot_functions: self.get_hot_functions(10),
438            memory_usage_stats: self.get_memory_usage_stats(),
439            gpu_kernel_stats: self.get_gpu_kernel_stats(),
440            differential_analysis: self.get_differential_analysis(),
441            performance_insights: self.generate_performance_insights(),
442        }
443    }
444
445    // Private helper methods
446
447    fn capture_stack_trace(&self) -> Result<Vec<StackFrame>> {
448        // Simplified stack trace capture
449        // In a real implementation, this would use platform-specific APIs
450        Ok(vec![StackFrame {
451            function_name: "captured_function".to_string(),
452            module_name: Some("trustformers_debug".to_string()),
453            file_name: Some("profiler.rs".to_string()),
454            line_number: Some(1800),
455            address: None,
456        }])
457    }
458
459    fn get_current_thread_id(&self) -> u64 {
460        // Simplified thread ID - would use thread::current().id() in practice
461        1
462    }
463
464    fn get_current_cpu_id(&self) -> Option<u32> {
465        // Would query current CPU ID in practice
466        Some(0)
467    }
468
469    fn merge_sample_into_tree(&self, node: &mut FlameGraphNode, sample: &FlameGraphSample) {
470        if sample.stack.is_empty() {
471            node.value += sample.duration_ns;
472            return;
473        }
474
475        let frame = &sample.stack[0];
476        let child =
477            node.children
478                .entry(frame.function_name.clone())
479                .or_insert_with(|| FlameGraphNode {
480                    name: frame.function_name.clone(),
481                    value: 0,
482                    delta: None,
483                    children: HashMap::new(),
484                    total_value: 0,
485                    self_value: 0,
486                    percentage: 0.0,
487                    color: None,
488                    metadata: HashMap::new(),
489                });
490
491        if sample.stack.len() == 1 {
492            child.value += sample.duration_ns;
493        } else {
494            let mut remaining_sample = sample.clone();
495            remaining_sample.stack = sample.stack[1..].to_vec();
496            self.merge_sample_into_tree(child, &remaining_sample);
497        }
498    }
499
500    fn calculate_node_metrics(&self, node: &mut FlameGraphNode) {
501        let mut total_children_value = 0;
502
503        for child in node.children.values_mut() {
504            self.calculate_node_metrics(child);
505            total_children_value += child.total_value;
506        }
507
508        node.total_value = node.value + total_children_value;
509        node.self_value = node.value;
510
511        if node.total_value > 0 && node.name != "root" {
512            // Get the total from root node for percentage calculation
513            let total_for_percentage = if let Some(root) = &self.root_node {
514                root.total_value
515            } else {
516                node.total_value // fallback
517            };
518
519            if total_for_percentage > 0 {
520                node.percentage = (node.total_value as f64 / total_for_percentage as f64) * 100.0;
521            }
522        }
523    }
524
525    fn apply_differential_analysis(&self, node: &mut FlameGraphNode) -> Result<()> {
526        if let Some(baseline_samples) = &self.baseline_samples {
527            // Build baseline tree
528            let mut baseline_root = FlameGraphNode {
529                name: "root".to_string(),
530                value: 0,
531                delta: None,
532                children: HashMap::new(),
533                total_value: 0,
534                self_value: 0,
535                percentage: 100.0,
536                color: None,
537                metadata: HashMap::new(),
538            };
539
540            for sample in baseline_samples {
541                self.merge_sample_into_tree(&mut baseline_root, sample);
542            }
543
544            // Calculate deltas
545            self.calculate_deltas(node, &baseline_root);
546        }
547        Ok(())
548    }
549
550    fn calculate_deltas(&self, current: &mut FlameGraphNode, baseline: &FlameGraphNode) {
551        let baseline_value =
552            baseline.children.get(&current.name).map(|n| n.total_value as i64).unwrap_or(0);
553
554        current.delta = Some(current.total_value as i64 - baseline_value);
555
556        for (name, child) in &mut current.children {
557            if let Some(baseline_child) = baseline.children.get(name) {
558                self.calculate_deltas(child, baseline_child);
559            } else {
560                child.delta = Some(child.total_value as i64);
561            }
562        }
563    }
564
565    fn filter_noise_nodes(&self, node: &mut FlameGraphNode) {
566        let threshold = (node.total_value as f64 * self.config.noise_threshold / 100.0) as u64;
567
568        node.children.retain(|_, child| {
569            self.filter_noise_nodes(child);
570            child.total_value >= threshold
571        });
572    }
573
574    fn count_unique_functions(&self, node: &FlameGraphNode) -> u64 {
575        let mut count = 1; // Count this node
576        for child in node.children.values() {
577            count += self.count_unique_functions(child);
578        }
579        count
580    }
581
582    fn render_svg_node(
583        &self,
584        svg: &mut String,
585        node: &FlameGraphNode,
586        x: i32,
587        y: i32,
588        width: i32,
589        depth: i32,
590    ) -> Result<()> {
591        if width < 1 {
592            return Ok(());
593        }
594
595        let height = 20;
596        let color = self.get_node_color(node);
597
598        svg.push_str(&format!(
599            r#"<rect x="{}" y="{}" width="{}" height="{}" fill="{}" stroke="white" stroke-width="0.5">
600<title>{}: {:.2}% ({} samples)</title>
601</rect>
602<text x="{}" y="{}" font-size="12" font-family="Verdana" fill="black">{}</text>
603"#,
604            x, y + depth * height, width, height,
605            color,
606            node.name, node.percentage, node.value,
607            x + 2, y + depth * height + 14,
608            if width > 50 { &node.name } else { "" }
609        ));
610
611        // Render children
612        let mut child_x = x;
613        for child in node.children.values() {
614            let child_width = if node.total_value > 0 {
615                (width as f64 * child.total_value as f64 / node.total_value as f64) as i32
616            } else {
617                0
618            };
619            if child_width > 0 {
620                self.render_svg_node(svg, child, child_x, y, child_width, depth + 1)?;
621                child_x += child_width;
622            }
623        }
624
625        Ok(())
626    }
627
628    fn get_node_color(&self, node: &FlameGraphNode) -> String {
629        match &self.config.color_scheme {
630            FlameGraphColorScheme::Hot => {
631                let intensity = (node.percentage / 100.0 * 255.0) as u8;
632                format!("rgb({}, {}, 0)", 255, 255 - intensity)
633            },
634            FlameGraphColorScheme::Cool => {
635                let intensity = (node.percentage / 100.0 * 255.0) as u8;
636                format!("rgb(0, {}, {})", intensity, 255)
637            },
638            FlameGraphColorScheme::Memory => {
639                if node.name.contains("alloc") || node.name.contains("malloc") {
640                    "#ff6b6b".to_string()
641                } else {
642                    "#4ecdc4".to_string()
643                }
644            },
645            FlameGraphColorScheme::Differential => {
646                match node.delta {
647                    Some(delta) if delta > 0 => "#ff4444".to_string(), // Red for increases
648                    Some(delta) if delta < 0 => "#44ff44".to_string(), // Green for decreases
649                    _ => "#cccccc".to_string(),                        // Gray for no change
650                }
651            },
652            FlameGraphColorScheme::Java => "#ff9800".to_string(),
653            FlameGraphColorScheme::Random => {
654                let hash = self.hash_string(&node.name);
655                format!("hsl({}, 70%, 60%)", hash % 360)
656            },
657            FlameGraphColorScheme::Custom(colors) => {
658                colors.get(&node.name).cloned().unwrap_or_else(|| "#cccccc".to_string())
659            },
660        }
661    }
662
663    fn hash_string(&self, s: &str) -> u32 {
664        let mut hash = 0u32;
665        for byte in s.bytes() {
666            hash = hash.wrapping_mul(31).wrapping_add(byte as u32);
667        }
668        hash
669    }
670
671    fn convert_to_speedscope_format(&self, root: &FlameGraphNode) -> Result<serde_json::Value> {
672        // Simplified Speedscope format conversion
673        Ok(serde_json::json!({
674            "version": "0.7.1",
675            "profiles": [{
676                "type": "sampled",
677                "name": self.config.title,
678                "unit": "nanoseconds",
679                "startValue": 0,
680                "endValue": root.total_value,
681                "samples": [],
682                "weights": []
683            }]
684        }))
685    }
686
687    fn convert_to_d3_format(&self, root: &FlameGraphNode) -> Result<serde_json::Value> {
688        Ok(serde_json::to_value(root)?)
689    }
690
691    fn get_hot_functions(&self, limit: usize) -> Vec<HotFunctionInfo> {
692        let mut functions = Vec::new();
693
694        if let Some(root) = &self.root_node {
695            self.collect_hot_functions(root, &mut functions);
696        }
697
698        functions.sort_by_key(|item| std::cmp::Reverse(item.total_time_ns));
699        functions.truncate(limit);
700        functions
701    }
702
703    fn collect_hot_functions(&self, node: &FlameGraphNode, functions: &mut Vec<HotFunctionInfo>) {
704        functions.push(HotFunctionInfo {
705            name: node.name.clone(),
706            total_time_ns: node.total_value,
707            self_time_ns: node.self_value,
708            percentage: node.percentage,
709            call_count: 1, // Simplified
710        });
711
712        for child in node.children.values() {
713            self.collect_hot_functions(child, functions);
714        }
715    }
716
717    fn get_memory_usage_stats(&self) -> MemoryUsageStats {
718        let memory_samples: Vec<usize> =
719            self.samples.iter().filter_map(|s| s.memory_usage).collect();
720
721        if memory_samples.is_empty() {
722            return MemoryUsageStats::default();
723        }
724
725        let total: usize = memory_samples.iter().sum();
726        let max = memory_samples.iter().max().copied().unwrap_or(0);
727        let min = memory_samples.iter().min().copied().unwrap_or(0);
728        let avg = total / memory_samples.len();
729
730        MemoryUsageStats {
731            peak_memory_bytes: max,
732            avg_memory_bytes: avg,
733            min_memory_bytes: min,
734            total_samples: memory_samples.len(),
735        }
736    }
737
738    fn get_gpu_kernel_stats(&self) -> GpuKernelStats {
739        let gpu_samples: Vec<&FlameGraphSample> =
740            self.samples.iter().filter(|s| s.gpu_kernel.is_some()).collect();
741
742        let total_gpu_time: u64 = gpu_samples.iter().map(|s| s.duration_ns).sum();
743        let unique_kernels: std::collections::HashSet<String> =
744            gpu_samples.iter().filter_map(|s| s.gpu_kernel.clone()).collect();
745
746        GpuKernelStats {
747            total_kernel_time_ns: total_gpu_time,
748            unique_kernels: unique_kernels.len(),
749            total_kernel_calls: gpu_samples.len(),
750        }
751    }
752
753    fn get_differential_analysis(&self) -> Option<DifferentialAnalysis> {
754        if !self.config.differential_mode || self.baseline_samples.is_none() {
755            return None;
756        }
757
758        let current_total: u64 = self.samples.iter().map(|s| s.duration_ns).sum();
759        let baseline_total: u64 =
760            self.baseline_samples.as_ref()?.iter().map(|s| s.duration_ns).sum();
761
762        let performance_change = if baseline_total > 0 {
763            ((current_total as f64 - baseline_total as f64) / baseline_total as f64) * 100.0
764        } else {
765            0.0
766        };
767
768        Some(DifferentialAnalysis {
769            baseline_samples: self.baseline_samples.as_ref()?.len(),
770            current_samples: self.samples.len(),
771            performance_change_percent: performance_change,
772            is_regression: performance_change > 5.0,
773            is_improvement: performance_change < -5.0,
774        })
775    }
776
777    fn generate_performance_insights(&self) -> Vec<String> {
778        let mut insights = Vec::new();
779
780        if let Some(root) = &self.root_node {
781            let hot_functions = self.get_hot_functions(3);
782
783            if let Some(hottest) = hot_functions.first() {
784                if hottest.percentage > 50.0 {
785                    insights.push(format!(
786                        "Function '{}' dominates execution time ({:.1}%)",
787                        hottest.name, hottest.percentage
788                    ));
789                }
790            }
791
792            let gpu_stats = self.get_gpu_kernel_stats();
793            if gpu_stats.total_kernel_calls > 0 {
794                let gpu_percentage =
795                    (gpu_stats.total_kernel_time_ns as f64 / root.total_value as f64) * 100.0;
796                insights.push(format!(
797                    "GPU kernels account for {:.1}% of execution time",
798                    gpu_percentage
799                ));
800            }
801
802            if let Some(diff) = self.get_differential_analysis() {
803                if diff.is_regression {
804                    insights.push(format!(
805                        "Performance regression detected: {:.1}% slower than baseline",
806                        diff.performance_change_percent
807                    ));
808                } else if diff.is_improvement {
809                    insights.push(format!(
810                        "Performance improvement: {:.1}% faster than baseline",
811                        -diff.performance_change_percent
812                    ));
813                }
814            }
815        }
816
817        if insights.is_empty() {
818            insights.push("No significant performance patterns detected".to_string());
819        }
820
821        insights
822    }
823}
824
825/// Hot function information
826#[derive(Debug, Clone, Serialize, Deserialize)]
827pub struct HotFunctionInfo {
828    pub name: String,
829    pub total_time_ns: u64,
830    pub self_time_ns: u64,
831    pub percentage: f64,
832    pub call_count: usize,
833}
834
835/// Memory usage statistics
836#[derive(Debug, Clone, Serialize, Deserialize, Default)]
837pub struct MemoryUsageStats {
838    pub peak_memory_bytes: usize,
839    pub avg_memory_bytes: usize,
840    pub min_memory_bytes: usize,
841    pub total_samples: usize,
842}
843
844/// GPU kernel statistics
845#[derive(Debug, Clone, Serialize, Deserialize)]
846pub struct GpuKernelStats {
847    pub total_kernel_time_ns: u64,
848    pub unique_kernels: usize,
849    pub total_kernel_calls: usize,
850}
851
852/// Differential analysis results
853#[derive(Debug, Clone, Serialize, Deserialize)]
854pub struct DifferentialAnalysis {
855    pub baseline_samples: usize,
856    pub current_samples: usize,
857    pub performance_change_percent: f64,
858    pub is_regression: bool,
859    pub is_improvement: bool,
860}
861
862/// Flame graph analysis report
863#[derive(Debug, Clone, Serialize, Deserialize)]
864pub struct FlameGraphAnalysisReport {
865    pub total_samples: usize,
866    pub total_duration_ns: u64,
867    pub unique_functions: u64,
868    pub max_stack_depth: u64,
869    pub hot_functions: Vec<HotFunctionInfo>,
870    pub memory_usage_stats: MemoryUsageStats,
871    pub gpu_kernel_stats: GpuKernelStats,
872    pub differential_analysis: Option<DifferentialAnalysis>,
873    pub performance_insights: Vec<String>,
874}
875
876/// Default configuration for flame graphs
877impl Default for FlameGraphConfig {
878    fn default() -> Self {
879        Self {
880            sampling_rate: 1000, // 1000 Hz
881            min_width: 0.01,
882            color_scheme: FlameGraphColorScheme::Hot,
883            direction: FlameGraphDirection::TopDown,
884            title: "Flame Graph".to_string(),
885            subtitle: None,
886            include_memory: true,
887            include_gpu: true,
888            differential_mode: false,
889            merge_similar_stacks: true,
890            filter_noise: true,
891            noise_threshold: 0.1, // 0.1%
892        }
893    }
894}
895
896/// Integration with main Profiler
897impl Profiler {
898    /// Create flame graph profiler with current configuration
899    pub fn create_flame_graph_profiler(&self) -> FlameGraphProfiler {
900        let config = FlameGraphConfig {
901            title: "TrustformeRS Debug Flame Graph".to_string(),
902            subtitle: Some("Performance Analysis".to_string()),
903            ..Default::default()
904        };
905        FlameGraphProfiler::new(config)
906    }
907
908    /// Start flame graph profiling
909    pub async fn start_flame_graph_profiling(&mut self) -> Result<()> {
910        // This would integrate with the main profiler's timing events
911        tracing::info!("Starting integrated flame graph profiling");
912        Ok(())
913    }
914
915    /// Export flame graph from current profiling data
916    pub async fn export_flame_graph(
917        &self,
918        format: FlameGraphExportFormat,
919        output_path: &Path,
920    ) -> Result<()> {
921        let mut flame_profiler = self.create_flame_graph_profiler();
922
923        // Convert existing events to flame graph samples
924        for event in self.get_events() {
925            match event {
926                ProfileEvent::FunctionCall {
927                    function_name,
928                    duration,
929                    ..
930                } => {
931                    let sample = FlameGraphSample {
932                        stack: vec![StackFrame {
933                            function_name: function_name.clone(),
934                            module_name: None,
935                            file_name: None,
936                            line_number: None,
937                            address: None,
938                        }],
939                        duration_ns: duration.as_nanos() as u64,
940                        timestamp: 0,
941                        thread_id: 0,
942                        cpu_id: None,
943                        memory_usage: None,
944                        gpu_kernel: None,
945                        metadata: HashMap::new(),
946                    };
947                    flame_profiler.add_sample(sample);
948                },
949                ProfileEvent::LayerExecution {
950                    layer_name,
951                    layer_type,
952                    forward_time,
953                    ..
954                } => {
955                    let sample = FlameGraphSample {
956                        stack: vec![
957                            StackFrame {
958                                function_name: "neural_network".to_string(),
959                                module_name: Some("trustformers".to_string()),
960                                file_name: None,
961                                line_number: None,
962                                address: None,
963                            },
964                            StackFrame {
965                                function_name: format!("{}::{}", layer_type, layer_name),
966                                module_name: Some("layers".to_string()),
967                                file_name: None,
968                                line_number: None,
969                                address: None,
970                            },
971                        ],
972                        duration_ns: forward_time.as_nanos() as u64,
973                        timestamp: 0,
974                        thread_id: 0,
975                        cpu_id: None,
976                        memory_usage: None,
977                        gpu_kernel: None,
978                        metadata: HashMap::new(),
979                    };
980                    flame_profiler.add_sample(sample);
981                },
982                _ => {}, // Handle other event types as needed
983            }
984        }
985
986        flame_profiler.build_flame_graph()?;
987        flame_profiler.export(format, output_path).await?;
988        Ok(())
989    }
990}
991
992#[cfg(test)]
993mod tests {
994    use super::*;
995
996    fn make_config() -> FlameGraphConfig {
997        FlameGraphConfig {
998            sampling_rate: 100,
999            min_width: 0.1,
1000            color_scheme: FlameGraphColorScheme::Hot,
1001            direction: FlameGraphDirection::TopDown,
1002            title: "Test Profile".to_string(),
1003            subtitle: None,
1004            include_memory: false,
1005            include_gpu: false,
1006            differential_mode: false,
1007            merge_similar_stacks: false,
1008            filter_noise: false,
1009            noise_threshold: 0.01,
1010        }
1011    }
1012
1013    fn make_sample(func_name: &str, duration_ns: u64) -> FlameGraphSample {
1014        FlameGraphSample {
1015            stack: vec![StackFrame {
1016                function_name: func_name.to_string(),
1017                module_name: None,
1018                file_name: None,
1019                line_number: None,
1020                address: None,
1021            }],
1022            duration_ns,
1023            timestamp: 1000,
1024            thread_id: 1,
1025            cpu_id: Some(0),
1026            memory_usage: None,
1027            gpu_kernel: None,
1028            metadata: HashMap::new(),
1029        }
1030    }
1031
1032    fn make_nested_sample(funcs: &[&str], duration_ns: u64) -> FlameGraphSample {
1033        let stack = funcs
1034            .iter()
1035            .map(|name| StackFrame {
1036                function_name: name.to_string(),
1037                module_name: None,
1038                file_name: None,
1039                line_number: None,
1040                address: None,
1041            })
1042            .collect();
1043        FlameGraphSample {
1044            stack,
1045            duration_ns,
1046            timestamp: 1000,
1047            thread_id: 1,
1048            cpu_id: None,
1049            memory_usage: None,
1050            gpu_kernel: None,
1051            metadata: HashMap::new(),
1052        }
1053    }
1054
1055    #[test]
1056    fn test_flame_graph_profiler_creation() {
1057        let profiler = FlameGraphProfiler::new(make_config());
1058        assert!(profiler.samples.is_empty());
1059        assert!(profiler.root_node.is_none());
1060    }
1061
1062    #[test]
1063    fn test_start_sampling() {
1064        let mut profiler = FlameGraphProfiler::new(make_config());
1065        let result = profiler.start_sampling();
1066        assert!(result.is_ok());
1067        assert!(profiler.sampling_timer.is_some());
1068    }
1069
1070    #[test]
1071    fn test_add_sample() {
1072        let mut profiler = FlameGraphProfiler::new(make_config());
1073        profiler.add_sample(make_sample("main", 1000));
1074        assert_eq!(profiler.samples.len(), 1);
1075    }
1076
1077    #[test]
1078    fn test_add_multiple_samples() {
1079        let mut profiler = FlameGraphProfiler::new(make_config());
1080        for i in 0..10 {
1081            profiler.add_sample(make_sample(&format!("func_{}", i), (i + 1) * 100));
1082        }
1083        assert_eq!(profiler.samples.len(), 10);
1084    }
1085
1086    #[test]
1087    fn test_sample_gpu_kernel() {
1088        let mut profiler = FlameGraphProfiler::new(make_config());
1089        profiler.sample_gpu_kernel("matmul_kernel", 5000);
1090        assert_eq!(profiler.samples.len(), 1);
1091        assert_eq!(
1092            profiler.samples[0].gpu_kernel,
1093            Some("matmul_kernel".to_string())
1094        );
1095    }
1096
1097    #[test]
1098    fn test_build_flame_graph_empty() {
1099        let mut profiler = FlameGraphProfiler::new(make_config());
1100        let result = profiler.build_flame_graph();
1101        assert!(result.is_err());
1102    }
1103
1104    #[test]
1105    fn test_build_flame_graph_single_sample() {
1106        let mut profiler = FlameGraphProfiler::new(make_config());
1107        profiler.add_sample(make_sample("main", 1000));
1108        let result = profiler.build_flame_graph();
1109        assert!(result.is_ok());
1110        assert!(profiler.root_node.is_some());
1111    }
1112
1113    #[test]
1114    fn test_build_flame_graph_nested_stacks() {
1115        let mut profiler = FlameGraphProfiler::new(make_config());
1116        profiler.add_sample(make_nested_sample(&["main", "compute", "matmul"], 5000));
1117        profiler.add_sample(make_nested_sample(&["main", "compute", "softmax"], 3000));
1118        profiler.add_sample(make_nested_sample(&["main", "io", "load_data"], 2000));
1119        let result = profiler.build_flame_graph();
1120        assert!(result.is_ok());
1121        let root = profiler.root_node.as_ref().expect("root should exist");
1122        assert!(root.children.contains_key("main"));
1123    }
1124
1125    #[test]
1126    fn test_set_baseline() {
1127        let mut profiler = FlameGraphProfiler::new(make_config());
1128        profiler.add_sample(make_sample("func_a", 100));
1129        profiler.add_sample(make_sample("func_b", 200));
1130        profiler.set_baseline();
1131        assert!(profiler.baseline_samples.is_some());
1132        let baseline = profiler.baseline_samples.as_ref().expect("baseline should exist");
1133        assert_eq!(baseline.len(), 2);
1134    }
1135
1136    #[test]
1137    fn test_performance_counters() {
1138        let mut profiler = FlameGraphProfiler::new(make_config());
1139        let start_result = profiler.start_sampling();
1140        assert!(start_result.is_ok());
1141        profiler.add_sample(make_nested_sample(&["a", "b", "c"], 100));
1142        profiler.add_sample(make_nested_sample(&["a", "d"], 200));
1143        let counter = profiler.performance_counters.get("samples_collected");
1144        assert_eq!(counter, Some(&2));
1145        let depth = profiler.performance_counters.get("stack_depth_max");
1146        assert_eq!(depth, Some(&3));
1147    }
1148
1149    #[test]
1150    fn test_stop_sampling_with_data() {
1151        let mut profiler = FlameGraphProfiler::new(make_config());
1152        let _ = profiler.start_sampling();
1153        profiler.add_sample(make_sample("test_func", 500));
1154        let result = profiler.stop_sampling();
1155        assert!(result.is_ok());
1156        assert!(profiler.sampling_timer.is_none());
1157        assert!(profiler.root_node.is_some());
1158    }
1159
1160    #[test]
1161    fn test_flame_graph_node_structure() {
1162        let mut profiler = FlameGraphProfiler::new(make_config());
1163        profiler.add_sample(make_nested_sample(&["root_fn", "child_fn"], 1000));
1164        profiler.add_sample(make_nested_sample(&["root_fn", "child_fn"], 2000));
1165        let _ = profiler.build_flame_graph();
1166        let root = profiler.root_node.as_ref().expect("root should exist");
1167        assert_eq!(root.name, "root");
1168    }
1169
1170    #[test]
1171    fn test_stack_frame_equality() {
1172        let frame1 = StackFrame {
1173            function_name: "test".to_string(),
1174            module_name: None,
1175            file_name: None,
1176            line_number: None,
1177            address: None,
1178        };
1179        let frame2 = StackFrame {
1180            function_name: "test".to_string(),
1181            module_name: None,
1182            file_name: None,
1183            line_number: None,
1184            address: None,
1185        };
1186        assert_eq!(frame1, frame2);
1187    }
1188
1189    #[test]
1190    fn test_flame_graph_config_differential_mode() {
1191        let mut config = make_config();
1192        config.differential_mode = true;
1193        let mut profiler = FlameGraphProfiler::new(config);
1194        profiler.add_sample(make_sample("func_a", 100));
1195        profiler.set_baseline();
1196        profiler.add_sample(make_sample("func_a", 200));
1197        let result = profiler.build_flame_graph();
1198        assert!(result.is_ok());
1199    }
1200
1201    #[test]
1202    fn test_flame_graph_config_noise_filter() {
1203        let mut config = make_config();
1204        config.filter_noise = true;
1205        config.noise_threshold = 0.05;
1206        let mut profiler = FlameGraphProfiler::new(config);
1207        profiler.add_sample(make_nested_sample(&["main", "big_func"], 10000));
1208        profiler.add_sample(make_nested_sample(&["main", "tiny_func"], 1));
1209        let result = profiler.build_flame_graph();
1210        assert!(result.is_ok());
1211    }
1212
1213    #[test]
1214    fn test_sample_current_stack() {
1215        let mut profiler = FlameGraphProfiler::new(make_config());
1216        let result = profiler.sample_current_stack(500);
1217        assert!(result.is_ok());
1218        assert_eq!(profiler.samples.len(), 1);
1219    }
1220
1221    #[tokio::test]
1222    async fn test_export_no_graph() {
1223        let profiler = FlameGraphProfiler::new(make_config());
1224        let tmp = std::env::temp_dir().join("test_flamegraph_export.json");
1225        let result = profiler.export(FlameGraphExportFormat::JSON, &tmp).await;
1226        assert!(result.is_err());
1227    }
1228
1229    #[tokio::test]
1230    async fn test_export_json() {
1231        let mut profiler = FlameGraphProfiler::new(make_config());
1232        profiler.add_sample(make_sample("test", 100));
1233        let _ = profiler.build_flame_graph();
1234        let tmp = std::env::temp_dir().join("test_flamegraph_export_ok.json");
1235        let result = profiler.export(FlameGraphExportFormat::JSON, &tmp).await;
1236        assert!(result.is_ok());
1237        let _ = tokio::fs::remove_file(&tmp).await;
1238    }
1239
1240    #[test]
1241    fn test_flame_graph_color_schemes() {
1242        let schemes = [
1243            FlameGraphColorScheme::Hot,
1244            FlameGraphColorScheme::Cool,
1245            FlameGraphColorScheme::Java,
1246            FlameGraphColorScheme::Memory,
1247            FlameGraphColorScheme::Differential,
1248            FlameGraphColorScheme::Random,
1249            FlameGraphColorScheme::Custom(HashMap::new()),
1250        ];
1251        assert_eq!(schemes.len(), 7);
1252    }
1253
1254    #[test]
1255    fn test_flame_graph_directions() {
1256        let directions = [FlameGraphDirection::TopDown, FlameGraphDirection::BottomUp];
1257        assert_eq!(directions.len(), 2);
1258    }
1259
1260    #[test]
1261    fn test_flame_graph_export_formats() {
1262        let formats = [
1263            FlameGraphExportFormat::SVG,
1264            FlameGraphExportFormat::InteractiveHTML,
1265            FlameGraphExportFormat::JSON,
1266            FlameGraphExportFormat::Speedscope,
1267            FlameGraphExportFormat::D3,
1268            FlameGraphExportFormat::Folded,
1269        ];
1270        assert_eq!(formats.len(), 6);
1271    }
1272
1273    #[test]
1274    fn test_flame_graph_config_with_subtitle() {
1275        let mut config = make_config();
1276        config.subtitle = Some("Test Subtitle".to_string());
1277        let profiler = FlameGraphProfiler::new(config);
1278        assert!(profiler.samples.is_empty());
1279    }
1280
1281    #[test]
1282    fn test_flame_graph_config_with_gpu_and_memory() {
1283        let mut config = make_config();
1284        config.include_gpu = true;
1285        config.include_memory = true;
1286        let profiler = FlameGraphProfiler::new(config);
1287        assert!(profiler.samples.is_empty());
1288    }
1289
1290    #[test]
1291    fn test_flame_graph_node_creation() {
1292        let node = FlameGraphNode {
1293            name: "test_func".to_string(),
1294            value: 1000,
1295            delta: None,
1296            children: HashMap::new(),
1297            total_value: 1000,
1298            self_value: 500,
1299            percentage: 50.0,
1300            color: Some("#ff0000".to_string()),
1301            metadata: HashMap::new(),
1302        };
1303        assert_eq!(node.name, "test_func");
1304        assert_eq!(node.value, 1000);
1305        assert!((node.percentage - 50.0).abs() < f64::EPSILON);
1306    }
1307
1308    #[test]
1309    fn test_stack_frame_with_all_fields() {
1310        let frame = StackFrame {
1311            function_name: "my_func".to_string(),
1312            module_name: Some("my_module".to_string()),
1313            file_name: Some("src/lib.rs".to_string()),
1314            line_number: Some(42),
1315            address: Some(0x12345678),
1316        };
1317        assert_eq!(frame.function_name, "my_func");
1318        assert_eq!(frame.module_name, Some("my_module".to_string()));
1319        assert_eq!(frame.line_number, Some(42));
1320    }
1321}