#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::runtime::vm::Backtrace;
use crate::{AsContext, CallHook, Module};
use crate::{Engine, prelude::*};
use core::cmp::Ordering;
use fxprof_processed_profile::debugid::DebugId;
use fxprof_processed_profile::{
CategoryHandle, Frame, FrameFlags, FrameInfo, LibraryInfo, MarkerLocations, MarkerTiming,
Profile, ReferenceTimestamp, StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, 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,
marker: CallMarker,
}
#[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(
engine: &Engine,
module_name: &str,
interval: Duration,
modules: impl IntoIterator<Item = (String, Module)>,
) -> Result<Self> {
if engine.tunables().debug_guest {
crate::bail!("Profiling cannot be performed when guest-debugging is enabled.");
}
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)| {
assert!(Engine::same(module.engine(), engine));
let compiled = module.compiled_module();
let text_range = {
let start = compiled.finished_function_ranges().next()?.1.start;
let end = compiled.finished_function_ranges().last()?.1.end;
let start = (module.engine_code().text_range().start + start).raw();
let end = (module.engine_code().text_range().start + end).raw();
start..end
};
module_symbols(name, &module).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();
let marker = CallMarker::new(&mut profile);
Ok(Self {
profile,
modules,
process,
thread,
start,
marker,
})
}
#[cfg(feature = "component-model")]
pub fn new_component(
engine: &Engine,
component_name: &str,
interval: Duration,
component: Component,
extra_modules: impl IntoIterator<Item = (String, Module)>,
) -> Result<Self> {
let modules = component
.static_modules()
.map(|m| (m.name().unwrap_or("<unknown>").to_string(), m.clone()))
.chain(extra_modules);
Self::new(engine, 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);
let stack = self
.profile
.intern_stack_frames(self.thread, frames.into_iter());
self.profile
.add_sample(self.thread, now, stack, 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);
let marker = self.profile.add_marker(
self.thread,
MarkerTiming::IntervalStart(now),
self.marker,
);
let stack = self
.profile
.intern_stack_frames(self.thread, frames.into_iter());
self.profile.set_marker_stack(self.thread, marker, stack);
}
CallHook::ReturningFromHost => {
self.profile
.add_marker(self.thread, MarkerTiming::IntervalEnd(now), self.marker);
}
}
}
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, module: &Module) -> Option<LibraryInfo> {
let compiled = module.compiled_module();
let symbols = Vec::from_iter(
module
.env_module()
.defined_func_indices()
.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(),
});
})
}
#[derive(Debug, Clone, Copy)]
struct CallMarker {
name: StringHandle,
}
impl CallMarker {
fn new(profile: &mut Profile) -> Self {
let name = profile.intern_string(Self::UNIQUE_MARKER_TYPE_NAME);
Self { name }
}
}
impl StaticSchemaMarker for CallMarker {
const UNIQUE_MARKER_TYPE_NAME: &'static str = "hostcall";
const FIELDS: &'static [StaticSchemaMarkerField] = &[];
const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART
.union(MarkerLocations::MARKER_TABLE.union(MarkerLocations::TIMELINE_OVERVIEW));
fn name(&self, _profile: &mut Profile) -> StringHandle {
self.name
}
fn category(&self, _profile: &mut Profile) -> CategoryHandle {
CategoryHandle::OTHER
}
fn string_field_value(&self, _field_index: u32) -> StringHandle {
unreachable!("no fields")
}
fn number_field_value(&self, _field_index: u32) -> f64 {
unreachable!("no fields")
}
}