profile_inspect/parser/
heap_profile.rs1use std::collections::HashMap;
2use std::path::Path;
3
4use crate::classify::FrameClassifier;
5use crate::ir::{Frame, FrameId, ProfileIR, Sample, Stack, StackId};
6use crate::types::{HeapProfile, HeapProfileNode};
7
8use super::ParseError;
9
10pub struct HeapProfileParser {
12 classifier: FrameClassifier,
13}
14
15impl HeapProfileParser {
16 pub fn new(classifier: FrameClassifier) -> Self {
18 Self { classifier }
19 }
20
21 pub fn parse_file(&self, path: &Path) -> Result<ProfileIR, ParseError> {
26 let content = std::fs::read_to_string(path)?;
27 let source_file = path.file_name().map(|s| s.to_string_lossy().to_string());
28 self.parse_str(&content, source_file)
29 }
30
31 pub fn parse_str(
36 &self,
37 json: &str,
38 source_file: Option<String>,
39 ) -> Result<ProfileIR, ParseError> {
40 let profile = HeapProfile::from_json(json)?;
41 Ok(self.convert_to_ir(&profile, source_file))
42 }
43
44 #[expect(clippy::cast_possible_truncation)]
46 fn convert_to_ir(&self, profile: &HeapProfile, source_file: Option<String>) -> ProfileIR {
47 let mut frames = Vec::new();
48 let mut stacks = Vec::new();
49 let mut samples = Vec::new();
50
51 let mut node_to_frame: HashMap<u32, FrameId> = HashMap::new();
52 let mut stack_map: HashMap<Vec<FrameId>, StackId> = HashMap::new();
53
54 let mut next_frame_id = 0u32;
56 self.walk_tree(
57 &profile.head,
58 &[],
59 &mut frames,
60 &mut stacks,
61 &mut samples,
62 &mut node_to_frame,
63 &mut stack_map,
64 &mut next_frame_id,
65 );
66
67 for sample in &profile.samples {
69 if let Some(&frame_id) = node_to_frame.get(&sample.node_id) {
71 let stack_frames = vec![frame_id];
74 let stack_id = if let Some(&id) = stack_map.get(&stack_frames) {
75 id
76 } else {
77 let id = StackId(stacks.len() as u32);
78 stack_map.insert(stack_frames.clone(), id);
79 stacks.push(Stack::new(id, stack_frames));
80 id
81 };
82
83 samples.push(Sample::new(
84 u64::from(sample.ordinal),
85 stack_id,
86 sample.size,
87 ));
88 }
89 }
90
91 ProfileIR::new_heap(frames, stacks, samples, source_file)
92 }
93
94 #[expect(
96 clippy::too_many_arguments,
97 clippy::cast_possible_truncation,
98 clippy::cast_sign_loss
99 )]
100 fn walk_tree(
101 &self,
102 node: &HeapProfileNode,
103 parent_stack: &[FrameId],
104 frames: &mut Vec<Frame>,
105 stacks: &mut Vec<Stack>,
106 samples: &mut Vec<Sample>,
107 node_to_frame: &mut HashMap<u32, FrameId>,
108 stack_map: &mut HashMap<Vec<FrameId>, StackId>,
109 next_frame_id: &mut u32,
110 ) {
111 let cf = &node.call_frame;
112
113 let frame_id = FrameId(*next_frame_id);
115 *next_frame_id += 1;
116
117 let (kind, category) = self.classifier.classify(&cf.url, &cf.function_name);
118
119 let frame = Frame::new(
120 frame_id,
121 cf.function_name.clone(),
122 if cf.url.is_empty() {
123 None
124 } else {
125 Some(cf.url.clone())
126 },
127 if cf.line_number > 0 {
128 Some(cf.line_number as u32)
129 } else {
130 None
131 },
132 if cf.column_number > 0 {
133 Some(cf.column_number as u32)
134 } else {
135 None
136 },
137 kind,
138 category,
139 );
140
141 node_to_frame.insert(node.id, frame_id);
142 frames.push(frame);
143
144 let mut current_stack: Vec<FrameId> = parent_stack.to_vec();
146 current_stack.push(frame_id);
147
148 if node.self_size > 0 {
150 let stack_id = if let Some(&id) = stack_map.get(¤t_stack) {
151 id
152 } else {
153 let id = StackId(stacks.len() as u32);
154 stack_map.insert(current_stack.clone(), id);
155 stacks.push(Stack::new(id, current_stack.clone()));
156 id
157 };
158
159 samples.push(Sample::new(0, stack_id, node.self_size));
160 }
161
162 for child in &node.children {
164 self.walk_tree(
165 child,
166 ¤t_stack,
167 frames,
168 stacks,
169 samples,
170 node_to_frame,
171 stack_map,
172 next_frame_id,
173 );
174 }
175 }
176}
177
178impl Default for HeapProfileParser {
179 fn default() -> Self {
180 Self::new(FrameClassifier::default())
181 }
182}