wasmtime_guest_pprof/
lib.rs1#![forbid(unsafe_code)]
12#![warn(missing_docs)]
13
14use std::sync::Arc;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::thread::{self, JoinHandle};
17use std::time::Duration;
18
19use anyhow::{Context as _, Result, anyhow};
20use firefox_to_pprof::{Builder, FirefoxProfile, FuncTableResolver, SampleWeighting};
21use wasmtime::{Engine, GuestProfiler, Module, Store, UpdateDeadline};
22
23pub struct ProfileSession {
31 inner: Option<GuestProfiler>,
32}
33
34impl ProfileSession {
35 pub fn new(
38 engine: &Engine,
39 name: &str,
40 interval: Duration,
41 modules: impl IntoIterator<Item = (String, Module)>,
42 ) -> Result<Self> {
43 let prof = GuestProfiler::new(engine, name, interval, modules)?;
44 Ok(Self { inner: Some(prof) })
45 }
46
47 pub fn get_mut(&mut self) -> Option<&mut GuestProfiler> {
50 self.inner.as_mut()
51 }
52
53 pub fn take(&mut self) -> Option<GuestProfiler> {
56 self.inner.take()
57 }
58
59 pub fn put_back(&mut self, profiler: GuestProfiler) {
61 self.inner = Some(profiler);
62 }
63
64 pub fn into_pprof(self) -> Result<Vec<u8>> {
67 let mut json = Vec::new();
68 self.into_json(&mut json)?;
69 json_to_pprof(&json)
70 }
71
72 pub fn into_json(self, w: &mut Vec<u8>) -> Result<()> {
74 let profiler = self
75 .inner
76 .ok_or_else(|| anyhow!("profiler was taken but never returned"))?;
77 profiler.finish(w)?;
78 Ok(())
79 }
80}
81
82pub fn json_to_pprof(json: &[u8]) -> Result<Vec<u8>> {
87 let profile: FirefoxProfile =
88 serde_json::from_slice(json).context("parsing Firefox Profiler JSON")?;
89 Builder::new(
90 &profile,
91 FuncTableResolver,
92 SampleWeighting::PerSampleTimeDeltas {
93 default_interval_ns: (profile.meta.interval.max(1.0) * 1_000_000.0) as i64,
94 },
95 )
96 .encode()
97}
98
99pub trait ProfilerHost: Sized {
102 fn profiler(&mut self) -> &mut ProfileSession;
104}
105
106pub trait ProfilerHostExt: ProfilerHost {
108 fn install(store: &mut Store<Self>)
111 where
112 Self: 'static,
113 {
114 store.set_epoch_deadline(1);
115 store.epoch_deadline_callback(|mut ctx| {
116 if let Some(mut prof) = ctx.data_mut().profiler().take() {
117 prof.sample(&ctx, Duration::ZERO);
118 ctx.data_mut().profiler().put_back(prof);
119 }
120 Ok(UpdateDeadline::Continue(1))
121 });
122 }
123
124 fn start_ticker(engine: &Engine, interval: Duration) -> EpochTicker {
128 EpochTicker::start(engine, interval)
129 }
130
131 fn finish_pprof(store: Store<Self>) -> Result<Vec<u8>>
134 where
135 Self: TakeProfileSession,
136 {
137 let session = Self::take_session(store);
138 session.into_pprof()
139 }
140}
141
142impl<T: ProfilerHost> ProfilerHostExt for T {}
143
144pub trait TakeProfileSession: ProfilerHost {
147 fn take_session(store: Store<Self>) -> ProfileSession;
149}
150
151pub struct EpochTicker {
154 stop: Arc<AtomicBool>,
155 handle: Option<JoinHandle<()>>,
156}
157
158impl EpochTicker {
159 pub fn start(engine: &Engine, interval: Duration) -> Self {
162 let stop = Arc::new(AtomicBool::new(false));
163 let stop_flag = stop.clone();
164 let engine = engine.clone();
165 let handle = thread::spawn(move || {
166 while !stop_flag.load(Ordering::Relaxed) {
167 thread::sleep(interval);
168 engine.increment_epoch();
169 }
170 });
171 Self {
172 stop,
173 handle: Some(handle),
174 }
175 }
176}
177
178impl Drop for EpochTicker {
179 fn drop(&mut self) {
180 self.stop.store(true, Ordering::Relaxed);
181 if let Some(h) = self.handle.take() {
182 let _ = h.join();
183 }
184 }
185}