1use wasmtime::{AsContext, CallHook, Engine, Module};
6
7pub const PROFILE_VARIABLE: &str = "WARP_PROFILE";
10
11#[cfg(feature = "profiling")]
12pub use sampled::Profile;
13
14#[cfg(feature = "profiling")]
15mod sampled {
16 use super::*;
17 use std::path::{Path, PathBuf};
18 use std::sync::atomic::{AtomicUsize, Ordering};
19 use std::sync::{Arc, Mutex};
20 use std::time::Duration;
21 use wasmtime::GuestProfiler;
22
23 #[derive(Clone)]
25 pub struct Profile(Arc<Mutex<RunProfile>>);
26
27 struct RunProfile {
28 profiler: Option<GuestProfiler>,
29 file: PathBuf,
30 sampled_at: Duration,
32 }
33
34 impl Profile {
35 pub fn of(engine: &Engine, module: &Module) -> Option<Profile> {
37 let file = profile_file(&std::env::var(PROFILE_VARIABLE).ok().filter(|file| !file.is_empty())?);
38 let name = module.name().unwrap_or("main").to_string();
39 let interval = crate::engine::EPOCH_TICK;
40 match GuestProfiler::new(engine, &name, interval, [(name.clone(), module.clone())]) {
41 Ok(profiler) => {
42 let sampled_at = crate::engine::thread_cpu_time();
43 Some(Profile(Arc::new(Mutex::new(RunProfile { profiler: Some(profiler), file, sampled_at }))))
44 }
45 Err(failure) => {
46 eprintln!("{PROFILE_VARIABLE}: no profile of {name}: {failure}");
47 None
48 }
49 }
50 }
51
52 pub fn sample(&self, store: impl AsContext) {
53 let mut run = self.0.lock().unwrap();
54 let now = crate::engine::thread_cpu_time();
55 let delta = now.saturating_sub(run.sampled_at);
56 run.sampled_at = now;
57 if let Some(profiler) = run.profiler.as_mut() {
58 profiler.sample(store, delta);
59 }
60 }
61
62 pub fn call_hook(&self, store: impl AsContext, hook: CallHook) {
63 if let Some(profiler) = self.0.lock().unwrap().profiler.as_mut() {
64 profiler.call_hook(store, hook);
65 }
66 }
67 }
68
69 impl Drop for RunProfile {
70 fn drop(&mut self) {
71 let Some(profiler) = self.profiler.take() else { return };
72 let written = std::fs::File::create(&self.file)
73 .map_err(wasmtime::Error::from)
74 .and_then(|file| profiler.finish(std::io::BufWriter::new(file)));
75 match written {
76 Ok(()) => eprintln!("profile: {} (open it at https://profiler.firefox.com)", self.file.display()),
77 Err(failure) => eprintln!("{PROFILE_VARIABLE}: {} not written: {failure}", self.file.display()),
78 }
79 }
80 }
81
82 fn profile_file(file: &str) -> PathBuf {
84 static PROFILES: AtomicUsize = AtomicUsize::new(0);
85 let number = PROFILES.fetch_add(1, Ordering::Relaxed) + 1;
86 let file = Path::new(file);
87 if number == 1 {
88 return file.to_path_buf();
89 }
90 let stem = file.file_stem().unwrap_or_default().to_string_lossy();
91 let numbered = match file.extension() {
92 Some(extension) => format!("{stem}-{number}.{}", extension.to_string_lossy()),
93 None => format!("{stem}-{number}"),
94 };
95 file.with_file_name(numbered)
96 }
97}
98
99#[cfg(not(feature = "profiling"))]
101#[derive(Clone)]
102pub enum Profile {}
103
104#[cfg(not(feature = "profiling"))]
105impl Profile {
106 pub fn of(_engine: &Engine, _module: &Module) -> Option<Profile> {
107 if std::env::var_os(PROFILE_VARIABLE).is_some() {
108 eprintln!("{PROFILE_VARIABLE}: this build has no profiler (warp-runtime feature profiling)");
109 }
110 None
111 }
112
113 pub fn sample(&self, _store: impl AsContext) {
114 match *self {}
115 }
116
117 pub fn call_hook(&self, _store: impl AsContext, _hook: CallHook) {
118 match *self {}
119 }
120}