Skip to content

Commit 178ae8e

Browse files
committed
Auto merge of #1791 - Aaron1011:measureme, r=RalfJung
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, the timing 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>`
2 parents 4fa9363 + c89a5d6 commit 178ae8e

File tree

6 files changed

+156
-12
lines changed

6 files changed

+156
-12
lines changed

Cargo.lock

+88
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ smallvec = "1.4.2"
3030
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
3131
# for more information.
3232
rustc-workspace-hack = "1.0.0"
33+
measureme = "9.1.2"
3334

3435
# Enable some feature flags that dev-dependencies need but dependencies
3536
# do not. This makes `./miri install` after `./miri build` faster.

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ environment variable:
222222
times to exclude several variables. On Windows, the `TERM` environment
223223
variable is excluded by default.
224224
* `-Zmiri-ignore-leaks` disables the memory leak checker.
225+
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
226+
This can be used to find which parts of your program are executing slowly under Miri.
227+
The profile is written out to a file with the prefix `<name>`, and can be processed
228+
using the tools in the repository https://github.com/rust-lang/measureme.
225229
* `-Zmiri-seed=<hex>` configures the seed of the RNG that Miri uses to resolve
226230
non-determinism. This RNG is used to pick base addresses for allocations.
227231
When isolation is enabled (the default), this is also used to emulate system

src/bin/miri.rs

+4
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

+5-1
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 a file
58+
/// with the specified prefix.
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

+54-11
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever
3434
pub const NUM_CPUS: u64 = 1;
3535

3636
/// Extra data stored with each stack frame
37-
#[derive(Debug)]
3837
pub struct FrameData<'tcx> {
3938
/// Extra data for Stacked Borrows.
4039
pub call_id: stacked_borrows::CallId,
@@ -43,6 +42,21 @@ pub struct FrameData<'tcx> {
4342
/// called by `try`). When this frame is popped during unwinding a panic,
4443
/// we stop unwinding, use the `CatchUnwindData` to handle catching.
4544
pub catch_unwind: Option<CatchUnwindData<'tcx>>,
45+
46+
/// If `measureme` profiling is enabled, holds timing information
47+
/// for the start of this frame. When we finish executing this frame,
48+
/// we use this to register a completed event with `measureme`.
49+
pub timing: Option<measureme::DetachedTiming>,
50+
}
51+
52+
impl<'tcx> std::fmt::Debug for FrameData<'tcx> {
53+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54+
// Omitting `timing`, it does not support `Debug`.
55+
f.debug_struct("FrameData")
56+
.field("call_id", &self.call_id)
57+
.field("catch_unwind", &self.catch_unwind)
58+
.finish()
59+
}
4660
}
4761

4862
/// Extra memory kinds
@@ -270,16 +284,22 @@ pub struct Evaluator<'mir, 'tcx> {
270284

271285
/// Allocations that are considered roots of static memory (that may leak).
272286
pub(crate) static_roots: Vec<AllocId>,
287+
288+
/// The `measureme` profiler used to record timing information about
289+
/// the emulated program.
290+
profiler: Option<measureme::Profiler>,
291+
/// Used with `profiler` to cache the `StringId`s for event names
292+
/// uesd with `measureme`.
293+
string_cache: FxHashMap<String, measureme::StringId>,
273294
}
274295

275296
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
276-
pub(crate) fn new(
277-
communicate: bool,
278-
validate: bool,
279-
layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>,
280-
) -> Self {
297+
pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Self {
281298
let layouts =
282299
PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
300+
let profiler = config.measureme_out.as_ref().map(|out| {
301+
measureme::Profiler::new(out).expect("Couldn't create `measureme` profiler")
302+
});
283303
Evaluator {
284304
// `env_vars` could be initialized properly here if `Memory` were available before
285305
// calling this method.
@@ -288,14 +308,16 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
288308
argv: None,
289309
cmd_line: None,
290310
tls: TlsData::default(),
291-
communicate,
292-
validate,
311+
communicate: config.communicate,
312+
validate: config.validate,
293313
file_handler: Default::default(),
294314
dir_handler: Default::default(),
295315
time_anchor: Instant::now(),
296316
layouts,
297317
threads: ThreadManager::default(),
298318
static_roots: Vec::new(),
319+
profiler,
320+
string_cache: Default::default(),
299321
}
300322
}
301323
}
@@ -597,11 +619,27 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
597619
ecx: &mut InterpCx<'mir, 'tcx, Self>,
598620
frame: Frame<'mir, 'tcx, Tag>,
599621
) -> InterpResult<'tcx, Frame<'mir, 'tcx, Tag, FrameData<'tcx>>> {
622+
// Start recording our event before doing anything else
623+
let timing = if let Some(profiler) = ecx.machine.profiler.as_ref() {
624+
let fn_name = frame.instance.to_string();
625+
let entry = ecx.machine.string_cache.entry(fn_name.clone());
626+
let name = entry.or_insert_with(|| profiler.alloc_string(&*fn_name));
627+
628+
Some(profiler.start_recording_interval_event_detached(
629+
*name,
630+
measureme::EventId::from_label(*name),
631+
ecx.get_active_thread().to_u32(),
632+
))
633+
} else {
634+
None
635+
};
636+
600637
let stacked_borrows = ecx.memory.extra.stacked_borrows.as_ref();
601638
let call_id = stacked_borrows.map_or(NonZeroU64::new(1).unwrap(), |stacked_borrows| {
602639
stacked_borrows.borrow_mut().new_call()
603640
});
604-
let extra = FrameData { call_id, catch_unwind: None };
641+
642+
let extra = FrameData { call_id, catch_unwind: None, timing };
605643
Ok(frame.with_extra(extra))
606644
}
607645

@@ -625,10 +663,15 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
625663
#[inline(always)]
626664
fn after_stack_pop(
627665
ecx: &mut InterpCx<'mir, 'tcx, Self>,
628-
frame: Frame<'mir, 'tcx, Tag, FrameData<'tcx>>,
666+
mut frame: Frame<'mir, 'tcx, Tag, FrameData<'tcx>>,
629667
unwinding: bool,
630668
) -> InterpResult<'tcx, StackPopJump> {
631-
ecx.handle_stack_pop(frame.extra, unwinding)
669+
let timing = frame.extra.timing.take();
670+
let res = ecx.handle_stack_pop(frame.extra, unwinding);
671+
if let Some(profiler) = ecx.machine.profiler.as_ref() {
672+
profiler.finish_recording_interval_event(timing.unwrap());
673+
}
674+
res
632675
}
633676

634677
#[inline(always)]

0 commit comments

Comments
 (0)