From eca34e9a26e084f1ef8b978c2c15ff3f4bba2a9a Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 15 Aug 2025 23:15:43 -0400 Subject: [PATCH] Add markers to event stream --- src/emulator/shrooms_vb_core.rs | 6 +- src/profiler.rs | 18 ++++- src/profiler/recording.rs | 39 ++++++++++- src/profiler/state.rs | 116 +++++++++++++++++--------------- 4 files changed, 116 insertions(+), 63 deletions(-) diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index 0edd327..56684b6 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -1,4 +1,4 @@ -use std::{ffi::c_void, ptr, slice}; +use std::{borrow::Cow, ffi::c_void, ptr, slice}; use anyhow::{Result, anyhow}; use bitflags::bitflags; @@ -191,6 +191,9 @@ extern "C" fn on_frame(sim: *mut VB) -> c_int { // There is no way for the userdata to be null or otherwise invalid. let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() }; data.frame_seen = true; + if data.monitor.enabled { + data.monitor.event = Some(SimEvent::Marker(Cow::Borrowed("Frame Drawn"))); + } 1 } @@ -328,6 +331,7 @@ pub enum SimEvent { Halt, Interrupt(u16, u32), Reti, + Marker(Cow<'static, str>), } struct EventMonitor { diff --git a/src/profiler.rs b/src/profiler.rs index 62ebac1..d818b0d 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -213,10 +213,22 @@ impl ProfilerSession { } fn track_event(&mut self, event: SimEvent) -> Result<()> { - if let Some(program) = &mut self.program { - program.track_event(event)?; + let Some(program) = &mut self.program else { + return Ok(()); + }; + match event { + SimEvent::Call(address) => program.track_call(address), + SimEvent::Return => program.track_return(), + SimEvent::Halt => program.track_halt(), + SimEvent::Interrupt(code, address) => program.track_interrupt(code, address), + SimEvent::Reti => program.track_reti(), + SimEvent::Marker(name) => { + if let Some(recording) = &mut self.recording { + recording.track_marker(name); + }; + Ok(()) + } } - Ok(()) } fn start_recording(&mut self) { diff --git a/src/profiler/recording.rs b/src/profiler/recording.rs index d4836eb..0b1b778 100644 --- a/src/profiler/recording.rs +++ b/src/profiler/recording.rs @@ -1,8 +1,9 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use fxprof_processed_profile::{ - CategoryHandle, CpuDelta, Frame, FrameFlags, FrameInfo, ProcessHandle, Profile, - ReferenceTimestamp, SamplingInterval, StackHandle, ThreadHandle, Timestamp, + CategoryHandle, CpuDelta, Frame, FrameFlags, FrameInfo, MarkerTiming, ProcessHandle, Profile, + ReferenceTimestamp, SamplingInterval, StackHandle, StaticSchemaMarker, StringHandle, + ThreadHandle, Timestamp, }; use crate::profiler::state::{ProgramState, RESET_CODE, StackFrame}; @@ -72,6 +73,15 @@ impl Recording { } } + 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") } @@ -92,6 +102,29 @@ impl Recording { } } +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(), diff --git a/src/profiler/state.rs b/src/profiler/state.rs index 40bdc72..d2da976 100644 --- a/src/profiler/state.rs +++ b/src/profiler/state.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf}; use anyhow::{Result, bail}; -use crate::{emulator::SimEvent, profiler::symbols::SymbolFile}; +use crate::profiler::symbols::SymbolFile; pub struct ProgramState { symbol_file: SymbolFile, @@ -42,62 +42,66 @@ impl ProgramState { Some((*code, call_stack)) } - pub fn track_event(&mut self, event: SimEvent) -> Result<()> { - match event { - SimEvent::Call(address) => { - let Some(code) = self.context_stack.last() else { - bail!("How did we call anything when we're halted?"); - }; - let Some(stack) = self.call_stacks.get_mut(code) else { - bail!("missing stack {code:04x}"); - }; - stack.push(StackFrame { address }); - } - SimEvent::Return => { - let Some(code) = self.context_stack.last() else { - bail!("how did we return when we're halted?"); - }; - let Some(stack) = self.call_stacks.get_mut(code) else { - bail!("missing stack {code:04x}"); - }; - if stack.pop().is_none() { - bail!("returned from {code:04x} but stack was empty"); - } - if stack.is_empty() { - bail!("returned to oblivion"); - } - } - SimEvent::Halt => { - let Some(RESET_CODE) = self.context_stack.pop() else { - bail!("halted when not in an interrupt"); - }; - } - SimEvent::Interrupt(code, address) => { - // if the CPU was halted before, wake it up now - if self.context_stack.is_empty() { - self.context_stack.push(RESET_CODE); - } + pub fn track_call(&mut self, address: u32) -> Result<()> { + let Some(code) = self.context_stack.last() else { + bail!("How did we call anything when we're halted?"); + }; + let Some(stack) = self.call_stacks.get_mut(code) else { + bail!("missing stack {code:04x}"); + }; + stack.push(StackFrame { address }); + Ok(()) + } - self.context_stack.push(code); - if self - .call_stacks - .insert(code, vec![StackFrame { address }]) - .is_some() - { - bail!("{code:04x} fired twice"); - } - } - SimEvent::Reti => { - let Some(code) = self.context_stack.pop() else { - bail!("RETI when halted"); - }; - if code == RESET_CODE { - bail!("RETI when not in interrupt"); - } - if self.call_stacks.remove(&code).is_none() { - bail!("{code:04x} popped but never called"); - } - } + pub fn track_return(&mut self) -> Result<()> { + let Some(code) = self.context_stack.last() else { + bail!("how did we return when we're halted?"); + }; + let Some(stack) = self.call_stacks.get_mut(code) else { + bail!("missing stack {code:04x}"); + }; + if stack.pop().is_none() { + bail!("returned from {code:04x} but stack was empty"); + } + if stack.is_empty() { + bail!("returned to oblivion"); + } + Ok(()) + } + + pub fn track_halt(&mut self) -> Result<()> { + let Some(RESET_CODE) = self.context_stack.pop() else { + bail!("halted when not in an interrupt"); + }; + Ok(()) + } + + pub fn track_interrupt(&mut self, code: u16, address: u32) -> Result<()> { + // if the CPU was halted before, wake it up now + if self.context_stack.is_empty() { + self.context_stack.push(RESET_CODE); + } + + self.context_stack.push(code); + if self + .call_stacks + .insert(code, vec![StackFrame { address }]) + .is_some() + { + bail!("{code:04x} fired twice"); + } + Ok(()) + } + + pub fn track_reti(&mut self) -> Result<()> { + let Some(code) = self.context_stack.pop() else { + bail!("RETI when halted"); + }; + if code == RESET_CODE { + bail!("RETI when not in interrupt"); + } + if self.call_stacks.remove(&code).is_none() { + bail!("{code:04x} popped but never called"); } Ok(()) }