forge/profile/analyze/
mod.rs1mod aggregate;
2mod observations;
3mod symbols;
4mod types;
5
6use aggregate::{
7 build_call_edges, build_categories, build_function_list, build_hotspot_context, count_samples,
8 find_bottlenecks,
9};
10use observations::generate_observations;
11use symbols::{round1, round2};
12use wowlab_types::sim::FastMap;
13
14use crate::profile::samply::RawProfile;
15
16#[rustfmt::skip]
17pub(super) use symbols::clean_name;
18
19#[rustfmt::skip]
20#[expect(
21 unused_imports,
22 reason = "report modes consume different analysis projections"
23)]
24pub(super) use types::{CallEdge, ContextEntry, Function, HotspotContext, ProfileAnalysis};
25
26const MAX_SOURCE_FILES: usize = 20;
27const MIN_SOURCE_FILE_PCT: f64 = 0.3;
28
29pub(super) fn analyze(
30 profile: &RawProfile,
31 symbols: &FastMap<String, String>,
32 spec: &str,
33 iterations: u32,
34 duration_sec: f64,
35 top_n: usize,
36) -> ProfileAnalysis {
37 let total_weight = profile.total_weight.max(1);
38
39 let sym = |idx: usize| -> &str {
40 let raw = profile.strings.get(idx).map_or("?", String::as_str);
42
43 symbols.get(raw).map_or(raw, String::as_str)
44 };
45
46 let counts = count_samples(profile, &sym);
47 let func_result = build_function_list(&counts, total_weight);
48 let bottlenecks = find_bottlenecks(&counts, total_weight);
49 let call_edges = build_call_edges(&counts, &func_result.all_functions, top_n, total_weight);
50 let hotspot_context = build_hotspot_context(&counts, &func_result.all_functions, total_weight);
51 let categories = build_categories(func_result.category_self);
52
53 let mut source_files: Vec<(String, f64)> = func_result
54 .source_self
55 .into_iter()
56 .filter(|(_, p)| *p >= MIN_SOURCE_FILE_PCT)
57 .map(|(k, v)| (k, round2(v)))
58 .collect();
59
60 source_files.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
61 source_files.truncate(MAX_SOURCE_FILES);
62
63 let observations = generate_observations(&categories, &func_result.all_functions, &bottlenecks);
64
65 let unresolved =
66 u32::try_from(symbols.values().filter(|v| v.starts_with("0x")).count()).unwrap_or(u32::MAX);
67
68 let throughput = if duration_sec > 0.0 {
69 f64::from(iterations) / duration_sec
70 } else {
71 0.0
72 };
73
74 ProfileAnalysis {
75 spec: spec.to_string(),
76 iterations,
77 duration_sec: round2(duration_sec),
78 throughput: round1(throughput),
79 total_samples: profile.samples.len() as u64,
80 unresolved_symbols: unresolved,
81 functions: func_result.all_functions.into_iter().take(top_n).collect(),
82 bottlenecks,
83 categories,
84 category_functions: func_result.cat_funcs,
85 call_edges,
86 hotspot_context,
87 source_files,
88 observations,
89 }
90}