Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/pulley/src/profile.rs
1690 views
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 {
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.
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.
64
pub fn is_done(&self) -> bool {
65
self.0.done.load(Relaxed)
66
}
67
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<'_> {
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.
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.
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.
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.
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.
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.
153
pub fn num_samples(&self) -> u32 {
154
self.samples
155
}
156
157
fn finalize(&mut self) {
158
self.data[..4].copy_from_slice(&self.samples.to_le_bytes());
159
}
160
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 {
169
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.
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
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
256