Files
lemur/src/profiler.rs
T

265 lines
7.2 KiB
Rust

use std::{
sync::{Arc, Mutex},
thread,
};
use anyhow::Result;
use tokio::{select, sync::mpsc};
use crate::emulator::{
EmulatorClient, EmulatorCommand, GameInfo, InlineStack, ProfileEvent, SimEvent, SimId,
};
use recording::Recording;
use state::ProgramState;
mod recording;
mod state;
pub struct Profiler {
sim_id: SimId,
client: EmulatorClient,
status: Arc<Mutex<ProfilerStatus>>,
action: Option<mpsc::UnboundedSender<RecordingAction>>,
killer: Option<oneshot::Sender<()>>,
}
impl Profiler {
pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
Self {
sim_id,
client,
status: Arc::new(Mutex::new(ProfilerStatus::Disabled)),
action: None,
killer: None,
}
}
pub fn status(&self) -> ProfilerStatus {
self.status.lock().unwrap().clone()
}
pub fn enable(&mut self) {
let sim_id = self.sim_id;
let client = self.client.clone();
let status = self.status.clone();
let (action_tx, action_rx) = mpsc::unbounded_channel();
self.action = Some(action_tx);
let (killer_tx, killer_rx) = oneshot::channel();
self.killer = Some(killer_tx);
thread::spawn(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async move {
select! {
_ = run_profile(sim_id, client, status.clone(), action_rx) => {}
_ = killer_rx => {
*status.lock().unwrap() = ProfilerStatus::Disabled;
}
}
})
});
}
pub fn disable(&mut self) {
if let Some(killer) = self.killer.take() {
let _ = killer.send(());
}
}
pub fn start_recording(&mut self) {
if let Some(action) = &self.action {
let _ = action.send(RecordingAction::Start);
}
}
pub fn finish_recording(&mut self) -> oneshot::Receiver<Vec<u8>> {
let (tx, rx) = oneshot::channel();
if let Some(action) = &self.action {
let _ = action.send(RecordingAction::Finish(tx));
}
rx
}
pub fn cancel_recording(&mut self) {
if let Some(action) = &self.action {
let _ = action.send(RecordingAction::Cancel);
}
}
}
impl Drop for Profiler {
fn drop(&mut self) {
self.disable();
}
}
async fn run_profile(
sim_id: SimId,
client: EmulatorClient,
status: Arc<Mutex<ProfilerStatus>>,
mut action_source: mpsc::UnboundedReceiver<RecordingAction>,
) {
let (profile_sync, mut profile_source) = mpsc::unbounded_channel();
client.send_command(EmulatorCommand::StartProfiling(sim_id, profile_sync));
*status.lock().unwrap() = ProfilerStatus::Enabled;
let mut session = ProfilerSession::new();
loop {
select! {
maybe_event = profile_source.recv() => {
let Some(event) = maybe_event else {
break; // emulator thread disconnected
};
if let Err(error) = handle_event(event, &mut session).await {
*status.lock().unwrap() = ProfilerStatus::Error(error.to_string());
return;
}
}
maybe_action = action_source.recv() => {
let Some(action) = maybe_action else {
break; // ui thread disconnected
};
handle_action(action, &mut session, &status);
}
}
}
*status.lock().unwrap() = ProfilerStatus::Disabled;
}
async fn handle_event(event: ProfileEvent, session: &mut ProfilerSession) -> Result<()> {
match event {
ProfileEvent::Start { info } => session.start_profiling(info).await,
ProfileEvent::Update {
cycles,
event,
inline_stack,
} => {
session.track_elapsed_cycles(cycles);
if let Some(event) = event {
session.track_event(event)?;
}
if let Some(stack) = inline_stack {
session.track_inline_stack(stack);
}
}
}
Ok(())
}
fn handle_action(
action: RecordingAction,
session: &mut ProfilerSession,
status: &Mutex<ProfilerStatus>,
) {
match action {
RecordingAction::Start => {
session.start_recording();
*status.lock().unwrap() = ProfilerStatus::Recording;
}
RecordingAction::Finish(rx) => {
if let Some(bytes) = session.finish_recording() {
let _ = rx.send(bytes);
}
*status.lock().unwrap() = ProfilerStatus::Enabled;
}
RecordingAction::Cancel => {
session.cancel_recording();
*status.lock().unwrap() = ProfilerStatus::Enabled;
}
}
}
#[derive(Clone)]
pub enum ProfilerStatus {
Disabled,
Enabled,
Recording,
Error(String),
}
impl ProfilerStatus {
pub fn enabled(&self) -> bool {
matches!(self, Self::Enabled | Self::Recording)
}
}
enum RecordingAction {
Start,
Finish(oneshot::Sender<Vec<u8>>),
Cancel,
}
struct ProfilerSession {
program: Option<ProgramState>,
recording: Option<Recording>,
}
impl ProfilerSession {
fn new() -> Self {
Self {
program: None,
recording: None,
}
}
async fn start_profiling(&mut self, info: Arc<GameInfo>) {
let program = ProgramState::new(info).await;
let recording = if self.recording.is_some() {
Some(Recording::new(&program))
} else {
None
};
self.program = Some(program);
self.recording = recording;
}
fn track_elapsed_cycles(&mut self, cycles: u32) {
if let (Some(state), Some(recording)) = (&self.program, &mut self.recording) {
recording.track_elapsed_cycles(state, cycles);
}
}
fn track_event(&mut self, event: SimEvent) -> Result<()> {
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(())
}
}
}
fn track_inline_stack(&mut self, inline_stack: InlineStack) {
if let Some(program) = &mut self.program {
program.track_inline_stack(inline_stack);
}
}
fn start_recording(&mut self) {
if let Some(program) = &self.program {
self.recording = Some(Recording::new(program));
}
}
fn finish_recording(&mut self) -> Option<Vec<u8>> {
self.recording.take().map(|r| r.finish())
}
fn cancel_recording(&mut self) {
self.recording.take();
}
}