lemur/src/profiler/recording.rs

153 lines
5.4 KiB
Rust

use std::{borrow::Cow, collections::HashMap};
use fxprof_processed_profile::{
CategoryHandle, CpuDelta, Frame, FrameFlags, FrameInfo, MarkerTiming, ProcessHandle, Profile,
ReferenceTimestamp, SamplingInterval, StackHandle, StaticSchemaMarker, StringHandle,
ThreadHandle, Timestamp,
};
use crate::profiler::state::{ProgramState, RESET_CODE, StackFrame};
pub struct Recording {
profile: Profile,
process: ProcessHandle,
threads: HashMap<u16, ThreadHandle>,
now: u64,
}
impl Recording {
pub fn new(state: &ProgramState) -> Self {
let reference_timestamp = ReferenceTimestamp::from_millis_since_unix_epoch(0.0);
let interval = SamplingInterval::from_hz(20_000_000.0);
let mut profile = Profile::new(state.name(), reference_timestamp, interval);
let process =
profile.add_process(state.name(), 1, Timestamp::from_nanos_since_reference(0));
let lib = profile.add_lib(state.library_info().clone());
profile.add_lib_mapping(process, lib, 0x00000000, 0xffffffff, 0);
let mut me = Self {
profile,
process,
threads: HashMap::new(),
now: 0,
};
me.track_elapsed_cycles(state, 0);
me
}
pub fn track_elapsed_cycles(&mut self, state: &ProgramState, cycles: u32) {
self.now += cycles as u64;
let timestamp = Timestamp::from_nanos_since_reference(self.now * 50);
let weight = 1;
let active_code = if let Some((code, frames)) = state.current_stack() {
let thread = *self.threads.entry(code).or_insert_with(|| {
let process = self.process;
let tid = code as u32;
let start_time = Timestamp::from_nanos_since_reference(self.now * 50);
let is_main = code == RESET_CODE;
let thread = self.profile.add_thread(process, tid, start_time, is_main);
self.profile
.set_thread_name(thread, &thread_name_for_code(code));
thread
});
let stack = self.handle_for_stack(thread, frames);
let cpu_delta = CpuDelta::from_nanos((self.now - cycles as u64) * 50);
self.profile
.add_sample(thread, timestamp, stack, cpu_delta, weight);
Some(code)
} else {
None
};
for (code, thread) in &self.threads {
if active_code == Some(*code) {
continue;
}
self.profile
.add_sample_same_stack_zero_cpu(*thread, timestamp, weight);
}
}
pub fn track_marker(&mut self, name: Cow<'static, str>) {
let Some(thread) = self.threads.get(&RESET_CODE) else {
return;
};
let timing = MarkerTiming::Instant(Timestamp::from_nanos_since_reference(self.now * 50));
let marker = SimpleMarker(name);
self.profile.add_marker(*thread, timing, marker);
}
pub fn finish(self) -> Vec<u8> {
serde_json::to_vec(&self.profile).expect("could not serialize profile")
}
fn handle_for_stack(
&mut self,
thread: ThreadHandle,
frames: &[StackFrame],
) -> Option<StackHandle> {
let frames = frames
.iter()
.map(|f| {
let frame = match f {
StackFrame::Address(address) => Frame::InstructionPointer(*address as u64),
StackFrame::Label(label) => Frame::Label(self.profile.intern_string(label)),
};
FrameInfo {
frame,
category_pair: CategoryHandle::OTHER.into(),
flags: FrameFlags::empty(),
}
})
.collect::<Vec<_>>();
self.profile.intern_stack_frames(thread, frames.into_iter())
}
}
struct SimpleMarker(Cow<'static, str>);
impl StaticSchemaMarker for SimpleMarker {
const UNIQUE_MARKER_TYPE_NAME: &'static str = "Simple";
const FIELDS: &'static [fxprof_processed_profile::StaticSchemaMarkerField] = &[];
fn name(&self, profile: &mut Profile) -> StringHandle {
profile.intern_string(&self.0)
}
fn category(&self, _profile: &mut Profile) -> CategoryHandle {
CategoryHandle::OTHER
}
fn string_field_value(&self, _field_index: u32) -> StringHandle {
unreachable!()
}
fn number_field_value(&self, _field_index: u32) -> f64 {
unreachable!()
}
}
fn thread_name_for_code(code: u16) -> std::borrow::Cow<'static, str> {
match code {
RESET_CODE => "Main".into(),
0xffd0 => "Duplexed exception".into(),
0xfe40 => "VIP interrupt".into(),
0xfe30 => "Communication interrupt".into(),
0xfe20 => "Game pak interrupt".into(),
0xfe10 => "Timer interrupt".into(),
0xfe00 => "Game pad interrupt".into(),
0xffc0 => "Address trap".into(),
0xffa0..0xffc0 => format!("Trap (vector {})", code - 0xffa0).into(),
0xff90 => "Illegal opcode exception".into(),
0xff80 => "Zero division exception".into(),
0xff60 => "Floating-point reserved operand exception".into(),
0xff70 => "Floating-point invalid operation exception".into(),
0xff68 => "Floating-point zero division exception".into(),
0xff64 => "Floating-point overflow exception".into(),
other => format!("Unrecognized handler (0x{other:04x})").into(),
}
}