use crate::{AsContext, Module};
use anyhow::Result;
use fxprof_processed_profile::debugid::DebugId;
use fxprof_processed_profile::{
CategoryHandle, CpuDelta, Frame, FrameFlags, FrameInfo, LibraryInfo, Profile,
ReferenceTimestamp, Symbol, SymbolTable, Timestamp,
};
use std::ops::Range;
use std::sync::Arc;
use std::time::{Duration, Instant};
use wasmtime_jit::CompiledModule;
use wasmtime_runtime::Backtrace;
#[derive(Debug)]
pub struct GuestProfiler {
profile: Profile,
modules: Vec<(Range<usize>, fxprof_processed_profile::LibraryHandle)>,
process: fxprof_processed_profile::ProcessHandle,
thread: fxprof_processed_profile::ThreadHandle,
start: Instant,
}
impl GuestProfiler {
pub fn new(module_name: &str, interval: Duration, modules: Vec<(String, Module)>) -> Self {
let zero = ReferenceTimestamp::from_millis_since_unix_epoch(0.0);
let mut profile = Profile::new(module_name, zero, interval.into());
let mut modules: Vec<_> = modules
.into_iter()
.filter_map(|(name, module)| {
let compiled = module.compiled_module();
let text = compiled.text().as_ptr_range();
let address_range = text.start as usize..text.end as usize;
module_symbols(name, compiled).map(|lib| (address_range, profile.add_lib(lib)))
})
.collect();
modules.sort_unstable_by_key(|(range, _)| range.start);
profile.set_reference_timestamp(std::time::SystemTime::now().into());
let process = profile.add_process(module_name, 0, Timestamp::from_nanos_since_reference(0));
let thread = profile.add_thread(process, 0, Timestamp::from_nanos_since_reference(0), true);
let start = Instant::now();
Self {
profile,
modules,
process,
thread,
start,
}
}
pub fn sample(&mut self, store: impl AsContext) {
let now = Timestamp::from_nanos_since_reference(
self.start.elapsed().as_nanos().try_into().unwrap(),
);
let backtrace = Backtrace::new(store.as_context().0.vmruntime_limits());
let frames = backtrace
.frames()
.rev()
.filter_map(|frame| {
let module_idx = self
.modules
.partition_point(|(range, _)| range.start > frame.pc());
if let Some((range, lib)) = self.modules.get(module_idx) {
if range.contains(&frame.pc()) {
return Some(FrameInfo {
frame: Frame::RelativeAddressFromReturnAddress(
*lib,
u32::try_from(frame.pc() - range.start).unwrap(),
),
category_pair: CategoryHandle::OTHER.into(),
flags: FrameFlags::empty(),
});
}
}
None
});
self.profile
.add_sample(self.thread, now, frames, CpuDelta::ZERO, 1);
}
pub fn finish(mut self, output: impl std::io::Write) -> Result<()> {
let now = Timestamp::from_nanos_since_reference(
self.start.elapsed().as_nanos().try_into().unwrap(),
);
self.profile.set_thread_end_time(self.thread, now);
self.profile.set_process_end_time(self.process, now);
serde_json::to_writer(output, &self.profile)?;
Ok(())
}
}
fn module_symbols(name: String, compiled: &CompiledModule) -> Option<LibraryInfo> {
let symbols = Vec::from_iter(compiled.finished_functions().map(|(defined_idx, _)| {
let loc = compiled.func_loc(defined_idx);
let func_idx = compiled.module().func_index(defined_idx);
let name = match compiled.func_name(func_idx) {
None => format!("wasm_function_{}", defined_idx.as_u32()),
Some(name) => name.to_string(),
};
Symbol {
address: loc.start,
size: Some(loc.length),
name,
}
}));
if symbols.is_empty() {
return None;
}
Some(LibraryInfo {
name,
debug_name: String::new(),
path: String::new(),
debug_path: String::new(),
debug_id: DebugId::nil(),
code_id: None,
arch: None,
symbol_table: Some(Arc::new(SymbolTable::new(symbols))),
})
}