Introduction

Is your application leaking memory? Using too much memory? Making too many allocations? Do you want to figure out why, and where exactly?

If so you're in the right place!

Getting started

Download prebuilt binaries

You can download a precompiled binary release of the profiler from here.

Build from source

Alternatively you can build everything from sources yourself.

Make sure you have the following installed:

  1. Rust nightly
  2. Full GCC toolchain
  3. Yarn package manager

Then you can build the profiler:

$ cargo build --release -p bytehound-preload
$ cargo build --release -p bytehound-cli

...and grab the binaries from from target/release/libbytehound.so and target/release/bytehound.

Gathering data

You can gather the profiling data by attaching the profiler to your application using LD_PRELOAD. Just put the libbytehound.so in the same directory as your program and then run the following:

$ export MEMORY_PROFILER_LOG=info
$ LD_PRELOAD=./libbytehound.so ./your_application

You can further configure the profiler through environment variables, although often that is not be necessary.

Analysis

After you've gathered your data you can load it for analysis:

$ ./bytehound server memory-profiling_*.dat

Then open your web browser and point it at http://localhost:8080 to access the GUI.

If the profiler crashes when loading the data you most likely don't have enough RAM to load the whole thing into memory; see the common issues section for how to handle such situation.

Case study: Memory leak analysis

If you'd like to follow along this example check out the simulation directory in the root of the profiler's repository where you'll find the program being analyzed here.

We use the built-in scripting capabilities of the profiler for analysis here (which is available either through the script subcommand of the CLI, or through the scripting console in the GUI), however you can also use the built-in GUI to achieve roughly similar results. This example is more about demonstrating the mindset you need to have when analyzing the data in search of memory leaks as opposed to a step-by-step guide which you could apply everywhere.

Step one: let's take a look at the timeline

First let's try to graph all of the allocations:

graph()
    .add(allocations())
    .save();

It definitely looks like we might have some sort of a memory leak here, but we can't see much on this graph alone due to all of the noise.

Step two: let's find all of the obvious memory leaks

Let's try the simplest thing imaginable: graph only those allocations which were never deallocated up until the very end:

graph()
    .add("Leaked", allocations().only_leaked())
    .add("Temporary", allocations())
    .save();

Aha! We can see an obvious linear growth here! Let's try to split up the leaking part by backtrace:

let groups = allocations()
    .only_leaked()
    .group_by_backtrace()
        .sort_by_size();

graph().add(groups).save();

Looks like we have a few potential leaks here. First, let's start will defining a small helper function which will graph all of the allocations coming from that one single backtrace, and also print out that backtrace:

fn analyze_group(list) {
    let list_all = allocations().only_matching_backtraces(list);

    graph()
        .add("Leaked", list_all.only_leaked())
        .add("Temporary", list_all)
        .save();

    println("Total: {}", list_all.len());
    println("Leaked: {}", list_all.only_leaked().len());
    println();
    println("Backtrace:");
    println(list_all[0].backtrace().strip());
}

Group #0

So now let's start with the biggest one:

analyze_group(groups[0]);

Total: 1646
Leaked: 1646

Backtrace:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#17 [simulation] simulation::main [main.rs:122]
#18 [simulation] simulation::allocate_linear_leak_never_deallocated [main.rs:32]
#19 [simulation] alloc::vec::Vec<T,A>::resize [mod.rs:2366]

We have a clear-cut memory leak here! Every allocation from this backtrace was leaked.

Group #1

Let's try yet another one:

analyze_group(groups[1]);

Total: 1838
Leaked: 864

Backtrace:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#17 [simulation] simulation::main [main.rs:127]
#18 [simulation] simulation::allocate_bounded_leak [main.rs:72]
#19 [simulation] alloc::vec::Vec<T,A>::resize [mod.rs:2366]

Now this is interesting. If we only look at the supposedly leaked part it sure does look like it's an unbounded memory leak which grows linearly with time, but if we graph every allocation from this backtrace we can see that its memory usage is actually bounded! The longer we would profile this program the more "flat" the leaked part would get.

So is this a problem? Usually not. If you have something like, say, an LRU cache, you might see this kind of allocation pattern.

Group #2

Let's look at the last group from our original leaked graph:

analyze_group(groups[2]);

Total: 179391
Leaked: 183

Backtrace:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#17 [simulation] simulation::main [main.rs:132]
#18 [simulation] simulation::allocate_both_temporary_and_linear_leak [main.rs:96]
#19 [simulation] alloc::vec::Vec<T,A>::resize [mod.rs:2366]

This is the toughest case so far. Do we have a memory leak here on not? Well, it depends.

It could be a case of a bounded leak which hasn't yet reached a saturation point, or it could be simply a case of only some allocations ending up leaked. We'd either need to profile for a longer period of time, or analyze the code.

Step three: we need to go deeper

So is this all? Did we actually find all of the memory leaks? Not necessarily.

What we did was that we only looked at those allocations which were never deallocated. So what about those allocations which were deallocated, but only at the very end when the program was shut down? Should we consider those allocations as leaks? Well, probably!

First, let's try to graph the memory usage again, but only including the allocations which were deallocated before the program ended.

graph()
    .add(allocations().only_temporary())
    .save();

Hmm... there might or might not be a leak here. We need a more powerful filter!

First, let's filter out all of the allocations from the previous section; we've already analyzed those so we don't want them here to confuse us:

let remaining = allocations().only_not_matching_backtraces(groups);

And now, we want a list of allocations which weren't deallocated right until the end, right? Well, we can do that!

let leaked_until_end = remaining
    .only_leaked_or_deallocated_after(data().runtime() * 0.98);

graph().add(leaked_until_end).save();

This indeed looks promising. But let's clean in up a little first.

What's with the peak right at the end? Well, we asked for allocations which were only leaked or deallocated after 98% of the runtime has elapsed, so naturally those short lived allocations from near the end which were also deallocated after that time will still be included.

Let's get rid of them:

let leaked_until_end = remaining
    .only_leaked_or_deallocated_after(data().runtime() * 0.98)
    .only_alive_for_at_least(data().runtime() * 0.02);

graph().add(leaked_until_end).save();

Much better!

Now let's graph those by backtrace:

let groups = leaked_until_end.group_by_backtrace().sort_by_size();
graph().add(groups).save();

Bingo! There was something hidden in all of those temporary allocations after all!

Let's define another helper function to help us with our analysis:

fn analyze_group(list) {
    let list_all = allocations().only_matching_backtraces(list);
    let list_selected = list_all
        .only_deallocated_after_at_least(data().runtime() * 0.98);

    graph()
        .add("Deallocated after 98%", list_selected)
        .add("Deallocated before 98%", list_all)
        .save();

    println("Total: {}", list_all.len());
    println("Deallocated after 98%: {}", list_selected.len());
    println();
    println("Backtrace:");
    println(list_all[0].backtrace().strip());
}

Let's try to use it!

Group #0

analyze_group(groups[0]);

Total: 1710
Deallocated after 98%: 1710

Backtrace:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#17 [simulation] simulation::main [main.rs:123]
#18 [simulation] simulation::allocate_linear_leak_deallocated_at_the_end [main.rs:44]
#19 [simulation] alloc::vec::Vec<T,A>::resize [mod.rs:2366]

We have a winner! This definitely looks like a leak.

Group #1

analyze_group(groups[1]);

Total: 9
Deallocated after 98%: 1

Backtrace:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#17 [simulation] simulation::main [main.rs:123]
#18 [simulation] simulation::allocate_linear_leak_deallocated_at_the_end [main.rs:45]
#19 [simulation] alloc::vec::Vec<T,A>::push [mod.rs:1833]
#20 [simulation] alloc::raw_vec::RawVec<T,A>::reserve_for_push [raw_vec.rs:298]
#21 [simulation] alloc::raw_vec::RawVec<T,A>::grow_amortized [raw_vec.rs:400]
#22 [simulation] alloc::raw_vec::finish_grow [raw_vec.rs:466]
#23 [simulation] <alloc::alloc::Global as core::alloc::Allocator>::grow [alloc.rs:266]
#24 [simulation] alloc::alloc::Global::grow_impl [alloc.rs:213]
#25 [simulation] alloc::alloc::realloc [alloc.rs:213]
#26 [libbytehound.so] realloc [api.rs:377]

This is Vec that, from the look of it, is just growing in size.

It probably contains whatever is leaking (and if you read the backtrace it actualy does), but it's not what we're looking for.

In fact, if we look at the original graph most of what we have remaining are probably cases like this. Let's double-check by filtering out the leak we've already found and graph everything again:

let group = groups
    .ungroup()
    .only_not_matching_backtraces(groups[0])
    .group_by_backtrace();

graph().add(group).save();

This does indeed look like all of the long lived allocations here might have been just Vecs.

Let's verify that hypothesis:

graph()
    .add("Vecs", group.ungroup().only_passing_through_function("raw_vec::finish_grow"))
    .add("Remaining", group.ungroup())
    .save();

Indeed we were right!

Common issues

The profiler generates too much data, and I don't have enough disk space to hold it!

By default the profiler will gather every allocation that's made by your application. If you don't need all of that data then you can set the MEMORY_PROFILER_CULL_TEMPORARY_ALLOCATIONS environment variable to 1 before you start profiling. This will prevent the profiler from emitting the majority of short-lived allocations which should cut down on how big the resulting file will be.

You can also adjust the MEMORY_PROFILER_TEMPORARY_ALLOCATION_LIFETIME_THRESHOLD option to specify which allocations will be considered temporary by the profiler.

The profiler crashes and/or is killed when I try to load my data file!

You're most likely trying to load a really big file, and you're running out of RAM.

You can use the strip subcommand to strip away unnecessary allocations making it possible to load your data file for analysis even if you don't have enough RAM.

For example, here's how you'd strip away all of the allocations with a lifetime of less than 60 seconds:

$ ./bytehound strip --threshold 60 -o stripped.dat original.dat

After running this command the stripped.dat will only contain allocations which lived for at least 60 seconds or more.

Configuring the profiler

The profiler is configured through environment variables; here's a list of all of the supported environment variables that you can set.

MEMORY_PROFILER_OUTPUT

Default: memory-profiling_%e_%t_%p.dat

A path to a file to which the data will be written to.

This environment variable supports placeholders which will be replaced at runtime with the following:

  • %p -> PID of the process
  • %t -> number of seconds since UNIX epoch
  • %e -> name of the executable
  • %n -> auto-incrementing counter (0, 1, .., 9, 10, etc.)

MEMORY_PROFILER_LOG

Default: unset

The log level to use; possible values:

  • trace
  • debug
  • info
  • warn
  • error

Unset by default, which disables logging altogether.

MEMORY_PROFILER_LOGFILE

Default: unset

Path to the file to which the logs will be written to; if unset the logs will be emitted to stderr (if they're enabled with MEMORY_PROFILER_LOG).

This supports placeholders similar to MEMORY_PROFILER_OUTPUT (except %n).

MEMORY_PROFILER_CULL_TEMPORARY_ALLOCATIONS

Default: 0

When set to 1 the profiler will cull temporary allocations and omit them from the output.

Use this if you only care about memory leaks or you want to do long term profiling over several days.

MEMORY_PROFILER_TEMPORARY_ALLOCATION_LIFETIME_THRESHOLD

Default: 10000

The minimum lifetime of an allocation, in milliseconds, to not be considered a temporary allocation, and hence not get culled.

Only makes sense when MEMORY_PROFILER_CULL_TEMPORARY_ALLOCATIONS is turned on.

MEMORY_PROFILER_TEMPORARY_ALLOCATION_PENDING_THRESHOLD

Default: None

The maximum number of allocations to be kept in memory when tracking which allocations are temporary and which are not.

Every allocation whose lifetime hasn't yet crossed the temporary allocation interval will be temporarily kept in a buffer, and removed from it once it either gets deallocated or its lifetime crosses the temporary allocation interval.

If the number of allocations stored in this buffer exceeds the value set here the buffer will be cleared and all of the allocations contained within will be written to disk, regardless of their lifetime.

Only makes sense when MEMORY_PROFILER_CULL_TEMPORARY_ALLOCATIONS is turned on.

MEMORY_PROFILER_GRAB_BACKTRACES_ON_FREE

Default: 1

When set to 1 the backtraces will be also be gathered when the memory is freed.

MEMORY_PROFILER_DISABLE_BY_DEFAULT

Default: 0

When set to 1 the tracing will be disabled be default at startup.

MEMORY_PROFILER_REGISTER_SIGUSR1

Default: 1

When set to 1 the profiler will register a SIGUSR1 signal handler which can be used to toggle (enable or disable) profiling.

MEMORY_PROFILER_REGISTER_SIGUSR2

Default: 1

When set to 1 the profiler will register a SIGUSR2 signal handler which can be used to toggle (enable or disable) profiling.

MEMORY_PROFILER_ENABLE_SERVER

Default: 0

When set to 1 the profiled process will start an embedded server which can be used to stream the profiling data through TCP using bytehound gather and bytehound-gather.

This server will only be started when profiling is first enabled.

MEMORY_PROFILER_BASE_SERVER_PORT

Default: 8100

TCP port of the embedded server on which the profiler will listen on.

If the profiler won't be able to bind a socket to this port it will try to find the next free port to bind to. It will succesively probe the ports in a linear fashion, e.g. 8100, 8101, 8102, etc., up to 100 times before giving up.

Requires MEMORY_PROFILER_ENABLE_SERVER to be set to 1.

MEMORY_PROFILER_ENABLE_BROADCAST

Default: 0

When set to 1 the profiled process will send UDP broadcasts announcing that it's being profiled. This is used by bytehound gather and bytehound-gather to automatically discover bytehound instances to which to connect.

Requires MEMORY_PROFILER_ENABLE_SERVER to be set to 1.

MEMORY_PROFILER_WRITE_BINARIES_TO_OUTPUT

Default: 1

Controls whenever the profiler will embed the profiled application (and all of the libraries used by the application) inside of the profiling data it writes to disk.

This makes it possible to later decode the profiling data without having to manually hunt down the original binaries.

MEMORY_PROFILER_ZERO_MEMORY

Default: 0

Decides whenever malloc will behave like calloc and fill the memory it returns with zeros.

MEMORY_PROFILER_BACKTRACE_CACHE_SIZE_LEVEL_1

Default: 16384

Controls the size of the internal backtrace cache used to deduplicate emitted stack traces.

This is the size of the per-thread cache.

MEMORY_PROFILER_BACKTRACE_CACHE_SIZE_LEVEL_2

Default: 327680

Controls the size of the internal backtrace cache used to deduplicate emitted stack traces.

This is the size of the global cache.

MEMORY_PROFILER_GATHER_MMAP_CALLS

Default: 0

Controls whenever the profiler will also gather calls to mmap and munmap.

(Those are not treated as allocations and are only available under the /mmaps API endpoint.)

MEMORY_PROFILER_USE_SHADOW_STACK

Default: 1

Whenever to use a more intrusive, faster unwinding algorithm; enabled by default.

Setting it to 0 will on average significantly slow down unwinding. This option is provided only for debugging purposes.

MEMORY_PROFILER_TRACK_CHILD_PROCESSES

Default: 0

If set to 1, bytehound will also track the memory allocations of child processes spawned by the profiled process. This only applies to child processes spawned by the common fork() + exec() combination.

Note that if you enable this, you should use a value with appropriates placeholders (like PID) in MEMORY_PROFILER_OUTPUT, so that the output filenames for the parent and child processes are different. Otherwise, they would overwrite each other's data.

API reference

The profiler supports an embedded domain specific language based on Rhai for easy data analysis. Here's an API reference of everything the DSL exposes.

Globally available functions

These are the functions that are globally defined and can be called anywhere.

allocations

fn allocations() -> AllocationList

Returns an allocation list of the the currently globally loaded data file; equivalent to data().allocations().

If there is no globally loaded data file then it will throw an exception.

allocations

fn maps() -> MapList

Returns a map list of the the currently globally loaded data file; equivalent to data().maps().

If there is no globally loaded data file then it will throw an exception.

data

fn data() -> Data

Return the currently globally loaded data file.

When running the script through the scripting console this will return whatever data you currently have loaded. When running through the script subcommand it will return the data file specified with the --data parameter; if it wasn't specified then it will throw an exception.

graph

fn graph() -> Graph

Constructs a new Graph object.

info

fn info(
    message: String
)

Generates an info print; only visible on the command-line.

load

fn load(
    path: String
) -> Data

Loads a new data file from the given path.

Makes sense only for scripts executed through the script subcommand. Will throw an exception when called from the scripting console.

println

fn println()
fn println(
    value: Any
)
fn println(
    format: String,
    arg_1: Any
)
fn println(
    format: String,
    arg_1: Any,
    arg_2: Any
)
fn println(
    format: String,
    arg_1: Any,
    arg_2: Any,
    arg_3: Any
)

Prints out a given value or message, with optional Rust-like string interpolation. (At the moment only {} is supported in the format string.)

For scripts executed through the scripting console it will print out the message directly on the web page; for scripts executed through the script subcommand it will print out the message on stdout.

h

fn h(
    hours: Integer|Float
) -> Duration

Returns a new Duration with the specified number of hours.

m

fn m(
    minutes: Integer|Float
) -> Duration

Returns a new Duration with the specified number of minutes.

s

fn s(
    seconds: Integer|Float
) -> Duration

Returns a new Duration with the specified number of seconds.

ms

fn ms(
    milliseconds: Integer|Float
) -> Duration

Returns a new Duration with the specified number of milliseconds.

us

fn us(
    microseconds: Integer|Float
) -> Duration

Returns a new Duration with the specified number of microseconds.

kb

fn kb(
    value: Integer
) -> Integer

A convenience function equivalent to value * 1000.

mb

fn mb(
    value: Integer
) -> Integer

A convenience function equivalent to value * 1000 * 1000.

gb

fn gb(
    value: Integer
) -> Integer

A convenience function equivalent to value * 1000 * 1000 * 1000.

argv

fn argv() -> [String]

Returns a list of arguments passed on the command-line.

Makes sense only for scripts executed through the script subcommand. For scripts executed from the scripting console this will always return an empty array.

chdir

fn chdir(
    path: String
)

Changes the current directory to the given path.

Will physically change the current directory only for scripts executed through the script subcommand. For scripts executed from the scripting console no access to the local filesystem is provided, and a virtual filesystem will be simulated instead.

dirname

fn dirname(
    path: String
) -> String

Returns the given path without its final component.

exit

fn exit()
fn exit(
    status_code: Integer
) -> Duration

Immediately aborts execution with the given status_code.

mkdir_p

fn mkdir_p(
    path: String
)

Creates a new directory and all of its parent components if they are missing.

Equivalent to Rust's std::fs::create_dir_all or mkdir -p.

Will physically create directories only for scripts executed through the script subcommand. For scripts executed from the scripting console no access to the local filesystem is provided, and a virtual filesystem will be simulated instead.

Allocation

Allocation is an object representing a single allocation.

Allocation::allocated_at

fn allocated_at(
    self: Allocation
) -> Duration

Returns when this allocation was made, as a time offset from the start of the profiling.

Examples

println(allocations()[0].allocated_at());
62ms 661us

Allocation::backtrace

fn backtrace(
    self: Allocation
) -> Backtrace

Returns the backtrace of this allocation.

Examples

println(allocations()[0].backtrace());
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#04 [simulation] std::rt::lang_start [rt.rs:165]
#05 [simulation] std::rt::lang_start_internal [rt.rs:147]
#06 [simulation] std::panic::catch_unwind [panic.rs:137]
#07 [simulation] std::panicking::try [panicking.rs:447]
#08 [simulation] std::panicking::try::do_call [panicking.rs:483]
#09 [simulation] std::rt::lang_start_internal::{{closure}} [rt.rs:147]
#10 [simulation] std::rt::init [rt.rs:99]
#11 [simulation] std::sys::unix::thread::guard::init [thread.rs:746]
#12 [simulation] std::sys::unix::thread::guard::get_stack_start_aligned [thread.rs:714]
#13 [simulation] std::sys::unix::thread::guard::get_stack_start [thread.rs:697]
#14 [libc.so.6] pthread_getattr_np
#15 [libc.so.6] 7f8bb4fa127f
#16 [libbytehound.so] malloc [api.rs:293]
#17 [libbytehound.so] bytehound::api::allocate [api.rs:273]
#18 [libbytehound.so] bytehound::unwind::grab [unwind.rs:337]

Allocation::deallocated_at

fn deallocated_at(
    self: Allocation
) -> Option<Duration>

Returns when this allocation was freed, as a time offset from the start of the profiling.

Examples

println((allocations().only_leaked())[0].deallocated_at());
println((allocations().only_temporary())[0].deallocated_at());
None
Some(62ms 799us)

AllocationList

AllocationList is an object which holds a list of allocations.

AllocationList::-

fn -(
    lhs: AllocationList,
    rhs: AllocationList
) -> AllocationList

Returns a new allocation list with all of the allocations from lhs which are not present in rhs.

Examples

Here are graphs of two distinct allocation lists:

And here's how they look when merged through the - operator:

let lhs = allocations()
    .only_temporary()
    .only_deallocated_until_at_most(data().runtime() * 0.7);

let rhs = allocations()
    .only_temporary()
    .only_allocated_after_at_least(data().runtime() * 0.3);

graph()
    .add(lhs - rhs)
    .save();

AllocationList::+

fn +(
    lhs: AllocationList,
    rhs: AllocationList
) -> AllocationList

Returns a new allocation list with all of the allocations from lhs and rhs combined.

Examples

Here are graphs of two distinct allocation lists:

And here's how they look when merged through the + operator:

let lhs = allocations()
    .only_temporary()
    .only_deallocated_until_at_most(data().runtime() * 0.6);

let rhs = allocations()
    .only_temporary()
    .only_allocated_after_at_least(data().runtime() * 0.4);

graph()
    .add(lhs + rhs)
    .save();

AllocationList::&

fn &(
    lhs: AllocationList,
    rhs: AllocationList
) -> AllocationList

Returns a new allocation list with all of the allocations that are both in lhs and rhs.

Examples

Here are graphs of two distinct allocation lists:

And here's how they look when merged through the & operator:

let lhs = allocations()
    .only_temporary()
    .only_deallocated_until_at_most(data().runtime() * 0.6);

let rhs = allocations()
    .only_temporary()
    .only_allocated_after_at_least(data().runtime() * 0.4);

graph()
    .add(lhs & rhs)
    .save();

AllocationList::[]

fn [](
    index: Integer
) -> Allocation

Returns a given Allocation from the list.

Examples

println(allocations()[0].backtrace());
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#04 [simulation] std::rt::lang_start [rt.rs:165]
#05 [simulation] std::rt::lang_start_internal [rt.rs:147]
#06 [simulation] std::panic::catch_unwind [panic.rs:137]
#07 [simulation] std::panicking::try [panicking.rs:447]
#08 [simulation] std::panicking::try::do_call [panicking.rs:483]
#09 [simulation] std::rt::lang_start_internal::{{closure}} [rt.rs:147]
#10 [simulation] std::rt::init [rt.rs:99]
#11 [simulation] std::sys::unix::thread::guard::init [thread.rs:746]
#12 [simulation] std::sys::unix::thread::guard::get_stack_start_aligned [thread.rs:714]
#13 [simulation] std::sys::unix::thread::guard::get_stack_start [thread.rs:697]
#14 [libc.so.6] pthread_getattr_np
#15 [libc.so.6] 7f8bb4fa127f
#16 [libbytehound.so] malloc [api.rs:293]
#17 [libbytehound.so] bytehound::api::allocate [api.rs:273]
#18 [libbytehound.so] bytehound::unwind::grab [unwind.rs:337]

AllocationList::group_by_backtrace

fn group_by_backtrace(
    self: AllocationList
) -> AllocationGroupList

Groups all of the allocations according to their backtrace.

AllocationList::len

fn len(
    self: AllocationList
) -> Integer

Returns the number of allocations within the list.

Examples

println(allocations().len());
319339

AllocationList::only_address_at_least

fn only_address_at_least(
    self: AllocationList,
    address: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose address is equal or higher than the one specified.

AllocationList::only_address_at_most

fn only_address_at_most(
    self: AllocationList,
    address: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose address is equal or lower than the one specified.

AllocationList::only_alive_at

fn only_alive_at(
    self: AllocationList,
    durations: [Duration]
) -> AllocationList

Returns a new AllocationList with only the allocations that were alive at all of the times specified by durations as measured from the start of profiling.

AllocationList::only_alive_for_at_least

fn only_alive_for_at_least(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were alive for at least the given duration.

This only considers the span of time from when the allocation was last allocated (e.g. through malloc or realloc) until it was freed or reallocated (e.g. through free or realloc), or until the profiling was stopped.

For example, for the following allocation pattern:

void * a0 = malloc(size);
sleep(1);

void * a1 = realloc(a0, size + 1);
void * a2 = realloc(a2, size + 2);
free(a2);

this code:

allocations().only_alive_for_at_least(s(1))

will only match the first a0 allocation since only it lived for at least one second.

You can use only_chain_alive_for_at_least if you'd like to take the lifetime of the whole allocation chain into account starting from the very first malloc and persisting through any future reallocations.

AllocationList::only_alive_for_at_most

fn only_alive_for_at_most(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were alive for at most the given duration.

This only considers the span of time from when the allocation was last allocated (e.g. through malloc or realloc) until it was freed or reallocated (e.g. through free or realloc), or until the profiling was stopped.

For example, for the following allocation pattern:

void * a0 = malloc(size);
sleep(1);

void * a1 = realloc(a0, size + 1);
void * a2 = realloc(a2, size + 2);
free(a2);

this code:

allocations().only_alive_for_at_most(s(0.5))

will only match the last two allocations (a1 and a2) since only they were alive for at most half a second.

You can use only_chain_alive_for_at_most if you'd like to take the lifetime of the whole allocation chain into account starting from the very first malloc and persisting through any future reallocations.

AllocationList::only_allocated_after_at_least

fn only_allocated_after_at_least(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were allocated after at least duration from the start of profiling.

AllocationList::only_allocated_until_at_most

fn only_allocated_until_at_most(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were allocated until at most duration from the start of profiling.

AllocationList::only_backtrace_length_at_least

fn only_backtrace_length_at_least(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that have a backtrace which has at least threshold many frames.

AllocationList::only_backtrace_length_at_most

fn only_backtrace_length_at_most(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that have a backtrace which has at most threshold many frames.

AllocationList::only_chain_alive_for_at_least

fn only_chain_alive_for_at_least(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations whose whole allocation chain was alive for at least the given duration.

This considers the whole span of time from when the allocation was first allocated (e.g. through malloc), through any potential reallocations, and until it was freed (e.g. through free) or the profiling was stopped. It will match every allocation in that chain.

For example, for the following allocation pattern:

void * a0 = malloc(size);
sleep(1);

void * a1 = realloc(a0, size + 1);
void * a2 = realloc(a2, size + 2);
free(a2);

this code:

allocations().only_chain_alive_for_at_least(s(1))

will match all three allocations (a0, a1, a2), since their whole allocation chain lived for at least a second.

You can use only_alive_for_at_least if you'd like to only take the lifetime of a single allocation into account.

AllocationList::only_chain_alive_for_at_most

fn only_chain_alive_for_at_most(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations whose whole allocation chain was alive for at most the given duration.

This considers the whole span of time from when the allocation was first allocated (e.g. through malloc), through any potential reallocations, and until it was freed (e.g. through free) or the profiling was stopped. It will match every allocation in that chain.

For example, for the following allocation pattern:

void * a0 = malloc(size);
sleep(1);

void * a1 = realloc(a0, size + 1);
void * a2 = realloc(a2, size + 2);
free(a2);

this code:

allocations().only_chain_alive_for_at_most(s(2))

will match all three allocations (a0, a1, a2), since their whole allocation chain lived for less than two seconds.

You can use only_alive_for_at_most if you'd like to only take the lifetime of a single allocation into account.

AllocationList::only_chain_length_at_least

fn only_chain_length_at_least(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose whole allocation chain was at least threshold allocations long.

For example, for the following allocation pattern:

void * a0 = malloc(size);

void * b0 = malloc(size);
void * b1 = realloc(b0, size + 1);

this code:

allocations().only_chain_length_at_least(2)

will only match b0 and b1, since their whole allocation chain has at least two allocations.

AllocationList::only_chain_length_at_most

fn only_chain_length_at_most(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose whole allocation chain was at most threshold allocations long.

For example, for the following allocation pattern:

void * a0 = malloc(size);
void * a1 = realloc(a0, size + 1);

void * b0 = malloc(size);
void * b1 = realloc(b0, size + 1);
void * b2 = realloc(b1, size + 2);

this code:

allocations().only_chain_length_at_most(2)

will only match a0 and a1, since their whole allocation chain has at most two allocations.

AllocationList::only_deallocated_after_at_least

fn only_deallocated_after_at_least(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were deallocated after at least duration from the start of profiling.

AllocationList::only_deallocated_until_at_most

fn only_deallocated_until_at_most(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were deallocated until at most duration from the start of profiling.

AllocationList::only_first_size_larger_or_equal

fn only_first_size_larger_or_equal(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the first allocation's size is larger or equal to the given threshold.

AllocationList::only_first_size_larger

fn only_first_size_larger(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the first allocation's size is larger than the given threshold.

AllocationList::only_first_size_smaller_or_equal

fn only_first_size_smaller_or_equal(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the first allocation's size is smaller or equal to the given threshold.

AllocationList::only_first_size_smaller

fn only_first_size_smaller(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the first allocation's size is smaller than the given threshold.

AllocationList::only_from_maps

fn only_from_maps(
    self: MapList,
    map_ids: Map|MapList|Integer
) -> MapList
fn only_from_maps(
    self: MapList,
    map_ids: [Map|MapList|Integer]
) -> MapList

Returns a new AllocationList with only the allocations that come from within given map_ids.

AllocationList::only_group_allocations_at_least

fn only_group_allocations_at_least(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace which produced at least threshold allocations.

AllocationList::only_group_allocations_at_most

fn only_group_allocations_at_most(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace which produced at most threshold allocations.

AllocationList::only_group_interval_at_least

fn only_group_interval_at_least(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace which produced allocations spanning at least duration, as measured from the very first allocation, to the very last allocation from the same location.

AllocationList::only_group_interval_at_most

fn only_group_interval_at_most(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace which produced allocations spanning at most duration, as measured from the very first allocation, to the very last allocation from the same location.

AllocationList::only_group_leaked_allocations_at_least

fn only_group_leaked_allocations_at_least(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace which produced at least threshold leaked allocations.

AllocationList::only_group_leaked_allocations_at_most

fn only_group_leaked_allocations_at_most(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace which produced at most threshold leaked allocations.

AllocationList::only_group_max_total_usage_first_seen_at_least

fn only_group_max_total_usage_first_seen_at_least(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace whose total maximum memory usage first peaked after at least duration from the start of profiling.

AllocationList::only_group_max_total_usage_first_seen_at_most

fn only_group_max_total_usage_first_seen_at_most(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that come from a stack trace whose total maximum memory usage first peaked before at most duration from the start of profiling.

AllocationList::only_jemalloc

fn only_jemalloc(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only allocations which were allocated through one of the jemalloc interfaces.

AllocationList::only_larger_or_equal

fn only_larger_or_equal(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose size is larger or equal to the given threshold.

AllocationList::only_larger

fn only_larger(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose size is larger than the given threshold.

AllocationList::only_last_size_larger_or_equal

fn only_last_size_larger_or_equal(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the last allocation's size is larger or equal to the given threshold.

AllocationList::only_last_size_larger

fn only_last_size_larger(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the last allocation's size is larger than the given threshold.

AllocationList::only_last_size_smaller_or_equal

fn only_last_size_smaller_or_equal(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the last allocation's size is smaller or equal to the given threshold.

AllocationList::only_last_size_smaller

fn only_last_size_smaller(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that are part of an allocation chain where the last allocation's size is smaller than the given threshold.

AllocationList::only_leaked_or_deallocated_after

fn only_leaked_or_deallocated_after(
    self: AllocationList,
    duration: Duration
) -> AllocationList

Returns a new AllocationList with only the allocations that were either leaked or deallocated after duration from the start of profiling.

AllocationList::only_leaked

fn only_leaked(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only leaked allocations.

A leaked allocation is an allocation which was never deallocated.

Opposite of only_temporary.

AllocationList::only_matching_backtraces

fn only_matching_backtraces(
    self: AllocationList,
    backtrace_ids: [Backtrace|AllocationList|MapList|AllocationGroupList|Integer]
) -> AllocationList
fn only_matching_backtraces(
    self: AllocationList,
    backtrace_ids: Backtrace|AllocationList|MapList|AllocationGroupList|Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that come from one of the given backtrace_ids.

AllocationList::only_not_jemalloc

fn only_not_jemalloc(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only allocations which were not allocated through one of the jemalloc interfaces.

AllocationList::only_not_matching_backtraces

fn only_not_matching_backtraces(
    self: AllocationList,
    backtrace_ids: [Backtrace|AllocationList|MapList|AllocationGroupList|Integer]
) -> AllocationList
fn only_not_matching_backtraces(
    self: AllocationList,
    backtrace_ids: Backtrace|AllocationList|MapList|AllocationGroupList|Integer
) -> AllocationList

Returns a new AllocationList with only the allocations that do not come from one of the given backtrace_ids.

AllocationList::only_not_passing_through_function

fn only_not_passing_through_function(
    self: AllocationList,
    regex: String
) -> AllocationList

Returns a new AllocationList with only the allocations whose backtrace does not contain a function which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

AllocationList::only_not_passing_through_source

fn only_not_passing_through_source(
    self: AllocationList,
    regex: String
) -> AllocationList

Returns a new AllocationList with only the allocations whose backtrace does not contain a frame which passes through a source file which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

AllocationList::only_passing_through_function

fn only_passing_through_function(
    self: AllocationList,
    regex: String
) -> AllocationList

Returns a new AllocationList with only the allocations whose backtrace contains a function which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

AllocationList::only_passing_through_source

fn only_passing_through_source(
    self: AllocationList,
    regex: String
) -> AllocationList

Returns a new AllocationList with only the allocations whose backtrace contains a frame which passes through a source file which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

AllocationList::only_ptmalloc_from_main_arena

fn only_ptmalloc_from_main_arena(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only ptmalloc allocations which were internally allocated on the main arena.

AllocationList::only_ptmalloc_mmaped

fn only_ptmalloc_mmaped(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only ptmalloc allocations which were internally allocated through mmap.

AllocationList::only_ptmalloc_not_from_main_arena

fn only_ptmalloc_not_from_main_arena(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only ptmalloc allocations which were internally not allocated on the main arena.

AllocationList::only_ptmalloc_not_mmaped

fn only_ptmalloc_not_mmaped(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only ptmalloc allocations which were internally not allocated through mmap.

AllocationList::only_smaller_or_equal

fn only_smaller_or_equal(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose size is smaller or equal to the given threshold.

AllocationList::only_smaller

fn only_smaller(
    self: AllocationList,
    threshold: Integer
) -> AllocationList

Returns a new AllocationList with only the allocations whose size is smaller than the given threshold.

AllocationList::only_temporary

fn only_temporary(
    self: AllocationList
) -> AllocationList

Returns a new AllocationList with only temporary allocations.

A temporary allocation is an allocation which was eventually deallocated.

Opposite of only_leaked.

AllocationList::save_as_flamegraph

fn save_as_flamegraph(
    self: AllocationList
) -> AllocationList
fn save_as_flamegraph(
    self: AllocationList,
    path: String
) -> AllocationList

Saves the allocation list as a flamegraph. The path argument is optional; if missing the filename will be automatically generated.

Examples

allocations()
    .only_temporary()
    .save_as_flamegraph("allocations.svg");

AllocationList::save_as_graph

fn save_as_graph(
    self: AllocationList
) -> AllocationList
fn save_as_graph(
    self: AllocationList,
    path: String
) -> AllocationList

Saves the allocation list as a graph. The path argument is optional; if missing the filename will be automatically generated.

Examples

allocations()
    .only_temporary()
    .save_as_graph("allocations.svg");

AllocationGroupList

AllocationGroupList is an object which holds multiple AllocationLists grouped according to a specified criteria.

AllocationGroupList::(iterator)

AllocationGroupList can be iterated with a for.

Examples

for group in allocations().group_by_backtrace().take(2) {
    println("Allocations in group: {}", group.len());
}
Allocations in group: 1710
Allocations in group: 9

AllocationGroupList::[]

fn [](
    index: Integer
) -> AllocationList

Returns a given AllocationList from the list.

Examples

let groups = allocations().group_by_backtrace().sort_by_count();
println(groups[0].len());
println(groups[1].len());
179391
134709

AllocationGroupList::len

fn len(
    self: AllocationGroupList
) -> Integer

Returns the number of allocation groups within the list.

Examples

println(allocations().group_by_backtrace().len());
23

AllocationGroupList::only_all_leaked

fn only_all_leaked(
    self: AllocationGroupList
) -> AllocationGroupList

Returns a new AllocationGroupList with only those groups where all of the allocations where leaked.

AllocationGroupList::only_count_at_least

fn only_count_at_least(
    self: AllocationGroupList,
    threshold: Integer
) -> AllocationGroupList

Returns a new AllocationGroupList with only those groups where the number of allocations is at least threshold allocations or more.

AllocationGroupList::sort_by_count_ascending

fn sort_by_count_ascending(
    self: AllocationGroupList
) -> AllocationGroupList

Sorts the groups by allocation count in an ascending order.

Examples

let groups = allocations().group_by_backtrace().sort_by_count_ascending();
println(groups[0].len());
println(groups[1].len());
println(groups[groups.len() - 1].len());
1
1
179391

AllocationGroupList::sort_by_count_descending

fn sort_by_count_descending(
    self: AllocationGroupList
) -> AllocationGroupList

Sorts the groups by allocation count in a descending order.

Examples

let groups = allocations().group_by_backtrace().sort_by_count_descending();
println(groups[0].len());
println(groups[1].len());
println(groups[groups.len() - 1].len());
179391
134709
1

AllocationGroupList::sort_by_count

Alias for sort_by_count_descending.

AllocationGroupList::sort_by_size_ascending

fn sort_by_size_ascending(
    self: AllocationGroupList
) -> AllocationGroupList

Sorts the groups by their memory usage in an ascending order.

AllocationGroupList::sort_by_size_descending

fn sort_by_size_descending(
    self: AllocationGroupList
) -> AllocationGroupList

Sorts the groups by their memory usage in a descending order.

AllocationGroupList::sort_by_size

Alias for sort_by_size_descending.

AllocationGroupList::take

fn take(
    self: AllocationGroupList,
    count: AllocationGroupList
) -> AllocanioGroupList

Returns a new list with at most count items.

Examples

let groups = allocations().group_by_backtrace();
println(groups.len());
println(groups.take(3).len());
println(groups.take(100).len());
23
3
23

AllocationGroupList::ungroup

fn ungroup(
    self: AllocationGroupList
) -> AllocationList

Ungroups all of the allocations back into a flat list.

Backtrace

Backtrace is an object representing a single backtrace.

Backtrace::strip

fn strip(
    self: Backtrace
) -> Backtrace

Strips out useless junk from the backtrace.

Examples

let groups = allocations().group_by_backtrace().sort_by_size();
let backtrace = groups[0][0].backtrace();

println("Before:");
println(backtrace);

println();
println("After:");
println(backtrace.strip());
Before:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#04 [simulation] std::rt::lang_start [rt.rs:165]
#05 [simulation] std::rt::lang_start_internal [rt.rs:148]
#06 [simulation] std::panic::catch_unwind [panic.rs:137]
#07 [simulation] std::panicking::try [panicking.rs:447]
#08 [simulation] std::panicking::try::do_call [panicking.rs:483]
#09 [simulation] std::rt::lang_start_internal::{{closure}} [rt.rs:148]
#10 [simulation] std::panic::catch_unwind [panic.rs:137]
#11 [simulation] std::panicking::try [panicking.rs:447]
#12 [simulation] std::panicking::try::do_call [panicking.rs:483]
#13 [simulation] core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once [function.rs:286]
#14 [simulation] std::rt::lang_start::{{closure}} [rt.rs:166]
#15 [simulation] std::sys_common::backtrace::__rust_begin_short_backtrace [backtrace.rs:122]
#16 [simulation] core::ops::function::FnOnce::call_once [function.rs:251]
#17 [simulation] simulation::main [main.rs:121]
#18 [simulation] simulation::allocate_temporary [main.rs:16]
#19 [simulation] alloc::vec::Vec<T,A>::resize [mod.rs:2366]
#20 [simulation] alloc::vec::Vec<T,A>::extend_with [mod.rs:2510]
#21 [simulation] alloc::vec::Vec<T,A>::reserve [mod.rs:906]
#22 [simulation] alloc::raw_vec::RawVec<T,A>::reserve [raw_vec.rs:289]
#23 [simulation] alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle [raw_vec.rs:285]
#24 [simulation] alloc::raw_vec::RawVec<T,A>::grow_amortized [raw_vec.rs:400]
#25 [simulation] alloc::raw_vec::finish_grow [raw_vec.rs:469]
#26 [simulation] <alloc::alloc::Global as core::alloc::Allocator>::allocate [alloc.rs:241]
#27 [simulation] alloc::alloc::Global::alloc_impl [alloc.rs:181]
#28 [simulation] alloc::alloc::alloc [alloc.rs:181]
#29 [libbytehound.so] malloc [api.rs:293]

After:
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#17 [simulation] simulation::main [main.rs:121]
#18 [simulation] simulation::allocate_temporary [main.rs:16]
#19 [simulation] alloc::vec::Vec<T,A>::resize [mod.rs:2366]

Data

Data is an object which holds a loaded data file.

You can either use data to access the currently loaded data file, or you can use load to load one.

Data::allocations

fn allocation(
    self: Data
) -> AllocationList

Returns a list of all of the allocations for this data file.

Data::runtime

fn runtime(
    self: Data
) -> Duration

Returns the whole duration of the runtime as measured from the start of profiling.

Duration

Duration is an object which represents a span of time.

Usually you'd use one of these functions to create one:

Duration::+

fn +(
    lhs: Duration,
    rhs: Duration
) -> Duration

Adds two durations together.

Duration::-

fn -(
    lhs: Duration,
    rhs: Duration
) -> Duration

Subtracts two durations together.

Duration::*

fn *(
    lhs: Integer|Float,
    rhs: Duration
) -> Duration
fn *(
    lhs: Duration,
    rhs: Integer|Float
) -> Duration

Multiplies a given duration by a number.

Graph

Graph is a builder object used to render an allocation graph.

Use graph to construct a new instance.

Graph::add

fn add(
    self: Graph,
    list: AllocationList|AllocationGroupList|MapList
) -> Graph
fn add(
    self: Graph,
    series_name: String,
    list: AllocationList|MapList
) -> Graph

Adds a new series to the graph with the given list.

If you add multiple allocation lists the graph will become an area graph, where every extra add will only add new allocations to the graph which were not present in any of the previously added lists.

A single graph can either show allocations, or maps. Mixing both in a single graph is not supported.

Examples

graph()
    .add("Only leaked", allocations().only_leaked())
    .add("Remaining", allocations())
    .save();

Graph::only_non_empty_series

fn only_non_empty_series(
    self: Graph
) -> Graph

Hides legend entries for any series which are empty.

Examples

Let's say we have the following code:

graph()
    .add("Leaked", allocations().only_leaked())
    .add("Temporary", allocations().only_temporary())
    .add("Remaining", allocations())
    .save();

As we can see we have an extra "Remaining" series which is empty; we can automatically hide it using only_non_empty_series:

graph()
    .add("Leaked", allocations().only_leaked())
    .add("Temporary", allocations().only_temporary())
    .add("Remaining", allocations())
    .only_non_empty_series()
    .save();

Graph::save_each_series_as_flamegraph

fn save_each_series_as_flamegraph(
    self: Graph
) -> Graph
fn save_each_series_as_flamegraph(
    self: Graph,
    output_directory: String
) -> Graph

Saves each series of the graph into a separate file as a flamegraph. The output_directory argument is optional; if missing the files will be generated in the current directory.

Examples

graph()
    .add("Temporary", allocations().only_temporary())
    .add("Leaked", allocations().only_leaked())
    .save_each_series_as_flamegraph();

Graph::save_each_series_as_graph

fn save_each_series_as_graph(
    self: Graph
) -> Graph
fn save_each_series_as_graph(
    self: Graph,
    output_directory: String
) -> Graph

Saves each series of the graph into a separate file. The output_directory argument is optional; if missing the files will be generated in the current directory.

Examples

graph()
    .add("Temporary", allocations().only_temporary())
    .add("Leaked", allocations().only_leaked())
    .save_each_series_as_graph();

Graph::save

fn save(
    self: Graph
) -> Graph
fn save(
    self: Graph,
    path: String
) -> Graph

Saves the graph to a file. The path argument is optional; if missing the filename will be automatically generated.

Examples

graph()
    .add(allocations())
    .save("allocations.svg");

Graph::show_address_space

fn show_address_space(
    self: Graph
) -> Graph

Configures the graph to show maps' used address space.

Examples

graph()
    .add(maps())
    .show_address_space()
    .save();

Graph::show_deallocations

fn show_deallocations(
    self: Graph
) -> Graph

Configures the graph to show deallocations.

Examples

graph()
    .add(allocations())
    .show_deallocations()
    .save();

Graph::show_live_allocations

fn show_live_allocations(
    self: Graph
) -> Graph

Configures the graph to show the number of allocations which are alive.

Examples

graph()
    .add(allocations())
    .show_live_allocations()
    .save();

Graph::show_memory_usage

fn show_memory_usage(
    self: Graph
) -> Graph

Configures the graph to show memory usage.

This is the default.

Examples

graph()
    .add(allocations())
    .show_memory_usage()
    .save();

Graph::show_new_allocations

fn show_new_allocations(
    self: Graph
) -> Graph

Configures the graph to show new allocations.

Examples

graph()
    .add(allocations())
    .show_new_allocations()
    .save();

Graph::show_rss

fn show_rss(
    self: Graph
) -> Graph

Configures the graph to show maps' RSS.

Examples

graph()
    .add(maps())
    .show_rss()
    .save();

Graph::trim_left

fn trim_left(
    self: Graph
) -> Graph

Trims any empty space in the left portion of the graph.

Examples

Here's a graph which has a significant amount of empty space at the start with no allocations:

By applying trim_left to it here's how it'll look like:

Graph::trim_right

fn trim_right(
    self: Graph
) -> Graph

Trims any empty space in the right portion of the graph.

Examples

Here's a graph which has a significant amount of empty space at the end:

By applying trim_right to it here's how it'll look like:

Graph::trim

fn trim_right(
    self: Graph
) -> Graph

Trims any empty space on both sides of the graph.

Examples

Here's a graph which has a significant amount of empty on both sides:

By applying trim to it here's how it'll look like:

Graph::start_at

fn start_at(
    self: Graph,
    duration: Duration
) -> Graph

Make the graph start after the given duration as measured from the start of the profiling.

Examples

Assuming we have the following graph:

graph()
    .add(allocations())
    .save();

We can make it start later like this:

graph()
    .add(allocations())
    .start_at(s(2))
    .save();

Graph::end_at

fn end_at(
    self: Graph,
    duration: Duration
) -> Graph

Truncate or extend the graph until given duration as measured from the start of the profiling.

Examples

Assuming we have the following graph:

graph()
    .add(allocations())
    .save();

We can truncate it like this:

graph()
    .add(allocations())
    .end_at(data().runtime() - s(2))
    .save();

It can also be used to extend the graph:

graph()
    .add(allocations())
    .end_at(data().runtime() + s(5))
    .save();

Graph::with_gradient_color_scheme

fn with_gradient_color_scheme(
    self: Graph,
    start: String,
    end: String
) -> Graph

Sets the graph color scheme so that the bottommost series is of the start color and the topmost series is of the end color.

Examples

let xs = allocations().only_temporary();
graph()
    .add(xs.only_alive_for_at_least(data().runtime() * 0.8))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.7))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.6))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.5))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.4))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.3))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.2))
    .add(xs.only_alive_for_at_least(data().runtime() * 0.1))
    .with_gradient_color_scheme("red", "blue")
    .save();

Graph::without_axes

fn without_axes(
    self: Graph
) -> Graph

Removes the horizonal and vertical axis of the graph.

Examples

Before:

graph()
    .add(allocations())
    .save();

After:

graph()
    .add(allocations())
    .without_axes()
    .save();

Graph::without_grid

fn without_grid(
    self: Graph
) -> Graph

Removes the grid from the graph.

Examples

Before:

graph()
    .add(allocations())
    .save();

After:

graph()
    .add(allocations())
    .without_grid()
    .save();

Graph::without_legend

fn without_legend(
    self: Graph
) -> Graph

Removes the legend from the graph.

Examples

Before:

graph()
    .add("Allocations", allocations())
    .save();

After:

graph()
    .add("Allocations", allocations())
    .without_legend()
    .save();

Map

Map is an object representing a single map.

Map::allocated_at

fn allocated_at(
    self: Map
) -> Duration

Returns when this map was mapped, as a time offset from the start of the profiling.

Examples

println(maps()[0].allocated_at());
62ms 590us

Map::backtrace

fn backtrace(
    self: Map
) -> Option<Backtrace>

Returns the backtrace of this map.

Examples

println(maps()[0].backtrace());
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#04 [simulation] std::rt::lang_start [rt.rs:165]
#05 [simulation] std::rt::lang_start_internal [rt.rs:147]
#06 [simulation] std::panic::catch_unwind [panic.rs:137]
#07 [simulation] std::panicking::try [panicking.rs:447]
#08 [simulation] std::panicking::try::do_call [panicking.rs:483]
#09 [simulation] std::rt::lang_start_internal::{{closure}} [rt.rs:147]
#10 [simulation] std::rt::init [rt.rs:97]
#11 [simulation] std::sys::unix::init [mod.rs:69]
#12 [simulation] std::sys::unix::stack_overflow::imp::init [stack_overflow.rs:120]
#13 [simulation] std::sys::unix::stack_overflow::imp::make_handler [stack_overflow.rs:161]
#14 [simulation] std::sys::unix::stack_overflow::imp::get_stack [stack_overflow.rs:150]
#15 [simulation] std::sys::unix::stack_overflow::imp::get_stackp [stack_overflow.rs:138]
#16 [libbytehound.so] mmap64 [api.rs:837]
#17 [libbytehound.so] bytehound::api::mmap_internal [api.rs:876]
#18 [libbytehound.so] bytehound::unwind::grab_from_any [api.rs]

Map::deallocated_at

fn deallocated_at(
    self: Map
) -> Option<Duration>

Returns when this map was unmapped, as a time offset from the start of the profiling.

Examples

println((maps().only_leaked())[0].deallocated_at());
println((maps().only_temporary())[0].deallocated_at());
None
Some(10s 65ms 367us)

MapList

MapList is an object which holds a list of maps.

MapList::len

fn len(
    self: MapList
) -> Integer

Returns the number of maps within the list.

Examples

println(maps().len());
61

MapList::only_address_at_least

fn only_address_at_least(
    self: MapList,
    address: Integer
) -> MapList

Returns a new MapList with only the maps whose address is equal or higher than the one specified.

MapList::only_address_at_most

fn only_address_at_most(
    self: MapList,
    address: Integer
) -> MapList

Returns a new MapList with only the maps whose address is equal or lower than the one specified.

MapList::only_alive_at

fn only_alive_at(
    self: MapList,
    durations: [Duration]
) -> MapList

Returns a new MapList with only the maps that were alive at all of the times specified by durations as measured from the start of profiling.

MapList::only_alive_for_at_least

fn only_alive_for_at_least(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were alive for at least the given duration.

MapList::only_alive_for_at_most

fn only_alive_for_at_most(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were alive for at most the given duration.

MapList::only_allocated_after_at_least

fn only_allocated_after_at_least(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were mapped after at least duration from the start of profiling.

MapList::only_allocated_until_at_most

fn only_allocated_until_at_most(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were mapped until at most duration from the start of profiling.

MapList::only_backtrace_length_at_least

fn only_backtrace_length_at_least(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only the maps that have a backtrace which has at least threshold many frames.

MapList::only_backtrace_length_at_most

fn only_backtrace_length_at_most(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only the maps that have a backtrace which has at most threshold many frames.

MapList::only_bytehound

fn only_bytehound(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped by Bytehound itself.

MapList::only_deallocated_after_at_least

fn only_deallocated_after_at_least(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were unmapped after at least duration from the start of profiling.

MapList::only_deallocated_until_at_most

fn only_deallocated_until_at_most(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were unmapped until at most duration from the start of profiling.

MapList::only_executable

fn only_executable(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped with the executable flag set.

MapList::only_jemalloc

fn only_jemalloc(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped by jemalloc.

MapList::only_larger

fn only_larger(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only the maps whose size (address space) is larger than the given threshold.

MapList::only_larger_or_equal

fn only_larger_or_equal(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only the maps whose size (address space) is larger or equal to the given threshold.

MapList::only_leaked

fn only_leaked(
    self: MapList
) -> MapList

Returns a new MapList with only leaked maps.

Opposite of only_temporary.

MapList::only_leaked_or_deallocated_after

fn only_leaked_or_deallocated_after(
    self: MapList,
    duration: Duration
) -> MapList

Returns a new MapList with only the maps that were either leaked or unmapped after duration from the start of profiling.

MapList::only_matching_backtraces

fn only_matching_backtraces(
    self: MapList,
    backtrace_ids: [Backtrace|AllocationList|MapList|AllocationGroupList|Integer]
) -> MapList
fn only_matching_backtraces(
    self: MapList,
    backtrace_ids: Backtrace|AllocationList|MapList|AllocationGroupList|Integer
) -> MapList

Returns a new MapList with only the maps that come from one of the given backtrace_ids.

MapList::only_matching_deallocation_backtraces

fn only_matching_deallocation_backtraces(
    self: MapList,
    backtrace_ids: [Backtrace|AllocationList|MapList|AllocationGroupList|Integer]
) -> MapList
fn only_matching_deallocation_backtraces(
    self: MapList,
    backtrace_ids: Backtrace|AllocationList|MapList|AllocationGroupList|Integer
) -> MapList

Returns a new MapList with only the maps that were unmapped at one of the given backtrace_ids.

MapList::only_not_bytehound

fn only_not_bytehound(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were not mapped by Bytehound itself.

MapList::only_not_executable

fn only_not_executable(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped without the executable flag set.

MapList::only_not_jemalloc

fn only_not_jemalloc(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were not mapped by jemalloc.

MapList::only_not_matching_backtraces

fn only_not_matching_backtraces(
    self: MapList,
    backtrace_ids: [Backtrace|AllocationList|MapList|AllocationGroupList|Integer]
) -> MapList
fn only_not_matching_backtraces(
    self: MapList,
    backtrace_ids: Backtrace|AllocationList|MapList|AllocationGroupList|Integer
) -> MapList

Returns a new MapList with only the maps that do not come from one of the given backtrace_ids.

MapList::only_not_matching_deallocation_backtraces

fn only_not_matching_deallocation_backtraces(
    self: MapList,
    backtrace_ids: [Backtrace|AllocationList|MapList|AllocationGroupList|Integer]
) -> MapList
fn only_not_matching_deallocation_backtraces(
    self: MapList,
    backtrace_ids: Backtrace|AllocationList|MapList|AllocationGroupList|Integer
) -> MapList

Returns a new MapList with only the maps that were not unmapped at one of the given backtrace_ids.

MapList::only_not_passing_through_function

fn only_not_passing_through_function(
    self: MapList,
    regex: String
) -> MapList

Returns a new MapList with only the maps whose backtrace does not contain a function which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

MapList::only_not_passing_through_source

fn only_not_passing_through_source(
    self: MapList,
    regex: String
) -> MapList

Returns a new MapList with only the maps whose backtrace does not contain a frame which passes through a source file which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

MapList::only_not_readable

fn only_not_readable(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped without the readable flag set.

MapList::only_not_writable

fn only_not_writable(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped without the writable flag set.

MapList::only_passing_through_function

fn only_passing_through_function(
    self: MapList,
    regex: String
) -> MapList

Returns a new MapList with only the maps whose backtrace contains a function which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

MapList::only_passing_through_source

fn only_passing_through_source(
    self: MapList,
    regex: String
) -> MapList

Returns a new MapList with only the maps whose backtrace contains a frame which passes through a source file which matches a given regex.

The flavor of regexps used here is the same as Rust's regex crate.

MapList::only_peak_rss_at_least

fn only_peak_rss_at_least(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only those maps whose peak RSS is at least the given threshold.

MapList::only_peak_rss_at_most

fn only_peak_rss_at_most(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only those maps whose peak RSS is at most the given threshold.

MapList::only_readable

fn only_readable(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped with the readable flag set.

MapList::only_smaller

fn only_smaller(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only the maps whose size (address space) is smaller than the given threshold.

MapList::only_smaller_or_equal

fn only_smaller_or_equal(
    self: MapList,
    threshold: Integer
) -> MapList

Returns a new MapList with only the maps whose size (address space) is smaller or equal to the given threshold.

MapList::only_temporary

fn only_temporary(
    self: MapList
) -> MapList

Returns a new MapList with only temporary maps.

Opposite of only_leaked.

MapList::only_writable

fn only_writable(
    self: MapList
) -> MapList

Returns a new MapList with only maps which were mapped with the writable flag set.

MapList::-

fn -(
    lhs: MapList,
    rhs: MapList
) -> MapList

Returns a new map list with all of the maps from lhs which are not present in rhs.

MapList::+

fn +(
    lhs: MapList,
    rhs: MapList
) -> MapList

Returns a new map list with all of the maps from lhs and rhs combined.

MapList::&

fn &(
    lhs: MapList,
    rhs: MapList
) -> MapList

Returns a new map list with all of the maps that are both in lhs and rhs.

MapList::[]

fn [](
    index: Integer
) -> Map

Returns a given Map from the list.

Examples

println(maps()[0].backtrace());
#00 [simulation] _start [start.S:115]
#01 [libc.so.6] __libc_start_main
#02 [libc.so.6] 7f8bb4f5128f
#03 [simulation] main
#04 [simulation] std::rt::lang_start [rt.rs:165]
#05 [simulation] std::rt::lang_start_internal [rt.rs:147]
#06 [simulation] std::panic::catch_unwind [panic.rs:137]
#07 [simulation] std::panicking::try [panicking.rs:447]
#08 [simulation] std::panicking::try::do_call [panicking.rs:483]
#09 [simulation] std::rt::lang_start_internal::{{closure}} [rt.rs:147]
#10 [simulation] std::rt::init [rt.rs:97]
#11 [simulation] std::sys::unix::init [mod.rs:69]
#12 [simulation] std::sys::unix::stack_overflow::imp::init [stack_overflow.rs:120]
#13 [simulation] std::sys::unix::stack_overflow::imp::make_handler [stack_overflow.rs:161]
#14 [simulation] std::sys::unix::stack_overflow::imp::get_stack [stack_overflow.rs:150]
#15 [simulation] std::sys::unix::stack_overflow::imp::get_stackp [stack_overflow.rs:138]
#16 [libbytehound.so] mmap64 [api.rs:837]
#17 [libbytehound.so] bytehound::api::mmap_internal [api.rs:876]
#18 [libbytehound.so] bytehound::unwind::grab_from_any [api.rs]