1c3177fc4SPaul Osborne #[cfg(feature = "component-model")]
2c3177fc4SPaul Osborne use crate::component::Component;
372004aadSNick Fitzgerald use crate::runtime::vm::Backtrace;
42d467e0fSJamey Sharp use crate::{AsContext, CallHook, Module};
599ecf728SChris Fallin use crate::{Engine, prelude::*};
6c3177fc4SPaul Osborne use core::cmp::Ordering;
72d467e0fSJamey Sharp use fxprof_processed_profile::debugid::DebugId;
8d4242001SAdam Bratschi-Kaye use fxprof_processed_profile::{
9e0af5d7fSPat Hickey     CategoryHandle, Frame, FrameFlags, FrameInfo, LibraryInfo, MarkerLocations, MarkerTiming,
10e0af5d7fSPat Hickey     Profile, ReferenceTimestamp, StaticSchemaMarker, StaticSchemaMarkerField, StringHandle, Symbol,
11e0af5d7fSPat Hickey     SymbolTable, Timestamp,
12d4242001SAdam Bratschi-Kaye };
13d4242001SAdam Bratschi-Kaye use std::ops::Range;
14d4242001SAdam Bratschi-Kaye use std::sync::Arc;
15d4242001SAdam Bratschi-Kaye use std::time::{Duration, Instant};
1613bbd6a2SYaodong Yang use wasmtime_environ::demangle_function_name_or_index;
17d4242001SAdam Bratschi-Kaye 
18d4242001SAdam Bratschi-Kaye // TODO: collect more data
19d4242001SAdam Bratschi-Kaye // - On non-Windows, measure thread-local CPU usage between events with
20d4242001SAdam Bratschi-Kaye //   rustix::time::clock_gettime(ClockId::ThreadCPUTime)
21d4242001SAdam Bratschi-Kaye // - Report which wasm module, and maybe instance, each frame came from
22d4242001SAdam Bratschi-Kaye 
23d4242001SAdam Bratschi-Kaye /// Collects basic profiling data for a single WebAssembly guest.
24d4242001SAdam Bratschi-Kaye ///
25d4242001SAdam Bratschi-Kaye /// This profiler can't provide measurements that are as accurate or detailed
26d4242001SAdam Bratschi-Kaye /// as a platform-specific profiler, such as `perf` on Linux. On the other
27d4242001SAdam Bratschi-Kaye /// hand, this profiler works on every platform that Wasmtime supports. Also,
28d4242001SAdam Bratschi-Kaye /// as an embedder you can use this profiler selectively on individual guest
29d4242001SAdam Bratschi-Kaye /// instances rather than profiling the entire process.
30d4242001SAdam Bratschi-Kaye ///
31d4242001SAdam Bratschi-Kaye /// To use this, you'll need to arrange to call [`GuestProfiler::sample`] at
32d4242001SAdam Bratschi-Kaye /// regular intervals while the guest is on the stack. The most straightforward
33d4242001SAdam Bratschi-Kaye /// way to do that is to call it from a callback registered with
34d4242001SAdam Bratschi-Kaye /// [`Store::epoch_deadline_callback()`](crate::Store::epoch_deadline_callback).
35d4242001SAdam Bratschi-Kaye ///
36d4242001SAdam Bratschi-Kaye /// # Accuracy
37d4242001SAdam Bratschi-Kaye ///
38d4242001SAdam Bratschi-Kaye /// The data collection granularity is limited by the mechanism you use to
39d4242001SAdam Bratschi-Kaye /// interrupt guest execution and collect a profiling sample.
40d4242001SAdam Bratschi-Kaye ///
41d4242001SAdam Bratschi-Kaye /// If you use epoch interruption, then samples will only be collected at
42d4242001SAdam Bratschi-Kaye /// function entry points and loop headers. This introduces some bias to the
43d4242001SAdam Bratschi-Kaye /// results. In addition, samples will only be taken at times when WebAssembly
44d4242001SAdam Bratschi-Kaye /// functions are running, not during host-calls.
45d4242001SAdam Bratschi-Kaye ///
46d4242001SAdam Bratschi-Kaye /// It is technically possible to use fuel interruption instead. That
47d4242001SAdam Bratschi-Kaye /// introduces worse bias since samples occur after a certain number of
48d4242001SAdam Bratschi-Kaye /// WebAssembly instructions, which can take different amounts of time.
49d4242001SAdam Bratschi-Kaye ///
50d4242001SAdam Bratschi-Kaye /// You may instead be able to use platform-specific methods, such as
51d4242001SAdam Bratschi-Kaye /// `setitimer(ITIMER_VIRTUAL, ...)` on POSIX-compliant systems, to sample on
52d4242001SAdam Bratschi-Kaye /// a more accurate interval. The only current requirement is that the guest
53d4242001SAdam Bratschi-Kaye /// you wish to profile must be on the same stack where you call `sample`,
54d4242001SAdam Bratschi-Kaye /// and executing within the same thread. However, the `GuestProfiler::sample`
55d4242001SAdam Bratschi-Kaye /// method is not currently async-signal-safe, so doing this correctly is not
56d4242001SAdam Bratschi-Kaye /// easy.
57d4242001SAdam Bratschi-Kaye ///
58d4242001SAdam Bratschi-Kaye /// # Security
59d4242001SAdam Bratschi-Kaye ///
60d4242001SAdam Bratschi-Kaye /// Profiles produced using this profiler do not include any configuration
61d4242001SAdam Bratschi-Kaye /// details from the host, such as virtual memory addresses, or from any
62d4242001SAdam Bratschi-Kaye /// WebAssembly modules that you haven't specifically allowed. So for
63d4242001SAdam Bratschi-Kaye /// example, these profiles should be safe to share with untrusted users
64d4242001SAdam Bratschi-Kaye /// who have provided untrusted code that you are running in a multi-tenancy
65d4242001SAdam Bratschi-Kaye /// environment.
66d4242001SAdam Bratschi-Kaye ///
67d4242001SAdam Bratschi-Kaye /// However, the profile does include byte offsets into the text section of
68d4242001SAdam Bratschi-Kaye /// the compiled module, revealing some information about the size of the code
69d4242001SAdam Bratschi-Kaye /// generated for each module. For user-provided modules, the user could get
70d4242001SAdam Bratschi-Kaye /// the same information by compiling the module for themself using a similar
71d4242001SAdam Bratschi-Kaye /// version of Wasmtime on the same target architecture, but for any module
72d4242001SAdam Bratschi-Kaye /// where they don't already have the WebAssembly module binary available this
73d4242001SAdam Bratschi-Kaye /// could theoretically lead to an undesirable information disclosure. So you
74d4242001SAdam Bratschi-Kaye /// should only include user-provided modules in profiles.
75d4242001SAdam Bratschi-Kaye #[derive(Debug)]
76d4242001SAdam Bratschi-Kaye pub struct GuestProfiler {
77d4242001SAdam Bratschi-Kaye     profile: Profile,
782d467e0fSJamey Sharp     modules: Modules,
79d4242001SAdam Bratschi-Kaye     process: fxprof_processed_profile::ProcessHandle,
80d4242001SAdam Bratschi-Kaye     thread: fxprof_processed_profile::ThreadHandle,
81d4242001SAdam Bratschi-Kaye     start: Instant,
82e0af5d7fSPat Hickey     marker: CallMarker,
83d4242001SAdam Bratschi-Kaye }
84d4242001SAdam Bratschi-Kaye 
85c3177fc4SPaul Osborne #[derive(Debug)]
86c3177fc4SPaul Osborne struct ProfiledModule {
87c3177fc4SPaul Osborne     module: Module,
88c3177fc4SPaul Osborne     fxprof_libhandle: fxprof_processed_profile::LibraryHandle,
89c3177fc4SPaul Osborne     text_range: Range<usize>,
90c3177fc4SPaul Osborne }
91c3177fc4SPaul Osborne 
92c3177fc4SPaul Osborne type Modules = Vec<ProfiledModule>;
932d467e0fSJamey Sharp 
94d4242001SAdam Bratschi-Kaye impl GuestProfiler {
95d4242001SAdam Bratschi-Kaye     /// Begin profiling a new guest. When this function is called, the current
96d4242001SAdam Bratschi-Kaye     /// wall-clock time is recorded as the start time for the guest.
97d4242001SAdam Bratschi-Kaye     ///
98d4242001SAdam Bratschi-Kaye     /// The `module_name` parameter is recorded in the profile to help identify
99d4242001SAdam Bratschi-Kaye     /// where the profile came from.
100d4242001SAdam Bratschi-Kaye     ///
101d4242001SAdam Bratschi-Kaye     /// The `interval` parameter should match the rate at which you intend
102d4242001SAdam Bratschi-Kaye     /// to call `sample`. However, this is used as a hint and not required to
103d4242001SAdam Bratschi-Kaye     /// exactly match the real sample rate.
104d4242001SAdam Bratschi-Kaye     ///
105d4242001SAdam Bratschi-Kaye     /// Only modules which are present in the `modules` vector will appear in
106d4242001SAdam Bratschi-Kaye     /// stack traces in this profile. Any stack frames which were executing
107d4242001SAdam Bratschi-Kaye     /// host code or functions from other modules will be omitted. See the
108d4242001SAdam Bratschi-Kaye     /// "Security" section of the [`GuestProfiler`] documentation for guidance
109d4242001SAdam Bratschi-Kaye     /// on what modules should not be included in this list.
new( engine: &Engine, module_name: &str, interval: Duration, modules: impl IntoIterator<Item = (String, Module)>, ) -> Result<Self>110c3177fc4SPaul Osborne     pub fn new(
11199ecf728SChris Fallin         engine: &Engine,
112c3177fc4SPaul Osborne         module_name: &str,
113c3177fc4SPaul Osborne         interval: Duration,
114c3177fc4SPaul Osborne         modules: impl IntoIterator<Item = (String, Module)>,
11599ecf728SChris Fallin     ) -> Result<Self> {
11699ecf728SChris Fallin         // Check that guest debugging is not enabled. The
11799ecf728SChris Fallin         // instrumentation would make profiling results unreliable,
11899ecf728SChris Fallin         // but more fundamentally, it means that code is cloned per
11999ecf728SChris Fallin         // instantiation (for breakpoint patching) so the logic below
12099ecf728SChris Fallin         // is incorrect.
12199ecf728SChris Fallin         if engine.tunables().debug_guest {
122*96e19700SNick Fitzgerald             crate::bail!("Profiling cannot be performed when guest-debugging is enabled.");
12399ecf728SChris Fallin         }
12499ecf728SChris Fallin 
125d4242001SAdam Bratschi-Kaye         let zero = ReferenceTimestamp::from_millis_since_unix_epoch(0.0);
126d4242001SAdam Bratschi-Kaye         let mut profile = Profile::new(module_name, zero, interval.into());
127d4242001SAdam Bratschi-Kaye 
128c3177fc4SPaul Osborne         // Past this point, we just need to think about modules as we pull out
129c3177fc4SPaul Osborne         // the disparate module information from components.
130d4242001SAdam Bratschi-Kaye         let mut modules: Vec<_> = modules
131d4242001SAdam Bratschi-Kaye             .into_iter()
132d4242001SAdam Bratschi-Kaye             .filter_map(|(name, module)| {
13399ecf728SChris Fallin                 assert!(Engine::same(module.engine(), engine));
134d4242001SAdam Bratschi-Kaye                 let compiled = module.compiled_module();
135c3177fc4SPaul Osborne                 let text_range = {
136c3177fc4SPaul Osborne                     // Assumption: within text, the code for a given module is packed linearly and
137c3177fc4SPaul Osborne                     // is non-overlapping; if this is violated, it should be safe but might result
138c3177fc4SPaul Osborne                     // in incorrect profiling results.
13999ecf728SChris Fallin                     let start = compiled.finished_function_ranges().next()?.1.start;
14099ecf728SChris Fallin                     let end = compiled.finished_function_ranges().last()?.1.end;
14199ecf728SChris Fallin 
14299ecf728SChris Fallin                     let start = (module.engine_code().text_range().start + start).raw();
14399ecf728SChris Fallin                     let end = (module.engine_code().text_range().start + end).raw();
144c3177fc4SPaul Osborne                     start..end
145c3177fc4SPaul Osborne                 };
146c3177fc4SPaul Osborne 
14799ecf728SChris Fallin                 module_symbols(name, &module).map(|lib| {
148c3177fc4SPaul Osborne                     let libhandle = profile.add_lib(lib);
149c3177fc4SPaul Osborne                     ProfiledModule {
150c3177fc4SPaul Osborne                         module,
151c3177fc4SPaul Osborne                         fxprof_libhandle: libhandle,
152c3177fc4SPaul Osborne                         text_range,
153c3177fc4SPaul Osborne                     }
154c3177fc4SPaul Osborne                 })
155d4242001SAdam Bratschi-Kaye             })
156d4242001SAdam Bratschi-Kaye             .collect();
157d4242001SAdam Bratschi-Kaye 
158c3177fc4SPaul Osborne         modules.sort_unstable_by_key(|m| m.text_range.start);
159d4242001SAdam Bratschi-Kaye 
160d4242001SAdam Bratschi-Kaye         profile.set_reference_timestamp(std::time::SystemTime::now().into());
161d4242001SAdam Bratschi-Kaye         let process = profile.add_process(module_name, 0, Timestamp::from_nanos_since_reference(0));
162d4242001SAdam Bratschi-Kaye         let thread = profile.add_thread(process, 0, Timestamp::from_nanos_since_reference(0), true);
163d4242001SAdam Bratschi-Kaye         let start = Instant::now();
164e0af5d7fSPat Hickey         let marker = CallMarker::new(&mut profile);
16599ecf728SChris Fallin         Ok(Self {
166d4242001SAdam Bratschi-Kaye             profile,
167d4242001SAdam Bratschi-Kaye             modules,
168d4242001SAdam Bratschi-Kaye             process,
169d4242001SAdam Bratschi-Kaye             thread,
170d4242001SAdam Bratschi-Kaye             start,
171e0af5d7fSPat Hickey             marker,
17299ecf728SChris Fallin         })
173d4242001SAdam Bratschi-Kaye     }
174d4242001SAdam Bratschi-Kaye 
175c3177fc4SPaul Osborne     /// Create a new profiler for the provided component
176c3177fc4SPaul Osborne     ///
177c3177fc4SPaul Osborne     /// See [`GuestProfiler::new`] for additional information; this function
178c3177fc4SPaul Osborne     /// works identically except that it takes a component and sets up
179c3177fc4SPaul Osborne     /// instrumentation to track calls in each of its constituent modules.
180c3177fc4SPaul Osborne     #[cfg(feature = "component-model")]
new_component( engine: &Engine, component_name: &str, interval: Duration, component: Component, extra_modules: impl IntoIterator<Item = (String, Module)>, ) -> Result<Self>181c3177fc4SPaul Osborne     pub fn new_component(
18299ecf728SChris Fallin         engine: &Engine,
183c3177fc4SPaul Osborne         component_name: &str,
184c3177fc4SPaul Osborne         interval: Duration,
185c3177fc4SPaul Osborne         component: Component,
186c3177fc4SPaul Osborne         extra_modules: impl IntoIterator<Item = (String, Module)>,
18799ecf728SChris Fallin     ) -> Result<Self> {
188c3177fc4SPaul Osborne         let modules = component
189c3177fc4SPaul Osborne             .static_modules()
190c3177fc4SPaul Osborne             .map(|m| (m.name().unwrap_or("<unknown>").to_string(), m.clone()))
191c3177fc4SPaul Osborne             .chain(extra_modules);
19299ecf728SChris Fallin         Self::new(engine, component_name, interval, modules)
193c3177fc4SPaul Osborne     }
194c3177fc4SPaul Osborne 
195d4242001SAdam Bratschi-Kaye     /// Add a sample to the profile. This function collects a backtrace from
196d4242001SAdam Bratschi-Kaye     /// any stack frames for allowed modules on the current stack. It should
197d4242001SAdam Bratschi-Kaye     /// typically be called from a callback registered using
198d4242001SAdam Bratschi-Kaye     /// [`Store::epoch_deadline_callback()`](crate::Store::epoch_deadline_callback).
1997d0bdcceSMilek7     ///
2007d0bdcceSMilek7     /// The `delta` parameter is the amount of CPU time that was used by this
2017d0bdcceSMilek7     /// guest since the previous sample. It is allowed to pass `Duration::ZERO`
2027d0bdcceSMilek7     /// here if recording CPU usage information is not needed.
sample(&mut self, store: impl AsContext, delta: Duration)2037d0bdcceSMilek7     pub fn sample(&mut self, store: impl AsContext, delta: Duration) {
204d4242001SAdam Bratschi-Kaye         let now = Timestamp::from_nanos_since_reference(
205d4242001SAdam Bratschi-Kaye             self.start.elapsed().as_nanos().try_into().unwrap(),
206d4242001SAdam Bratschi-Kaye         );
20707eeac40SAlex Crichton         let backtrace = Backtrace::new(store.as_context().0);
2082d467e0fSJamey Sharp         let frames = lookup_frames(&self.modules, &backtrace);
209e0af5d7fSPat Hickey         let stack = self
210e0af5d7fSPat Hickey             .profile
211e0af5d7fSPat Hickey             .intern_stack_frames(self.thread, frames.into_iter());
212d4242001SAdam Bratschi-Kaye         self.profile
213e0af5d7fSPat Hickey             .add_sample(self.thread, now, stack, delta.into(), 1);
214d4242001SAdam Bratschi-Kaye     }
215d4242001SAdam Bratschi-Kaye 
2162d467e0fSJamey Sharp     /// Add a marker for transitions between guest and host to the profile.
2172d467e0fSJamey Sharp     /// This function should typically be called from a callback registered
2182d467e0fSJamey Sharp     /// using [`Store::call_hook()`](crate::Store::call_hook), and the `kind`
2192d467e0fSJamey Sharp     /// parameter should be the value of the same type passed into that hook.
call_hook(&mut self, store: impl AsContext, kind: CallHook)2202d467e0fSJamey Sharp     pub fn call_hook(&mut self, store: impl AsContext, kind: CallHook) {
2212d467e0fSJamey Sharp         let now = Timestamp::from_nanos_since_reference(
2222d467e0fSJamey Sharp             self.start.elapsed().as_nanos().try_into().unwrap(),
2232d467e0fSJamey Sharp         );
2242d467e0fSJamey Sharp         match kind {
2252d467e0fSJamey Sharp             CallHook::CallingWasm | CallHook::ReturningFromWasm => {}
2262d467e0fSJamey Sharp             CallHook::CallingHost => {
22707eeac40SAlex Crichton                 let backtrace = Backtrace::new(store.as_context().0);
2282d467e0fSJamey Sharp                 let frames = lookup_frames(&self.modules, &backtrace);
229e0af5d7fSPat Hickey                 let marker = self.profile.add_marker(
2302d467e0fSJamey Sharp                     self.thread,
2312d467e0fSJamey Sharp                     MarkerTiming::IntervalStart(now),
232e0af5d7fSPat Hickey                     self.marker,
2332d467e0fSJamey Sharp                 );
234e0af5d7fSPat Hickey                 let stack = self
235e0af5d7fSPat Hickey                     .profile
236e0af5d7fSPat Hickey                     .intern_stack_frames(self.thread, frames.into_iter());
237e0af5d7fSPat Hickey                 self.profile.set_marker_stack(self.thread, marker, stack);
2382d467e0fSJamey Sharp             }
2392d467e0fSJamey Sharp             CallHook::ReturningFromHost => {
240e0af5d7fSPat Hickey                 self.profile
241e0af5d7fSPat Hickey                     .add_marker(self.thread, MarkerTiming::IntervalEnd(now), self.marker);
2422d467e0fSJamey Sharp             }
2432d467e0fSJamey Sharp         }
2442d467e0fSJamey Sharp     }
2452d467e0fSJamey Sharp 
246d4242001SAdam Bratschi-Kaye     /// When the guest finishes running, call this function to write the
247d4242001SAdam Bratschi-Kaye     /// profile to the given `output`. The output is a JSON-formatted object in
248d4242001SAdam Bratschi-Kaye     /// the [Firefox "processed profile format"][fmt]. Files in this format may
249d4242001SAdam Bratschi-Kaye     /// be visualized at <https://profiler.firefox.com/>.
250d4242001SAdam Bratschi-Kaye     ///
251d4242001SAdam Bratschi-Kaye     /// [fmt]: https://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md
finish(mut self, output: impl std::io::Write) -> Result<()>252d4242001SAdam Bratschi-Kaye     pub fn finish(mut self, output: impl std::io::Write) -> Result<()> {
253d4242001SAdam Bratschi-Kaye         let now = Timestamp::from_nanos_since_reference(
254d4242001SAdam Bratschi-Kaye             self.start.elapsed().as_nanos().try_into().unwrap(),
255d4242001SAdam Bratschi-Kaye         );
256d4242001SAdam Bratschi-Kaye         self.profile.set_thread_end_time(self.thread, now);
257d4242001SAdam Bratschi-Kaye         self.profile.set_process_end_time(self.process, now);
258d4242001SAdam Bratschi-Kaye 
259d4242001SAdam Bratschi-Kaye         serde_json::to_writer(output, &self.profile)?;
260d4242001SAdam Bratschi-Kaye         Ok(())
261d4242001SAdam Bratschi-Kaye     }
262d4242001SAdam Bratschi-Kaye }
263d4242001SAdam Bratschi-Kaye 
module_symbols(name: String, module: &Module) -> Option<LibraryInfo>26499ecf728SChris Fallin fn module_symbols(name: String, module: &Module) -> Option<LibraryInfo> {
26599ecf728SChris Fallin     let compiled = module.compiled_module();
26699ecf728SChris Fallin     let symbols = Vec::from_iter(
26799ecf728SChris Fallin         module
26899ecf728SChris Fallin             .env_module()
26999ecf728SChris Fallin             .defined_func_indices()
27099ecf728SChris Fallin             .map(|defined_idx| {
271d4242001SAdam Bratschi-Kaye                 let loc = compiled.func_loc(defined_idx);
272d4242001SAdam Bratschi-Kaye                 let func_idx = compiled.module().func_index(defined_idx);
273d4242001SAdam Bratschi-Kaye                 let mut name = String::new();
27413bbd6a2SYaodong Yang                 demangle_function_name_or_index(
27513bbd6a2SYaodong Yang                     &mut name,
27613bbd6a2SYaodong Yang                     compiled.func_name(func_idx),
27713bbd6a2SYaodong Yang                     defined_idx.as_u32() as usize,
27813bbd6a2SYaodong Yang                 )
27913bbd6a2SYaodong Yang                 .unwrap();
280d4242001SAdam Bratschi-Kaye                 Symbol {
281d4242001SAdam Bratschi-Kaye                     address: loc.start,
282d4242001SAdam Bratschi-Kaye                     size: Some(loc.length),
283d4242001SAdam Bratschi-Kaye                     name,
284d4242001SAdam Bratschi-Kaye                 }
28599ecf728SChris Fallin             }),
28699ecf728SChris Fallin     );
287d4242001SAdam Bratschi-Kaye     if symbols.is_empty() {
288d4242001SAdam Bratschi-Kaye         return None;
289d4242001SAdam Bratschi-Kaye     }
290d4242001SAdam Bratschi-Kaye 
291d4242001SAdam Bratschi-Kaye     Some(LibraryInfo {
292d4242001SAdam Bratschi-Kaye         name,
293d4242001SAdam Bratschi-Kaye         debug_name: String::new(),
294d4242001SAdam Bratschi-Kaye         path: String::new(),
295d4242001SAdam Bratschi-Kaye         debug_path: String::new(),
296d4242001SAdam Bratschi-Kaye         debug_id: DebugId::nil(),
297d4242001SAdam Bratschi-Kaye         code_id: None,
298d4242001SAdam Bratschi-Kaye         arch: None,
299d4242001SAdam Bratschi-Kaye         symbol_table: Some(Arc::new(SymbolTable::new(symbols))),
300d4242001SAdam Bratschi-Kaye     })
301d4242001SAdam Bratschi-Kaye }
3022d467e0fSJamey Sharp 
lookup_frames<'a>( modules: &'a Modules, backtrace: &'a Backtrace, ) -> impl Iterator<Item = FrameInfo> + 'a3032d467e0fSJamey Sharp fn lookup_frames<'a>(
3042d467e0fSJamey Sharp     modules: &'a Modules,
3052d467e0fSJamey Sharp     backtrace: &'a Backtrace,
3062d467e0fSJamey Sharp ) -> impl Iterator<Item = FrameInfo> + 'a {
3072d467e0fSJamey Sharp     backtrace
3082d467e0fSJamey Sharp         .frames()
3092d467e0fSJamey Sharp         // Samply needs to see the oldest frame first, but we list the newest
3102d467e0fSJamey Sharp         // first, so iterate in reverse.
3112d467e0fSJamey Sharp         .rev()
3122d467e0fSJamey Sharp         .filter_map(|frame| {
313c3177fc4SPaul Osborne             let idx = modules
314c3177fc4SPaul Osborne                 .binary_search_by(|probe| {
315c3177fc4SPaul Osborne                     if probe.text_range.contains(&frame.pc()) {
316c3177fc4SPaul Osborne                         Ordering::Equal
317c3177fc4SPaul Osborne                     } else {
318c3177fc4SPaul Osborne                         probe.text_range.start.cmp(&frame.pc())
319c3177fc4SPaul Osborne                     }
320c3177fc4SPaul Osborne                 })
321c3177fc4SPaul Osborne                 .ok()?;
322c3177fc4SPaul Osborne             let module = modules.get(idx)?;
323c3177fc4SPaul Osborne 
324c3177fc4SPaul Osborne             // We need to point to the modules full text (not just its functions) as
325c3177fc4SPaul Osborne             // the offset for the final phase; these can be different for component
326c3177fc4SPaul Osborne             // model modules.
327c3177fc4SPaul Osborne             let module_text_start = module.module.text().as_ptr_range().start as usize;
3282d467e0fSJamey Sharp             return Some(FrameInfo {
3292d467e0fSJamey Sharp                 frame: Frame::RelativeAddressFromReturnAddress(
330c3177fc4SPaul Osborne                     module.fxprof_libhandle,
331c3177fc4SPaul Osborne                     u32::try_from(frame.pc() - module_text_start).unwrap(),
3322d467e0fSJamey Sharp                 ),
3332d467e0fSJamey Sharp                 category_pair: CategoryHandle::OTHER.into(),
3342d467e0fSJamey Sharp                 flags: FrameFlags::empty(),
3352d467e0fSJamey Sharp             });
3362d467e0fSJamey Sharp         })
3372d467e0fSJamey Sharp }
3382d467e0fSJamey Sharp 
339e0af5d7fSPat Hickey #[derive(Debug, Clone, Copy)]
340e0af5d7fSPat Hickey struct CallMarker {
341e0af5d7fSPat Hickey     name: StringHandle,
342e0af5d7fSPat Hickey }
3432d467e0fSJamey Sharp 
344e0af5d7fSPat Hickey impl CallMarker {
new(profile: &mut Profile) -> Self345e0af5d7fSPat Hickey     fn new(profile: &mut Profile) -> Self {
346e0af5d7fSPat Hickey         let name = profile.intern_string(Self::UNIQUE_MARKER_TYPE_NAME);
347e0af5d7fSPat Hickey         Self { name }
3482d467e0fSJamey Sharp     }
3492d467e0fSJamey Sharp }
3502d467e0fSJamey Sharp 
351e0af5d7fSPat Hickey impl StaticSchemaMarker for CallMarker {
352e0af5d7fSPat Hickey     const UNIQUE_MARKER_TYPE_NAME: &'static str = "hostcall";
353e0af5d7fSPat Hickey     const FIELDS: &'static [StaticSchemaMarkerField] = &[];
354e0af5d7fSPat Hickey     const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART
355e0af5d7fSPat Hickey         .union(MarkerLocations::MARKER_TABLE.union(MarkerLocations::TIMELINE_OVERVIEW));
356e0af5d7fSPat Hickey 
name(&self, _profile: &mut Profile) -> StringHandle357e0af5d7fSPat Hickey     fn name(&self, _profile: &mut Profile) -> StringHandle {
358e0af5d7fSPat Hickey         self.name
359e0af5d7fSPat Hickey     }
category(&self, _profile: &mut Profile) -> CategoryHandle360e0af5d7fSPat Hickey     fn category(&self, _profile: &mut Profile) -> CategoryHandle {
361e0af5d7fSPat Hickey         CategoryHandle::OTHER
362e0af5d7fSPat Hickey     }
string_field_value(&self, _field_index: u32) -> StringHandle363e0af5d7fSPat Hickey     fn string_field_value(&self, _field_index: u32) -> StringHandle {
364e0af5d7fSPat Hickey         unreachable!("no fields")
365e0af5d7fSPat Hickey     }
number_field_value(&self, _field_index: u32) -> f64366e0af5d7fSPat Hickey     fn number_field_value(&self, _field_index: u32) -> f64 {
367e0af5d7fSPat Hickey         unreachable!("no fields")
3682d467e0fSJamey Sharp     }
3692d467e0fSJamey Sharp }
370