hydro_deploy/localhost/
samply.rs

1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use futures::future::join_all;
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8use serde_json::Number;
9use wholesym::debugid::DebugId;
10use wholesym::{LookupAddress, MultiArchDisambiguator, SymbolManager, SymbolManagerConfig};
11
12#[derive(Serialize, Deserialize, Debug)]
13pub struct FxProfile {
14    threads: Vec<Thread>,
15    libs: Vec<Lib>,
16}
17
18#[derive(Serialize, Deserialize, Debug)]
19pub struct Lib {
20    pub path: String,
21    #[serde(rename = "breakpadId")]
22    pub breakpad_id: String,
23}
24
25#[derive(Serialize, Deserialize, Debug)]
26pub struct Thread {
27    #[serde(rename = "stackTable")]
28    pub stack_table: StackTable,
29    #[serde(rename = "frameTable")]
30    pub frame_table: FrameTable,
31    #[serde(rename = "funcTable")]
32    pub func_table: FuncTable,
33    pub samples: Samples,
34    #[serde(rename = "isMainThread")]
35    pub is_main_thread: bool,
36}
37
38#[derive(Serialize, Deserialize, Debug)]
39pub struct Samples {
40    pub stack: Vec<Option<usize>>,
41    pub weight: Vec<u64>,
42}
43
44#[derive(Serialize, Deserialize, Debug)]
45pub struct StackTable {
46    pub prefix: Vec<Option<usize>>,
47    pub frame: Vec<usize>,
48}
49
50#[derive(Serialize, Deserialize, Debug)]
51pub struct FrameTable {
52    /// `Vec` of `u64` or `-1`.
53    pub address: Vec<Number>,
54    pub func: Vec<usize>,
55}
56
57#[derive(Serialize, Deserialize, Debug)]
58pub struct FuncTable {
59    // `Vec` of `usize` or `-1`.
60    pub resource: Vec<Number>,
61}
62
63pub async fn samply_to_folded(loaded: FxProfile) -> String {
64    let symbol_manager = SymbolManager::with_config(SymbolManagerConfig::default());
65
66    let mut symbol_maps = vec![];
67    for lib in &loaded.libs {
68        symbol_maps.push(
69            symbol_manager
70                .load_symbol_map_for_binary_at_path(
71                    &PathBuf::from_str(&lib.path).unwrap(),
72                    Some(MultiArchDisambiguator::DebugId(
73                        DebugId::from_breakpad(&lib.breakpad_id).unwrap(),
74                    )),
75                )
76                .await
77                .ok(),
78        );
79    }
80
81    let mut folded_frames: BTreeMap<Vec<Option<String>>, u64> = BTreeMap::new();
82    for thread in loaded.threads.into_iter().filter(|t| t.is_main_thread) {
83        let frame_lookuped = join_all((0..thread.frame_table.address.len()).map(|frame_id| {
84            let fr_address = &thread.frame_table.address;
85            let fr_func = &thread.frame_table.func;
86            let fn_resource = &thread.func_table.resource;
87            let symbol_maps = &symbol_maps;
88            async move {
89                let address = fr_address[frame_id].as_u64()?;
90                let func_id = fr_func[frame_id];
91                let resource_id = fn_resource[func_id].as_u64()?;
92                let symbol_map = symbol_maps[resource_id as usize].as_ref()?;
93                let lookuped = symbol_map
94                    .lookup(LookupAddress::Relative(address as u32))
95                    .await?;
96
97                if let Some(inline_frames) = lookuped.frames {
98                    Some(
99                        inline_frames
100                            .into_iter()
101                            .rev()
102                            .map(|inline| inline.function.unwrap_or_else(|| "unknown".to_string()))
103                            .join(";"),
104                    )
105                } else {
106                    Some(lookuped.symbol.name)
107                }
108            }
109        }))
110        .await;
111
112        let all_leaves_grouped = thread
113            .samples
114            .stack
115            .iter()
116            .enumerate()
117            .filter_map(|(idx, s)| s.map(|s| (idx, s)))
118            .map(|(idx, leaf)| (leaf, thread.samples.weight[idx]))
119            .chunk_by(|&(leaf, _)| leaf)
120            .into_iter()
121            .map(|(leaf, group)| {
122                let weight = group.map(|(_leaf, weight)| weight).sum();
123                (leaf, weight)
124            })
125            .collect::<Vec<(usize, u64)>>();
126
127        for (leaf, weight) in all_leaves_grouped {
128            let mut cur_stack = Some(leaf);
129            let mut stack = vec![];
130            while let Some(sample) = cur_stack {
131                let frame_id = thread.stack_table.frame[sample];
132                stack.push(frame_lookuped[frame_id].clone());
133                cur_stack = thread.stack_table.prefix[sample];
134            }
135
136            *folded_frames.entry(stack).or_default() += weight;
137        }
138    }
139
140    let mut output = String::new();
141    for (stack, weight) in folded_frames {
142        for (i, s) in stack.iter().rev().enumerate() {
143            if i != 0 {
144                output.push(';');
145            }
146            output.push_str(s.as_deref().unwrap_or("unknown"));
147        }
148
149        output.push_str(&format!(" {}\n", weight));
150    }
151
152    output
153}