xref: /wasmtime-44.0.1/pulley/src/profile.rs (revision 90ac295e)
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