Skip to content

Commit 7e9da8d

Browse files
committed
Add measureme integration for profiling the interpreted program
This PR uses the `measureme` crate to profile the call stack of the program being interpreted by Miri. This is accomplished by starting a measureme 'event' when we enter a function call, and ending the event when we exit the call. The `measureme` tooling can be used to produce a call stack from the generated profile data. Limitations: * We currently record every single entry/exit. This might generate very large profile outputs for programs with a large number of function calls. In follow-up work, we might want to explore sampling (e.g. only recording every N function calls). * This does not integrate very well with Miri's concurrency support. Each event we record starts when we push a frame, and ends when we pop a frame. As a result, switching between virtual threads will cause events from different threads to be interleaved. Additionally, the recorded for a particular frame will include all of the work Miri does before that frame completes, including executing another thread. The `measureme` integration is off by default, and must be enabled via `-Zmiri-measureme=<output_name>`
1 parent 4fa9363 commit 7e9da8d

File tree

7 files changed

+143
-10
lines changed

7 files changed

+143
-10
lines changed

Cargo.lock

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ test = false # we have no unit tests
1818
doctest = false # and no doc tests
1919

2020
[dependencies]
21+
measureme = { git = "https://github.com/rust-lang/measureme", rev = "501d6a3c192beee5e633a6c5f79130bedfdadcb5" }
2122
getrandom = { version = "0.2", features = ["std"] }
2223
env_logger = "0.8"
2324
log = "0.4"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ environment variable:
258258
this pointer. Note that it is not currently guaranteed that code that works
259259
with `-Zmiri-track-raw-pointers` also works without
260260
`-Zmiri-track-raw-pointers`, but for the vast majority of code, this will be the case.
261+
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
262+
This can be used to find which parts of your program are executing slowly under Miri.
263+
The profile is written out to a file with the prefix `<name>`, and can be processed
264+
using the tools in the repository https://github.com/rust-lang/measureme
261265

262266
Some native rustc `-Z` flags are also very relevant for Miri:
263267

src/bin/miri.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,10 @@ fn main() {
318318
};
319319
miri_config.cmpxchg_weak_failure_rate = rate;
320320
}
321+
arg if arg.starts_with("-Zmiri-measureme=") => {
322+
let measureme_out = arg.strip_prefix("-Zmiri-measureme=").unwrap();
323+
miri_config.measureme_out = Some(measureme_out.to_string());
324+
}
321325
_ => {
322326
// Forward to rustc.
323327
rustc_args.push(arg);

src/eval.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub struct MiriConfig {
5454
/// Rate of spurious failures for compare_exchange_weak atomic operations,
5555
/// between 0.0 and 1.0, defaulting to 0.8 (80% chance of failure).
5656
pub cmpxchg_weak_failure_rate: f64,
57+
/// If `Some`, enable the `measureme` profiler, writing results to the specified
58+
/// directory.
59+
pub measureme_out: Option<String>,
5760
}
5861

5962
impl Default for MiriConfig {
@@ -73,6 +76,7 @@ impl Default for MiriConfig {
7376
track_raw: false,
7477
data_race_detector: true,
7578
cmpxchg_weak_failure_rate: 0.8,
79+
measureme_out: None,
7680
}
7781
}
7882
}
@@ -92,7 +96,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
9296
tcx,
9397
rustc_span::source_map::DUMMY_SP,
9498
param_env,
95-
Evaluator::new(config.communicate, config.validate, layout_cx),
99+
Evaluator::new(&config, layout_cx),
96100
MemoryExtra::new(&config),
97101
);
98102
// Complete initialization.

src/machine.rs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use std::time::Instant;
1010
use log::trace;
1111
use rand::rngs::StdRng;
1212
use rand::SeedableRng;
13+
use std::collections::hash_map::Entry;
14+
use measureme::{Profiler, StringId, EventId, DetachedTiming};
1315

1416
use rustc_data_structures::fx::FxHashMap;
1517
use rustc_middle::{
@@ -34,7 +36,6 @@ pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever
3436
pub const NUM_CPUS: u64 = 1;
3537

3638
/// Extra data stored with each stack frame
37-
#[derive(Debug)]
3839
pub struct FrameData<'tcx> {
3940
/// Extra data for Stacked Borrows.
4041
pub call_id: stacked_borrows::CallId,
@@ -43,6 +44,8 @@ pub struct FrameData<'tcx> {
4344
/// called by `try`). When this frame is popped during unwinding a panic,
4445
/// we stop unwinding, use the `CatchUnwindData` to handle catching.
4546
pub catch_unwind: Option<CatchUnwindData<'tcx>>,
47+
48+
pub timing: Option<DetachedTiming>,
4649
}
4750

4851
/// Extra memory kinds
@@ -270,16 +273,21 @@ pub struct Evaluator<'mir, 'tcx> {
270273

271274
/// Allocations that are considered roots of static memory (that may leak).
272275
pub(crate) static_roots: Vec<AllocId>,
276+
277+
profiler: Option<Profiler>,
278+
string_cache: FxHashMap<String, StringId>,
273279
}
274280

275281
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
276282
pub(crate) fn new(
277-
communicate: bool,
278-
validate: bool,
283+
config: &MiriConfig,
279284
layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>,
280285
) -> Self {
281286
let layouts =
282287
PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
288+
let profiler = config.measureme_out.as_ref().map(|out| {
289+
Profiler::new(out).expect("Couldn't create `measureme` profiler")
290+
});
283291
Evaluator {
284292
// `env_vars` could be initialized properly here if `Memory` were available before
285293
// calling this method.
@@ -288,14 +296,16 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
288296
argv: None,
289297
cmd_line: None,
290298
tls: TlsData::default(),
291-
communicate,
292-
validate,
299+
communicate: config.communicate,
300+
validate: config.validate,
293301
file_handler: Default::default(),
294302
dir_handler: Default::default(),
295303
time_anchor: Instant::now(),
296304
layouts,
297305
threads: ThreadManager::default(),
298306
static_roots: Vec::new(),
307+
profiler,
308+
string_cache: Default::default(),
299309
}
300310
}
301311
}
@@ -601,7 +611,26 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
601611
let call_id = stacked_borrows.map_or(NonZeroU64::new(1).unwrap(), |stacked_borrows| {
602612
stacked_borrows.borrow_mut().new_call()
603613
});
604-
let extra = FrameData { call_id, catch_unwind: None };
614+
let timing = if let Some(profiler) = ecx.machine.profiler.as_ref() {
615+
let fn_name = frame.instance.to_string();
616+
let entry = ecx.machine.string_cache.entry(fn_name.clone());
617+
let name = match entry {
618+
Entry::Occupied(e) => *e.get(),
619+
Entry::Vacant(e) => {
620+
*e.insert(profiler.alloc_string(&*fn_name))
621+
}
622+
};
623+
624+
Some(profiler.start_recording_interval_event_detached(
625+
name,
626+
EventId::from_label(name),
627+
0
628+
))
629+
} else {
630+
None
631+
};
632+
633+
let extra = FrameData { call_id, catch_unwind: None, timing };
605634
Ok(frame.with_extra(extra))
606635
}
607636

@@ -625,10 +654,15 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
625654
#[inline(always)]
626655
fn after_stack_pop(
627656
ecx: &mut InterpCx<'mir, 'tcx, Self>,
628-
frame: Frame<'mir, 'tcx, Tag, FrameData<'tcx>>,
657+
mut frame: Frame<'mir, 'tcx, Tag, FrameData<'tcx>>,
629658
unwinding: bool,
630659
) -> InterpResult<'tcx, StackPopJump> {
631-
ecx.handle_stack_pop(frame.extra, unwinding)
660+
let timing = frame.extra.timing.take();
661+
let res = ecx.handle_stack_pop(frame.extra, unwinding);
662+
if let Some(profiler) = ecx.machine.profiler.as_ref() {
663+
profiler.finish_recording_interval_event(timing.unwrap());
664+
}
665+
res
632666
}
633667

634668
#[inline(always)]

src/shims/panic.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
119119
) -> InterpResult<'tcx, StackPopJump> {
120120
let this = self.eval_context_mut();
121121

122-
trace!("handle_stack_pop(extra = {:?}, unwinding = {})", extra, unwinding);
123122
if let Some(stacked_borrows) = &this.memory.extra.stacked_borrows {
124123
stacked_borrows.borrow_mut().end_call(extra.call_id);
125124
}

0 commit comments

Comments
 (0)