1 //! Low-level support for profiling pulley.
2 //!
3 //! This is used in conjunction with the `profiler-html.rs` example with Pulley
4 //! and the `pulley.rs` ProfilingAgent in Wasmtime.
5
6 use anyhow::{Context, Result, anyhow, bail};
7 use std::fs::{File, OpenOptions};
8 use std::io::{BufWriter, Write};
9 use std::sync::Arc;
10 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
11 use std::vec::Vec;
12
13 // Header markers for sections in the binary `*.data` file.
14
15 /// Section of the `*.data` file which looks like:
16 ///
17 /// ```text
18 /// * byte: ID_FUNCTION
19 /// * addr: 8-byte little-endian address that this body was located at
20 /// * name_len: 4-byte little-endian byte length of `name`
21 /// * name: contents of the name of the function
22 /// * body_len: 4-byte little-endian byte length of `body`
23 /// * body: contents of the body of the function
24 /// ```
25 const ID_FUNCTION: u8 = 1;
26
27 /// Section of the `*.data` file which looks like:
28 ///
29 /// ```text
30 /// * byte: ID_SAMPLES
31 /// * sample_len: 4-byte little-endian element count of `samples`
32 /// * samples: sequence of 8-byte little endian addresses
33 /// ```
34 const ID_SAMPLES: u8 = 2;
35
36 /// Representation of a currently executing program counter of an interpreter.
37 ///
38 /// Stores an `Arc` internally that is safe to clone/read from other threads.
39 #[derive(Default, Clone)]
40 pub struct ExecutingPc(Arc<ExecutingPcState>);
41
42 #[derive(Default)]
43 struct ExecutingPcState {
44 current_pc: AtomicUsize,
45 done: AtomicBool,
46 }
47
48 impl ExecutingPc {
as_ref(&self) -> ExecutingPcRef<'_>49 pub(crate) fn as_ref(&self) -> ExecutingPcRef<'_> {
50 ExecutingPcRef(&self.0.current_pc)
51 }
52
53 /// Loads the currently executing program counter, if the interpreter is
54 /// running.
get(&self) -> Option<usize>55 pub fn get(&self) -> Option<usize> {
56 match self.0.current_pc.load(Relaxed) {
57 0 => None,
58 n => Some(n),
59 }
60 }
61
62 /// Returns whether the interpreter has been destroyed and will no longer
63 /// execute any code.
is_done(&self) -> bool64 pub fn is_done(&self) -> bool {
65 self.0.done.load(Relaxed)
66 }
67
set_done(&self)68 pub(crate) fn set_done(&self) {
69 self.0.done.store(true, Relaxed)
70 }
71 }
72
73 #[derive(Copy, Clone)]
74 #[repr(transparent)]
75 pub(crate) struct ExecutingPcRef<'a>(&'a AtomicUsize);
76
77 impl ExecutingPcRef<'_> {
record(&self, pc: usize)78 pub(crate) fn record(&self, pc: usize) {
79 self.0.store(pc, Relaxed);
80 }
81 }
82
83 /// Utility to record profiling information to a file.
84 pub struct Recorder {
85 /// The buffered writer used to write profiling data. Note that this is
86 /// buffered to amortize the cost of writing out information to the
87 /// filesystem to help avoid profiling overhead.
88 file: BufWriter<File>,
89 }
90
91 impl Recorder {
92 /// Creates a new recorder which will write to the specified filename.
new(filename: &str) -> Result<Recorder>93 pub fn new(filename: &str) -> Result<Recorder> {
94 Ok(Recorder {
95 file: BufWriter::new(
96 OpenOptions::new()
97 .write(true)
98 .create_new(true)
99 .open(filename)
100 .with_context(|| format!("failed to open `{filename}` for writing"))?,
101 ),
102 })
103 }
104
105 /// Adds a new function that may be sampled in the future.
106 ///
107 /// This must be given `code` where it resides and will be executed in the
108 /// host address space.
add_function(&mut self, name: &str, code: &[u8]) -> Result<()>109 pub fn add_function(&mut self, name: &str, code: &[u8]) -> Result<()> {
110 self.file.write_all(&[ID_FUNCTION])?;
111 self.file
112 .write_all(&u64::try_from(code.as_ptr() as usize)?.to_le_bytes())?;
113 self.file
114 .write_all(&u32::try_from(name.len())?.to_le_bytes())?;
115 self.file.write_all(name.as_bytes())?;
116 self.file
117 .write_all(&u32::try_from(code.len())?.to_le_bytes())?;
118 self.file.write_all(code)?;
119 Ok(())
120 }
121
122 /// Adds a new set of samples to this recorded.
add_samples(&mut self, samples: &mut Samples) -> Result<()>123 pub fn add_samples(&mut self, samples: &mut Samples) -> Result<()> {
124 self.file.write_all(&[ID_SAMPLES])?;
125
126 samples.finalize();
127 self.file.write_all(&samples.data)?;
128 samples.reset();
129 Ok(())
130 }
131
132 /// Flushes out all pending data to the filesystem.
flush(&mut self) -> Result<()>133 pub fn flush(&mut self) -> Result<()> {
134 self.file.flush()?;
135 Ok(())
136 }
137 }
138
139 /// A set of samples of program counters that have been collected over time.
140 pub struct Samples {
141 data: Vec<u8>,
142 samples: u32,
143 }
144
145 impl Samples {
146 /// Adds a new program counter to this sample.
append(&mut self, sample: usize)147 pub fn append(&mut self, sample: usize) {
148 self.data.extend_from_slice(&(sample as u64).to_le_bytes());
149 self.samples += 1;
150 }
151
152 /// Returns the number of samples that have been collected.
num_samples(&self) -> u32153 pub fn num_samples(&self) -> u32 {
154 self.samples
155 }
156
finalize(&mut self)157 fn finalize(&mut self) {
158 self.data[..4].copy_from_slice(&self.samples.to_le_bytes());
159 }
160
reset(&mut self)161 fn reset(&mut self) {
162 self.data.truncate(0);
163 self.data.extend_from_slice(&[0; 4]);
164 self.samples = 0;
165 }
166 }
167
168 impl Default for Samples {
default() -> Samples169 fn default() -> Samples {
170 let mut samples = Samples {
171 data: Vec::new(),
172 samples: 0,
173 };
174 samples.reset();
175 samples
176 }
177 }
178
179 /// Sections that can be parsed from a `*.data` file.
180 ///
181 /// This is the reverse of [`Recorder`] above.
182 pub enum Event<'a> {
183 /// A named function was loaded at the specified address with the specified
184 /// contents.
185 Function(u64, &'a str, &'a [u8]),
186 /// A set of samples were taken.
187 Samples(&'a [SamplePc]),
188 }
189
190 /// A small wrapper around `u64` to reduce its alignment to 1.
191 #[repr(packed)]
192 pub struct SamplePc(pub u64);
193
194 /// Decodes a `*.data` file presented in its entirety as `bytes` into a sequence
195 /// of `Event`s.
decode(mut bytes: &[u8]) -> impl Iterator<Item = Result<Event<'_>>> + use<'_>196 pub fn decode(mut bytes: &[u8]) -> impl Iterator<Item = Result<Event<'_>>> + use<'_> {
197 std::iter::from_fn(move || {
198 if bytes.is_empty() {
199 None
200 } else {
201 Some(decode_one(&mut bytes))
202 }
203 })
204 }
205
decode_one<'a>(bytes: &mut &'a [u8]) -> Result<Event<'a>>206 fn decode_one<'a>(bytes: &mut &'a [u8]) -> Result<Event<'a>> {
207 match bytes.split_first().unwrap() {
208 (&ID_FUNCTION, rest) => {
209 let (addr, rest) = rest
210 .split_first_chunk()
211 .ok_or_else(|| anyhow!("invalid addr"))?;
212 let addr = u64::from_le_bytes(*addr);
213
214 let (name_len, rest) = rest
215 .split_first_chunk()
216 .ok_or_else(|| anyhow!("invalid name byte len"))?;
217 let name_len = u32::from_le_bytes(*name_len);
218 let (name, rest) = rest
219 .split_at_checked(name_len as usize)
220 .ok_or_else(|| anyhow!("invalid name contents"))?;
221 let name = std::str::from_utf8(name)?;
222
223 let (body_len, rest) = rest
224 .split_first_chunk()
225 .ok_or_else(|| anyhow!("invalid body byte len"))?;
226 let body_len = u32::from_le_bytes(*body_len);
227 let (body, rest) = rest
228 .split_at_checked(body_len as usize)
229 .ok_or_else(|| anyhow!("invalid body contents"))?;
230
231 *bytes = rest;
232 Ok(Event::Function(addr, name, body))
233 }
234
235 (&ID_SAMPLES, rest) => {
236 let (samples, rest) = rest
237 .split_first_chunk()
238 .ok_or_else(|| anyhow!("invalid sample count"))?;
239 let samples = u32::from_le_bytes(*samples);
240 let (samples, rest) = rest
241 .split_at_checked(samples as usize * 8)
242 .ok_or_else(|| anyhow!("invalid sample data"))?;
243 *bytes = rest;
244
245 let (before, mid, after) = unsafe { samples.align_to::<SamplePc>() };
246 if !before.is_empty() || !after.is_empty() {
247 bail!("invalid sample data contents");
248 }
249 Ok(Event::Samples(mid))
250 }
251
252 _ => bail!("unknown ID in profile"),
253 }
254 }
255