[go: up one dir, main page]

dhat/
lib.rs

1#![deny(missing_docs)]
2#![deny(rustdoc::missing_doc_code_examples)]
3#![deny(missing_debug_implementations)]
4
5//! **Warning:** *This crate is experimental. It relies on implementation
6//! techniques that are hard to keep working for 100% of configurations. It may
7//! work fine for you, or it may crash, hang, or otherwise do the wrong thing.
8//! Its maintenance is not a high priority of the author. Support requests such
9//! as issues and pull requests may receive slow responses, or no response at
10//! all. Sorry!*
11//!
12//! This crate provides heap profiling and [ad hoc profiling] capabilities to
13//! Rust programs, similar to those provided by [DHAT].
14//!
15//! [ad hoc profiling]: https://github.com/nnethercote/counts/#ad-hoc-profiling
16//! [DHAT]: https://www.valgrind.org/docs/manual/dh-manual.html
17//!
18//! The heap profiling works by using a global allocator that wraps the system
19//! allocator, tracks all heap allocations, and on program exit writes data to
20//! file so it can be viewed with DHAT's viewer. This corresponds to DHAT's
21//! `--mode=heap` mode.
22//!
23//! The ad hoc profiling is via a second mode of operation, where ad hoc events
24//! can be manually inserted into a Rust program for aggregation and viewing.
25//! This corresponds to DHAT's `--mode=ad-hoc` mode.
26//!
27//! `dhat` also supports *heap usage testing*, where you can write tests and
28//! then check that they allocated as much heap memory as you expected. This
29//! can be useful for performance-sensitive code.
30//!
31//! # Motivation
32//!
33//! DHAT is a powerful heap profiler that comes with Valgrind. This crate is a
34//! related but alternative choice for heap profiling Rust programs. DHAT and
35//! this crate have the following differences.
36//! - This crate works on any platform, while DHAT only works on some platforms
37//!   (Linux, mostly). (Note that DHAT's viewer is just HTML+JS+CSS and should
38//!   work in any modern web browser on any platform.)
39//! - This crate typically causes a smaller slowdown than DHAT.
40//! - This crate requires some modifications to a program's source code and
41//!   recompilation, while DHAT does not.
42//! - This crate cannot track memory accesses the way DHAT does, because it does
43//!   not instrument all memory loads and stores.
44//! - This crate does not provide profiling of copy functions such as `memcpy`
45//!   and `strcpy`, unlike DHAT.
46//! - The backtraces produced by this crate may be better than those produced
47//!   by DHAT.
48//! - DHAT measures a program's entire execution, but this crate only measures
49//!   what happens within `main`. It will miss the small number of allocations
50//!   that occur before or after `main`, within the Rust runtime.
51//! - This crate enables heap usage testing.
52//!
53//! # Configuration (profiling and testing)
54//!
55//! In your `Cargo.toml` file, as well as specifying `dhat` as a dependency,
56//! you should (a) enable source line debug info, and (b) create a feature or
57//! two that lets you easily switch profiling on and off:
58//! ```toml
59//! [profile.release]
60//! debug = 1
61//!
62//! [features]
63//! dhat-heap = []    # if you are doing heap profiling
64//! dhat-ad-hoc = []  # if you are doing ad hoc profiling
65//! ```
66//! You should only use `dhat` in release builds. Debug builds are too slow to
67//! be useful.
68//!
69//! # Setup (heap profiling)
70//!
71//! For heap profiling, enable the global allocator by adding this code to your
72//! program:
73//! ```
74//! # // Tricky: comment out the `cfg` so it shows up in docs but the following
75//! # // line is still tsted by `cargo test`.
76//! # /*
77//! #[cfg(feature = "dhat-heap")]
78//! # */
79//! #[global_allocator]
80//! static ALLOC: dhat::Alloc = dhat::Alloc;
81//! ```
82//! Then add the following code to the very start of your `main` function:
83//! ```
84//! # // Tricky: comment out the `cfg` so it shows up in docs but the following
85//! # // line is still tsted by `cargo test`.
86//! # /*
87//! #[cfg(feature = "dhat-heap")]
88//! # */
89//! let _profiler = dhat::Profiler::new_heap();
90//! ```
91//! Then run this command to enable heap profiling during the lifetime of the
92//! [`Profiler`] instance:
93//! ```text
94//! cargo run --features dhat-heap
95//! ```
96//! [`dhat::Alloc`](Alloc) is slower than the normal allocator, so it should
97//! only be enabled while profiling.
98//!
99//! # Setup (ad hoc profiling)
100//!
101//! [Ad hoc profiling] involves manually annotating hot code points and then
102//! aggregating the executed annotations in some fashion.
103//!
104//! [Ad hoc profiling]: https://github.com/nnethercote/counts/#ad-hoc-profiling
105//!
106//! To do this, add the following code to the very start of your `main`
107//! function:
108//!```
109//! # // Tricky: comment out the `cfg` so it shows up in docs but the following
110//! # // line is still tsted by `cargo test`.
111//! # /*
112//! #[cfg(feature = "dhat-ad-hoc")]
113//! # */
114//! let _profiler = dhat::Profiler::new_ad_hoc();
115//! ```
116//! Then insert calls like this at points of interest:
117//! ```
118//! # // Tricky: comment out the `cfg` so it shows up in docs but the following
119//! # // line is still tsted by `cargo test`.
120//! # /*
121//! #[cfg(feature = "dhat-ad-hoc")]
122//! # */
123//! dhat::ad_hoc_event(100);
124//! ```
125//! Then run this command to enable ad hoc profiling during the lifetime of the
126//! [`Profiler`] instance:
127//! ```text
128//! cargo run --features dhat-ad-hoc
129//! ```
130//! For example, imagine you have a hot function that is called from many call
131//! sites. You might want to know how often it is called and which other
132//! functions called it the most. In that case, you would add an
133//! [`ad_hoc_event`] call to that function, and the data collected by this
134//! crate and viewed with DHAT's viewer would show you exactly what you want to
135//! know.
136//!
137//! The meaning of the integer argument to `ad_hoc_event` will depend on
138//! exactly what you are measuring. If there is no meaningful weight to give to
139//! an event, you can just use `1`.
140//!
141//! # Running
142//!
143//! For both heap profiling and ad hoc profiling, the program will run more
144//! slowly than normal. The exact slowdown is hard to predict because it
145//! depends greatly on the program being profiled, but it can be large. (Even
146//! more so on Windows, because backtrace gathering can be drastically slower
147//! on Windows than on other platforms.)
148//!
149//! When the [`Profiler`] is dropped at the end of `main`, some basic
150//! information will be printed to `stderr`. For heap profiling it will look
151//! like the following.
152//! ```text
153//! dhat: Total:     1,256 bytes in 6 blocks
154//! dhat: At t-gmax: 1,256 bytes in 6 blocks
155//! dhat: At t-end:  1,256 bytes in 6 blocks
156//! dhat: The data has been saved to dhat-heap.json, and is viewable with dhat/dh_view.html
157//! ```
158//! ("Blocks" is a synonym for "allocations".)
159//!
160//! For ad hoc profiling it will look like the following.
161//! ```text
162//! dhat: Total:     141 units in 11 events
163//! dhat: The data has been saved to dhat-ad-hoc.json, and is viewable with dhat/dh_view.html
164//! ```
165//! A file called `dhat-heap.json` (for heap profiling) or `dhat-ad-hoc.json`
166//! (for ad hoc profiling) will be written. It can be viewed in DHAT's viewer.
167//!
168//! If you don't see this output, it may be because your program called
169//! [`std::process::exit`], which exits a program without running any
170//! destructors. To work around this, explicitly call `drop` on the
171//! [`Profiler`] value just before exiting.
172//!
173//! When doing heap profiling, if you unexpectedly see zero allocations in the
174//! output it may be because you forgot to set [`dhat::Alloc`](Alloc) as the
175//! global allocator.
176//!
177//! When doing heap profiling it is recommended that the lifetime of the
178//! [`Profiler`] value cover all of `main`. But it is still possible for
179//! allocations and deallocations to occur outside of its lifetime. Such cases
180//! are handled in the following ways.
181//! - Allocated before, untouched within: ignored.
182//! - Allocated before, freed within: ignored.
183//! - Allocated before, reallocated within: treated like a new allocation
184//!   within.
185//! - Allocated after: ignored.
186//!
187//! These cases are not ideal, but it is impossible to do better. `dhat`
188//! deliberately provides no way to reset the heap profiling state mid-run
189//! precisely because it leaves open the possibility of many such occurrences.
190//!
191//! # Viewing
192//!
193//! Open a copy of DHAT's viewer, version 3.17 or later. There are two ways to
194//! do this.
195//! - Easier: Use the [online version].
196//! - Harder: Clone the [Valgrind repository] with `git clone
197//!   git://sourceware.org/git/valgrind.git` and open `dhat/dh_view.html`.
198//!   There is no need to build any code in this repository.
199//!
200//! [online version]: https://nnethercote.github.io/dh_view/dh_view.html
201//! [Valgrind repository]: https://www.valgrind.org/downloads/repository.html
202//!
203//! Then click on the "Load…" button to load `dhat-heap.json` or
204//! `dhat-ad-hoc.json`.
205//!
206//! DHAT's viewer shows a tree with nodes that look like this.
207//! ```text
208//! PP 1.1/2 {
209//!   Total:     1,024 bytes (98.46%, 14,422,535.21/s) in 1 blocks (50%, 14,084.51/s), avg size 1,024 bytes, avg lifetime 35 µs (49.3% of program duration)
210//!   Max:       1,024 bytes in 1 blocks, avg size 1,024 bytes
211//!   At t-gmax: 1,024 bytes (98.46%) in 1 blocks (50%), avg size 1,024 bytes
212//!   At t-end:  1,024 bytes (100%) in 1 blocks (100%), avg size 1,024 bytes
213//!   Allocated at {
214//!     #1: 0x10ae8441b: <alloc::alloc::Global as core::alloc::Allocator>::allocate (alloc/src/alloc.rs:226:9)
215//!     #2: 0x10ae8441b: alloc::raw_vec::RawVec<T,A>::allocate_in (alloc/src/raw_vec.rs:207:45)
216//!     #3: 0x10ae8441b: alloc::raw_vec::RawVec<T,A>::with_capacity_in (alloc/src/raw_vec.rs:146:9)
217//!     #4: 0x10ae8441b: alloc::vec::Vec<T,A>::with_capacity_in (src/vec/mod.rs:609:20)
218//!     #5: 0x10ae8441b: alloc::vec::Vec<T>::with_capacity (src/vec/mod.rs:470:9)
219//!     #6: 0x10ae8441b: std::io::buffered::bufwriter::BufWriter<W>::with_capacity (io/buffered/bufwriter.rs:115:33)
220//!     #7: 0x10ae8441b: std::io::buffered::linewriter::LineWriter<W>::with_capacity (io/buffered/linewriter.rs:109:29)
221//!     #8: 0x10ae8441b: std::io::buffered::linewriter::LineWriter<W>::new (io/buffered/linewriter.rs:89:9)
222//!     #9: 0x10ae8441b: std::io::stdio::stdout::{{closure}} (src/io/stdio.rs:680:58)
223//!     #10: 0x10ae8441b: std::lazy::SyncOnceCell<T>::get_or_init_pin::{{closure}} (std/src/lazy.rs:375:25)
224//!     #11: 0x10ae8441b: std::sync::once::Once::call_once_force::{{closure}} (src/sync/once.rs:320:40)
225//!     #12: 0x10aea564c: std::sync::once::Once::call_inner (src/sync/once.rs:419:21)
226//!     #13: 0x10ae81b1b: std::sync::once::Once::call_once_force (src/sync/once.rs:320:9)
227//!     #14: 0x10ae81b1b: std::lazy::SyncOnceCell<T>::get_or_init_pin (std/src/lazy.rs:374:9)
228//!     #15: 0x10ae81b1b: std::io::stdio::stdout (src/io/stdio.rs:679:16)
229//!     #16: 0x10ae81b1b: std::io::stdio::print_to (src/io/stdio.rs:1196:21)
230//!     #17: 0x10ae81b1b: std::io::stdio::_print (src/io/stdio.rs:1209:5)
231//!     #18: 0x10ae2fe20: dhatter::main (dhatter/src/main.rs:8:5)
232//!   }
233//! }
234//! ```
235//! Full details about the output are in the [DHAT documentation]. Note that
236//! DHAT uses the word "block" as a synonym for "allocation".
237//!
238//! [DHAT documentation]: https://valgrind.org/docs/manual/dh-manual.html
239//!
240//! When heap profiling, this crate doesn't track memory accesses (unlike DHAT)
241//! and so the "reads" and "writes" measurements are not shown within DHAT's
242//! viewer, and "sort metric" views involving reads, writes, or accesses are
243//! not available.
244//!
245//! The backtraces produced by this crate are trimmed to reduce output file
246//! sizes and improve readability in DHAT's viewer, in the following ways.
247//! - Only one allocation-related frame will be shown at the top of the
248//!   backtrace. That frame may be a function within `alloc::alloc`, a function
249//!   within this crate, or a global allocation function like `__rg_alloc`.
250//! - Common frames at the bottom of all backtraces, below `main`, are omitted.
251//!
252//! Backtrace trimming is inexact and if the above heuristics fail more frames
253//! will be shown. [`ProfilerBuilder::trim_backtraces`] allows (approximate)
254//! control of how deep backtraces will be.
255//!
256//! # Heap usage testing
257//!
258//! `dhat` lets you write tests that check that a certain piece of code does a
259//! certain amount of heap allocation when it runs. This is sometimes called
260//! "high water mark" testing. Sometimes it is precise (e.g. "this code should
261//! do exactly 96 allocations" or "this code should free all allocations before
262//! finishing") and sometimes it is less precise (e.g. "the peak heap usage of
263//! this code should be less than 10 MiB").
264//!
265//! These tests are somewhat fragile, because heap profiling involves global
266//! state (allocation stats), which introduces complications.
267//! - `dhat` will panic if more than one `Profiler` is running at a time, but
268//!   Rust tests run in parallel by default. So parallel running of heap usage
269//!   tests must be prevented.
270//! - If you use something like the
271//!   [`serial_test`](https://docs.rs/serial_test/) crate to run heap usage
272//!   tests in serial, Rust's test runner code by default still runs in
273//!   parallel with those tests, and it allocates memory. These allocations
274//!   will be counted by the `Profiler` as if they are part of the test, which
275//!   will likely cause test failures.
276//!
277//! Therefore, the best approach is to put each heap usage test in its own
278//! integration test file. Each integration test runs in its own process, and
279//! so cannot interfere with any other test. Also, if there is only one test in
280//! an integration test file, Rust's test runner code does not use any
281//! parallelism, and so will not interfere with the test. If you do this, a
282//! simple `cargo test` will work as expected.
283//!
284//! Alternatively, if you really want multiple heap usage tests in a single
285//! integration test file you can write your own [custom test harness], which
286//! is simpler than it sounds.
287//!
288//! [custom test harness]: https://www.infinyon.com/blog/2021/04/rust-custom-test-harness/
289//!
290//! But integration tests have some limits. For example, they only be used to
291//! test items from libraries, not binaries. One way to get around this is to
292//! restructure things so that most of the functionality is in a library, and
293//! the binary is a thin wrapper around the library.
294//!
295//! Failing that, a blunt fallback is to run `cargo tests -- --test-threads=1`.
296//! This disables all parallelism in tests, avoiding all the problems. This
297//! allows the use of unit tests and multiples tests per integration test file,
298//! at the cost of a non-standard invocation and slower test execution.
299//!
300//! With all that in mind, configuration of `Cargo.toml` is much the same as
301//! for the profiling use case.
302//!
303//! Here is an example showing what is possible. This code would go in an
304//! integration test within a crate's `tests/` directory:
305//! ```
306//! #[global_allocator]
307//! static ALLOC: dhat::Alloc = dhat::Alloc;
308//!
309//! # // Tricky: comment out the `#[test]` because it's needed in an actual
310//! # // test but messes up things here.
311//! # /*
312//! #[test]
313//! # */
314//! fn test() {
315//!     let _profiler = dhat::Profiler::builder().testing().build();
316//!
317//!     let _v1 = vec![1, 2, 3, 4];
318//!     let v2 = vec![5, 6, 7, 8];
319//!     drop(v2);
320//!     let v3 = vec![9, 10, 11, 12];
321//!     drop(v3);
322//!
323//!     let stats = dhat::HeapStats::get();
324//!
325//!     // Three allocations were done in total.
326//!     dhat::assert_eq!(stats.total_blocks, 3);
327//!     dhat::assert_eq!(stats.total_bytes, 48);
328//!
329//!     // At the point of peak heap size, two allocations totalling 32 bytes existed.
330//!     dhat::assert_eq!(stats.max_blocks, 2);
331//!     dhat::assert_eq!(stats.max_bytes, 32);
332//!
333//!     // Now a single allocation remains alive.
334//!     dhat::assert_eq!(stats.curr_blocks, 1);
335//!     dhat::assert_eq!(stats.curr_bytes, 16);
336//! }
337//! # test()
338//! ```
339//! The [`testing`](ProfilerBuilder::testing) call puts the profiler into
340//! testing mode, which allows the stats provided by [`HeapStats::get`] to be
341//! checked with [`dhat::assert!`](assert) and similar assertions. These
342//! assertions work much the same as normal assertions, except that if any of
343//! them fail a heap profile will be saved.
344//!
345//! When viewing the heap profile after a test failure, the best choice of sort
346//! metric in the viewer will depend on which stat was involved in the
347//! assertion failure.
348//! - `total_blocks`: "Total (blocks)"
349//! - `total_bytes`: "Total (bytes)"
350//! - `max_blocks` or `max_bytes`: "At t-gmax (bytes)"
351//! - `curr_blocks` or `curr_bytes`: "At t-end (bytes)"
352//!
353//! This should give you a good understanding of why the assertion failed.
354//!
355//! Note: if you try this example test it may work in a debug build but fail in
356//! a release build. This is because the compiler may optimize away some of the
357//! allocations that are unused. This is a common problem for contrived
358//! examples but less common for real tests. The unstable
359//! [`std::hint::black_box`](std::hint::black_box) function may also be helpful
360//! in this situation.
361//!
362//! # Ad hoc usage testing
363//!
364//! Ad hoc usage testing is also possible. It can be used to ensure certain
365//! code points in your program are hit a particular number of times during
366//! execution. It works in much the same way as heap usage testing, but
367//! [`ProfilerBuilder::ad_hoc`] must be specified, [`AdHocStats::get`] is
368//! used instead of [`HeapStats::get`], and there is no possibility of Rust's
369//! test runner code interfering with the tests.
370
371use backtrace::SymbolName;
372use lazy_static::lazy_static;
373// In normal Rust code, the allocator is on a lower level than the mutex
374// implementation, which means the mutex implementation can use the allocator.
375// But DHAT implements an allocator which requires a mutex. If we're not
376// careful, this can easily result in deadlocks from circular sequences of
377// operations, E.g. see #18 and #25 from when we used `parking_lot::Mutex`. We
378// now use `mintex::Mutex`, which is guaranteed to not allocate, effectively
379// making the mutex implementation on a lower level than the allocator,
380// allowing the allocator to depend on it.
381use mintex::Mutex;
382use rustc_hash::FxHashMap;
383use serde::Serialize;
384use std::alloc::{GlobalAlloc, Layout, System};
385use std::cell::Cell;
386use std::fs::File;
387use std::hash::{Hash, Hasher};
388use std::io::BufWriter;
389use std::ops::AddAssign;
390use std::path::{Path, PathBuf};
391use std::time::{Duration, Instant};
392use thousands::Separable;
393
394lazy_static! {
395    static ref TRI_GLOBALS: Mutex<Phase<Globals>> = Mutex::new(Phase::Ready);
396}
397
398// State transition diagram:
399//
400// +---------------> Ready
401// |                   |
402// | Profiler::        | ProfilerBuilder::
403// | drop_inner()      | build()
404// |                   v
405// +---------------- Running
406// |                   |
407// |                   | check_assert_condition()
408// |                   | [if the check fails]
409// |                   v
410// +---------------- PostAssert
411//
412// Note: the use of `std::process::exit` or `std::mem::forget` (on the
413// `Profiler`) can result in termination while the profiler is still running,
414// i.e. it won't produce output.
415#[derive(PartialEq)]
416enum Phase<T> {
417    // We are ready to start running a `Profiler`.
418    Ready,
419
420    // A `Profiler` is running.
421    Running(T),
422
423    // The current `Profiler` has stopped due to as assertion failure, but
424    // hasn't been dropped yet.
425    PostAssert,
426}
427
428// Type used in frame trimming.
429#[derive(PartialEq)]
430enum TB {
431    Top,
432    Bottom,
433}
434
435// Global state that can be accessed from any thread and is therefore protected
436// by a `Mutex`.
437struct Globals {
438    // The file name for the saved data.
439    file_name: PathBuf,
440
441    // Are we in testing mode?
442    testing: bool,
443
444    // How should we trim backtraces?
445    trim_backtraces: Option<usize>,
446
447    // Print the JSON to stderr when saving it?
448    eprint_json: bool,
449
450    // The backtrace at startup. Used for backtrace trimmming.
451    start_bt: Backtrace,
452
453    // Frames to trim at the top and bottom of backtraces. Computed once the
454    // first backtrace is obtained during profiling; that backtrace is then
455    // compared to `start_bt`.
456    //
457    // Each element is the address of a frame, and thus actually a `*mut
458    // c_void`, but we store it as a `usize` because (a) we never dereference
459    // it, and (b) using `*mut c_void` leads to compile errors because raw
460    // pointers don't implement `Send`.
461    frames_to_trim: Option<FxHashMap<usize, TB>>,
462
463    // When `Globals` is created, which is when the `Profiler` is created.
464    start_instant: Instant,
465
466    // All the `PpInfos` gathered during execution. Elements are never deleted.
467    // Each element is referred to by exactly one `Backtrace` from
468    // `backtraces`, and referred to by any number of live blocks from
469    // `live_blocks`. Storing all the `PpInfos` in a `Vec` is a bit clumsy, but
470    // allows multiple references from `backtraces` and `live_blocks` without
471    // requiring any unsafety, because the references are just indices rather
472    // than `Rc`s or raw pointers or whatever.
473    pp_infos: Vec<PpInfo>,
474
475    // Each `Backtrace` is associated with a `PpInfo`. The `usize` is an index
476    // into `pp_infos`. Entries are not deleted during execution.
477    backtraces: FxHashMap<Backtrace, usize>,
478
479    // Counts for the entire run.
480    total_blocks: u64, // For ad hoc profiling it's actually `total_events`.
481    total_bytes: u64,  // For ad hoc profiling it's actually `total_units`.
482
483    // Extra things kept when heap profiling.
484    heap: Option<HeapGlobals>,
485}
486
487struct HeapGlobals {
488    // Each live block is associated with a `PpInfo`. An element is deleted
489    // when the corresponding allocation is freed.
490    //
491    // Each key is the address of a live block, and thus actually a `*mut u8`,
492    // but we store it as a `usize` because (a) we never dereference it, and
493    // (b) using `*mut u8` leads to compile errors because raw pointers don't
494    // implement `Send`.
495    live_blocks: FxHashMap<usize, LiveBlock>,
496
497    // Current counts.
498    curr_blocks: usize,
499    curr_bytes: usize,
500
501    // Counts at the global max, i.e. when `curr_bytes` peaks.
502    max_blocks: usize,
503    max_bytes: usize,
504
505    // Time of the global max.
506    tgmax_instant: Instant,
507}
508
509impl Globals {
510    fn new(
511        testing: bool,
512        file_name: PathBuf,
513        trim_backtraces: Option<usize>,
514        eprint_json: bool,
515        heap: Option<HeapGlobals>,
516    ) -> Self {
517        Self {
518            testing,
519            file_name,
520            trim_backtraces,
521            eprint_json,
522            // `None` here because we don't want any frame trimming for this
523            // backtrace.
524            start_bt: new_backtrace_inner(None, &FxHashMap::default()),
525            frames_to_trim: None,
526            start_instant: Instant::now(),
527            pp_infos: Vec::default(),
528            backtraces: FxHashMap::default(),
529            total_blocks: 0,
530            total_bytes: 0,
531            heap,
532        }
533    }
534
535    // Get the PpInfo for this backtrace, creating it if necessary.
536    fn get_pp_info<F: FnOnce() -> PpInfo>(&mut self, bt: Backtrace, new: F) -> usize {
537        let pp_infos = &mut self.pp_infos;
538        *self.backtraces.entry(bt).or_insert_with(|| {
539            let pp_info_idx = pp_infos.len();
540            pp_infos.push(new());
541            pp_info_idx
542        })
543    }
544
545    fn record_block(&mut self, ptr: *mut u8, pp_info_idx: usize, now: Instant) {
546        let h = self.heap.as_mut().unwrap();
547        let old = h.live_blocks.insert(
548            ptr as usize,
549            LiveBlock {
550                pp_info_idx,
551                allocation_instant: now,
552            },
553        );
554        std::assert!(matches!(old, None));
555    }
556
557    fn update_counts_for_alloc(
558        &mut self,
559        pp_info_idx: usize,
560        size: usize,
561        delta: Option<Delta>,
562        now: Instant,
563    ) {
564        self.total_blocks += 1;
565        self.total_bytes += size as u64;
566
567        let h = self.heap.as_mut().unwrap();
568        if let Some(delta) = delta {
569            // realloc
570            h.curr_blocks += 0; // unchanged
571            h.curr_bytes += delta;
572        } else {
573            // alloc
574            h.curr_blocks += 1;
575            h.curr_bytes += size;
576        }
577
578        // The use of `>=` not `>` means that if there are multiple equal peaks
579        // we record the latest one, like `check_for_global_peak` does.
580        if h.curr_bytes >= h.max_bytes {
581            h.max_blocks = h.curr_blocks;
582            h.max_bytes = h.curr_bytes;
583            h.tgmax_instant = now;
584        }
585
586        self.pp_infos[pp_info_idx].update_counts_for_alloc(size, delta);
587    }
588
589    fn update_counts_for_dealloc(
590        &mut self,
591        pp_info_idx: usize,
592        size: usize,
593        alloc_duration: Duration,
594    ) {
595        let h = self.heap.as_mut().unwrap();
596        h.curr_blocks -= 1;
597        h.curr_bytes -= size;
598
599        self.pp_infos[pp_info_idx].update_counts_for_dealloc(size, alloc_duration);
600    }
601
602    fn update_counts_for_ad_hoc_event(&mut self, pp_info_idx: usize, weight: usize) {
603        std::assert!(self.heap.is_none());
604        self.total_blocks += 1;
605        self.total_bytes += weight as u64;
606
607        self.pp_infos[pp_info_idx].update_counts_for_ad_hoc_event(weight);
608    }
609
610    // If we are at peak memory, update `at_tgmax_{blocks,bytes}` in all
611    // `PpInfo`s. This is somewhat expensive so we avoid calling it on every
612    // allocation; instead we call it upon a deallocation (when we might be
613    // coming down from a global peak) and at termination (when we might be at
614    // a global peak).
615    fn check_for_global_peak(&mut self) {
616        let h = self.heap.as_mut().unwrap();
617        if h.curr_bytes == h.max_bytes {
618            // It's a peak. (If there are multiple equal peaks we record the
619            // latest one.) Record it in every PpInfo.
620            for pp_info in self.pp_infos.iter_mut() {
621                let h = pp_info.heap.as_mut().unwrap();
622                h.at_tgmax_blocks = h.curr_blocks;
623                h.at_tgmax_bytes = h.curr_bytes;
624            }
625        }
626    }
627
628    fn get_heap_stats(&self) -> HeapStats {
629        match &self.heap {
630            Some(heap) => HeapStats {
631                total_blocks: self.total_blocks,
632                total_bytes: self.total_bytes,
633                curr_blocks: heap.curr_blocks,
634                curr_bytes: heap.curr_bytes,
635                max_blocks: heap.max_blocks,
636                max_bytes: heap.max_bytes,
637            },
638            None => panic!("dhat: getting heap stats while doing ad hoc profiling"),
639        }
640    }
641
642    fn get_ad_hoc_stats(&self) -> AdHocStats {
643        match self.heap {
644            None => AdHocStats {
645                total_events: self.total_blocks,
646                total_units: self.total_bytes,
647            },
648            Some(_) => panic!("dhat: getting ad hoc stats while doing heap profiling"),
649        }
650    }
651
652    // Finish tracking allocations and deallocations, print a summary message
653    // to `stderr` and save the profile to file/memory if requested.
654    fn finish(mut self, memory_output: Option<&mut String>) {
655        let now = Instant::now();
656
657        if self.heap.is_some() {
658            // Total bytes is at a possible peak.
659            self.check_for_global_peak();
660
661            let h = self.heap.as_ref().unwrap();
662
663            // Account for the lifetimes of all remaining live blocks.
664            for &LiveBlock {
665                pp_info_idx,
666                allocation_instant,
667            } in h.live_blocks.values()
668            {
669                self.pp_infos[pp_info_idx]
670                    .heap
671                    .as_mut()
672                    .unwrap()
673                    .total_lifetimes_duration += now.duration_since(allocation_instant);
674            }
675        }
676
677        // We give each unique frame an index into `ftbl`, starting with 0
678        // for the special frame "[root]".
679        let mut ftbl_indices: FxHashMap<String, usize> = FxHashMap::default();
680        ftbl_indices.insert("[root]".to_string(), 0);
681        let mut next_ftbl_idx = 1;
682
683        // Because `self` is being consumed, we can consume `self.backtraces`
684        // and replace it with an empty `FxHashMap`. (This is necessary because
685        // we modify the *keys* here with `resolve`, which isn't allowed with a
686        // non-consuming iterator.)
687        let pps: Vec<_> = std::mem::take(&mut self.backtraces)
688            .into_iter()
689            .map(|(mut bt, pp_info_idx)| {
690                // Do the potentially expensive debug info lookups to get
691                // symbol names, line numbers, etc.
692                bt.0.resolve();
693
694                // Trim boring frames at the top and bottom of the backtrace.
695                let first_symbol_to_show = if self.trim_backtraces.is_some() {
696                    if self.heap.is_some() {
697                        bt.first_heap_symbol_to_show()
698                    } else {
699                        bt.first_ad_hoc_symbol_to_show()
700                    }
701                } else {
702                    0
703                };
704
705                // Determine the frame indices for this backtrace. This
706                // involves getting the string for each frame and adding a
707                // new entry to `ftbl_indices` if it hasn't been seen
708                // before.
709                let mut fs = vec![];
710                let mut i = 0;
711                for frame in bt.0.frames().iter() {
712                    for symbol in frame.symbols().iter() {
713                        i += 1;
714                        if (i - 1) < first_symbol_to_show {
715                            continue;
716                        }
717                        let s = Backtrace::frame_to_string(frame, symbol);
718                        let &mut ftbl_idx = ftbl_indices.entry(s).or_insert_with(|| {
719                            next_ftbl_idx += 1;
720                            next_ftbl_idx - 1
721                        });
722                        fs.push(ftbl_idx);
723                    }
724                }
725
726                PpInfoJson::new(&self.pp_infos[pp_info_idx], fs)
727            })
728            .collect();
729
730        // We pre-allocate `ftbl` with empty strings, and then fill it in.
731        let mut ftbl = vec![String::new(); ftbl_indices.len()];
732        for (frame, ftbl_idx) in ftbl_indices.into_iter() {
733            ftbl[ftbl_idx] = frame;
734        }
735
736        let h = self.heap.as_ref();
737        let is_heap = h.is_some();
738        let json = DhatJson {
739            dhatFileVersion: 2,
740            mode: if is_heap { "rust-heap" } else { "rust-ad-hoc" },
741            verb: "Allocated",
742            bklt: is_heap,
743            bkacc: false,
744            bu: if is_heap { None } else { Some("unit") },
745            bsu: if is_heap { None } else { Some("units") },
746            bksu: if is_heap { None } else { Some("events") },
747            tu: "µs",
748            Mtu: "s",
749            tuth: if is_heap { Some(10) } else { None },
750            cmd: std::env::args().collect::<Vec<_>>().join(" "),
751            pid: std::process::id(),
752            tg: h.map(|h| {
753                h.tgmax_instant
754                    .saturating_duration_since(self.start_instant)
755                    .as_micros()
756            }),
757            te: now.duration_since(self.start_instant).as_micros(),
758            pps,
759            ftbl,
760        };
761
762        eprintln!(
763            "dhat: Total:     {} {} in {} {}",
764            self.total_bytes.separate_with_commas(),
765            json.bsu.unwrap_or("bytes"),
766            self.total_blocks.separate_with_commas(),
767            json.bksu.unwrap_or("blocks"),
768        );
769        if let Some(h) = &self.heap {
770            eprintln!(
771                "dhat: At t-gmax: {} bytes in {} blocks",
772                h.max_bytes.separate_with_commas(),
773                h.max_blocks.separate_with_commas(),
774            );
775            eprintln!(
776                "dhat: At t-end:  {} bytes in {} blocks",
777                h.curr_bytes.separate_with_commas(),
778                h.curr_blocks.separate_with_commas(),
779            );
780        }
781
782        if let Some(memory_output) = memory_output {
783            // Default pretty printing is fine here, it's only used for small
784            // tests.
785            *memory_output = serde_json::to_string_pretty(&json).unwrap();
786            eprintln!("dhat: The data has been saved to the memory buffer");
787        } else {
788            let write = || -> std::io::Result<()> {
789                let buffered_file = BufWriter::new(File::create(&self.file_name)?);
790                // `to_writer` produces JSON that is compact.
791                // `to_writer_pretty` produces JSON that is readable. This code
792                // gives us JSON that is fairly compact and fairly readable.
793                // Ideally it would be more like what DHAT produces, e.g. one
794                // space indents, no spaces after `:` and `,`, and `fs` arrays
795                // on a single line, but this is as good as we can easily
796                // achieve.
797                let formatter = serde_json::ser::PrettyFormatter::with_indent(b"");
798                let mut ser = serde_json::Serializer::with_formatter(buffered_file, formatter);
799                json.serialize(&mut ser)?;
800                Ok(())
801            };
802            match write() {
803                Ok(()) => eprintln!(
804                    "dhat: The data has been saved to {}, and is viewable with dhat/dh_view.html",
805                    self.file_name.to_string_lossy()
806                ),
807                Err(e) => eprintln!(
808                    "dhat: error: Writing to {} failed: {}",
809                    self.file_name.to_string_lossy(),
810                    e
811                ),
812            }
813        }
814        if self.eprint_json {
815            eprintln!(
816                "dhat: json = `{}`",
817                serde_json::to_string_pretty(&json).unwrap()
818            );
819        }
820    }
821}
822
823impl HeapGlobals {
824    fn new() -> Self {
825        Self {
826            live_blocks: FxHashMap::default(),
827            curr_blocks: 0,
828            curr_bytes: 0,
829            max_blocks: 0,
830            max_bytes: 0,
831            tgmax_instant: Instant::now(),
832        }
833    }
834}
835
836struct PpInfo {
837    // The total number of blocks and bytes allocated by this PP.
838    total_blocks: u64,
839    total_bytes: u64,
840
841    heap: Option<HeapPpInfo>,
842}
843
844#[derive(Default)]
845struct HeapPpInfo {
846    // The current number of blocks and bytes allocated by this PP.
847    curr_blocks: usize,
848    curr_bytes: usize,
849
850    // The number of blocks and bytes at the PP max, i.e. when this PP's
851    // `curr_bytes` peaks.
852    max_blocks: usize,
853    max_bytes: usize,
854
855    // The number of blocks and bytes at the global max, i.e. when
856    // `Globals::curr_bytes` peaks.
857    at_tgmax_blocks: usize,
858    at_tgmax_bytes: usize,
859
860    // Total lifetimes of all blocks allocated by this PP. Includes blocks
861    // explicitly freed and blocks implicitly freed at termination.
862    total_lifetimes_duration: Duration,
863}
864
865impl PpInfo {
866    fn new_heap() -> Self {
867        Self {
868            total_blocks: 0,
869            total_bytes: 0,
870            heap: Some(HeapPpInfo::default()),
871        }
872    }
873
874    fn new_ad_hoc() -> Self {
875        Self {
876            total_blocks: 0,
877            total_bytes: 0,
878            heap: None,
879        }
880    }
881
882    fn update_counts_for_alloc(&mut self, size: usize, delta: Option<Delta>) {
883        self.total_blocks += 1;
884        self.total_bytes += size as u64;
885
886        let h = self.heap.as_mut().unwrap();
887        if let Some(delta) = delta {
888            // realloc
889            h.curr_blocks += 0; // unchanged
890            h.curr_bytes += delta;
891        } else {
892            // alloc
893            h.curr_blocks += 1;
894            h.curr_bytes += size;
895        }
896
897        // The use of `>=` not `>` means that if there are multiple equal peaks
898        // we record the latest one, like `check_for_global_peak` does.
899        if h.curr_bytes >= h.max_bytes {
900            h.max_blocks = h.curr_blocks;
901            h.max_bytes = h.curr_bytes;
902        }
903    }
904
905    fn update_counts_for_dealloc(&mut self, size: usize, alloc_duration: Duration) {
906        let h = self.heap.as_mut().unwrap();
907        h.curr_blocks -= 1;
908        h.curr_bytes -= size;
909        h.total_lifetimes_duration += alloc_duration;
910    }
911
912    fn update_counts_for_ad_hoc_event(&mut self, weight: usize) {
913        std::assert!(self.heap.is_none());
914        self.total_blocks += 1;
915        self.total_bytes += weight as u64;
916    }
917}
918
919struct LiveBlock {
920    // The index of the PpInfo for this block.
921    pp_info_idx: usize,
922
923    // When the block was allocated.
924    allocation_instant: Instant,
925}
926
927// We record info about allocations and deallocations. A wrinkle: the recording
928// done may trigger additional allocations. We must ignore these because (a)
929// they're part of `dhat`'s execution, not the original program's execution,
930// and (b) they would be intercepted and trigger additional allocations, which
931// would be intercepted and trigger additional allocations, and so on, leading
932// to infinite loops.
933//
934// With this type we can run one code path if we are already ignoring
935// allocations. Otherwise, we can a second code path while ignoring
936// allocations. In practice, the first code path is unreachable except within
937// the `GlobalAlloc` methods.
938//
939// WARNING: This type must be used for any code within this crate that can
940// trigger allocations.
941struct IgnoreAllocs {
942    was_already_ignoring_allocs: bool,
943}
944
945thread_local!(static IGNORE_ALLOCS: Cell<bool> = Cell::new(false));
946
947impl IgnoreAllocs {
948    fn new() -> Self {
949        Self {
950            was_already_ignoring_allocs: IGNORE_ALLOCS.with(|b| b.replace(true)),
951        }
952    }
953}
954
955/// If code panics while `IgnoreAllocs` is live, this will still reset
956/// `IGNORE_ALLOCS` so that it can be used again.
957impl Drop for IgnoreAllocs {
958    fn drop(&mut self) {
959        if !self.was_already_ignoring_allocs {
960            IGNORE_ALLOCS.with(|b| b.set(false));
961        }
962    }
963}
964
965/// A type whose lifetime dictates the start and end of profiling.
966///
967/// Profiling starts when the first value of this type is created. Profiling
968/// stops when (a) this value is dropped or (b) a `dhat` assertion fails,
969/// whichever comes first. When that happens, profiling data may be written to
970/// file, depending on how the `Profiler` has been configured. Only one
971/// `Profiler` can be running at any point in time.
972//
973// The actual profiler state is stored in `Globals`, so it can be accessed from
974// places like `Alloc::alloc` and `ad_hoc_event()` when the `Profiler`
975// instance isn't within reach.
976#[derive(Debug)]
977pub struct Profiler;
978
979impl Profiler {
980    /// Initiates allocation profiling.
981    ///
982    /// Typically the first thing in `main`. Its result should be assigned to a
983    /// variable whose lifetime ends at the end of `main`.
984    ///
985    /// # Panics
986    ///
987    /// Panics if another `Profiler` is running.
988    ///
989    /// # Examples
990    /// ```
991    /// let _profiler = dhat::Profiler::new_heap();
992    /// ```
993    pub fn new_heap() -> Self {
994        Self::builder().build()
995    }
996
997    /// Initiates ad hoc profiling.
998    ///
999    /// Typically the first thing in `main`. Its result should be assigned to a
1000    /// variable whose lifetime ends at the end of `main`.
1001    ///
1002    /// # Panics
1003    ///
1004    /// Panics if another `Profiler` is running.
1005    ///
1006    /// # Examples
1007    /// ```
1008    /// let _profiler = dhat::Profiler::new_ad_hoc();
1009    /// ```
1010    pub fn new_ad_hoc() -> Self {
1011        Self::builder().ad_hoc().build()
1012    }
1013
1014    /// Creates a new [`ProfilerBuilder`], which defaults to heap profiling.
1015    pub fn builder() -> ProfilerBuilder {
1016        ProfilerBuilder {
1017            ad_hoc: false,
1018            testing: false,
1019            file_name: None,
1020            trim_backtraces: Some(10),
1021            eprint_json: false,
1022        }
1023    }
1024}
1025
1026/// A builder for [`Profiler`], for cases beyond the basic ones provided by
1027/// [`Profiler`].
1028///
1029/// Created with [`Profiler::builder`].
1030#[derive(Debug)]
1031pub struct ProfilerBuilder {
1032    ad_hoc: bool,
1033    testing: bool,
1034    file_name: Option<PathBuf>,
1035    trim_backtraces: Option<usize>,
1036    eprint_json: bool,
1037}
1038
1039impl ProfilerBuilder {
1040    /// Requests ad hoc profiling.
1041    ///
1042    /// # Examples
1043    /// ```
1044    /// let _profiler = dhat::Profiler::builder().ad_hoc().build();
1045    /// ```
1046    pub fn ad_hoc(mut self) -> Self {
1047        self.ad_hoc = true;
1048        self
1049    }
1050
1051    /// Requests testing mode, which allows the use of
1052    /// [`dhat::assert!`](assert) and related macros, and disables saving of
1053    /// profile data on [`Profiler`] drop.
1054    ///
1055    /// # Examples
1056    /// ```
1057    /// let _profiler = dhat::Profiler::builder().testing().build();
1058    /// ```
1059    pub fn testing(mut self) -> Self {
1060        self.testing = true;
1061        self
1062    }
1063
1064    /// Sets the name of the file in which profiling data will be saved.
1065    ///
1066    /// # Examples
1067    /// ```
1068    /// let file_name = format!("heap-{}.json", std::process::id());
1069    /// let _profiler = dhat::Profiler::builder().file_name(file_name).build();
1070    /// # std::mem::forget(_profiler); // Don't write the file in `cargo tests`
1071    /// ```
1072    pub fn file_name<P: AsRef<Path>>(mut self, file_name: P) -> Self {
1073        self.file_name = Some(file_name.as_ref().to_path_buf());
1074        self
1075    }
1076
1077    /// Sets how backtrace trimming is performed.
1078    ///
1079    /// `dhat` can use heuristics to trim uninteresting frames from the top and
1080    /// bottom of backtraces, which makes the output easier to read. It can
1081    /// also limit the number of frames, which improves performance.
1082    ///
1083    /// The argument can be specified in several ways.
1084    /// - `None`: no backtrace trimming will be performed, and there is no
1085    ///   frame count limit. This makes profiling much slower and increases the
1086    ///   size of saved data files.
1087    /// - `Some(n)`: top and bottom trimming will be performed, and the number
1088    ///   of frames will be limited by `n`. Values of `n` less than 4 will be
1089    ///   clamped to 4.
1090    /// - `Some(usize::MAX)`: top and bottom trimming with be performed, but
1091    ///   there is no frame count limit. This makes profiling much slower and
1092    ///   increases the size of saved data files.
1093    ///
1094    /// The default value (used if this function is not called) is `Some(10)`.
1095    ///
1096    /// The number of frames shown in viewed profiles may differ from the
1097    /// number requested here, for two reasons.
1098    /// - Inline frames do not count towards this length. In release builds it
1099    ///   is common for the number of inline frames to equal or even exceed the
1100    ///   number of "real" frames.
1101    /// - Backtrace trimming will remove a small number of frames from heap
1102    ///   profile backtraces. The number removed will likely be more in a debug
1103    ///   build than in a release build.
1104    ///
1105    /// # Examples
1106    /// ```
1107    /// let _profiler = dhat::Profiler::builder().trim_backtraces(None).build();
1108    /// ```
1109    pub fn trim_backtraces(mut self, max_frames: Option<usize>) -> Self {
1110        self.trim_backtraces = max_frames.map(|m| std::cmp::max(m, 4));
1111        self
1112    }
1113
1114    // For testing purposes only. Useful for seeing what went wrong if a test
1115    // fails on CI.
1116    #[doc(hidden)]
1117    pub fn eprint_json(mut self) -> Self {
1118        self.eprint_json = true;
1119        self
1120    }
1121
1122    /// Creates a [`Profiler`] from the builder and initiates profiling.
1123    ///
1124    /// # Panics
1125    ///
1126    /// Panics if another [`Profiler`] is running.
1127    pub fn build(self) -> Profiler {
1128        let ignore_allocs = IgnoreAllocs::new();
1129        std::assert!(!ignore_allocs.was_already_ignoring_allocs);
1130
1131        let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1132        match phase {
1133            Phase::Ready => {
1134                let file_name = if let Some(file_name) = self.file_name {
1135                    file_name
1136                } else if !self.ad_hoc {
1137                    PathBuf::from("dhat-heap.json")
1138                } else {
1139                    PathBuf::from("dhat-ad-hoc.json")
1140                };
1141                let h = if !self.ad_hoc {
1142                    Some(HeapGlobals::new())
1143                } else {
1144                    None
1145                };
1146                *phase = Phase::Running(Globals::new(
1147                    self.testing,
1148                    file_name,
1149                    self.trim_backtraces,
1150                    self.eprint_json,
1151                    h,
1152                ));
1153            }
1154            Phase::Running(_) | Phase::PostAssert => {
1155                panic!("dhat: creating a profiler while a profiler is already running")
1156            }
1157        }
1158        Profiler
1159    }
1160}
1161
1162// Get a backtrace according to `$g`'s settings. A macro rather than a `Global`
1163// method to avoid putting an extra frame into backtraces.
1164macro_rules! new_backtrace {
1165    ($g:expr) => {{
1166        if $g.frames_to_trim.is_none() {
1167            // This is the first backtrace from profiling. Work out what we
1168            // will be trimming from the top and bottom of all backtraces.
1169            // `None` here because we don't want any frame trimming for this
1170            // backtrace.
1171            let bt = new_backtrace_inner(None, &FxHashMap::default());
1172            $g.frames_to_trim = Some(bt.get_frames_to_trim(&$g.start_bt));
1173        }
1174
1175        // Get the backtrace.
1176        new_backtrace_inner($g.trim_backtraces, $g.frames_to_trim.as_ref().unwrap())
1177    }};
1178}
1179
1180// Get a backtrace, possibly trimmed.
1181//
1182// Note: it's crucial that there only be a single call to `backtrace::trace()`
1183// that is used everywhere, so that all traces will have the same backtrace
1184// function IPs in their top frames. (With multiple call sites we would have
1185// multiple closures, giving multiple instances of `backtrace::trace<F>`, and
1186// monomorphisation would put them into different functions in the binary.)
1187// Without this, top frame trimming wouldn't work. That's why this is a
1188// function (with `inline(never)` just to be safe) rather than a macro like
1189// `new_backtrace`. The frame for this function will be removed by top frame
1190// trimming.
1191#[inline(never)]
1192fn new_backtrace_inner(
1193    trim_backtraces: Option<usize>,
1194    frames_to_trim: &FxHashMap<usize, TB>,
1195) -> Backtrace {
1196    // Get the backtrace, trimming if necessary at the top and bottom and for
1197    // length.
1198    let mut frames = Vec::new();
1199    backtrace::trace(|frame| {
1200        let ip = frame.ip() as usize;
1201        if trim_backtraces.is_some() {
1202            match frames_to_trim.get(&ip) {
1203                Some(TB::Top) => return true,     // ignore frame and continue
1204                Some(TB::Bottom) => return false, // ignore frame and stop
1205                _ => {}                           // use this frame
1206            }
1207        }
1208
1209        frames.push(frame.clone().into());
1210
1211        if let Some(max_frames) = trim_backtraces {
1212            frames.len() < max_frames // stop if we have enough frames
1213        } else {
1214            true // continue
1215        }
1216    });
1217    Backtrace(frames.into())
1218}
1219
1220/// A global allocator that tracks allocations and deallocations on behalf of
1221/// the [`Profiler`] type.
1222///
1223/// It must be set as the global allocator (via `#[global_allocator]`) when
1224/// doing heap profiling.
1225#[derive(Debug)]
1226pub struct Alloc;
1227
1228unsafe impl GlobalAlloc for Alloc {
1229    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
1230        let ignore_allocs = IgnoreAllocs::new();
1231        if ignore_allocs.was_already_ignoring_allocs {
1232            System.alloc(layout)
1233        } else {
1234            let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1235            let ptr = System.alloc(layout);
1236            if ptr.is_null() {
1237                return ptr;
1238            }
1239
1240            if let Phase::Running(g @ Globals { heap: Some(_), .. }) = phase {
1241                let size = layout.size();
1242                let bt = new_backtrace!(g);
1243                let pp_info_idx = g.get_pp_info(bt, PpInfo::new_heap);
1244
1245                let now = Instant::now();
1246                g.record_block(ptr, pp_info_idx, now);
1247                g.update_counts_for_alloc(pp_info_idx, size, None, now);
1248            }
1249            ptr
1250        }
1251    }
1252
1253    unsafe fn realloc(&self, old_ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
1254        let ignore_allocs = IgnoreAllocs::new();
1255        if ignore_allocs.was_already_ignoring_allocs {
1256            System.realloc(old_ptr, layout, new_size)
1257        } else {
1258            let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1259            let new_ptr = System.realloc(old_ptr, layout, new_size);
1260            if new_ptr.is_null() {
1261                return new_ptr;
1262            }
1263
1264            if let Phase::Running(g @ Globals { heap: Some(_), .. }) = phase {
1265                let old_size = layout.size();
1266                let delta = Delta::new(old_size, new_size);
1267
1268                if delta.shrinking {
1269                    // Total bytes is coming down from a possible peak.
1270                    g.check_for_global_peak();
1271                }
1272
1273                // Remove the record of the existing live block and get the
1274                // `PpInfo`. If it's not in the live block table, it must
1275                // have been allocated before `TRI_GLOBALS` was set up, and
1276                // we treat it like an `alloc`.
1277                let h = g.heap.as_mut().unwrap();
1278                let live_block = h.live_blocks.remove(&(old_ptr as usize));
1279                let (pp_info_idx, delta) = if let Some(live_block) = live_block {
1280                    (live_block.pp_info_idx, Some(delta))
1281                } else {
1282                    let bt = new_backtrace!(g);
1283                    let pp_info_idx = g.get_pp_info(bt, PpInfo::new_heap);
1284                    (pp_info_idx, None)
1285                };
1286
1287                let now = Instant::now();
1288                g.record_block(new_ptr, pp_info_idx, now);
1289                g.update_counts_for_alloc(pp_info_idx, new_size, delta, now);
1290            }
1291            new_ptr
1292        }
1293    }
1294
1295    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
1296        let ignore_allocs = IgnoreAllocs::new();
1297        if ignore_allocs.was_already_ignoring_allocs {
1298            System.dealloc(ptr, layout)
1299        } else {
1300            let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1301            System.dealloc(ptr, layout);
1302
1303            if let Phase::Running(g @ Globals { heap: Some(_), .. }) = phase {
1304                let size = layout.size();
1305
1306                // Remove the record of the live block and get the
1307                // `PpInfo`. If it's not in the live block table, it must
1308                // have been allocated before `TRI_GLOBALS` was set up, and
1309                // we just ignore it.
1310                let h = g.heap.as_mut().unwrap();
1311                if let Some(LiveBlock {
1312                    pp_info_idx,
1313                    allocation_instant,
1314                }) = h.live_blocks.remove(&(ptr as usize))
1315                {
1316                    // Total bytes is coming down from a possible peak.
1317                    g.check_for_global_peak();
1318
1319                    let alloc_duration = allocation_instant.elapsed();
1320                    g.update_counts_for_dealloc(pp_info_idx, size, alloc_duration);
1321                }
1322            }
1323        }
1324    }
1325}
1326
1327/// Registers an event during ad hoc profiling.
1328///
1329/// The meaning of the weight argument is determined by the user. A call to
1330/// this function has no effect if a [`Profiler`] is not running or not doing ad
1331/// hoc profiling.
1332pub fn ad_hoc_event(weight: usize) {
1333    let ignore_allocs = IgnoreAllocs::new();
1334    std::assert!(!ignore_allocs.was_already_ignoring_allocs);
1335
1336    let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1337    if let Phase::Running(g @ Globals { heap: None, .. }) = phase {
1338        let bt = new_backtrace!(g);
1339        let pp_info_idx = g.get_pp_info(bt, PpInfo::new_ad_hoc);
1340
1341        // Update counts.
1342        g.update_counts_for_ad_hoc_event(pp_info_idx, weight);
1343    }
1344}
1345
1346impl Profiler {
1347    fn drop_inner(&mut self, memory_output: Option<&mut String>) {
1348        let ignore_allocs = IgnoreAllocs::new();
1349        std::assert!(!ignore_allocs.was_already_ignoring_allocs);
1350
1351        let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1352        match std::mem::replace(phase, Phase::Ready) {
1353            Phase::Ready => unreachable!(),
1354            Phase::Running(g) => {
1355                if !g.testing {
1356                    g.finish(memory_output)
1357                }
1358            }
1359            Phase::PostAssert => {}
1360        }
1361    }
1362
1363    // For testing purposes only.
1364    #[doc(hidden)]
1365    pub fn drop_and_get_memory_output(&mut self) -> String {
1366        let mut memory_output = String::new();
1367        self.drop_inner(Some(&mut memory_output));
1368        memory_output
1369    }
1370}
1371
1372impl Drop for Profiler {
1373    fn drop(&mut self) {
1374        self.drop_inner(None);
1375    }
1376}
1377
1378// A wrapper for `backtrace::Backtrace` that implements `Eq` and `Hash`, which
1379// only look at the frame IPs. This assumes that any two
1380// `backtrace::Backtrace`s with the same frame IPs are equivalent.
1381#[derive(Debug)]
1382struct Backtrace(backtrace::Backtrace);
1383
1384impl Backtrace {
1385    // The top frame symbols in a backtrace (those relating to backtracing
1386    // itself) are typically the same, and look something like this (Mac or
1387    // Linux release build, Dec 2021):
1388    // - 0x10fca200a: backtrace::backtrace::libunwind::trace
1389    // - 0x10fca200a: backtrace::backtrace::trace_unsynchronized
1390    // - 0x10fca200a: backtrace::backtrace::trace
1391    // - 0x10fc97350: dhat::new_backtrace_inner
1392    // - 0x10fc97984: [interesting function]
1393    //
1394    // We compare the top frames of a stack obtained while profiling with those
1395    // in `start_bt`. Those that overlap are the frames relating to backtracing
1396    // that can be discarded.
1397    //
1398    // The bottom frame symbols in a backtrace (those below `main`) are
1399    // typically the same, and look something like this (Mac or Linux release
1400    // build, Dec 2021):
1401    // - 0x1060f70e8: dhatter::main
1402    // - 0x1060f7026: core::ops::function::FnOnce::call_once
1403    // - 0x1060f7026: std::sys_common::backtrace::__rust_begin_short_backtrace
1404    // - 0x1060f703c: std::rt::lang_start::{{closure}}
1405    // - 0x10614b79a: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
1406    // - 0x10614b79a: std::panicking::try::do_call
1407    // - 0x10614b79a: std::panicking::try
1408    // - 0x10614b79a: std::panic::catch_unwind
1409    // - 0x10614b79a: std::rt::lang_start_internal::{{closure}}
1410    // - 0x10614b79a: std::panicking::try::do_call
1411    // - 0x10614b79a: std::panicking::try
1412    // - 0x10614b79a: std::panic::catch_unwind
1413    // - 0x10614b79a: std::rt::lang_start_internal
1414    // - 0x1060f7259: ???
1415    //
1416    // We compare the bottom frames of a stack obtained while profiling with
1417    // those in `start_bt`. Those that overlap are the frames below main that
1418    // can be discarded.
1419    fn get_frames_to_trim(&self, start_bt: &Backtrace) -> FxHashMap<usize, TB> {
1420        let mut frames_to_trim = FxHashMap::default();
1421        let frames1 = self.0.frames();
1422        let frames2 = start_bt.0.frames();
1423
1424        let (mut i1, mut i2) = (0, 0);
1425        loop {
1426            if i1 == frames1.len() - 1 || i2 == frames2.len() - 1 {
1427                // This should never happen in practice, it's too much
1428                // similarity between the backtraces. If it does happen,
1429                // abandon top trimming entirely.
1430                frames_to_trim.retain(|_, v| *v == TB::Bottom);
1431                break;
1432            }
1433            if frames1[i1].ip() != frames2[i2].ip() {
1434                break;
1435            }
1436            frames_to_trim.insert(frames1[i1].ip() as usize, TB::Top);
1437            i1 += 1;
1438            i2 += 1;
1439        }
1440
1441        let (mut i1, mut i2) = (frames1.len() - 1, frames2.len() - 1);
1442        loop {
1443            if i1 == 0 || i2 == 0 {
1444                // This should never happen in practice, it's too much
1445                // similarity between the backtraces. If it does happen,
1446                // abandon bottom trimming entirely.
1447                frames_to_trim.retain(|_, v| *v == TB::Top);
1448                break;
1449            }
1450            if frames1[i1].ip() != frames2[i2].ip() {
1451                break;
1452            }
1453            frames_to_trim.insert(frames1[i1].ip() as usize, TB::Bottom);
1454            i1 -= 1;
1455            i2 -= 1;
1456        }
1457
1458        frames_to_trim
1459    }
1460
1461    // The top frame symbols in a trimmed heap profiling backtrace vary
1462    // significantly, depending on build configuration, platform, and program
1463    // point, and look something like this (Mac or Linux release build, Dec
1464    // 2021):
1465    // - 0x103ad464c: <dhat::Alloc as core::alloc::global::GlobalAlloc>::alloc
1466    // - 0x103acac99: __rg_alloc                    // sometimes missing
1467    // - 0x103acfe47: alloc::alloc::alloc           // sometimes missing
1468    // - 0x103acfe47: alloc::alloc::Global::alloc_impl
1469    // - 0x103acfe47: <alloc::alloc::Global as core::alloc::Allocator>::allocate
1470    // - 0x103acfe47: alloc::alloc::exchange_malloc // sometimes missing
1471    // - 0x103acfe47: [allocation point in program being profiled]
1472    //
1473    // We scan backwards for the first frame that looks like it comes from
1474    // allocator code, and all frames before it. If we don't find any such
1475    // frames, we show from frame 0, i.e. all frames.
1476    //
1477    // Note: this is a little dangerous. When deciding if a new backtrace has
1478    // been seen before, we consider all the IP addresses within it. And then
1479    // we trim some of those. It's possible that this will result in some
1480    // previously distinct traces becoming the same, which makes dh_view.html
1481    // abort. If that ever happens, look to see if something is going wrong
1482    // here.
1483    fn first_heap_symbol_to_show(&self) -> usize {
1484        // Examples of symbols that this search will match:
1485        // - alloc::alloc::{alloc,realloc,exchange_malloc}
1486        // - <alloc::alloc::Global as core::alloc::Allocator>::{allocate,grow}
1487        // - <dhat::Alloc as core::alloc::global::GlobalAlloc>::alloc
1488        // - __rg_{alloc,realloc}
1489        //
1490        // Be careful when changing this, because to do it properly requires
1491        // testing both debug and release builds on multiple platforms.
1492        self.first_symbol_to_show(|s| {
1493            s.starts_with("alloc::alloc::")
1494                || s.starts_with("<alloc::alloc::")
1495                || s.starts_with("<dhat::Alloc")
1496                || s.starts_with("__rg_")
1497        })
1498    }
1499
1500    // The top frame symbols in a trimmed ad hoc profiling backtrace are always
1501    // the same, something like this (Mac or Linux release build, Dec 2021):
1502    // - 0x10cc1f504: dhat::ad_hoc_event
1503    // - 0x10cc1954d: [dhat::ad_hoc_event call site in program being profiled]
1504    //
1505    // So need not trim frames, and can show from frame 0 onward.
1506    fn first_ad_hoc_symbol_to_show(&self) -> usize {
1507        0
1508    }
1509
1510    // Find the first symbol to show, based on the predicate `p`.
1511    fn first_symbol_to_show<P: Fn(&str) -> bool>(&self, p: P) -> usize {
1512        // Get the symbols into a vector so we can reverse iterate over them.
1513        let symbols: Vec<_> = self
1514            .0
1515            .frames()
1516            .iter()
1517            .flat_map(|f| f.symbols().iter())
1518            .collect();
1519
1520        for (i, symbol) in symbols.iter().enumerate().rev() {
1521            // Use `{:#}` to print the "alternate" form of the symbol name,
1522            // which omits the trailing hash (e.g. `::ha68e4508a38cc95a`).
1523            if let Some(s) = symbol.name().map(|name| format!("{:#}", name)) {
1524                if p(&s) {
1525                    return i;
1526                }
1527            }
1528        }
1529        0
1530    }
1531
1532    // Useful for debugging.
1533    #[allow(dead_code)]
1534    fn eprint(&self) {
1535        for frame in self.0.frames().iter() {
1536            for symbol in frame.symbols().iter() {
1537                eprintln!("{}", Backtrace::frame_to_string(frame, symbol));
1538            }
1539        }
1540    }
1541
1542    fn frame_to_string(
1543        frame: &backtrace::BacktraceFrame,
1544        symbol: &backtrace::BacktraceSymbol,
1545    ) -> String {
1546        format!(
1547            // Use `{:#}` to print the "alternate" form of the symbol name,
1548            // which omits the trailing hash (e.g. `::ha68e4508a38cc95a`).
1549            "{:?}: {:#} ({:#}:{}:{})",
1550            frame.ip(),
1551            symbol.name().unwrap_or_else(|| SymbolName::new(b"???")),
1552            match symbol.filename() {
1553                Some(path) => trim_path(path),
1554                None => Path::new("???"),
1555            }
1556            .display(),
1557            symbol.lineno().unwrap_or(0),
1558            symbol.colno().unwrap_or(0),
1559        )
1560    }
1561}
1562
1563impl PartialEq for Backtrace {
1564    fn eq(&self, other: &Self) -> bool {
1565        let mut frames1 = self.0.frames().iter();
1566        let mut frames2 = other.0.frames().iter();
1567        loop {
1568            let ip1 = frames1.next().map(|f| f.ip());
1569            let ip2 = frames2.next().map(|f| f.ip());
1570            if ip1 != ip2 {
1571                return false;
1572            }
1573            if ip1 == None {
1574                return true;
1575            }
1576            // Otherwise, continue.
1577        }
1578    }
1579}
1580
1581impl Eq for Backtrace {}
1582
1583impl Hash for Backtrace {
1584    fn hash<H: Hasher>(&self, state: &mut H) {
1585        for frame in self.0.frames().iter() {
1586            frame.ip().hash(state);
1587        }
1588    }
1589}
1590
1591// Trims a path with more than three components down to three (e.g.
1592// `/aa/bb/cc/dd.rs` becomes `bb/cc/dd.rs`), otherwise returns `path`
1593// unchanged.
1594fn trim_path(path: &Path) -> &Path {
1595    const N: usize = 3;
1596    let len = path.components().count();
1597    if len > N {
1598        let mut c = path.components();
1599        c.nth(len - (N + 1));
1600        c.as_path()
1601    } else {
1602        path
1603    }
1604}
1605
1606/// Stats from heap profiling.
1607#[derive(Clone, Debug, PartialEq, Eq)]
1608#[non_exhaustive]
1609pub struct HeapStats {
1610    /// Number of blocks (a.k.a. allocations) allocated over the entire run.
1611    pub total_blocks: u64,
1612
1613    /// Number of bytes allocated over the entire run.
1614    pub total_bytes: u64,
1615
1616    /// Number of blocks (a.k.a. allocations) currently allocated.
1617    pub curr_blocks: usize,
1618
1619    /// Number of bytes currently allocated.
1620    pub curr_bytes: usize,
1621
1622    /// Number of blocks (a.k.a. allocations) allocated at the global peak,
1623    /// i.e. when `curr_bytes` peaked.
1624    pub max_blocks: usize,
1625
1626    /// Number of bytes allocated at the global peak, i.e. when `curr_bytes`
1627    /// peaked.
1628    pub max_bytes: usize,
1629}
1630
1631/// Stats from ad hoc profiling.
1632#[derive(Clone, Debug, PartialEq, Eq)]
1633#[non_exhaustive]
1634pub struct AdHocStats {
1635    /// Number of events recorded for the entire run.
1636    pub total_events: u64,
1637
1638    /// Number of units recorded for the entire run.
1639    pub total_units: u64,
1640}
1641
1642impl HeapStats {
1643    /// Gets the current heap stats.
1644    ///
1645    /// # Panics
1646    ///
1647    /// Panics if called when a [`Profiler`] is not running or not doing heap
1648    /// profiling.
1649    pub fn get() -> Self {
1650        let ignore_allocs = IgnoreAllocs::new();
1651        std::assert!(!ignore_allocs.was_already_ignoring_allocs);
1652
1653        let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1654        match phase {
1655            Phase::Ready => {
1656                panic!("dhat: getting heap stats when no profiler is running")
1657            }
1658            Phase::Running(g) => g.get_heap_stats(),
1659            Phase::PostAssert => {
1660                panic!("dhat: getting heap stats after the profiler has asserted")
1661            }
1662        }
1663    }
1664}
1665
1666impl AdHocStats {
1667    /// Gets the current ad hoc stats.
1668    ///
1669    /// # Panics
1670    ///
1671    /// Panics if called when a [`Profiler`] is not running or not doing ad hoc
1672    /// profiling.
1673    pub fn get() -> Self {
1674        let ignore_allocs = IgnoreAllocs::new();
1675        std::assert!(!ignore_allocs.was_already_ignoring_allocs);
1676
1677        let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1678        match phase {
1679            Phase::Ready => {
1680                panic!("dhat: getting ad hoc stats when no profiler is running")
1681            }
1682            Phase::Running(g) => g.get_ad_hoc_stats(),
1683            Phase::PostAssert => {
1684                panic!("dhat: getting ad hoc stats after the profiler has asserted")
1685            }
1686        }
1687    }
1688}
1689
1690// Just an implementation detail of the assert macros.
1691// njn: invert sense of the return value?
1692#[doc(hidden)]
1693pub fn check_assert_condition<F>(cond: F) -> bool
1694where
1695    F: FnOnce() -> bool,
1696{
1697    // We do the test within `check_assert_condition` (as opposed to within the
1698    // `assert*` macros) so that we'll always detect if the profiler isn't
1699    // running.
1700    let ignore_allocs = IgnoreAllocs::new();
1701    std::assert!(!ignore_allocs.was_already_ignoring_allocs);
1702
1703    let phase: &mut Phase<Globals> = &mut TRI_GLOBALS.lock();
1704    match phase {
1705        Phase::Ready => panic!("dhat: asserting when no profiler is running"),
1706        Phase::Running(g) => {
1707            if !g.testing {
1708                panic!("dhat: asserting while not in testing mode");
1709            }
1710            if cond() {
1711                return false;
1712            }
1713        }
1714        Phase::PostAssert => panic!("dhat: asserting after the profiler has asserted"),
1715    }
1716
1717    // Failure.
1718    match std::mem::replace(phase, Phase::PostAssert) {
1719        Phase::Ready => unreachable!(),
1720        Phase::Running(g) => {
1721            g.finish(None);
1722            true
1723        }
1724        Phase::PostAssert => unreachable!(),
1725    }
1726}
1727
1728/// Asserts that an expression is true.
1729///
1730/// Like [`std::assert!`], additional format arguments are supported. On
1731/// failure, this macro will save the profile data and panic.
1732///
1733/// # Panics
1734///
1735/// Panics immediately (without saving the profile data) in the following
1736/// circumstances.
1737/// - If called when a [`Profiler`] is not running or is not in testing mode.
1738/// - If called after a previous `dhat` assertion has failed with the current
1739///   [`Profiler`]. This is possible if [`std::panic::catch_unwind`] is used.
1740#[macro_export]
1741macro_rules! assert {
1742    ($cond:expr) => ({
1743        if dhat::check_assert_condition(|| $cond) {
1744            panic!("dhat: assertion failed: {}", stringify!($cond));
1745        }
1746    });
1747    ($cond:expr, $($arg:tt)+) => ({
1748        if dhat::check_assert_condition(|| $cond) {
1749            panic!("dhat: assertion failed: {}: {}", stringify!($cond), format_args!($($arg)+));
1750        }
1751    });
1752}
1753
1754/// Asserts that two expressions are equal.
1755///
1756/// Like [`std::assert_eq!`], additional format arguments are supported. On
1757/// failure, this macro will save the profile data and panic.
1758///
1759/// # Panics
1760///
1761/// Panics immediately (without saving the profile data) in the following
1762/// circumstances.
1763/// - If called when a [`Profiler`] is not running or is not in testing mode.
1764/// - If called after a previous `dhat` assertion has failed with the current
1765///   [`Profiler`]. This is possible if [`std::panic::catch_unwind`] is used.
1766#[macro_export]
1767macro_rules! assert_eq {
1768    ($left:expr, $right:expr $(,)?) => ({
1769        if dhat::check_assert_condition( || $left == $right) {
1770            panic!(
1771                "dhat: assertion failed: `(left == right)`\n  left: `{:?}`,\n right: `{:?}`",
1772                $left, $right
1773            );
1774        }
1775    });
1776    ($left:expr, $right:expr, $($arg:tt)+) => ({
1777        if dhat::check_assert_condition(|| $left == $right) {
1778            panic!(
1779                "dhat: assertion failed: `(left == right)`\n  left: `{:?}`,\n right: `{:?}`: {}",
1780                $left, $right, format_args!($($arg)+)
1781            );
1782        }
1783    });
1784}
1785
1786/// Asserts that two expressions are not equal.
1787///
1788/// Like [`std::assert_ne!`], additional format arguments are supported. On
1789/// failure, this macro will save the profile data and panic.
1790///
1791/// # Panics
1792///
1793/// Panics immediately (without saving the profile data) in the following
1794/// circumstances.
1795/// - If called when a [`Profiler`] is not running or is not in testing mode.
1796/// - If called after a previous `dhat` assertion has failed with the current
1797///   [`Profiler`]. This is possible if [`std::panic::catch_unwind`] is used.
1798#[macro_export]
1799macro_rules! assert_ne {
1800    ($left:expr, $right:expr) => ({
1801        if dhat::check_assert_condition(|| $left != $right) {
1802            panic!(
1803                "dhat: assertion failed: `(left != right)`\n  left: `{:?}`,\n right: `{:?}`",
1804                $left, $right
1805            );
1806        }
1807    });
1808    ($left:expr, $right:expr, $($arg:tt)+) => ({
1809        if dhat::check_assert_condition(|| $left != $right) {
1810            panic!(
1811                "dhat: assertion failed: `(left != right)`\n  left: `{:?}`,\n right: `{:?}`: {}",
1812                $left, $right, format_args!($($arg)+)
1813            );
1814        }
1815    });
1816}
1817
1818// A Rust representation of DHAT's JSON file format, which is described in
1819// comments in dhat/dh_main.c in Valgrind's source code.
1820//
1821// Building this structure in order to serialize does take up some memory. We
1822// could instead stream the JSON output directly to file ourselves. This would
1823// be more efficient but make the code uglier.
1824#[derive(Serialize)]
1825#[allow(non_snake_case)]
1826struct DhatJson {
1827    dhatFileVersion: u32,
1828    mode: &'static str,
1829    verb: &'static str,
1830    bklt: bool,
1831    bkacc: bool,
1832    #[serde(skip_serializing_if = "Option::is_none")]
1833    bu: Option<&'static str>,
1834    #[serde(skip_serializing_if = "Option::is_none")]
1835    bsu: Option<&'static str>,
1836    #[serde(skip_serializing_if = "Option::is_none")]
1837    bksu: Option<&'static str>,
1838    tu: &'static str,
1839    Mtu: &'static str,
1840    #[serde(skip_serializing_if = "Option::is_none")]
1841    tuth: Option<usize>,
1842    cmd: String,
1843    pid: u32,
1844    #[serde(skip_serializing_if = "Option::is_none")]
1845    tg: Option<u128>,
1846    te: u128,
1847    pps: Vec<PpInfoJson>,
1848    ftbl: Vec<String>,
1849}
1850
1851// A Rust representation of a PpInfo within DHAT's JSON file format.
1852#[derive(Serialize)]
1853struct PpInfoJson {
1854    // `PpInfo::total_bytes and `PpInfo::total_blocks.
1855    tb: u64,
1856    tbk: u64,
1857
1858    // Derived from `PpInfo::total_lifetimes_duration`.
1859    #[serde(skip_serializing_if = "Option::is_none")]
1860    tl: Option<u128>,
1861
1862    // `PpInfo::max_bytes` and `PpInfo::max_blocks`.
1863    #[serde(skip_serializing_if = "Option::is_none")]
1864    mb: Option<usize>,
1865    #[serde(skip_serializing_if = "Option::is_none")]
1866    mbk: Option<usize>,
1867
1868    // `PpInfo::at_tgmax_bytes` and `PpInfo::at_tgmax_blocks`.
1869    #[serde(skip_serializing_if = "Option::is_none")]
1870    gb: Option<usize>,
1871    #[serde(skip_serializing_if = "Option::is_none")]
1872    gbk: Option<usize>,
1873
1874    // `PpInfo::curr_bytes` and `PpInfo::curr_blocks` (at termination, i.e.
1875    // "end").
1876    #[serde(skip_serializing_if = "Option::is_none")]
1877    eb: Option<usize>,
1878    #[serde(skip_serializing_if = "Option::is_none")]
1879    ebk: Option<usize>,
1880
1881    // Frames. Each element is an index into `ftbl`.
1882    fs: Vec<usize>,
1883}
1884
1885impl PpInfoJson {
1886    fn new(pp_info: &PpInfo, fs: Vec<usize>) -> Self {
1887        if let Some(h) = &pp_info.heap {
1888            Self {
1889                tb: pp_info.total_bytes,
1890                tbk: pp_info.total_blocks,
1891                tl: Some(h.total_lifetimes_duration.as_micros()),
1892                mb: Some(h.max_bytes),
1893                mbk: Some(h.max_blocks),
1894                gb: Some(h.at_tgmax_bytes),
1895                gbk: Some(h.at_tgmax_blocks),
1896                eb: Some(h.curr_bytes),
1897                ebk: Some(h.curr_blocks),
1898                fs,
1899            }
1900        } else {
1901            Self {
1902                tb: pp_info.total_bytes,
1903                tbk: pp_info.total_blocks,
1904                tl: None,
1905                mb: None,
1906                mbk: None,
1907                gb: None,
1908                gbk: None,
1909                eb: None,
1910                ebk: None,
1911                fs,
1912            }
1913        }
1914    }
1915}
1916
1917// A change in size. Used for `realloc`.
1918#[derive(Clone, Copy)]
1919struct Delta {
1920    shrinking: bool,
1921    size: usize,
1922}
1923
1924impl Delta {
1925    fn new(old_size: usize, new_size: usize) -> Delta {
1926        if new_size < old_size {
1927            Delta {
1928                shrinking: true,
1929                size: old_size - new_size,
1930            }
1931        } else {
1932            Delta {
1933                shrinking: false,
1934                size: new_size - old_size,
1935            }
1936        }
1937    }
1938}
1939
1940impl AddAssign<Delta> for usize {
1941    fn add_assign(&mut self, rhs: Delta) {
1942        if rhs.shrinking {
1943            *self -= rhs.size;
1944        } else {
1945            *self += rhs.size;
1946        }
1947    }
1948}
1949
1950impl AddAssign<Delta> for u64 {
1951    fn add_assign(&mut self, rhs: Delta) {
1952        if rhs.shrinking {
1953            *self -= rhs.size as u64;
1954        } else {
1955            *self += rhs.size as u64;
1956        }
1957    }
1958}
1959
1960// For testing purposes only.
1961#[doc(hidden)]
1962pub fn assert_is_panic<R, F: FnOnce() -> R + std::panic::UnwindSafe>(f: F, expected: &str) {
1963    let res = std::panic::catch_unwind(f);
1964    if let Err(err) = res {
1965        if let Some(actual) = err.downcast_ref::<&str>() {
1966            std::assert_eq!(expected, *actual);
1967        } else if let Some(actual) = err.downcast_ref::<String>() {
1968            std::assert_eq!(expected, actual);
1969        } else {
1970            panic!("assert_is_panic: Not a string: {:?}", err);
1971        }
1972    } else {
1973        panic!("assert_is_panic: Not an error");
1974    }
1975}
1976
1977#[cfg(test)]
1978mod test {
1979    use super::trim_path;
1980    use std::path::Path;
1981
1982    #[test]
1983    fn test_trim_path() {
1984        std::assert_eq!(trim_path(Path::new("")), Path::new(""));
1985        std::assert_eq!(trim_path(Path::new("/")), Path::new("/"));
1986        std::assert_eq!(trim_path(Path::new("aa.rs")), Path::new("aa.rs"));
1987        std::assert_eq!(trim_path(Path::new("/aa.rs")), Path::new("/aa.rs"));
1988        std::assert_eq!(trim_path(Path::new("bb/aa.rs")), Path::new("bb/aa.rs"));
1989        std::assert_eq!(trim_path(Path::new("/bb/aa.rs")), Path::new("/bb/aa.rs"));
1990        std::assert_eq!(
1991            trim_path(Path::new("cc/bb/aa.rs")),
1992            Path::new("cc/bb/aa.rs")
1993        );
1994        std::assert_eq!(
1995            trim_path(Path::new("/cc/bb/aa.rs")),
1996            Path::new("cc/bb/aa.rs")
1997        );
1998        std::assert_eq!(
1999            trim_path(Path::new("dd/cc/bb/aa.rs")),
2000            Path::new("cc/bb/aa.rs")
2001        );
2002        std::assert_eq!(
2003            trim_path(Path::new("/dd/cc/bb/aa.rs")),
2004            Path::new("cc/bb/aa.rs")
2005        );
2006    }
2007}