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:
- Rust nightly
- Full GCC toolchain
- 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 Vec
s.
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 AllocationList
s
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]