Skip to main content

forge/profile/analyze/
observations.rs

1use super::{symbols::clean_name, types::Function};
2
3const MAX_BOTTLENECK_OBS: usize = 3;
4const OBS_ALLOC_HEAVY_PCT: f64 = 25.0;
5const OBS_INIT_HEAVY_PCT: f64 = 15.0;
6const OBS_ROTATION_HEAVY_PCT: f64 = 20.0;
7const OBS_DATA_LOADING_PCT: f64 = 5.0;
8const OBS_OTHER_PCT: f64 = 15.0;
9const OBS_BOTTLENECK_TOTAL_PCT: f64 = 20.0;
10const OBS_BOTTLENECK_SELF_PCT: f64 = 2.0;
11
12// #t(fn: rust_alloc_in_loop) observation messages built per threshold check
13pub(super) fn generate_observations(
14    categories: &[(String, f64)],
15    functions: &[Function],
16    bottlenecks: &[Function],
17) -> Vec<String> {
18    let mut obs = Vec::new();
19    let cat_pct = |name: &str| -> f64 {
20        categories
21            .iter()
22            .find(|(n, _)| n == name)
23            .map_or(0.0, |(_, p)| *p)
24    };
25
26    let mem_pct = cat_pct("memory") + cat_pct("collections");
27
28    if mem_pct > OBS_ALLOC_HEAVY_PCT {
29        obs.push(format!(
30            "ALLOCATION HEAVY: {mem_pct:.0}% of time in memory/collections. \
31             Look for Vec resizing, HashMap rehashing, excessive clone/drop."
32        ));
33    }
34
35    let init_pct: f64 = functions
36        .iter()
37        .filter(|f| {
38            ["::new", "::with_", "::init", "::summon", "::build"]
39                .iter()
40                .any(|p| f.name.contains(p))
41        })
42        .map(|f| f.self_pct)
43        .sum();
44
45    if init_pct > OBS_INIT_HEAVY_PCT {
46        obs.push(format!(
47            "INIT HEAVY: {init_pct:.0}% of self-time in constructors/init functions. \
48             Consider object pooling or lazy initialization across iterations."
49        ));
50    }
51
52    let rotation_pct = cat_pct("rotation");
53
54    if rotation_pct > OBS_ROTATION_HEAVY_PCT {
55        obs.push(format!(
56            "ROTATION EVAL: {rotation_pct:.0}% in rotation evaluation. \
57             Check condition complexity, JIT codegen quality, descriptor evaluation."
58        ));
59    }
60
61    let data_pct = cat_pct("data");
62
63    if data_pct > OBS_DATA_LOADING_PCT {
64        obs.push(format!(
65            "DATA LOADING: {data_pct:.0}% in data loading (resolver). \
66             This should be near 0% for large iteration counts. \
67             Data may be re-loaded per sim instead of shared."
68        ));
69    }
70
71    let other_pct = cat_pct("other");
72
73    if other_pct > OBS_OTHER_PCT {
74        obs.push(format!(
75            "UNCLASSIFIED: {other_pct:.0}% in 'other' category. \
76             Functions not matching any engine subsystem pattern. \
77             May need new category rules or better symbol resolution."
78        ));
79    }
80
81    for b in bottlenecks.iter().take(MAX_BOTTLENECK_OBS) {
82        if b.total_pct >= OBS_BOTTLENECK_TOTAL_PCT && b.self_pct < OBS_BOTTLENECK_SELF_PCT {
83            let name = clean_name(&b.name);
84
85            obs.push(format!(
86                "BOTTLENECK: {name} has {:.1}% total but only {:.1}% self. \
87                 It orchestrates hot code -- optimizing its callees has outsized impact.",
88                b.total_pct, b.self_pct
89            ));
90        }
91    }
92
93    obs
94}