Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/pulley/examples/profiler-html.rs
1692 views
1
//! Example program of parsing and displaying pulley profiles.
2
//!
3
//! To use this program first build Wasmtime with support for profiling Pulley:
4
//!
5
//! ```text
6
//! $ cargo build --release --features profile-pulley
7
//! ```
8
//!
9
//! Next record a profile
10
//!
11
//! ```text
12
//! $ ./target/release/wasmtime run --profile pulley --target pulley64 \
13
//! your_wasm_file.wasm
14
//! ```
15
//!
16
//! This will emit `pulley-$pid.data` to the current working directory. That
17
//! file is then fed to this program:
18
//!
19
//! ```text
20
//! $ cargo run -p pulley-interpreter --example profiler-html --all-features \
21
//! ./pulley-$pid.data
22
//! ```
23
//!
24
//! This will print all functions and their disassemblies to stdout. Functions
25
//! are annotated with the % of samples that fell in that function. Instructions
26
//! in functions are annotated with the % of samples in that function that fell
27
//! on that instruction. Functions are dropped if their sample rate is below the
28
//! CLI threshold and instructions are un-annotated if they're below the
29
//! threshold.
30
31
use anyhow::{Context, Result, bail};
32
use clap::Parser;
33
use pulley_interpreter::decode::{Decoder, OpVisitor};
34
use pulley_interpreter::disas::Disassembler;
35
use pulley_interpreter::profile::{Event, decode};
36
use std::collections::BTreeMap;
37
use std::io::Write;
38
use std::path::PathBuf;
39
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
40
41
#[derive(Parser)]
42
struct ProfilerHtml {
43
/// The profile data to load which was generated by a `--profile pulley` run
44
/// of Wasmtime previously.
45
profile: PathBuf,
46
47
/// The minimum threshold to display a function or annotate an instruction.
48
#[clap(long, default_value = "0.5")]
49
threshold: f32,
50
51
/// Whether or not to show instruction disassemblies.
52
#[clap(long)]
53
instructions: Option<bool>,
54
}
55
56
struct Function<'a> {
57
addr: u64,
58
hits: u64,
59
name: &'a str,
60
body: &'a [u8],
61
instructions: BTreeMap<u32, u32>,
62
}
63
64
fn main() -> Result<()> {
65
let args = ProfilerHtml::parse();
66
let profile = std::fs::read(&args.profile)
67
.with_context(|| format!("failed to read {:?}", args.profile))?;
68
69
// All known functions and the total of all samples taken.
70
let mut functions = BTreeMap::new();
71
let mut total = 0;
72
73
let mut found_samples = false;
74
for event in decode(&profile) {
75
match event? {
76
Event::Function(addr, name, body) => {
77
let prev = functions.insert(
78
addr,
79
Function {
80
addr,
81
name,
82
body,
83
hits: 0,
84
instructions: BTreeMap::new(),
85
},
86
);
87
assert!(prev.is_none());
88
}
89
Event::Samples(samples) => {
90
found_samples = true;
91
for sample in samples {
92
let addr = sample.0;
93
let (_, function) = functions.range_mut(..=addr).next_back().unwrap();
94
assert!(addr >= function.addr);
95
assert!(addr < function.addr + (function.body.len() as u64));
96
97
total += 1;
98
function.hits += 1;
99
*function
100
.instructions
101
.entry(u32::try_from(addr - function.addr).unwrap())
102
.or_insert(0) += 1;
103
}
104
}
105
}
106
}
107
108
if functions.is_empty() {
109
bail!("no functions found in profile");
110
}
111
if !found_samples {
112
bail!("no samples found in profile");
113
}
114
115
let mut funcs = functions
116
.into_iter()
117
.map(|(_, func)| func)
118
.collect::<Vec<_>>();
119
funcs.sort_by_key(|f| f.hits);
120
121
let mut term = StandardStream::stdout(ColorChoice::Auto);
122
let mut reset = ColorSpec::new();
123
reset.set_reset(true);
124
125
for mut func in funcs {
126
let func_pct = (func.hits as f32) / (total as f32) * 100.0;
127
if func_pct < args.threshold {
128
continue;
129
}
130
writeln!(
131
term,
132
"{:6.02}% {}",
133
(func.hits as f32) / (total as f32) * 100.0,
134
func.name,
135
)?;
136
137
if !args.instructions.unwrap_or(true) {
138
continue;
139
}
140
141
let mut disas = Disassembler::new(func.body);
142
disas.hexdump(false);
143
disas.offsets(false);
144
disas.br_tables(false);
145
let mut decoder = Decoder::new();
146
let mut prev = 0;
147
let mut offset = 0;
148
let mut remaining = func.body.len();
149
150
let min_instruction = func
151
.instructions
152
.iter()
153
.map(|(_, hits)| *hits)
154
.min()
155
.unwrap_or(0);
156
let max_instruction = func
157
.instructions
158
.iter()
159
.map(|(_, hits)| *hits)
160
.max()
161
.unwrap_or(0);
162
163
while !disas.bytecode().as_slice().is_empty() {
164
decoder.decode_one(&mut disas)?;
165
let instr = &disas.disas()[prev..].trim();
166
let hits = func.instructions.remove(&offset).unwrap_or(0);
167
let pct = (hits as f32) / (func.hits as f32) * 100.;
168
if pct < args.threshold {
169
term.set_color(&reset)?;
170
writeln!(term, "\t {:6x}: {instr}", u64::from(offset))?;
171
} else {
172
// Attempt to do a bit of a gradient from red-to-green from
173
// least-hit to most-hit instruction. Note that un-annotated
174
// instructions will have no color still (e.g. they aren't
175
// green).
176
let mut color = ColorSpec::new();
177
color.set_bold(hits == max_instruction);
178
let pct_r =
179
(hits - min_instruction) as f32 / (max_instruction - min_instruction) as f32;
180
181
let r = ((0xff as f32) * pct_r) as u8;
182
let g = ((0xff as f32) * (1. - pct_r)) as u8;
183
let b = 0;
184
color.set_fg(Some(Color::Rgb(r, g, b)));
185
term.set_color(&color)?;
186
writeln!(term, "\t{pct:6.02}% {:6x}: {instr}", u64::from(offset))?;
187
}
188
offset += u32::try_from(remaining - disas.bytecode().as_slice().len()).unwrap();
189
remaining = disas.bytecode().as_slice().len();
190
prev = disas.disas().len();
191
}
192
193
term.set_color(&reset)?;
194
195
assert!(func.instructions.is_empty(), "{:?}", func.instructions);
196
}
197
198
Ok(())
199
}
200
201