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, 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 { serde_json::to_vec(&self.profile).expect("could not serialize profile") } fn handle_for_stack( &mut self, thread: ThreadHandle, frames: &[StackFrame], ) -> Option { 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::>(); 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(), } }