#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use anyhow::{Context as _, Result, anyhow};
use firefox_to_pprof::{Builder, FirefoxProfile, FuncTableResolver, SampleWeighting};
use wasmtime::{Engine, GuestProfiler, Module, Store, UpdateDeadline};
pub struct ProfileSession {
inner: Option<GuestProfiler>,
}
impl ProfileSession {
pub fn new(
engine: &Engine,
name: &str,
interval: Duration,
modules: impl IntoIterator<Item = (String, Module)>,
) -> Result<Self> {
let prof = GuestProfiler::new(engine, name, interval, modules)?;
Ok(Self { inner: Some(prof) })
}
pub fn get_mut(&mut self) -> Option<&mut GuestProfiler> {
self.inner.as_mut()
}
pub fn take(&mut self) -> Option<GuestProfiler> {
self.inner.take()
}
pub fn put_back(&mut self, profiler: GuestProfiler) {
self.inner = Some(profiler);
}
pub fn into_pprof(self) -> Result<Vec<u8>> {
let mut json = Vec::new();
self.into_json(&mut json)?;
json_to_pprof(&json)
}
pub fn into_json(self, w: &mut Vec<u8>) -> Result<()> {
let profiler = self
.inner
.ok_or_else(|| anyhow!("profiler was taken but never returned"))?;
profiler.finish(w)?;
Ok(())
}
}
pub fn json_to_pprof(json: &[u8]) -> Result<Vec<u8>> {
let profile: FirefoxProfile =
serde_json::from_slice(json).context("parsing Firefox Profiler JSON")?;
Builder::new(
&profile,
FuncTableResolver,
SampleWeighting::PerSampleTimeDeltas {
default_interval_ns: (profile.meta.interval.max(1.0) * 1_000_000.0) as i64,
},
)
.encode()
}
pub trait ProfilerHost: Sized {
fn profiler(&mut self) -> &mut ProfileSession;
}
pub trait ProfilerHostExt: ProfilerHost {
fn install(store: &mut Store<Self>)
where
Self: 'static,
{
store.set_epoch_deadline(1);
store.epoch_deadline_callback(|mut ctx| {
if let Some(mut prof) = ctx.data_mut().profiler().take() {
prof.sample(&ctx, Duration::ZERO);
ctx.data_mut().profiler().put_back(prof);
}
Ok(UpdateDeadline::Continue(1))
});
}
fn start_ticker(engine: &Engine, interval: Duration) -> EpochTicker {
EpochTicker::start(engine, interval)
}
fn finish_pprof(store: Store<Self>) -> Result<Vec<u8>>
where
Self: TakeProfileSession,
{
let session = Self::take_session(store);
session.into_pprof()
}
}
impl<T: ProfilerHost> ProfilerHostExt for T {}
pub trait TakeProfileSession: ProfilerHost {
fn take_session(store: Store<Self>) -> ProfileSession;
}
pub struct EpochTicker {
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl EpochTicker {
pub fn start(engine: &Engine, interval: Duration) -> Self {
let stop = Arc::new(AtomicBool::new(false));
let stop_flag = stop.clone();
let engine = engine.clone();
let handle = thread::spawn(move || {
while !stop_flag.load(Ordering::Relaxed) {
thread::sleep(interval);
engine.increment_epoch();
}
});
Self {
stop,
handle: Some(handle),
}
}
}
impl Drop for EpochTicker {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}