Skip to main content

forge/profile/analyze/
symbols.rs

1#![expect(
2    clippy::cast_precision_loss,
3    reason = "profiler sample weights are intentionally converted to percentages"
4)]
5
6use wowlab_types::constants::HUNDRED;
7
8const MIN_MODULE_PARTS: usize = 2;
9const HASH_SUFFIX_LEN: usize = 17;
10const ROUND1_FACTOR: f64 = 10.0;
11
12pub(super) const fn pct(n: u64, total_weight: u64) -> f64 {
13    (n as f64 * HUNDRED) / total_weight as f64
14}
15
16pub(super) fn parse_module(name: &str) -> String {
17    let parts: Vec<&str> = name.split("::").collect();
18    let parts = if parts.len() >= MIN_MODULE_PARTS {
19        // BOUNDS: last() is safe because len() >= 2
20        let last = parts.last().unwrap_or(&"");
21
22        if last.starts_with('h') && last.len() == HASH_SUFFIX_LEN {
23            // BOUNDS: len >= 2 checked above
24            &parts[..parts.len() - 1]
25        } else {
26            // BOUNDS: full slice, no out-of-bounds possible
27            &*parts
28        }
29    } else {
30        // BOUNDS: full slice, no out-of-bounds possible
31        &*parts
32    };
33
34    if parts.len() > 1 {
35        // BOUNDS: len > 1, so len - 1 >= 1
36        parts[..parts.len() - 1].join("::")
37    } else {
38        name.to_string()
39    }
40}
41
42pub(crate) fn clean_name(name: &str) -> String {
43    let parts: Vec<&str> = name.split("::").collect();
44
45    if parts.len() >= MIN_MODULE_PARTS {
46        // BOUNDS: last() is safe because len() >= 2
47        let last = parts.last().unwrap_or(&"");
48
49        if last.starts_with('h') && last.len() == HASH_SUFFIX_LEN {
50            // BOUNDS: len >= 2 checked above
51            return parts[..parts.len() - 1].join("::");
52        }
53    }
54
55    name.to_string()
56}
57
58pub(super) fn symbol_to_source(name: &str) -> Option<String> {
59    if !name.contains("wowlab_engine::") {
60        return None;
61    }
62
63    let parts: Vec<&str> = name.split("::").collect();
64    let start = parts.iter().position(|&p| p == "wowlab_engine")? + 1;
65
66    let mut path_parts = Vec::with_capacity(parts.len().saturating_sub(start));
67    // BOUNDS: start is always <= parts.len() because position() found a match
68
69    for &p in &parts[start..] {
70        if !p.is_empty() && p.chars().next().is_some_and(char::is_uppercase) {
71            break;
72        }
73
74        path_parts.push(p);
75    }
76
77    if path_parts.is_empty() {
78        return None;
79    }
80
81    if path_parts.len() > 1 {
82        Some(format!(
83            "src/{}/{}.rs",
84            // BOUNDS: len() > 1 guarantees both slices are non-empty
85            path_parts[..path_parts.len() - 1].join("/"),
86            path_parts.last().unwrap_or(&"")
87        ))
88    } else {
89        // BOUNDS: path_parts is non-empty (checked above), index 0 is safe
90        Some(format!("src/{}.rs", path_parts[0]))
91    }
92}
93
94pub(super) fn round1(v: f64) -> f64 {
95    (v * ROUND1_FACTOR).round() / ROUND1_FACTOR
96}
97
98pub(super) fn round2(v: f64) -> f64 {
99    (v * HUNDRED).round() / HUNDRED
100}