1use depyler_hir::hir::{HirExpr, HirFunction, HirProgram, HirStmt};
8use colored::Colorize;
9use std::collections::HashMap;
10
11pub struct Profiler {
13 config: ProfileConfig,
15 metrics: HashMap<String, FunctionMetrics>,
17 hot_paths: Vec<HotPath>,
19 predictions: Vec<PerformancePrediction>,
21}
22
23#[derive(Debug, Clone)]
24pub struct ProfileConfig {
25 pub count_instructions: bool,
27 pub track_allocations: bool,
29 pub detect_hot_paths: bool,
31 pub hot_path_threshold: usize,
33 pub generate_flamegraph: bool,
35 pub include_hints: bool,
37}
38
39impl Default for ProfileConfig {
40 fn default() -> Self {
41 Self {
42 count_instructions: true,
43 track_allocations: true,
44 detect_hot_paths: true,
45 hot_path_threshold: 100,
46 generate_flamegraph: false,
47 include_hints: true,
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
53pub struct FunctionMetrics {
54 pub name: String,
56 pub instruction_count: usize,
58 pub allocation_count: usize,
60 pub estimated_time: f64,
62 pub call_count: usize,
64 pub time_percentage: f64,
66 pub is_hot: bool,
68}
69
70#[derive(Debug, Clone)]
71pub struct HotPath {
72 pub call_chain: Vec<String>,
74 pub time_percentage: f64,
76 pub loop_depth: usize,
78 pub has_io: bool,
80}
81
82#[derive(Debug, Clone)]
83pub struct PerformancePrediction {
84 pub category: PredictionCategory,
86 pub confidence: f64,
88 pub speedup_factor: f64,
90 pub explanation: String,
92 pub functions: Vec<String>,
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum PredictionCategory {
98 TypeSystemOptimization,
100 ZeroCostAbstraction,
102 MemoryLayoutOptimization,
104 IteratorOptimization,
106 StringOptimization,
108 ParallelizationOpportunity,
110}
111
112#[derive(Debug, Clone)]
114pub struct ProfilingAnnotation {
115 pub kind: AnnotationKind,
117 pub target: String,
119 pub value: String,
121}
122
123#[derive(Debug, Clone)]
124pub enum AnnotationKind {
125 TimingProbe,
127 AllocationCounter,
129 HotPathMarker,
131 PerformanceHint,
133}
134
135impl Profiler {
136 pub fn new(config: ProfileConfig) -> Self {
137 Self {
138 config,
139 metrics: HashMap::new(),
140 hot_paths: Vec::new(),
141 predictions: Vec::new(),
142 }
143 }
144
145 pub fn analyze_program(&mut self, program: &HirProgram) -> ProfilingReport {
147 self.metrics.clear();
149 self.hot_paths.clear();
150 self.predictions.clear();
151
152 let mut total_instructions = 0;
154 let mut total_allocations = 0;
155
156 for func in &program.functions {
157 let metrics = self.analyze_function(func);
158 total_instructions += metrics.instruction_count;
159 total_allocations += metrics.allocation_count;
160 self.metrics.insert(func.name.clone(), metrics);
161 }
162
163 for metrics in self.metrics.values_mut() {
165 metrics.time_percentage = (metrics.estimated_time / total_instructions as f64) * 100.0;
166 metrics.is_hot = metrics.time_percentage > 10.0;
167 }
168
169 if self.config.detect_hot_paths {
171 self.detect_hot_paths(program);
172 }
173
174 self.generate_predictions(program);
176
177 ProfilingReport {
179 metrics: self.metrics.clone(),
180 hot_paths: self.hot_paths.clone(),
181 predictions: self.predictions.clone(),
182 total_instructions,
183 total_allocations,
184 annotations: self.generate_annotations(),
185 }
186 }
187
188 fn analyze_function(&self, func: &HirFunction) -> FunctionMetrics {
189 let mut instruction_count = 0;
190 let mut allocation_count = 0;
191 let mut loop_multiplier = 1.0;
192
193 for stmt in &func.body {
195 let (inst, alloc, loop_factor) = self.analyze_stmt(stmt, 1);
196 instruction_count += inst;
197 allocation_count += alloc;
198 loop_multiplier *= loop_factor;
199 }
200
201 let estimated_time = instruction_count as f64 * loop_multiplier;
203
204 FunctionMetrics {
205 name: func.name.clone(),
206 instruction_count,
207 allocation_count,
208 estimated_time,
209 call_count: 0, time_percentage: 0.0, is_hot: false, }
213 }
214
215 fn analyze_stmt(&self, stmt: &HirStmt, loop_depth: usize) -> (usize, usize, f64) {
216 match stmt {
217 HirStmt::Assign { value, .. } => self.analyze_assign(value),
218 HirStmt::Expr(expr) => self.analyze_expr_stmt(expr),
219 HirStmt::Return(Some(expr)) => self.analyze_return_with_value(expr),
220 HirStmt::Return(None) => (1, 0, 1.0),
221 HirStmt::If {
222 condition,
223 then_body,
224 else_body,
225 } => self.analyze_if(condition, then_body, else_body.as_deref(), loop_depth),
226 HirStmt::While { condition, body } => self.analyze_while(condition, body, loop_depth),
227 HirStmt::For { iter, body, .. } => self.analyze_for(iter, body, loop_depth),
228 _ => (1, 0, 1.0),
229 }
230 }
231
232 fn analyze_assign(&self, value: &HirExpr) -> (usize, usize, f64) {
233 let (inst, alloc) = self.analyze_expr(value);
234 (inst + 1, alloc, 1.0)
235 }
236
237 fn analyze_expr_stmt(&self, expr: &HirExpr) -> (usize, usize, f64) {
238 let (inst, alloc) = self.analyze_expr(expr);
239 (inst, alloc, 1.0)
240 }
241
242 fn analyze_return_with_value(&self, expr: &HirExpr) -> (usize, usize, f64) {
243 let (inst, alloc) = self.analyze_expr(expr);
244 (inst + 1, alloc, 1.0)
245 }
246
247 fn analyze_if(
248 &self,
249 condition: &HirExpr,
250 then_body: &[HirStmt],
251 else_body: Option<&[HirStmt]>,
252 loop_depth: usize,
253 ) -> (usize, usize, f64) {
254 let (cond_inst, cond_alloc) = self.analyze_expr(condition);
255 let mut total_inst = cond_inst + 1;
256 let mut total_alloc = cond_alloc;
257
258 for stmt in then_body {
259 let (inst, alloc, _) = self.analyze_stmt(stmt, loop_depth);
260 total_inst += inst;
261 total_alloc += alloc;
262 }
263
264 if let Some(else_stmts) = else_body {
265 for stmt in else_stmts {
266 let (inst, alloc, _) = self.analyze_stmt(stmt, loop_depth);
267 total_inst += inst / 2;
268 total_alloc += alloc / 2;
269 }
270 }
271
272 (total_inst, total_alloc, 1.0)
273 }
274
275 fn analyze_while(
276 &self,
277 condition: &HirExpr,
278 body: &[HirStmt],
279 loop_depth: usize,
280 ) -> (usize, usize, f64) {
281 let (cond_inst, cond_alloc) = self.analyze_expr(condition);
282 let (body_inst, body_alloc) = self.analyze_body(body, loop_depth + 1);
283 let loop_factor = 10.0_f64.powi(loop_depth as i32);
284 (
285 cond_inst + (body_inst * 10),
286 cond_alloc + (body_alloc * 10),
287 loop_factor,
288 )
289 }
290
291 fn analyze_for(
292 &self,
293 iter: &HirExpr,
294 body: &[HirStmt],
295 loop_depth: usize,
296 ) -> (usize, usize, f64) {
297 let (iter_inst, iter_alloc) = self.analyze_expr(iter);
298 let (body_inst, body_alloc) = self.analyze_body(body, loop_depth + 1);
299 let loop_factor = 10.0_f64.powi(loop_depth as i32);
300 (
301 iter_inst + (body_inst * 10),
302 iter_alloc + (body_alloc * 10),
303 loop_factor,
304 )
305 }
306
307 fn analyze_body(&self, body: &[HirStmt], loop_depth: usize) -> (usize, usize) {
308 let mut body_inst = 0;
309 let mut body_alloc = 0;
310 for stmt in body {
311 let (inst, alloc, _) = self.analyze_stmt(stmt, loop_depth);
312 body_inst += inst;
313 body_alloc += alloc;
314 }
315 (body_inst, body_alloc)
316 }
317
318 fn analyze_expr(&self, expr: &HirExpr) -> (usize, usize) {
319 analyze_expr_inner(expr)
320 }
321
322 fn detect_hot_paths(&mut self, _program: &HirProgram) {
323 let hot_functions: Vec<_> = self
325 .metrics
326 .values()
327 .filter(|m| m.is_hot)
328 .map(|m| m.name.clone())
329 .collect();
330
331 for func_name in hot_functions {
333 if let Some(metrics) = self.metrics.get(&func_name) {
334 self.hot_paths.push(HotPath {
335 call_chain: vec![func_name],
336 time_percentage: metrics.time_percentage,
337 loop_depth: 0, has_io: false, });
340 }
341 }
342 }
343
344 fn generate_predictions(&mut self, program: &HirProgram) {
345 let type_checks_removed = self.count_type_checks(program);
347 if type_checks_removed > 0 {
348 self.predictions.push(PerformancePrediction {
349 category: PredictionCategory::TypeSystemOptimization,
350 confidence: 0.9,
351 speedup_factor: 1.0 + (type_checks_removed as f64 * 0.1),
352 explanation: format!(
353 "Rust's type system eliminates {} runtime type checks",
354 type_checks_removed
355 ),
356 functions: vec![],
357 });
358 }
359
360 let iterator_opportunities = self.count_iterator_opportunities(program);
362 if iterator_opportunities > 0 {
363 self.predictions.push(PerformancePrediction {
364 category: PredictionCategory::IteratorOptimization,
365 confidence: 0.8,
366 speedup_factor: 1.2,
367 explanation: "Rust's iterator fusion can optimize chained operations".to_string(),
368 functions: vec![],
369 });
370 }
371
372 self.predictions.push(PerformancePrediction {
374 category: PredictionCategory::MemoryLayoutOptimization,
375 confidence: 0.7,
376 speedup_factor: 1.3,
377 explanation: "Rust's memory layout is more cache-friendly than Python".to_string(),
378 functions: vec![],
379 });
380 }
381
382 fn count_type_checks(&self, program: &HirProgram) -> usize {
383 let mut count = 0;
384 for func in &program.functions {
385 for stmt in &func.body {
386 count += self.count_type_checks_in_stmt(stmt);
387 }
388 }
389 count
390 }
391
392 fn count_type_checks_in_stmt(&self, stmt: &HirStmt) -> usize {
393 match stmt {
394 HirStmt::If {
395 condition,
396 then_body,
397 else_body,
398 } => {
399 let mut count = 0;
400 if self.is_type_check_expr(condition) {
401 count += 1;
402 }
403 for s in then_body {
404 count += self.count_type_checks_in_stmt(s);
405 }
406 if let Some(else_stmts) = else_body {
407 for s in else_stmts {
408 count += self.count_type_checks_in_stmt(s);
409 }
410 }
411 count
412 }
413 _ => 0,
414 }
415 }
416
417 fn is_type_check_expr(&self, expr: &HirExpr) -> bool {
418 if let HirExpr::Call { func, .. } = expr {
419 func == "isinstance" || func == "type"
420 } else {
421 false
422 }
423 }
424
425 fn count_iterator_opportunities(&self, program: &HirProgram) -> usize {
426 let mut count = 0;
427 for func in &program.functions {
428 for stmt in &func.body {
429 if matches!(stmt, HirStmt::For { .. }) {
430 count += 1;
431 }
432 }
433 }
434 count
435 }
436
437 fn generate_annotations(&self) -> Vec<ProfilingAnnotation> {
438 let mut annotations = Vec::new();
439
440 for (name, metrics) in &self.metrics {
442 if metrics.is_hot {
443 annotations.push(ProfilingAnnotation {
444 kind: AnnotationKind::TimingProbe,
445 target: name.clone(),
446 value: format!("hot_function_{}", name),
447 });
448 }
449 }
450
451 for (name, metrics) in &self.metrics {
453 if metrics.allocation_count > 10 {
454 annotations.push(ProfilingAnnotation {
455 kind: AnnotationKind::AllocationCounter,
456 target: name.clone(),
457 value: format!("alloc_count_{}", metrics.allocation_count),
458 });
459 }
460 }
461
462 annotations
463 }
464}
465
466#[derive(Debug, Clone)]
468pub struct ProfilingReport {
469 pub metrics: HashMap<String, FunctionMetrics>,
471 pub hot_paths: Vec<HotPath>,
473 pub predictions: Vec<PerformancePrediction>,
475 pub total_instructions: usize,
477 pub total_allocations: usize,
479 pub annotations: Vec<ProfilingAnnotation>,
481}
482
483impl ProfilingReport {
484 pub fn format_report(&self) -> String {
486 let mut output = String::new();
487 self.format_header(&mut output);
488 self.format_summary(&mut output);
489 self.format_hot_paths(&mut output);
490 self.format_function_metrics(&mut output);
491 self.format_predictions(&mut output);
492 self.format_overall_speedup(&mut output);
493 output
494 }
495
496 fn format_header(&self, output: &mut String) {
497 output.push_str(&format!("\n{}\n", "Profiling Report".bold().blue()));
498 output.push_str(&format!("{}\n\n", "═".repeat(50)));
499 }
500
501 fn format_summary(&self, output: &mut String) {
502 output.push_str(&format!("{}\n", "Summary".bold()));
503 output.push_str(&format!(
504 " Total estimated instructions: {}\n",
505 self.total_instructions.to_string().yellow()
506 ));
507 output.push_str(&format!(
508 " Total estimated allocations: {}\n",
509 self.total_allocations.to_string().yellow()
510 ));
511 output.push_str(&format!(
512 " Functions analyzed: {}\n\n",
513 self.metrics.len().to_string().yellow()
514 ));
515 }
516
517 fn format_hot_paths(&self, output: &mut String) {
518 if self.hot_paths.is_empty() {
519 return;
520 }
521 output.push_str(&format!("{}\n", "Hot Paths".bold().red()));
522 for (idx, path) in self.hot_paths.iter().enumerate() {
523 output.push_str(&format!(
524 " [{}] {} ({:.1}% of execution time)\n",
525 idx + 1,
526 path.call_chain.join(" → "),
527 path.time_percentage
528 ));
529 }
530 output.push('\n');
531 }
532
533 fn format_function_metrics(&self, output: &mut String) {
534 output.push_str(&format!("{}\n", "Function Metrics".bold()));
535 let mut sorted_metrics: Vec<_> = self.metrics.values().collect();
536 sorted_metrics.sort_by(|a, b| {
537 b.time_percentage
538 .partial_cmp(&a.time_percentage)
539 .unwrap_or(std::cmp::Ordering::Equal)
540 });
541
542 for metrics in sorted_metrics.iter().take(10) {
543 let hot_marker = if metrics.is_hot { "🔥" } else { " " };
544 output.push_str(&format!(
545 "{} {:<30} {:>6.1}% time | {:>6} inst | {:>4} alloc\n",
546 hot_marker,
547 metrics.name,
548 metrics.time_percentage,
549 metrics.instruction_count,
550 metrics.allocation_count
551 ));
552 }
553 output.push('\n');
554 }
555
556 fn format_predictions(&self, output: &mut String) {
557 if self.predictions.is_empty() {
558 return;
559 }
560 output.push_str(&format!("{}\n", "Performance Predictions".bold().green()));
561 for pred in &self.predictions {
562 output.push_str(&format!(
563 " • {} ({}x speedup, {:.0}% confidence)\n",
564 pred.explanation,
565 format!("{:.1}", pred.speedup_factor).green(),
566 pred.confidence * 100.0
567 ));
568 }
569 output.push('\n');
570 }
571
572 fn format_overall_speedup(&self, output: &mut String) {
573 let total_speedup: f64 = self.predictions.iter().map(|p| p.speedup_factor).product();
574 if total_speedup > 1.0 {
575 output.push_str(&format!(
576 "{} Estimated overall speedup: {}x\n",
577 "🚀".green(),
578 format!("{:.1}", total_speedup).bold().green()
579 ));
580 }
581 }
582
583 pub fn generate_flamegraph_data(&self) -> String {
585 let mut lines = Vec::new();
586
587 for (func_name, metrics) in &self.metrics {
588 let sample_count = (metrics.time_percentage * 100.0) as usize;
590 if sample_count > 0 {
591 lines.push(format!("{} {}", func_name, sample_count));
592 }
593 }
594
595 lines.join("\n")
596 }
597
598 pub fn generate_perf_annotations(&self) -> String {
600 let annotations: Vec<String> = self
601 .annotations
602 .iter()
603 .map(|annotation| self.format_annotation(annotation))
604 .collect();
605 annotations.join("\n")
606 }
607
608 fn format_annotation(&self, annotation: &ProfilingAnnotation) -> String {
609 match annotation.kind {
610 AnnotationKind::TimingProbe => self.format_timing_probe(&annotation.target),
611 AnnotationKind::AllocationCounter => {
612 self.format_allocation_counter(&annotation.target, &annotation.value)
613 }
614 AnnotationKind::HotPathMarker => self.format_hot_path_marker(&annotation.target),
615 AnnotationKind::PerformanceHint => {
616 self.format_performance_hint(&annotation.target, &annotation.value)
617 }
618 }
619 }
620
621 fn format_timing_probe(&self, target: &str) -> String {
622 format!("# @probe {}: timing probe", target)
623 }
624
625 fn format_allocation_counter(&self, target: &str, value: &str) -> String {
626 format!("# @probe {}: allocation counter = {}", target, value)
627 }
628
629 fn format_hot_path_marker(&self, target: &str) -> String {
630 format!("# @hot {}: hot path marker", target)
631 }
632
633 fn format_performance_hint(&self, target: &str, value: &str) -> String {
634 format!("# @hint {}: {}", target, value)
635 }
636}
637
638fn analyze_expr_inner(expr: &HirExpr) -> (usize, usize) {
639 match expr {
640 HirExpr::Literal(_) => (1, 0),
641 HirExpr::Var(_) => (1, 0),
642 HirExpr::Binary { left, right, .. } => analyze_binary_expr(left, right),
643 HirExpr::Call { args, .. } => analyze_call_expr(args),
644 HirExpr::List(items) => analyze_list_expr(items),
645 HirExpr::Dict(pairs) => analyze_dict_expr(pairs),
646 _ => (1, 0),
647 }
648}
649
650fn analyze_binary_expr(left: &HirExpr, right: &HirExpr) -> (usize, usize) {
651 let (l_inst, l_alloc) = analyze_expr_inner(left);
652 let (r_inst, r_alloc) = analyze_expr_inner(right);
653 (l_inst + r_inst + 1, l_alloc + r_alloc)
654}
655
656fn analyze_call_expr(args: &[HirExpr]) -> (usize, usize) {
657 let mut total_inst = 10;
658 let mut total_alloc = 0;
659 for arg in args {
660 let (inst, alloc) = analyze_expr_inner(arg);
661 total_inst += inst;
662 total_alloc += alloc;
663 }
664 (total_inst, total_alloc)
665}
666
667fn analyze_list_expr(items: &[HirExpr]) -> (usize, usize) {
668 let mut total_inst = 1;
669 let total_alloc = 1;
670 for item in items {
671 let (inst, _) = analyze_expr_inner(item);
672 total_inst += inst;
673 }
674 (total_inst, total_alloc)
675}
676
677fn analyze_dict_expr(pairs: &[(HirExpr, HirExpr)]) -> (usize, usize) {
678 let mut total_inst = 1;
679 let total_alloc = 1;
680 for (k, v) in pairs {
681 let (k_inst, _) = analyze_expr_inner(k);
682 let (v_inst, _) = analyze_expr_inner(v);
683 total_inst += k_inst + v_inst + 2;
684 }
685 (total_inst, total_alloc)
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691 use depyler_hir::hir::*;
692 use smallvec::smallvec;
693
694 fn create_test_function(name: &str, body: Vec<HirStmt>) -> HirFunction {
695 HirFunction {
696 name: name.to_string(),
697 params: smallvec![],
698 ret_type: Type::Unknown,
699 body,
700 properties: FunctionProperties::default(),
701 annotations: Default::default(),
702 docstring: None,
703 }
704 }
705
706 #[test]
709 fn test_profile_config_default() {
710 let config = ProfileConfig::default();
711 assert!(config.count_instructions);
712 assert!(config.track_allocations);
713 assert!(config.detect_hot_paths);
714 assert_eq!(config.hot_path_threshold, 100);
715 assert!(!config.generate_flamegraph);
716 assert!(config.include_hints);
717 }
718
719 #[test]
720 fn test_profile_config_clone() {
721 let config = ProfileConfig {
722 count_instructions: false,
723 hot_path_threshold: 50,
724 ..Default::default()
725 };
726 let cloned = config.clone();
727 assert!(!cloned.count_instructions);
728 assert_eq!(cloned.hot_path_threshold, 50);
729 }
730
731 #[test]
732 fn test_profile_config_debug() {
733 let config = ProfileConfig::default();
734 let debug = format!("{:?}", config);
735 assert!(debug.contains("ProfileConfig"));
736 }
737
738 #[test]
741 fn test_function_metrics_new() {
742 let metrics = FunctionMetrics {
743 name: "test_func".to_string(),
744 instruction_count: 100,
745 allocation_count: 5,
746 estimated_time: 150.0,
747 call_count: 10,
748 time_percentage: 25.0,
749 is_hot: true,
750 };
751 assert_eq!(metrics.name, "test_func");
752 assert!(metrics.is_hot);
753 }
754
755 #[test]
756 fn test_function_metrics_clone() {
757 let metrics = FunctionMetrics {
758 name: "clone_test".to_string(),
759 instruction_count: 50,
760 allocation_count: 2,
761 estimated_time: 75.0,
762 call_count: 5,
763 time_percentage: 10.0,
764 is_hot: false,
765 };
766 let cloned = metrics.clone();
767 assert_eq!(metrics.name, cloned.name);
768 }
769
770 #[test]
771 fn test_function_metrics_debug() {
772 let metrics = FunctionMetrics {
773 name: "debug".to_string(),
774 instruction_count: 0,
775 allocation_count: 0,
776 estimated_time: 0.0,
777 call_count: 0,
778 time_percentage: 0.0,
779 is_hot: false,
780 };
781 let debug = format!("{:?}", metrics);
782 assert!(debug.contains("FunctionMetrics"));
783 }
784
785 #[test]
788 fn test_hot_path_new() {
789 let path = HotPath {
790 call_chain: vec!["main".to_string(), "process".to_string()],
791 time_percentage: 45.0,
792 loop_depth: 2,
793 has_io: true,
794 };
795 assert_eq!(path.call_chain.len(), 2);
796 assert!(path.has_io);
797 }
798
799 #[test]
800 fn test_hot_path_clone() {
801 let path = HotPath {
802 call_chain: vec!["func".to_string()],
803 time_percentage: 30.0,
804 loop_depth: 1,
805 has_io: false,
806 };
807 let cloned = path.clone();
808 assert_eq!(path.time_percentage, cloned.time_percentage);
809 }
810
811 #[test]
812 fn test_hot_path_debug() {
813 let path = HotPath {
814 call_chain: vec![],
815 time_percentage: 0.0,
816 loop_depth: 0,
817 has_io: false,
818 };
819 let debug = format!("{:?}", path);
820 assert!(debug.contains("HotPath"));
821 }
822
823 #[test]
826 fn test_performance_prediction_new() {
827 let pred = PerformancePrediction {
828 category: PredictionCategory::TypeSystemOptimization,
829 confidence: 0.9,
830 speedup_factor: 2.5,
831 explanation: "Type checks removed".to_string(),
832 functions: vec!["func1".to_string()],
833 };
834 assert_eq!(pred.confidence, 0.9);
835 assert_eq!(pred.speedup_factor, 2.5);
836 }
837
838 #[test]
839 fn test_performance_prediction_clone() {
840 let pred = PerformancePrediction {
841 category: PredictionCategory::IteratorOptimization,
842 confidence: 0.8,
843 speedup_factor: 1.5,
844 explanation: "test".to_string(),
845 functions: vec![],
846 };
847 let cloned = pred.clone();
848 assert_eq!(pred.category, cloned.category);
849 }
850
851 #[test]
852 fn test_performance_prediction_debug() {
853 let pred = PerformancePrediction {
854 category: PredictionCategory::StringOptimization,
855 confidence: 0.5,
856 speedup_factor: 1.0,
857 explanation: "".to_string(),
858 functions: vec![],
859 };
860 let debug = format!("{:?}", pred);
861 assert!(debug.contains("PerformancePrediction"));
862 }
863
864 #[test]
867 fn test_prediction_category_variants() {
868 let categories = [
869 PredictionCategory::TypeSystemOptimization,
870 PredictionCategory::ZeroCostAbstraction,
871 PredictionCategory::MemoryLayoutOptimization,
872 PredictionCategory::IteratorOptimization,
873 PredictionCategory::StringOptimization,
874 PredictionCategory::ParallelizationOpportunity,
875 ];
876 assert_eq!(categories.len(), 6);
877 }
878
879 #[test]
880 fn test_prediction_category_eq() {
881 assert_eq!(
882 PredictionCategory::TypeSystemOptimization,
883 PredictionCategory::TypeSystemOptimization
884 );
885 assert_ne!(
886 PredictionCategory::TypeSystemOptimization,
887 PredictionCategory::StringOptimization
888 );
889 }
890
891 #[test]
892 fn test_prediction_category_clone() {
893 let cat = PredictionCategory::MemoryLayoutOptimization;
894 let cloned = cat.clone();
895 assert_eq!(cat, cloned);
896 }
897
898 #[test]
899 fn test_prediction_category_debug() {
900 let debug = format!("{:?}", PredictionCategory::ZeroCostAbstraction);
901 assert!(debug.contains("ZeroCostAbstraction"));
902 }
903
904 #[test]
907 fn test_profiling_annotation_new() {
908 let annotation = ProfilingAnnotation {
909 kind: AnnotationKind::TimingProbe,
910 target: "func".to_string(),
911 value: "probe_1".to_string(),
912 };
913 assert_eq!(annotation.target, "func");
914 }
915
916 #[test]
917 fn test_profiling_annotation_clone() {
918 let annotation = ProfilingAnnotation {
919 kind: AnnotationKind::AllocationCounter,
920 target: "test".to_string(),
921 value: "100".to_string(),
922 };
923 let cloned = annotation.clone();
924 assert_eq!(annotation.target, cloned.target);
925 }
926
927 #[test]
928 fn test_profiling_annotation_debug() {
929 let annotation = ProfilingAnnotation {
930 kind: AnnotationKind::HotPathMarker,
931 target: "".to_string(),
932 value: "".to_string(),
933 };
934 let debug = format!("{:?}", annotation);
935 assert!(debug.contains("ProfilingAnnotation"));
936 }
937
938 #[test]
941 fn test_annotation_kind_variants() {
942 let kinds = [
943 AnnotationKind::TimingProbe,
944 AnnotationKind::AllocationCounter,
945 AnnotationKind::HotPathMarker,
946 AnnotationKind::PerformanceHint,
947 ];
948 assert_eq!(kinds.len(), 4);
949 }
950
951 #[test]
952 fn test_annotation_kind_clone() {
953 let kind = AnnotationKind::PerformanceHint;
954 let cloned = kind.clone();
955 assert!(matches!(cloned, AnnotationKind::PerformanceHint));
956 }
957
958 #[test]
959 fn test_annotation_kind_debug() {
960 let debug = format!("{:?}", AnnotationKind::TimingProbe);
961 assert!(debug.contains("TimingProbe"));
962 }
963
964 #[test]
967 fn test_profiler_creation() {
968 let config = ProfileConfig::default();
969 let profiler = Profiler::new(config);
970 assert!(profiler.metrics.is_empty());
971 assert!(profiler.hot_paths.is_empty());
972 }
973
974 #[test]
975 fn test_profiler_empty_program() {
976 let mut profiler = Profiler::new(ProfileConfig::default());
977 let program = HirProgram {
978 functions: vec![],
979 classes: vec![],
980 imports: vec![],
981 };
982 let report = profiler.analyze_program(&program);
983 assert!(report.metrics.is_empty());
984 assert_eq!(report.total_instructions, 0);
985 }
986
987 #[test]
988 fn test_simple_function_profiling() {
989 let mut profiler = Profiler::new(ProfileConfig::default());
990
991 let func = create_test_function(
992 "simple",
993 vec![
994 HirStmt::Assign {
995 target: AssignTarget::Symbol("x".to_string()),
996 value: HirExpr::Literal(Literal::Int(42)),
997 type_annotation: None,
998 },
999 HirStmt::Return(Some(HirExpr::Var("x".to_string()))),
1000 ],
1001 );
1002
1003 let program = HirProgram {
1004 functions: vec![func],
1005 classes: vec![],
1006 imports: vec![],
1007 };
1008
1009 let report = profiler.analyze_program(&program);
1010 assert_eq!(report.metrics.len(), 1);
1011 assert!(report.total_instructions > 0);
1012 }
1013
1014 #[test]
1015 fn test_loop_detection_increases_cost() {
1016 let mut profiler = Profiler::new(ProfileConfig::default());
1017
1018 let func = create_test_function(
1019 "with_loop",
1020 vec![HirStmt::For {
1021 target: AssignTarget::Symbol("i".to_string()),
1022 iter: HirExpr::Call {
1023 func: "range".to_string(),
1024 args: vec![HirExpr::Literal(Literal::Int(10))],
1025 kwargs: vec![],
1026 },
1027 body: vec![HirStmt::Expr(HirExpr::Var("i".to_string()))],
1028 }],
1029 );
1030
1031 let program = HirProgram {
1032 functions: vec![func],
1033 classes: vec![],
1034 imports: vec![],
1035 };
1036
1037 let report = profiler.analyze_program(&program);
1038 let metrics = report.metrics.get("with_loop").unwrap();
1039 assert!(metrics.instruction_count > 10); }
1041
1042 #[test]
1043 fn test_while_loop_analysis() {
1044 let mut profiler = Profiler::new(ProfileConfig::default());
1045
1046 let func = create_test_function(
1047 "with_while",
1048 vec![HirStmt::While {
1049 condition: HirExpr::Literal(Literal::Bool(true)),
1050 body: vec![HirStmt::Expr(HirExpr::Var("x".to_string()))],
1051 }],
1052 );
1053
1054 let program = HirProgram {
1055 functions: vec![func],
1056 classes: vec![],
1057 imports: vec![],
1058 };
1059
1060 let report = profiler.analyze_program(&program);
1061 assert!(report.metrics.contains_key("with_while"));
1062 }
1063
1064 #[test]
1065 fn test_if_else_analysis() {
1066 let mut profiler = Profiler::new(ProfileConfig::default());
1067
1068 let func = create_test_function(
1069 "with_if",
1070 vec![HirStmt::If {
1071 condition: HirExpr::Literal(Literal::Bool(true)),
1072 then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
1073 else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(
1074 0,
1075 ))))]),
1076 }],
1077 );
1078
1079 let program = HirProgram {
1080 functions: vec![func],
1081 classes: vec![],
1082 imports: vec![],
1083 };
1084
1085 let report = profiler.analyze_program(&program);
1086 assert!(report.metrics.contains_key("with_if"));
1087 }
1088
1089 #[test]
1090 fn test_hot_path_detection() {
1091 let mut profiler = Profiler::new(ProfileConfig {
1092 detect_hot_paths: true,
1093 ..Default::default()
1094 });
1095
1096 let func = create_test_function(
1098 "hot_function",
1099 vec![HirStmt::For {
1100 target: AssignTarget::Symbol("i".to_string()),
1101 iter: HirExpr::Call {
1102 func: "range".to_string(),
1103 args: vec![HirExpr::Literal(Literal::Int(1000))],
1104 kwargs: vec![],
1105 },
1106 body: vec![HirStmt::For {
1107 target: AssignTarget::Symbol("j".to_string()),
1108 iter: HirExpr::Call {
1109 func: "range".to_string(),
1110 args: vec![HirExpr::Literal(Literal::Int(1000))],
1111 kwargs: vec![],
1112 },
1113 body: vec![HirStmt::Expr(HirExpr::Binary {
1114 op: BinOp::Add,
1115 left: Box::new(HirExpr::Var("i".to_string())),
1116 right: Box::new(HirExpr::Var("j".to_string())),
1117 })],
1118 }],
1119 }],
1120 );
1121
1122 let program = HirProgram {
1123 functions: vec![func],
1124 classes: vec![],
1125 imports: vec![],
1126 };
1127
1128 let report = profiler.analyze_program(&program);
1129 assert!(!report.hot_paths.is_empty());
1130 }
1131
1132 #[test]
1133 fn test_hot_path_disabled() {
1134 let mut profiler = Profiler::new(ProfileConfig {
1135 detect_hot_paths: false,
1136 ..Default::default()
1137 });
1138
1139 let func = create_test_function("test", vec![]);
1140 let program = HirProgram {
1141 functions: vec![func],
1142 classes: vec![],
1143 imports: vec![],
1144 };
1145
1146 let report = profiler.analyze_program(&program);
1147 assert!(report.hot_paths.is_empty());
1148 }
1149
1150 #[test]
1151 fn test_performance_predictions() {
1152 let mut profiler = Profiler::new(ProfileConfig::default());
1153
1154 let func = create_test_function(
1156 "with_type_check",
1157 vec![HirStmt::If {
1158 condition: HirExpr::Call {
1159 func: "isinstance".to_string(),
1160 args: vec![
1161 HirExpr::Var("x".to_string()),
1162 HirExpr::Var("int".to_string()),
1163 ],
1164 kwargs: vec![],
1165 },
1166 then_body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
1167 else_body: None,
1168 }],
1169 );
1170
1171 let program = HirProgram {
1172 functions: vec![func],
1173 classes: vec![],
1174 imports: vec![],
1175 };
1176
1177 let report = profiler.analyze_program(&program);
1178 assert!(!report.predictions.is_empty());
1179
1180 assert!(report
1182 .predictions
1183 .iter()
1184 .any(|p| p.category == PredictionCategory::TypeSystemOptimization));
1185 }
1186
1187 #[test]
1188 fn test_iterator_optimization_prediction() {
1189 let mut profiler = Profiler::new(ProfileConfig::default());
1190
1191 let func = create_test_function(
1192 "with_for",
1193 vec![HirStmt::For {
1194 target: AssignTarget::Symbol("i".to_string()),
1195 iter: HirExpr::Var("items".to_string()),
1196 body: vec![HirStmt::Expr(HirExpr::Var("i".to_string()))],
1197 }],
1198 );
1199
1200 let program = HirProgram {
1201 functions: vec![func],
1202 classes: vec![],
1203 imports: vec![],
1204 };
1205
1206 let report = profiler.analyze_program(&program);
1207 assert!(report
1208 .predictions
1209 .iter()
1210 .any(|p| p.category == PredictionCategory::IteratorOptimization));
1211 }
1212
1213 #[test]
1214 fn test_memory_layout_prediction() {
1215 let mut profiler = Profiler::new(ProfileConfig::default());
1216
1217 let func = create_test_function("empty", vec![]);
1218 let program = HirProgram {
1219 functions: vec![func],
1220 classes: vec![],
1221 imports: vec![],
1222 };
1223
1224 let report = profiler.analyze_program(&program);
1225 assert!(report
1226 .predictions
1227 .iter()
1228 .any(|p| p.category == PredictionCategory::MemoryLayoutOptimization));
1229 }
1230
1231 #[test]
1234 fn test_report_formatting() {
1235 let mut profiler = Profiler::new(ProfileConfig::default());
1236
1237 let func = create_test_function(
1238 "test",
1239 vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
1240 );
1241
1242 let program = HirProgram {
1243 functions: vec![func],
1244 classes: vec![],
1245 imports: vec![],
1246 };
1247
1248 let report = profiler.analyze_program(&program);
1249 let formatted = report.format_report();
1250
1251 assert!(formatted.contains("Profiling Report"));
1252 assert!(formatted.contains("Summary"));
1253 assert!(formatted.contains("Function Metrics"));
1254 }
1255
1256 #[test]
1257 fn test_report_with_hot_paths() {
1258 let report = ProfilingReport {
1259 metrics: HashMap::new(),
1260 hot_paths: vec![HotPath {
1261 call_chain: vec!["main".to_string(), "process".to_string()],
1262 time_percentage: 50.0,
1263 loop_depth: 1,
1264 has_io: false,
1265 }],
1266 predictions: vec![],
1267 total_instructions: 100,
1268 total_allocations: 10,
1269 annotations: vec![],
1270 };
1271
1272 let formatted = report.format_report();
1273 assert!(formatted.contains("Hot Paths"));
1274 assert!(formatted.contains("main"));
1275 }
1276
1277 #[test]
1278 fn test_report_with_predictions() {
1279 let report = ProfilingReport {
1280 metrics: HashMap::new(),
1281 hot_paths: vec![],
1282 predictions: vec![PerformancePrediction {
1283 category: PredictionCategory::TypeSystemOptimization,
1284 confidence: 0.9,
1285 speedup_factor: 2.0,
1286 explanation: "Type checks removed".to_string(),
1287 functions: vec![],
1288 }],
1289 total_instructions: 100,
1290 total_allocations: 10,
1291 annotations: vec![],
1292 };
1293
1294 let formatted = report.format_report();
1295 assert!(formatted.contains("Performance Predictions"));
1296 assert!(formatted.contains("Type checks removed"));
1297 }
1298
1299 #[test]
1300 fn test_report_clone() {
1301 let report = ProfilingReport {
1302 metrics: HashMap::new(),
1303 hot_paths: vec![],
1304 predictions: vec![],
1305 total_instructions: 50,
1306 total_allocations: 5,
1307 annotations: vec![],
1308 };
1309 let cloned = report.clone();
1310 assert_eq!(report.total_instructions, cloned.total_instructions);
1311 }
1312
1313 #[test]
1314 fn test_report_debug() {
1315 let report = ProfilingReport {
1316 metrics: HashMap::new(),
1317 hot_paths: vec![],
1318 predictions: vec![],
1319 total_instructions: 0,
1320 total_allocations: 0,
1321 annotations: vec![],
1322 };
1323 let debug = format!("{:?}", report);
1324 assert!(debug.contains("ProfilingReport"));
1325 }
1326
1327 #[test]
1328 fn test_generate_flamegraph_data() {
1329 let mut metrics = HashMap::new();
1330 metrics.insert(
1331 "func1".to_string(),
1332 FunctionMetrics {
1333 name: "func1".to_string(),
1334 instruction_count: 100,
1335 allocation_count: 5,
1336 estimated_time: 100.0,
1337 call_count: 10,
1338 time_percentage: 50.0,
1339 is_hot: true,
1340 },
1341 );
1342
1343 let report = ProfilingReport {
1344 metrics,
1345 hot_paths: vec![],
1346 predictions: vec![],
1347 total_instructions: 200,
1348 total_allocations: 10,
1349 annotations: vec![],
1350 };
1351
1352 let flamegraph = report.generate_flamegraph_data();
1353 assert!(flamegraph.contains("func1"));
1354 }
1355
1356 #[test]
1357 fn test_generate_perf_annotations() {
1358 let report = ProfilingReport {
1359 metrics: HashMap::new(),
1360 hot_paths: vec![],
1361 predictions: vec![],
1362 total_instructions: 0,
1363 total_allocations: 0,
1364 annotations: vec![
1365 ProfilingAnnotation {
1366 kind: AnnotationKind::TimingProbe,
1367 target: "func1".to_string(),
1368 value: "probe".to_string(),
1369 },
1370 ProfilingAnnotation {
1371 kind: AnnotationKind::AllocationCounter,
1372 target: "func2".to_string(),
1373 value: "100".to_string(),
1374 },
1375 ],
1376 };
1377
1378 let perf = report.generate_perf_annotations();
1379 assert!(perf.contains("@probe func1"));
1380 assert!(perf.contains("allocation counter"));
1381 }
1382
1383 #[test]
1384 fn test_format_annotation_hot_path() {
1385 let report = ProfilingReport {
1386 metrics: HashMap::new(),
1387 hot_paths: vec![],
1388 predictions: vec![],
1389 total_instructions: 0,
1390 total_allocations: 0,
1391 annotations: vec![],
1392 };
1393
1394 let annotation = ProfilingAnnotation {
1395 kind: AnnotationKind::HotPathMarker,
1396 target: "hot_func".to_string(),
1397 value: "".to_string(),
1398 };
1399
1400 let formatted = report.format_annotation(&annotation);
1401 assert!(formatted.contains("@hot"));
1402 assert!(formatted.contains("hot_func"));
1403 }
1404
1405 #[test]
1406 fn test_format_annotation_performance_hint() {
1407 let report = ProfilingReport {
1408 metrics: HashMap::new(),
1409 hot_paths: vec![],
1410 predictions: vec![],
1411 total_instructions: 0,
1412 total_allocations: 0,
1413 annotations: vec![],
1414 };
1415
1416 let annotation = ProfilingAnnotation {
1417 kind: AnnotationKind::PerformanceHint,
1418 target: "func".to_string(),
1419 value: "Consider caching".to_string(),
1420 };
1421
1422 let formatted = report.format_annotation(&annotation);
1423 assert!(formatted.contains("@hint"));
1424 assert!(formatted.contains("Consider caching"));
1425 }
1426
1427 #[test]
1430 fn test_analyze_literal() {
1431 let (inst, alloc) = analyze_expr_inner(&HirExpr::Literal(Literal::Int(42)));
1432 assert_eq!(inst, 1);
1433 assert_eq!(alloc, 0);
1434 }
1435
1436 #[test]
1437 fn test_analyze_var() {
1438 let (inst, alloc) = analyze_expr_inner(&HirExpr::Var("x".to_string()));
1439 assert_eq!(inst, 1);
1440 assert_eq!(alloc, 0);
1441 }
1442
1443 #[test]
1444 fn test_analyze_binary() {
1445 let expr = HirExpr::Binary {
1446 op: BinOp::Add,
1447 left: Box::new(HirExpr::Literal(Literal::Int(1))),
1448 right: Box::new(HirExpr::Literal(Literal::Int(2))),
1449 };
1450 let (inst, alloc) = analyze_expr_inner(&expr);
1451 assert_eq!(inst, 3); assert_eq!(alloc, 0);
1453 }
1454
1455 #[test]
1456 fn test_analyze_call() {
1457 let expr = HirExpr::Call {
1458 func: "foo".to_string(),
1459 args: vec![HirExpr::Literal(Literal::Int(1))],
1460 kwargs: vec![],
1461 };
1462 let (inst, alloc) = analyze_expr_inner(&expr);
1463 assert!(inst > 10); assert_eq!(alloc, 0);
1465 }
1466
1467 #[test]
1468 fn test_analyze_list() {
1469 let expr = HirExpr::List(vec![
1470 HirExpr::Literal(Literal::Int(1)),
1471 HirExpr::Literal(Literal::Int(2)),
1472 ]);
1473 let (inst, alloc) = analyze_expr_inner(&expr);
1474 assert!(inst >= 3); assert_eq!(alloc, 1); }
1477
1478 #[test]
1479 fn test_analyze_dict() {
1480 let expr = HirExpr::Dict(vec![(
1481 HirExpr::Literal(Literal::String("key".to_string())),
1482 HirExpr::Literal(Literal::Int(1)),
1483 )]);
1484 let (inst, alloc) = analyze_expr_inner(&expr);
1485 assert!(inst >= 4); assert_eq!(alloc, 1); }
1488
1489 #[test]
1492 fn test_multiple_functions() {
1493 let mut profiler = Profiler::new(ProfileConfig::default());
1494
1495 let func1 = create_test_function("func1", vec![HirStmt::Return(None)]);
1496 let func2 = create_test_function(
1497 "func2",
1498 vec![HirStmt::Expr(HirExpr::Literal(Literal::Int(1)))],
1499 );
1500
1501 let program = HirProgram {
1502 functions: vec![func1, func2],
1503 classes: vec![],
1504 imports: vec![],
1505 };
1506
1507 let report = profiler.analyze_program(&program);
1508 assert_eq!(report.metrics.len(), 2);
1509 assert!(report.metrics.contains_key("func1"));
1510 assert!(report.metrics.contains_key("func2"));
1511 }
1512
1513 #[test]
1516 fn test_type_check_isinstance() {
1517 let profiler = Profiler::new(ProfileConfig::default());
1518 let expr = HirExpr::Call {
1519 func: "isinstance".to_string(),
1520 args: vec![],
1521 kwargs: vec![],
1522 };
1523 assert!(profiler.is_type_check_expr(&expr));
1524 }
1525
1526 #[test]
1527 fn test_type_check_type() {
1528 let profiler = Profiler::new(ProfileConfig::default());
1529 let expr = HirExpr::Call {
1530 func: "type".to_string(),
1531 args: vec![],
1532 kwargs: vec![],
1533 };
1534 assert!(profiler.is_type_check_expr(&expr));
1535 }
1536
1537 #[test]
1538 fn test_not_type_check() {
1539 let profiler = Profiler::new(ProfileConfig::default());
1540 let expr = HirExpr::Call {
1541 func: "print".to_string(),
1542 args: vec![],
1543 kwargs: vec![],
1544 };
1545 assert!(!profiler.is_type_check_expr(&expr));
1546 }
1547
1548 #[test]
1549 fn test_not_type_check_non_call() {
1550 let profiler = Profiler::new(ProfileConfig::default());
1551 let expr = HirExpr::Var("isinstance".to_string());
1552 assert!(!profiler.is_type_check_expr(&expr));
1553 }
1554
1555 #[test]
1558 fn test_generate_annotations_for_hot_function() {
1559 let mut profiler = Profiler::new(ProfileConfig::default());
1560 profiler.metrics.insert(
1561 "hot".to_string(),
1562 FunctionMetrics {
1563 name: "hot".to_string(),
1564 instruction_count: 1000,
1565 allocation_count: 5,
1566 estimated_time: 1000.0,
1567 call_count: 100,
1568 time_percentage: 80.0,
1569 is_hot: true,
1570 },
1571 );
1572
1573 let annotations = profiler.generate_annotations();
1574 assert!(annotations
1575 .iter()
1576 .any(|a| matches!(a.kind, AnnotationKind::TimingProbe)));
1577 }
1578
1579 #[test]
1580 fn test_generate_annotations_for_high_allocation() {
1581 let mut profiler = Profiler::new(ProfileConfig::default());
1582 profiler.metrics.insert(
1583 "alloc_heavy".to_string(),
1584 FunctionMetrics {
1585 name: "alloc_heavy".to_string(),
1586 instruction_count: 100,
1587 allocation_count: 50,
1588 estimated_time: 100.0,
1589 call_count: 10,
1590 time_percentage: 10.0,
1591 is_hot: false,
1592 },
1593 );
1594
1595 let annotations = profiler.generate_annotations();
1596 assert!(annotations
1597 .iter()
1598 .any(|a| matches!(a.kind, AnnotationKind::AllocationCounter)));
1599 }
1600}