#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::instantiate::CompiledModule;
use crate::prelude::*;
use crate::runtime::vm::Backtrace;
use crate::{AsContext, CallHook, Module};
use core::cmp::Ordering;
use fxprof_processed_profile::debugid::DebugId;
use fxprof_processed_profile::{
CategoryHandle, Frame, FrameFlags, FrameInfo, LibraryInfo, MarkerLocation, MarkerSchema,
MarkerTiming, Profile, ProfilerMarker, ReferenceTimestamp, Symbol, SymbolTable, Timestamp,
};
use std::ops::Range;
use std::sync::Arc;
use std::time::{Duration, Instant};
use wasmtime_environ::demangle_function_name_or_index;
#[derive(Debug)]
pub struct GuestProfiler {
profile: Profile,
modules: Modules,
process: fxprof_processed_profile::ProcessHandle,
thread: fxprof_processed_profile::ThreadHandle,
start: Instant,
}
#[derive(Debug)]
struct ProfiledModule {
module: Module,
fxprof_libhandle: fxprof_processed_profile::LibraryHandle,
text_range: Range<usize>,
}
type Modules = Vec<ProfiledModule>;
impl GuestProfiler {
pub fn new(
module_name: &str,
interval: Duration,
modules: impl IntoIterator<Item = (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_range = {
let start =
compiled.finished_functions().next()?.1.as_ptr_range().start as usize;
let end = compiled.finished_functions().last()?.1.as_ptr_range().end as usize;
start..end
};
module_symbols(name, compiled).map(|lib| {
let libhandle = profile.add_lib(lib);
ProfiledModule {
module,
fxprof_libhandle: libhandle,
text_range,
}
})
})
.collect();
modules.sort_unstable_by_key(|m| m.text_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,
}
}
#[cfg(feature = "component-model")]
pub fn new_component(
component_name: &str,
interval: Duration,
component: Component,
extra_modules: impl IntoIterator<Item = (String, Module)>,
) -> Self {
let modules = component
.static_modules()
.map(|m| (m.name().unwrap_or("<unknown>").to_string(), m.clone()))
.chain(extra_modules);
Self::new(component_name, interval, modules)
}
pub fn sample(&mut self, store: impl AsContext, delta: Duration) {
let now = Timestamp::from_nanos_since_reference(
self.start.elapsed().as_nanos().try_into().unwrap(),
);
let backtrace = Backtrace::new(store.as_context().0);
let frames = lookup_frames(&self.modules, &backtrace);
self.profile
.add_sample(self.thread, now, frames, delta.into(), 1);
}
pub fn call_hook(&mut self, store: impl AsContext, kind: CallHook) {
let now = Timestamp::from_nanos_since_reference(
self.start.elapsed().as_nanos().try_into().unwrap(),
);
match kind {
CallHook::CallingWasm | CallHook::ReturningFromWasm => {}
CallHook::CallingHost => {
let backtrace = Backtrace::new(store.as_context().0);
let frames = lookup_frames(&self.modules, &backtrace);
self.profile.add_marker_with_stack(
self.thread,
"hostcall",
CallMarker,
MarkerTiming::IntervalStart(now),
frames,
);
}
CallHook::ReturningFromHost => {
self.profile.add_marker(
self.thread,
"hostcall",
CallMarker,
MarkerTiming::IntervalEnd(now),
);
}
}
}
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 mut name = String::new();
demangle_function_name_or_index(
&mut name,
compiled.func_name(func_idx),
defined_idx.as_u32() as usize,
)
.unwrap();
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))),
})
}
fn lookup_frames<'a>(
modules: &'a Modules,
backtrace: &'a Backtrace,
) -> impl Iterator<Item = FrameInfo> + 'a {
backtrace
.frames()
.rev()
.filter_map(|frame| {
let idx = modules
.binary_search_by(|probe| {
if probe.text_range.contains(&frame.pc()) {
Ordering::Equal
} else {
probe.text_range.start.cmp(&frame.pc())
}
})
.ok()?;
let module = modules.get(idx)?;
let module_text_start = module.module.text().as_ptr_range().start as usize;
return Some(FrameInfo {
frame: Frame::RelativeAddressFromReturnAddress(
module.fxprof_libhandle,
u32::try_from(frame.pc() - module_text_start).unwrap(),
),
category_pair: CategoryHandle::OTHER.into(),
flags: FrameFlags::empty(),
});
})
}
struct CallMarker;
impl ProfilerMarker for CallMarker {
const MARKER_TYPE_NAME: &'static str = "hostcall";
fn schema() -> MarkerSchema {
MarkerSchema {
type_name: Self::MARKER_TYPE_NAME,
locations: vec![
MarkerLocation::MarkerChart,
MarkerLocation::MarkerTable,
MarkerLocation::TimelineOverview,
],
chart_label: None,
tooltip_label: None,
table_label: None,
fields: vec![],
}
}
fn json_marker_data(&self) -> serde_json::Value {
serde_json::json!({ "type": Self::MARKER_TYPE_NAME })
}
}