pprof #
Reads and writes pprof profiles, the sampled stack format that
profile.proto defines and that go tool pprof, Perfetto,
speedscope and pyroscope all open.
Overview #
Most profile viewers read pprof, so a profiler that writes it gets those
viewers for free. A pprof profile is a list of samples. Each sample holds a
stack of location ids, leaf first, and one value for each sample type the
profile declares, such as alloc_objects/count, alloc_space/bytes or
cpu/nanoseconds. A location points to lines, a line to a function, and a
location may also point to the mapping its code was loaded from.
The wire format stores each string once, in a table, and refers to it by
index. The encoder builds that table and the decoder resolves it, so
Pprof.Profile.t holds ordinary OCaml strings. Ids are kept as ids, as in
profile.proto. The decoder also checks the cross-references the
specification requires, so a profile it returns as Ok is one that external
tools accept.
A profile file is protobuf wrapped in gzip. Pprof.to_string compresses
unless told otherwise. Pprof.of_string looks for the gzip magic bytes and
accepts a bare protobuf message when they are absent, which is how profiles
often arrive in HTTP bodies and archives.
Requires OCaml >= 4.14.
Install #
$ opam install pprof
Usage #
This builds an allocation profile with one sample and encodes it. The output starts with the gzip magic:
let fn : Pprof.Function.t =
{
id = 1L;
name = "List.map";
system_name = "camlStdlib__List__map_267";
filename = "list.ml";
start_line = 96L;
}
let loc : Pprof.Location.t =
{
id = 1L;
mapping_id = 0L;
address = 0L;
lines = [ { function_id = 1L; line = 100L; column = 0L } ];
is_folded = false;
}
let heap : Pprof.t =
{
Pprof.Profile.empty with
sample_types =
[
{ typ = "alloc_objects"; unit = "count" };
{ typ = "alloc_space"; unit = "bytes" };
];
samples = [ { location_ids = [ 1L ]; values = [ 3L; 96L ]; labels = [] } ];
locations = [ loc ];
functions = [ fn ];
period_type = Some { typ = "space"; unit = "bytes" };
period = 524_288L;
}
# String.sub (Pprof.to_string heap) 0 2 = "\x1f\x8b"
- : bool = true
Decoding the result gives the profile back:
# match Pprof.of_string (Pprof.to_string heap) with
| Ok p -> Fmt.str "%a" Pprof.Profile.pp p
| Error e -> Pprof.Error.to_string e
- : string =
"[alloc_objects/count, alloc_space/bytes] period=524288 1 samples, 1 locations, 1 functions, 0 mappings"
Pprof.Profile.validate refuses a sample that names a location the profile
does not declare:
# Pprof.Profile.validate
{ heap with
samples = [ { location_ids = [ 9L ]; values = [ 1L; 2L ]; labels = [] } ] }
|> Result.map_error Pprof.Error.to_string
- : (unit, string) result =
Error "sample.location_id references location id 9, which is not declared"
API #
The Pprof module reads with of_string, of_string_exn, of_reader and
of_reader_exn, and writes with to_string and to_writer. The writers take
?compress (default true) to choose whether to gzip. The readers take
?max_size, a bound on the decompressed bytes (256 MiB by default), because a
small gzip member can expand to any size.
Each profile.proto message has its own module, a record with equal and
pp:
Pprof.Profileis the whole document, withemptyandvalidate.Pprof.Sampleis a stack with its values and labels.Pprof.Value_typesays what a value measures and in which unit.Pprof.Labelis a string or numeric annotation on a sample.Pprof.Location,Pprof.Line,Pprof.FunctionandPprof.Mappingare the symbolisation tables.
Pprof.Error adds the pprof failures to Loc.Error.kind. A caller can match
on constructors such as Missing_string_table or
Dangling_id { table; id; from } without parsing an error message.
Related work #
Google defines profile.proto, and its Go implementation lives at
github.com/google/pprof. This library follows the same split
between wire indices and resolved strings as that code's preEncode and
postDecode, and its validation rules are those of CheckValid.
OCaml profilers each have their own format. memtrace writes CTF traces,
while landmarks and spacetime write their own dumps. nox-catapult writes
Chrome trace events, which record durations on a timeline instead of sampled
stacks, and ocaml-observe has a Fuchsia trace writer. A sampler that exports
pprof through this package can be read by the common viewers; obs pprof
uses it to convert memtrace traces.
Protobuf encoding comes from nox-protobuf and gzip from
bytesrw.zlib.
License #
ISC. See LICENSE.md.