use std::collections::HashMap;
use std::path::Path;
use crate::classify::FrameClassifier;
use crate::ir::{Frame, FrameId, ProfileIR, Sample, Stack, StackId};
use crate::types::{HeapProfile, HeapProfileNode};
use super::ParseError;
pub struct HeapProfileParser {
classifier: FrameClassifier,
}
impl HeapProfileParser {
pub fn new(classifier: FrameClassifier) -> Self {
Self { classifier }
}
pub fn parse_file(&self, path: &Path) -> Result<ProfileIR, ParseError> {
let content = std::fs::read_to_string(path)?;
let source_file = path.file_name().map(|s| s.to_string_lossy().to_string());
self.parse_str(&content, source_file)
}
pub fn parse_str(
&self,
json: &str,
source_file: Option<String>,
) -> Result<ProfileIR, ParseError> {
let profile = HeapProfile::from_json(json)?;
Ok(self.convert_to_ir(&profile, source_file))
}
#[expect(clippy::cast_possible_truncation)]
fn convert_to_ir(&self, profile: &HeapProfile, source_file: Option<String>) -> ProfileIR {
let mut frames = Vec::new();
let mut stacks = Vec::new();
let mut samples = Vec::new();
let mut node_to_frame: HashMap<u32, FrameId> = HashMap::new();
let mut stack_map: HashMap<Vec<FrameId>, StackId> = HashMap::new();
let mut next_frame_id = 0u32;
self.walk_tree(
&profile.head,
&[],
&mut frames,
&mut stacks,
&mut samples,
&mut node_to_frame,
&mut stack_map,
&mut next_frame_id,
);
for sample in &profile.samples {
if let Some(&frame_id) = node_to_frame.get(&sample.node_id) {
let stack_frames = vec![frame_id];
let stack_id = if let Some(&id) = stack_map.get(&stack_frames) {
id
} else {
let id = StackId(stacks.len() as u32);
stack_map.insert(stack_frames.clone(), id);
stacks.push(Stack::new(id, stack_frames));
id
};
samples.push(Sample::new(
u64::from(sample.ordinal),
stack_id,
sample.size,
));
}
}
ProfileIR::new_heap(frames, stacks, samples, source_file)
}
#[expect(
clippy::too_many_arguments,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
fn walk_tree(
&self,
node: &HeapProfileNode,
parent_stack: &[FrameId],
frames: &mut Vec<Frame>,
stacks: &mut Vec<Stack>,
samples: &mut Vec<Sample>,
node_to_frame: &mut HashMap<u32, FrameId>,
stack_map: &mut HashMap<Vec<FrameId>, StackId>,
next_frame_id: &mut u32,
) {
let cf = &node.call_frame;
let frame_id = FrameId(*next_frame_id);
*next_frame_id += 1;
let (kind, category) = self.classifier.classify(&cf.url, &cf.function_name);
let frame = Frame::new(
frame_id,
cf.function_name.clone(),
if cf.url.is_empty() {
None
} else {
Some(cf.url.clone())
},
if cf.line_number > 0 {
Some(cf.line_number as u32)
} else {
None
},
if cf.column_number > 0 {
Some(cf.column_number as u32)
} else {
None
},
kind,
category,
);
node_to_frame.insert(node.id, frame_id);
frames.push(frame);
let mut current_stack: Vec<FrameId> = parent_stack.to_vec();
current_stack.push(frame_id);
if node.self_size > 0 {
let stack_id = if let Some(&id) = stack_map.get(¤t_stack) {
id
} else {
let id = StackId(stacks.len() as u32);
stack_map.insert(current_stack.clone(), id);
stacks.push(Stack::new(id, current_stack.clone()));
id
};
samples.push(Sample::new(0, stack_id, node.self_size));
}
for child in &node.children {
self.walk_tree(
child,
¤t_stack,
frames,
stacks,
samples,
node_to_frame,
stack_map,
next_frame_id,
);
}
}
}
impl Default for HeapProfileParser {
fn default() -> Self {
Self::new(FrameClassifier::default())
}
}