Commit aec345d6 authored by Alberto Donizetti's avatar Alberto Donizetti Committed by Brad Fitzpatrick

cmd/vendor/github.com/google/pprof: refresh from upstream

Update vendored pprof to commit 4fc39a00b6b8c1aad05260f01429ec70e127252c
from github.com/google/pprof (2017-11-01).

Fixes #19380
Updates #21047

Change-Id: Ib64a94a45209039e5945acbcfa0392790c8ee41e
Reviewed-on: https://go-review.googlesource.com/57370
Run-TryBot: Alberto Donizetti <alb.donizetti@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 3039bff9
.DS_Store
*~
*.orig
*.exe
.*.swp
core
coverage.txt
pprof
Want to contribute? Great! First, read this page (including the small print at the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement]
(https://cla.developers.google.com/about/google-individual)
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
......@@ -17,11 +17,11 @@ possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
### Code reviews
All submissions, including submissions by project members, require review. We
use Github pull requests for this purpose.
All submissions, including submissions by project members, require review.
We use Github pull requests for this purpose.
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the
[Software Grant and Corporate Contributor License Agreement]
(https://cla.developers.google.com/about/google-corporate).
Contributions made by corporations are covered by a different agreement than the one above,
the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
[![Build Status](https://travis-ci.org/google/pprof.svg?branch=master)](https://travis-ci.org/google/pprof)
[![codecov](https://codecov.io/gh/google/pprof/graph/badge.svg)](https://codecov.io/gh/google/pprof)
# Introduction
pprof is a tool for visualization and analysis of profiling data.
......@@ -24,7 +27,7 @@ them through the use of the native binutils tools (addr2line and nm).
Prerequisites:
- Go development kit. Known to work with Go 1.5.
- Go development kit. Requires Go 1.7 or newer.
Follow [these instructions](http://golang.org/doc/code.html) to install the
go tool and set up GOPATH.
......@@ -35,6 +38,10 @@ To build and install it, use the `go get` tool.
go get github.com/google/pprof
Remember to set GOPATH to the directory where you want pprof to be
installed. The binary will be in `$GOPATH/bin` and the sources under
`$GOPATH/src/github.com/google/pprof`.
# Basic usage
pprof can read a profile from a file or directly from a server via http.
......@@ -70,12 +77,28 @@ This will open a simple shell that takes pprof commands to generate reports.
Type 'help' for available commands/options.
```
## Run pprof via a web interface
If the `-http` flag is specified, pprof starts a web server at
the specified host:port that provides an interactive web-based interface to pprof.
Host is optional, and is "localhost" by default. Port is optional, and is a
random available port by default. `-http=":"` starts a server locally at
a random port.
```
pprof -http=[host]:[port] [main_binary] profile.pb.gz
```
The preceding command should automatically open your web browser at
the right page; if not, you can manually visit the specified port in
your web browser.
## Using pprof with Linux Perf
pprof can read `perf.data` files generated by the
[Linux perf](https://perf.wiki.kernel.org/index.php) tool by using the
[Linux perf](https://perf.wiki.kernel.org/index.php/Main_Page) tool by using the
`perf_to_profile` program from the
[perf_data_converter](http://github.com/google/perf_data_converter) package.
[perf_data_converter](https://github.com/google/perf_data_converter) package.
## Further documentation
......
......@@ -128,9 +128,11 @@ size of 6MB.
Labels can be string-based or numeric. They are represented by the Label
message, with a key identifying the label and either a string or numeric
value. For numeric labels, by convention the key represents the measurement unit
of the numeric value. So for the previous example, the samples would have labels
{“bytes”, 2097152} and {“bytes”, 4194304}.
value. For numeric labels, the measurement unit can be specified in the profile.
If no unit is specified and the key is "request" or "alignment",
then the units are assumed to be "bytes". Otherwise when no unit is specified
the key will be used as the measurement unit of the numeric value. All tags with
the same key should have the same unit.
## Keep and drop expressions
......
......@@ -29,7 +29,40 @@ location. pprof is agnostic to the profile semantics, so other uses are
possible. The interpretation of the reports generated by pprof depends on the
semantics defined by the source of the profile.
# General usage
# Usage Modes
There are few different ways of using `pprof`.
## Report generation
If a report format is requested on the command line:
pprof <format> [options] source
pprof will generate a report in the specified format and exit.
Formats can be either text, or graphical. See below for details about
supported formats, options, and sources.
## Interactive terminal use
Without a format specifier:
pprof [options] source
pprof will start an interactive shell in which the user can type
commands. Type `help` to get online help.
## Web interface
If a host:port is specified on the command line:
pprof -http=[host]:[port] [options] source
pprof will start serving HTTP requests on the specified port. Visit
the HTTP url corresponding to the port (typically `http://<host>:<port>/`)
in a browser to see the interface.
# Details
The objective of pprof is to generate a report for a profile. The report is
generated from a location hierarchy, which is reconstructed from the profile
......@@ -38,14 +71,12 @@ itself, while *cum* is the value of the location plus all its
descendants. Samples that include a location multiple times (eg for recursive
functions) are counted only once per location.
The basic usage of pprof is
pprof <format> [options] source
## Options
Where *format* selects the nature of the report, and *options* configure the
contents of the report. Each option has a value, which can be boolean, numeric,
or strings. While only one format can be specified, most options can be selected
independently of each other.
*options* configure the contents of a report. Each option has a value,
which can be boolean, numeric, or strings. While only one format can
be specified, most options can be selected independently of each
other.
Some common pprof options are:
......@@ -74,10 +105,56 @@ number of values - 1) or the name of the sample value.
Sample values are numeric values associated to a unit. If pprof can recognize
these units, it will attempt to scale the values to a suitable unit for
visualization. The `unite=` option will force the use of a specific unit. For
example, `sample_index=sec` will force any time values to be reported in
visualization. The `unit=` option will force the use of a specific unit. For
example, `unit=sec` will force any time values to be reported in
seconds. pprof recognizes most common time and memory size units.
## Tag filtering
Samples in a profile may have tags. These tags have a name and a value; this
value can be either numeric or a string. pprof can select samples from a
profile based on these tags using the `-tagfocus` and `-tagignore` options.
Generally, these options work as follows:
* **-tagfocus=_regex_** or **-tagfocus=_range_:** Restrict to samples with tags
matched by regexp or in range.
* **-tagignore=_regex_** or **-tagignore=_range_:** Discard samples with tags
matched by regexp or in range.
When using `-tagfocus=regex` and `-tagignore=regex`, the regex will be compared
to each value associated with each tag. If one specifies a value
like `regex1,regex2`, then only samples with a tag value matching `regex1`
and a tag value matching `regex2` will be kept.
In addition to being able to filter on tag values, one can specify the name of
the tag which a certain value must be associated with using the notation
`-tagfocus=tagName=value`. Here, the `tagName` must match the tag's name
exactly, and the value can be either a regex or a range. If one specifies
a value like `regex1,regex2`, then samples with a tag value (paired with the
specified tag name) matching either `regex1` or matching `regex2` will match.
Here are examples explaining how `tagfocus` can be used:
* `-tagfocus 128kb:512kb` accepts a sample iff it has any numeric tag with
memory value in the specified range.
* `-tagfocus mytag=128kb:512kb` accepts a sample iff it has a numeric tag
`mytag` with memory value in the specified range. There isn't a way to say
`-tagfocus mytag=128kb:512kb,16kb:32kb`
or `-tagfocus mytag=128kb:512kb,mytag2=128kb:512kb`. Just single value or
range for numeric tags.
* `-tagfocus someregex` accepts a sample iff it has any string tag with
`tagName:tagValue` string matching specified regexp. In the future, this
will change to accept sample iff it has any string tag with `tagValue` string
matching specified regexp.
* `-tagfocus mytag=myvalue1,myvalue2` matches if either of the two tag values
are present.
`-tagignore` works similarly, except that it discards matching samples, instead
of keeping them.
If both the `-tagignore` and `-tagfocus` expressions (either a regexp or a
range) match a given sample, then the sample will be discarded.
## Text reports
pprof text reports show the location hierarchy in text format.
......
......@@ -29,10 +29,10 @@ import (
// manager. Then it generates a report formatted according to the
// options selected through the flags package.
func PProf(o *Options) error {
return internaldriver.PProf(o.InternalOptions())
return internaldriver.PProf(o.internalOptions())
}
func (o *Options) InternalOptions() *plugin.Options {
func (o *Options) internalOptions() *plugin.Options {
var obj plugin.ObjTool
if o.Obj != nil {
obj = &internalObjTool{o.Obj}
......@@ -273,9 +273,9 @@ type internalSymbolizer struct {
}
func (s *internalSymbolizer) Symbolize(mode string, srcs plugin.MappingSources, prof *profile.Profile) error {
isrcs := plugin.MappingSources{}
isrcs := MappingSources{}
for m, s := range srcs {
isrcs[m] = s
}
return s.Symbolize(mode, isrcs, prof)
return s.Symbolizer.Symbolize(mode, isrcs, prof)
}
......@@ -21,6 +21,7 @@ import (
"os/exec"
"strconv"
"strings"
"sync"
"github.com/google/pprof/internal/plugin"
)
......@@ -36,6 +37,7 @@ const (
// addr2Liner is a connection to an addr2line command for obtaining
// address and line number information from a binary.
type addr2Liner struct {
mu sync.Mutex
rw lineReaderWriter
base uint64
......@@ -170,9 +172,10 @@ func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
Line: linenumber}, false
}
// addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified.
func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
func (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) {
d.mu.Lock()
defer d.mu.Unlock()
if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil {
return nil, err
}
......@@ -201,6 +204,16 @@ func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
stack = append(stack, frame)
}
}
return stack, err
}
// addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified.
func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
stack, err := d.rawAddrInfo(addr)
if err != nil {
return nil, err
}
// Get better name from nm if possible.
if len(stack) > 0 && d.nm != nil {
......
......@@ -21,6 +21,7 @@ import (
"os/exec"
"strconv"
"strings"
"sync"
"github.com/google/pprof/internal/plugin"
)
......@@ -32,6 +33,7 @@ const (
// llvmSymbolizer is a connection to an llvm-symbolizer command for
// obtaining address and line number information from a binary.
type llvmSymbolizer struct {
sync.Mutex
filename string
rw lineReaderWriter
base uint64
......@@ -150,6 +152,9 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
// addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified.
func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) {
d.Lock()
defer d.Unlock()
if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil {
return nil, err
}
......
......@@ -48,22 +48,23 @@ func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
if cmd == "" {
cmd = defaultNM
}
a := &addr2LinerNM{
m: []symbolInfo{},
}
var b bytes.Buffer
c := exec.Command(cmd, "-n", file)
c.Stdout = &b
if err := c.Run(); err != nil {
return nil, err
}
return parseAddr2LinerNM(base, &b)
}
func parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) {
a := &addr2LinerNM{
m: []symbolInfo{},
}
// Parse nm output and populate symbol map.
// Skip lines we fail to parse.
buf := bufio.NewReader(&b)
buf := bufio.NewReader(nm)
for {
line, err := buf.ReadString('\n')
if line == "" && err != nil {
......
......@@ -24,14 +24,21 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/google/pprof/internal/elfexec"
"github.com/google/pprof/internal/plugin"
)
// A Binutils implements plugin.ObjTool by invoking the GNU binutils.
// SetConfig must be called before any of the other methods.
type Binutils struct {
mu sync.Mutex
rep *binrep
}
// binrep is an immutable representation for Binutils. It is atomically
// replaced on every mutation to provide thread-safe access.
type binrep struct {
// Commands to invoke.
llvmSymbolizer string
llvmSymbolizerFound bool
......@@ -47,11 +54,38 @@ type Binutils struct {
fast bool
}
// get returns the current representation for bu, initializing it if necessary.
func (bu *Binutils) get() *binrep {
bu.mu.Lock()
r := bu.rep
if r == nil {
r = &binrep{}
initTools(r, "")
bu.rep = r
}
bu.mu.Unlock()
return r
}
// update modifies the rep for bu via the supplied function.
func (bu *Binutils) update(fn func(r *binrep)) {
r := &binrep{}
bu.mu.Lock()
defer bu.mu.Unlock()
if bu.rep == nil {
initTools(r, "")
} else {
*r = *bu.rep
}
fn(r)
bu.rep = r
}
// SetFastSymbolization sets a toggle that makes binutils use fast
// symbolization (using nm), which is much faster than addr2line but
// provides only symbol name information (no file/line).
func (b *Binutils) SetFastSymbolization(fast bool) {
b.fast = fast
func (bu *Binutils) SetFastSymbolization(fast bool) {
bu.update(func(r *binrep) { r.fast = fast })
}
// SetTools processes the contents of the tools option. It
......@@ -59,7 +93,11 @@ func (b *Binutils) SetFastSymbolization(fast bool) {
// of the form t:path, where cmd will be used to look only for the
// tool named t. If t is not specified, the path is searched for all
// tools.
func (b *Binutils) SetTools(config string) {
func (bu *Binutils) SetTools(config string) {
bu.update(func(r *binrep) { initTools(r, config) })
}
func initTools(b *binrep, config string) {
// paths collect paths per tool; Key "" contains the default.
paths := make(map[string][]string)
for _, t := range strings.Split(config, ",") {
......@@ -91,11 +129,8 @@ func findExe(cmd string, paths []string) (string, bool) {
// Disasm returns the assembly instructions for the specified address range
// of a binary.
func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
if b.addr2line == "" {
// Update the command invocations if not initialized.
b.SetTools("")
}
func (bu *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
b := bu.get()
cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end),
......@@ -109,11 +144,8 @@ func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error)
}
// Open satisfies the plugin.ObjTool interface.
func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
if b.addr2line == "" {
// Update the command invocations if not initialized.
b.SetTools("")
}
func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
b := bu.get()
// Make sure file is a supported executable.
// The pprof driver uses Open to sniff the difference
......@@ -140,7 +172,7 @@ func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFil
return nil, fmt.Errorf("unrecognized binary: %s", name)
}
func (b *Binutils) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
of, err := macho.Open(name)
if err != nil {
return nil, fmt.Errorf("Parsing %s: %v", name, err)
......@@ -153,7 +185,7 @@ func (b *Binutils) openMachO(name string, start, limit, offset uint64) (plugin.O
return &fileAddr2Line{file: file{b: b, name: name}}, nil
}
func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
ef, err := elf.Open(name)
if err != nil {
return nil, fmt.Errorf("Parsing %s: %v", name, err)
......@@ -202,7 +234,7 @@ func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.Obj
// file implements the binutils.ObjFile interface.
type file struct {
b *Binutils
b *binrep
name string
base uint64
buildID string
......@@ -263,22 +295,27 @@ func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
// information). It can be slow for large binaries with debug
// information.
type fileAddr2Line struct {
once sync.Once
file
addr2liner *addr2Liner
llvmSymbolizer *llvmSymbolizer
}
func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
f.once.Do(f.init)
if f.llvmSymbolizer != nil {
return f.llvmSymbolizer.addrInfo(addr)
}
if f.addr2liner != nil {
return f.addr2liner.addrInfo(addr)
}
return nil, fmt.Errorf("could not find local addr2liner")
}
func (f *fileAddr2Line) init() {
if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil {
f.llvmSymbolizer = llvmSymbolizer
return f.llvmSymbolizer.addrInfo(addr)
return
}
if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {
......@@ -290,13 +327,14 @@ func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
f.addr2liner.nm = nm
}
return f.addr2liner.addrInfo(addr)
}
return nil, fmt.Errorf("could not find local addr2liner")
}
func (f *fileAddr2Line) Close() error {
if f.llvmSymbolizer != nil {
f.llvmSymbolizer.rw.close()
f.llvmSymbolizer = nil
}
if f.addr2liner != nil {
f.addr2liner.rw.close()
f.addr2liner = nil
......
......@@ -15,7 +15,13 @@
package binutils
import (
"bytes"
"fmt"
"math"
"path/filepath"
"reflect"
"regexp"
"runtime"
"testing"
"github.com/google/pprof/internal/plugin"
......@@ -37,7 +43,7 @@ func functionName(level int) (name string) {
func TestAddr2Liner(t *testing.T) {
const offset = 0x500
a := addr2Liner{&mockAddr2liner{}, offset, nil}
a := addr2Liner{rw: &mockAddr2liner{}, base: offset}
for i := 1; i < 8; i++ {
addr := i*0x1000 + offset
s, err := a.addrInfo(uint64(addr))
......@@ -112,24 +118,23 @@ func (a *mockAddr2liner) close() {
}
func TestAddr2LinerLookup(t *testing.T) {
oddSizedMap := addr2LinerNM{
m: []symbolInfo{
{0x1000, "0x1000"},
{0x2000, "0x2000"},
{0x3000, "0x3000"},
},
}
evenSizedMap := addr2LinerNM{
m: []symbolInfo{
{0x1000, "0x1000"},
{0x2000, "0x2000"},
{0x3000, "0x3000"},
{0x4000, "0x4000"},
},
}
for _, a := range []*addr2LinerNM{
&oddSizedMap, &evenSizedMap,
} {
const oddSizedData = `
00001000 T 0x1000
00002000 T 0x2000
00003000 T 0x3000
`
const evenSizedData = `
0000000000001000 T 0x1000
0000000000002000 T 0x2000
0000000000003000 T 0x3000
0000000000004000 T 0x4000
`
for _, d := range []string{oddSizedData, evenSizedData} {
a, err := parseAddr2LinerNM(0, bytes.NewBufferString(d))
if err != nil {
t.Errorf("nm parse error: %v", err)
continue
}
for address, want := range map[uint64]string{
0x1000: "0x1000",
0x1001: "0x1000",
......@@ -141,6 +146,11 @@ func TestAddr2LinerLookup(t *testing.T) {
t.Errorf("%x: got %v, want %s", address, got, want)
}
}
for _, unknown := range []uint64{0x0fff, 0x4001} {
if got, _ := a.addrInfo(unknown); got != nil {
t.Errorf("%x: got %v, want nil", unknown, got)
}
}
}
}
......@@ -150,3 +160,116 @@ func checkAddress(got []plugin.Frame, address uint64, want string) bool {
}
return got[0].Func == want
}
func TestSetTools(t *testing.T) {
// Test that multiple calls work.
bu := &Binutils{}
bu.SetTools("")
bu.SetTools("")
}
func TestSetFastSymbolization(t *testing.T) {
// Test that multiple calls work.
bu := &Binutils{}
bu.SetFastSymbolization(true)
bu.SetFastSymbolization(false)
}
func skipUnlessLinuxAmd64(t *testing.T) {
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
t.Skip("Disasm only tested on x86-64 linux")
}
}
func TestDisasm(t *testing.T) {
skipUnlessLinuxAmd64(t)
bu := &Binutils{}
insts, err := bu.Disasm(filepath.Join("testdata", "hello"), 0, math.MaxUint64)
if err != nil {
t.Fatalf("Disasm: unexpected error %v", err)
}
mainCount := 0
for _, x := range insts {
if x.Function == "main" {
mainCount++
}
}
if mainCount == 0 {
t.Error("Disasm: found no main instructions")
}
}
func TestObjFile(t *testing.T) {
skipUnlessLinuxAmd64(t)
bu := &Binutils{}
f, err := bu.Open(filepath.Join("testdata", "hello"), 0, math.MaxUint64, 0)
if err != nil {
t.Fatalf("Open: unexpected error %v", err)
}
defer f.Close()
syms, err := f.Symbols(regexp.MustCompile("main"), 0)
if err != nil {
t.Fatalf("Symbols: unexpected error %v", err)
}
find := func(name string) *plugin.Sym {
for _, s := range syms {
for _, n := range s.Name {
if n == name {
return s
}
}
}
return nil
}
m := find("main")
if m == nil {
t.Fatalf("Symbols: did not find main")
}
frames, err := f.SourceLine(m.Start)
if err != nil {
t.Fatalf("SourceLine: unexpected error %v", err)
}
expect := []plugin.Frame{
{Func: "main", File: "/tmp/hello.c", Line: 3},
}
if !reflect.DeepEqual(frames, expect) {
t.Fatalf("SourceLine for main: expect %v; got %v\n", expect, frames)
}
}
func TestLLVMSymbolizer(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("testtdata/llvm-symbolizer has only been tested on linux")
}
cmd := filepath.Join("testdata", "fake-llvm-symbolizer")
symbolizer, err := newLLVMSymbolizer(cmd, "foo", 0)
if err != nil {
t.Fatalf("newLLVMSymbolizer: unexpected error %v", err)
}
defer symbolizer.rw.close()
for _, c := range []struct {
addr uint64
frames []plugin.Frame
}{
{0x10, []plugin.Frame{
{Func: "Inlined_0x10", File: "foo.h", Line: 0},
{Func: "Func_0x10", File: "foo.c", Line: 2},
}},
{0x20, []plugin.Frame{
{Func: "Inlined_0x20", File: "foo.h", Line: 0},
{Func: "Func_0x20", File: "foo.c", Line: 2},
}},
} {
frames, err := symbolizer.addrInfo(c.addr)
if err != nil {
t.Errorf("LLVM: unexpected error %v", err)
continue
}
if !reflect.DeepEqual(frames, c.frames) {
t.Errorf("LLVM: expect %v; got %v\n", c.frames, frames)
}
}
}
......@@ -73,7 +73,7 @@ func TestFindSymbols(t *testing.T) {
func checkSymbol(got []*plugin.Sym, want []plugin.Sym) error {
if len(got) != len(want) {
return fmt.Errorf("unexpected number of symbols %d (want %d)\n", len(got), len(want))
return fmt.Errorf("unexpected number of symbols %d (want %d)", len(got), len(want))
}
for i, g := range got {
......@@ -134,8 +134,6 @@ func TestFunctionAssembly(t *testing.T) {
},
}
const objdump = "testdata/wrapper/objdump"
for _, tc := range testcases {
insts, err := disassemble([]byte(tc.asm))
if err != nil {
......
#!/bin/sh
#
# Copyright 2014 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Fake llvm-symbolizer to use in tests
set -f
IFS=" "
while read line; do
# line has form:
# filename 0xaddr
# Emit dummy output that matches llvm-symbolizer output format.
set -- $line
fname=$1
addr=$2
echo "Inlined_$addr"
echo "$fname.h"
echo "Func_$addr"
echo "$fname.c:2"
echo
done
......@@ -24,14 +24,17 @@ import (
)
type source struct {
Sources []string
ExecName string
BuildID string
Base []string
Seconds int
Timeout int
Symbolize string
Sources []string
ExecName string
BuildID string
Base []string
Normalize bool
Seconds int
Timeout int
Symbolize string
HTTPHostport string
Comment string
}
// Parse parses the command lines through the specified flags package
......@@ -41,9 +44,11 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
flag := o.Flagset
// Comparisons.
flagBase := flag.StringList("base", "", "Source for base profile for comparison")
// Internal options.
// Source options.
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile")
flagAddComment := flag.String("add_comment", "", "Annotation string to record in the profile")
// CPU profile options
flagSeconds := flag.Int("seconds", -1, "Length of time for dynamic profiles")
// Heap profile options
......@@ -57,7 +62,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
flagMeanDelay := flag.Bool("mean_delay", false, "Display mean delay at each region")
flagTools := flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames")
flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile")
flagHTTP := flag.String("http", "", "Present interactive web based UI at the specified http host:port")
// Flags used during command processing
installedFlags := installFlags(flag)
......@@ -106,6 +111,9 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
if err != nil {
return nil, nil, err
}
if cmd != nil && *flagHTTP != "" {
return nil, nil, fmt.Errorf("-http is not compatible with an output format on the command line")
}
si := pprofVariables["sample_index"].value
si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI)
......@@ -122,12 +130,14 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
}
source := &source{
Sources: args,
ExecName: execName,
BuildID: *flagBuildID,
Seconds: *flagSeconds,
Timeout: *flagTimeout,
Symbolize: *flagSymbolize,
Sources: args,
ExecName: execName,
BuildID: *flagBuildID,
Seconds: *flagSeconds,
Timeout: *flagTimeout,
Symbolize: *flagSymbolize,
HTTPHostport: *flagHTTP,
Comment: *flagAddComment,
}
for _, s := range *flagBase {
......@@ -136,6 +146,12 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
}
}
normalize := pprofVariables["normalize"].boolValue()
if normalize && len(source.Base) == 0 {
return nil, nil, fmt.Errorf("Must have base profile to normalize by")
}
source.Normalize = normalize
if bu, ok := o.Obj.(*binutils.Binutils); ok {
bu.SetTools(*flagTools)
}
......@@ -240,13 +256,33 @@ func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string,
return cmd, nil
}
var usageMsgHdr = "usage: pprof [options] [-base source] [binary] <source> ...\n"
var usageMsgHdr = `usage:
Produce output in the specified format.
pprof <format> [options] [binary] <source> ...
Omit the format to get an interactive shell whose commands can be used
to generate various views of a profile
pprof [options] [binary] <source> ...
Omit the format and provide the "-http" flag to get an interactive web
interface at the specified host:port that can be used to navigate through
various views of a profile.
pprof -http [host]:[port] [options] [binary] <source> ...
Details:
`
var usageMsgSrc = "\n\n" +
" Source options:\n" +
" -seconds Duration for time-based profile collection\n" +
" -timeout Timeout in seconds for profile collection\n" +
" -buildid Override build id for main binary\n" +
" -add_comment Free-form annotation to add to the profile\n" +
" Displayed on some reports or with pprof -comments\n" +
" -base source Source of profile to use as baseline\n" +
" profile.pb.gz Profile in compressed protobuf format\n" +
" legacy_profile Profile in legacy pprof format\n" +
......@@ -261,7 +297,19 @@ var usageMsgSrc = "\n\n" +
var usageMsgVars = "\n\n" +
" Misc options:\n" +
" -tools Search path for object tools\n" +
" -http Provide web based interface at host:port.\n" +
" Host is optional and 'localhost' by default.\n" +
" Port is optional and a randomly available port by default.\n" +
" -tools Search path for object tools\n" +
"\n" +
" Legacy convenience options:\n" +
" -inuse_space Same as -sample_index=inuse_space\n" +
" -inuse_objects Same as -sample_index=inuse_objects\n" +
" -alloc_space Same as -sample_index=alloc_space\n" +
" -alloc_objects Same as -sample_index=alloc_objects\n" +
" -total_delay Same as -sample_index=delay\n" +
" -contentions Same as -sample_index=contentions\n" +
" -mean_delay Same as -mean -sample_index=delay\n" +
"\n" +
" Environment Variables:\n" +
" PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" +
......
......@@ -139,10 +139,10 @@ var pprofVariables = variables{
// Comparisons.
"positive_percentages": &variable{boolKind, "f", "", helpText(
"Ignore negative samples when computing percentages",
" Do not count negative samples when computing the total value",
" of the profile, used to compute percentages. If set, and the -base",
" option is used, percentages reported will be computed against the",
" main profile, ignoring the base profile.")},
"Do not count negative samples when computing the total value",
"of the profile, used to compute percentages. If set, and the -base",
"option is used, percentages reported will be computed against the",
"main profile, ignoring the base profile.")},
// Graph handling options.
"call_tree": &variable{boolKind, "f", "", helpText(
......@@ -157,9 +157,9 @@ var pprofVariables = variables{
"unit": &variable{stringKind, "minimum", "", helpText(
"Measurement units to display",
"Scale the sample values to this unit.",
" For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
" For memory profiles, use megabytes, kilobytes, bytes, etc.",
" auto will scale each value independently to the most natural unit.")},
"For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
"For memory profiles, use megabytes, kilobytes, bytes, etc.",
"Using auto will scale each value independently to the most natural unit.")},
"compact_labels": &variable{boolKind, "f", "", "Show minimal headers"},
"source_path": &variable{stringKind, "", "", "Search path for source files"},
......@@ -195,11 +195,15 @@ var pprofVariables = variables{
"If set, only show nodes that match this location.",
"Matching includes the function name, filename or object name.")},
"tagfocus": &variable{stringKind, "", "", helpText(
"Restrict to samples with tags in range or matched by regexp",
"Discard samples that do not include a node with a tag matching this regexp.")},
"Restricts to samples with tags in range or matched by regexp",
"Use name=value syntax to limit the matching to a specific tag.",
"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:",
"String tag filter examples: foo, foo.*bar, mytag=foo.*bar")},
"tagignore": &variable{stringKind, "", "", helpText(
"Discard samples with tags in range or matched by regexp",
"Discard samples that do include a node with a tag matching this regexp.")},
"Use name=value syntax to limit the matching to a specific tag.",
"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:",
"String tag filter examples: foo, foo.*bar, mytag=foo.*bar")},
"tagshow": &variable{stringKind, "", "", helpText(
"Only consider tags matching this regexp",
"Discard tags that do not match this regexp")},
......@@ -218,6 +222,8 @@ var pprofVariables = variables{
"Sample value to report (0-based index or name)",
"Profiles contain multiple values per sample.",
"Use sample_index=i to select the ith value (starting at 0).")},
"normalize": &variable{boolKind, "f", "", helpText(
"Scales profile based on the base profile.")},
// Data sorting criteria
"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")},
......@@ -227,9 +233,6 @@ var pprofVariables = variables{
"functions": &variable{boolKind, "t", "granularity", helpText(
"Aggregate at the function level.",
"Takes into account the filename/lineno where the function was defined.")},
"functionnameonly": &variable{boolKind, "f", "granularity", helpText(
"Aggregate at the function level.",
"Ignores the filename/lineno where the function was defined.")},
"files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."},
"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
"addresses": &variable{boolKind, "f", "granularity", helpText(
......@@ -266,7 +269,7 @@ func usage(commandLine bool) string {
var help string
if commandLine {
help = " Output formats (select only one):\n"
help = " Output formats (select at most one):\n"
} else {
help = " Commands:\n"
commands = append(commands, fmtHelp("o/options", "List options and their current values"))
......@@ -471,7 +474,7 @@ func (vars variables) set(name, value string) error {
case boolKind:
var b bool
if b, err = stringToBool(value); err == nil {
if v.group != "" && b == false {
if v.group != "" && !b {
err = fmt.Errorf("%q can only be set to true", name)
}
}
......
......@@ -23,6 +23,7 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/report"
......@@ -52,24 +53,30 @@ func PProf(eo *plugin.Options) error {
return generateReport(p, cmd, pprofVariables, o)
}
if src.HTTPHostport != "" {
return serveWebInterface(src.HTTPHostport, p, o)
}
return interactive(p, o)
}
func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
func generateRawReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) (*command, *report.Report, error) {
p = p.Copy() // Prevent modification to the incoming profile.
// Identify units of numeric tags in profile.
numLabelUnits := identifyNumLabelUnits(p, o.UI)
vars = applyCommandOverrides(cmd, vars)
// Delay focus after configuring report to get percentages on all samples.
relative := vars["relative_percentages"].boolValue()
if relative {
if err := applyFocus(p, vars, o.UI); err != nil {
return err
if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
return nil, nil, err
}
}
ropt, err := reportOptions(p, vars)
ropt, err := reportOptions(p, numLabelUnits, vars)
if err != nil {
return err
return nil, nil, err
}
c := pprofCommands[cmd[0]]
if c == nil {
......@@ -79,18 +86,27 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.
if len(cmd) == 2 {
s, err := regexp.Compile(cmd[1])
if err != nil {
return fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
}
ropt.Symbol = s
}
rpt := report.New(p, ropt)
if !relative {
if err := applyFocus(p, vars, o.UI); err != nil {
return err
if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
return nil, nil, err
}
}
if err := aggregate(p, vars); err != nil {
return nil, nil, err
}
return c, rpt, nil
}
func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
c, rpt, err := generateRawReport(p, cmd, vars, o)
if err != nil {
return err
}
......@@ -160,20 +176,20 @@ func applyCommandOverrides(cmd []string, v variables) variables {
v.set("nodecount", "80")
}
}
if trim == false {
if !trim {
v.set("nodecount", "0")
v.set("nodefraction", "0")
v.set("edgefraction", "0")
}
if focus == false {
if !focus {
v.set("focus", "")
v.set("ignore", "")
}
if tagfocus == false {
if !tagfocus {
v.set("tagfocus", "")
v.set("tagignore", "")
}
if hide == false {
if !hide {
v.set("hide", "")
v.set("show", "")
}
......@@ -196,25 +212,20 @@ func aggregate(prof *profile.Profile, v variables) error {
case v["functions"].boolValue():
inlines = true
function = true
filename = true
case v["noinlines"].boolValue():
function = true
filename = true
case v["addressnoinlines"].boolValue():
function = true
filename = true
linenumber = true
address = true
case v["functionnameonly"].boolValue():
inlines = true
function = true
default:
return fmt.Errorf("unexpected granularity")
}
return prof.Aggregate(inlines, function, filename, linenumber, address)
}
func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) {
func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars variables) (*report.Options, error) {
si, mean := vars["sample_index"].value, vars["mean"].boolValue()
value, meanDiv, sample, err := sampleFormat(p, si, mean)
if err != nil {
......@@ -230,6 +241,14 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
return nil, fmt.Errorf("zero divisor specified")
}
var filters []string
for _, k := range []string{"focus", "ignore", "hide", "show", "tagfocus", "tagignore", "tagshow", "taghide"} {
v := vars[k].value
if v != "" {
filters = append(filters, k+"="+v)
}
}
ropt := &report.Options{
CumSort: vars["cum"].boolValue(),
CallTree: vars["call_tree"].boolValue(),
......@@ -243,6 +262,9 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
NodeFraction: vars["nodefraction"].floatValue(),
EdgeFraction: vars["edgefraction"].floatValue(),
ActiveFilters: filters,
NumLabelUnits: numLabelUnits,
SampleValue: value,
SampleMeanDivisor: meanDiv,
SampleType: stype,
......@@ -260,6 +282,19 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
return ropt, nil
}
// identifyNumLabelUnits returns a map of numeric label keys to the units
// associated with those keys.
func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
numLabelUnits, ignoredUnits := p.NumLabelUnits()
// Print errors for tags with multiple units associated with
// a single key.
for k, units := range ignoredUnits {
ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
}
return numLabelUnits
}
type sampleValueFunc func([]int64) int64
// sampleFormat returns a function to extract values out of a profile.Sample,
......
......@@ -28,13 +28,13 @@ import (
var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)")
// applyFocus filters samples based on the focus/ignore options
func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error {
func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variables, ui plugin.UI) error {
focus, err := compileRegexOption("focus", v["focus"].value, nil)
ignore, err := compileRegexOption("ignore", v["ignore"].value, err)
hide, err := compileRegexOption("hide", v["hide"].value, err)
show, err := compileRegexOption("show", v["show"].value, err)
tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, ui, err)
tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, ui, err)
tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err)
tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err)
prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err)
if err != nil {
return err
......@@ -59,7 +59,7 @@ func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error {
if prunefrom != nil {
prof.PruneFrom(prunefrom)
}
return nil
return err
}
func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) {
......@@ -73,23 +73,49 @@ func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) {
return rx, nil
}
func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) {
func compileTagFilter(name, value string, numLabelUnits map[string]string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) {
if value == "" || err != nil {
return nil, err
}
tagValuePair := strings.SplitN(value, "=", 2)
var wantKey string
if len(tagValuePair) == 2 {
wantKey = tagValuePair[0]
value = tagValuePair[1]
}
if numFilter := parseTagFilterRange(value); numFilter != nil {
ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp")
return func(s *profile.Sample) bool {
for key, vals := range s.NumLabel {
for _, val := range vals {
if numFilter(val, key) {
labelFilter := func(vals []int64, unit string) bool {
for _, val := range vals {
if numFilter(val, unit) {
return true
}
}
return false
}
numLabelUnit := func(key string) string {
return numLabelUnits[key]
}
if wantKey == "" {
return func(s *profile.Sample) bool {
for key, vals := range s.NumLabel {
if labelFilter(vals, numLabelUnit(key)) {
return true
}
}
return false
}, nil
}
return func(s *profile.Sample) bool {
if vals, ok := s.NumLabel[wantKey]; ok {
return labelFilter(vals, numLabelUnit(wantKey))
}
return false
}, nil
}
var rfx []*regexp.Regexp
for _, tagf := range strings.Split(value, ",") {
fx, err := regexp.Compile(tagf)
......@@ -98,19 +124,34 @@ func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profil
}
rfx = append(rfx, fx)
}
if wantKey == "" {
return func(s *profile.Sample) bool {
matchedrx:
for _, rx := range rfx {
for key, vals := range s.Label {
for _, val := range vals {
// TODO: Match against val, not key:val in future
if rx.MatchString(key + ":" + val) {
continue matchedrx
}
}
}
return false
}
return true
}, nil
}
return func(s *profile.Sample) bool {
matchedrx:
for _, rx := range rfx {
for key, vals := range s.Label {
if vals, ok := s.Label[wantKey]; ok {
for _, rx := range rfx {
for _, val := range vals {
if rx.MatchString(key + ":" + val) {
continue matchedrx
if rx.MatchString(val) {
return true
}
}
}
return false
}
return true
return false
}, nil
}
......
......@@ -41,39 +41,52 @@ import (
// there are some failures. It will return an error if it is unable to
// fetch any profiles.
func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
sources := make([]profileSource, 0, len(s.Sources)+len(s.Base))
sources := make([]profileSource, 0, len(s.Sources))
for _, src := range s.Sources {
sources = append(sources, profileSource{
addr: src,
source: s,
scale: 1,
})
}
bases := make([]profileSource, 0, len(s.Base))
for _, src := range s.Base {
sources = append(sources, profileSource{
bases = append(bases, profileSource{
addr: src,
source: s,
scale: -1,
})
}
p, msrcs, save, cnt, err := chunkedGrab(sources, o.Fetch, o.Obj, o.UI)
p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI)
if err != nil {
return nil, err
}
if cnt == 0 {
return nil, fmt.Errorf("failed to fetch any profiles")
}
if want, got := len(sources), cnt; want != got {
o.UI.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", got, want))
if pbase != nil {
if s.Normalize {
err := p.Normalize(pbase)
if err != nil {
return nil, err
}
}
pbase.Scale(-1)
p, m, err = combineProfiles([]*profile.Profile{p, pbase}, []plugin.MappingSources{m, mbase})
if err != nil {
return nil, err
}
}
// Symbolize the merged profile.
if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil {
if err := o.Sym.Symbolize(s.Symbolize, m, p); err != nil {
return nil, err
}
p.RemoveUninteresting()
unsourceMappings(p)
if s.Comment != "" {
p.Comments = append(p.Comments, s.Comment)
}
// Save a copy of the merged profile if there is at least one remote source.
if save {
dir, err := setTmpDir(o.UI)
......@@ -107,6 +120,47 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
return p, nil
}
func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, *profile.Profile, plugin.MappingSources, plugin.MappingSources, bool, error) {
wg := sync.WaitGroup{}
wg.Add(2)
var psrc, pbase *profile.Profile
var msrc, mbase plugin.MappingSources
var savesrc, savebase bool
var errsrc, errbase error
var countsrc, countbase int
go func() {
defer wg.Done()
psrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui)
}()
go func() {
defer wg.Done()
pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui)
}()
wg.Wait()
save := savesrc || savebase
if errsrc != nil {
return nil, nil, nil, nil, false, fmt.Errorf("problem fetching source profiles: %v", errsrc)
}
if errbase != nil {
return nil, nil, nil, nil, false, fmt.Errorf("problem fetching base profiles: %v,", errbase)
}
if countsrc == 0 {
return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any source profiles")
}
if countbase == 0 && len(bases) > 0 {
return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any base profiles")
}
if want, got := len(sources), countsrc; want != got {
ui.PrintErr(fmt.Sprintf("Fetched %d source profiles out of %d", got, want))
}
if want, got := len(bases), countbase; want != got {
ui.PrintErr(fmt.Sprintf("Fetched %d base profiles out of %d", got, want))
}
return psrc, pbase, msrc, mbase, save, nil
}
// chunkedGrab fetches the profiles described in source and merges them into
// a single profile. It fetches a chunk of profiles concurrently, with a maximum
// chunk size to limit its memory usage.
......@@ -142,6 +196,7 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo
count += chunkCount
}
}
return p, msrc, save, count, nil
}
......@@ -152,7 +207,7 @@ func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.Ob
for i := range sources {
go func(s *profileSource) {
defer wg.Done()
s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, s.scale, fetch, obj, ui)
s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui)
}(&sources[i])
}
wg.Wait()
......@@ -207,7 +262,6 @@ func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources)
type profileSource struct {
addr string
source *source
scale float64
p *profile.Profile
msrc plugin.MappingSources
......@@ -227,12 +281,18 @@ func homeEnv() string {
}
// setTmpDir prepares the directory to use to save profiles retrieved
// remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof.
// remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof, and, if
// $HOME is not set, falls back to os.TempDir().
func setTmpDir(ui plugin.UI) (string, error) {
var dirs []string
if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" {
return profileDir, nil
dirs = append(dirs, profileDir)
}
for _, tmpDir := range []string{os.Getenv(homeEnv()) + "/pprof", os.TempDir()} {
if homeDir := os.Getenv(homeEnv()); homeDir != "" {
dirs = append(dirs, filepath.Join(homeDir, "pprof"))
}
dirs = append(dirs, os.TempDir())
for _, tmpDir := range dirs {
if err := os.MkdirAll(tmpDir, 0755); err != nil {
ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
continue
......@@ -242,10 +302,12 @@ func setTmpDir(ui plugin.UI) (string, error) {
return "", fmt.Errorf("failed to identify temp dir")
}
const testSourceAddress = "pproftest.local"
// grabProfile fetches a profile. Returns the profile, sources for the
// profile mappings, a bool indicating if the profile was fetched
// remotely, and an error.
func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
var src string
duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
if fetcher != nil {
......@@ -266,9 +328,6 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher
return
}
// Apply local changes to the profile.
p.Scale(scale)
// Update the binary locations from command line and paths.
locateBinaries(p, s, obj, ui)
......@@ -276,6 +335,11 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher
if src != "" {
msrc = collectMappingSources(p, src)
remote = true
if strings.HasPrefix(src, "http://"+testSourceAddress) {
// Treat test inputs as local to avoid saving
// testcase profiles during driver testing.
remote = false
}
}
return
}
......@@ -366,20 +430,20 @@ mapping:
}
}
}
if len(p.Mapping) == 0 {
// If there are no mappings, add a fake mapping to attempt symbolization.
// This is useful for some profiles generated by the golang runtime, which
// do not include any mappings. Symbolization with a fake mapping will only
// be successful against a non-PIE binary.
m := &profile.Mapping{ID: 1}
p.Mapping = []*profile.Mapping{m}
for _, l := range p.Location {
l.Mapping = m
}
}
// Replace executable filename/buildID with the overrides from source.
// Assumes the executable is the first Mapping entry.
if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" {
if len(p.Mapping) == 0 {
// If there are no mappings, add a fake mapping to attempt symbolization.
// This is useful for some profiles generated by the golang runtime, which
// do not include any mappings. Symbolization with a fake mapping will only
// be successful against a non-PIE binary.
m := &profile.Mapping{ID: 1}
p.Mapping = []*profile.Mapping{m}
for _, l := range p.Location {
l.Mapping = m
}
}
m := p.Mapping[0]
if execName != "" {
m.File = execName
......
......@@ -15,8 +15,15 @@
package driver
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/url"
"os"
......@@ -24,11 +31,14 @@ import (
"reflect"
"regexp"
"runtime"
"strings"
"testing"
"time"
"github.com/google/pprof/internal/binutils"
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/proftest"
"github.com/google/pprof/internal/symbolizer"
"github.com/google/pprof/profile"
)
......@@ -165,6 +175,8 @@ func TestFetch(t *testing.T) {
const path = "testdata/"
// Intercept http.Get calls from HTTPFetcher.
savedHTTPGet := httpGet
defer func() { httpGet = savedHTTPGet }()
httpGet = stubHTTPGet
type testcase struct {
......@@ -176,7 +188,7 @@ func TestFetch(t *testing.T) {
{path + "go.nomappings.crash", "/bin/gotest.exe"},
{"http://localhost/profile?file=cppbench.cpu", ""},
} {
p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, 0, nil, testObj{}, &proftest.TestUI{T: t})
p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t})
if err != nil {
t.Fatalf("%s: %s", tc.source, err)
}
......@@ -194,6 +206,117 @@ func TestFetch(t *testing.T) {
}
}
func TestFetchWithBase(t *testing.T) {
baseVars := pprofVariables
defer func() { pprofVariables = baseVars }()
const path = "testdata/"
type testcase struct {
desc string
sources []string
bases []string
normalize bool
expectedSamples [][]int64
}
testcases := []testcase{
{
"not normalized base is same as source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention"},
false,
[][]int64{},
},
{
"not normalized single source, multiple base (all profiles same)",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention", path + "cppbench.contention"},
false,
[][]int64{{-2700, -608881724}, {-100, -23992}, {-200, -179943}, {-100, -17778444}, {-100, -75976}, {-300, -63568134}},
},
{
"not normalized, different base and source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.small.contention"},
false,
[][]int64{{1700, 608878600}, {100, 23992}, {200, 179943}, {100, 17778444}, {100, 75976}, {300, 63568134}},
},
{
"normalized base is same as source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention"},
true,
[][]int64{},
},
{
"normalized single source, multiple base (all profiles same)",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention", path + "cppbench.contention"},
true,
[][]int64{},
},
{
"normalized different base and source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.small.contention"},
true,
[][]int64{{-229, -370}, {28, 0}, {57, 0}, {28, 80}, {28, 0}, {85, 287}},
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
pprofVariables = baseVars.makeCopy()
base := make([]*string, len(tc.bases))
for i, s := range tc.bases {
base[i] = &s
}
f := testFlags{
stringLists: map[string][]*string{
"base": base,
},
bools: map[string]bool{
"normalize": tc.normalize,
},
}
f.args = tc.sources
o := setDefaults(nil)
o.Flagset = f
src, _, err := parseFlags(o)
if err != nil {
t.Fatalf("%s: %v", tc.desc, err)
}
p, err := fetchProfiles(src, o)
pprofVariables = baseVars
if err != nil {
t.Fatal(err)
}
if want, got := len(tc.expectedSamples), len(p.Sample); want != got {
t.Fatalf("want %d samples got %d", want, got)
}
if len(p.Sample) > 0 {
for i, sample := range p.Sample {
if want, got := len(tc.expectedSamples[i]), len(sample.Value); want != got {
t.Errorf("want %d values for sample %d, got %d", want, i, got)
}
for j, value := range sample.Value {
if want, got := tc.expectedSamples[i][j], value; want != got {
t.Errorf("want value of %d for value %d of sample %d, got %d", want, j, i, got)
}
}
}
}
})
}
}
// mappingSources creates MappingSources map with a single item.
func mappingSources(key, source string, start uint64) plugin.MappingSources {
return plugin.MappingSources{
......@@ -227,3 +350,123 @@ func stubHTTPGet(source string, _ time.Duration) (*http.Response, error) {
c := &http.Client{Transport: t}
return c.Get("file:///" + file)
}
func TestHttpsInsecure(t *testing.T) {
if runtime.GOOS == "nacl" {
t.Skip("test assumes tcp available")
}
baseVars := pprofVariables
pprofVariables = baseVars.makeCopy()
defer func() { pprofVariables = baseVars }()
tlsConfig := &tls.Config{Certificates: []tls.Certificate{selfSignedCert(t)}}
l, err := tls.Listen("tcp", "localhost:0", tlsConfig)
if err != nil {
t.Fatalf("net.Listen: got error %v, want no error", err)
}
donec := make(chan error, 1)
go func(donec chan<- error) {
donec <- http.Serve(l, nil)
}(donec)
defer func() {
if got, want := <-donec, "use of closed"; !strings.Contains(got.Error(), want) {
t.Fatalf("Serve got error %v, want %q", got, want)
}
}()
defer l.Close()
go func() {
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
// Simulate a hotspot function. Spin in the inner loop for 100M iterations
// to ensure we get most of the samples landed here rather than in the
// library calls. We assume Go compiler won't elide the empty loop.
for i := 0; i < 1e8; i++ {
}
runtime.Gosched()
}
}()
outputTempFile, err := ioutil.TempFile("", "profile_output")
if err != nil {
t.Fatalf("Failed to create tempfile: %v", err)
}
defer os.Remove(outputTempFile.Name())
defer outputTempFile.Close()
address := "https+insecure://" + l.Addr().String() + "/debug/pprof/profile"
s := &source{
Sources: []string{address},
Seconds: 10,
Timeout: 10,
Symbolize: "remote",
}
o := &plugin.Options{
Obj: &binutils.Binutils{},
UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
}
o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI}
p, err := fetchProfiles(s, o)
if err != nil {
t.Fatal(err)
}
if len(p.SampleType) == 0 {
t.Fatalf("fetchProfiles(%s) got empty profile: len(p.SampleType)==0", address)
}
if len(p.Function) == 0 {
t.Fatalf("fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0", address)
}
if err := checkProfileHasFunction(p, "TestHttpsInsecure"); !badSigprofOS[runtime.GOOS] && err != nil {
t.Fatalf("fetchProfiles(%s) %v", address, err)
}
}
// Some operating systems don't trigger the profiling signal right.
// See https://github.com/golang/go/issues/13841.
var badSigprofOS = map[string]bool{
"darwin": true,
"netbsd": true,
"plan9": true,
}
func checkProfileHasFunction(p *profile.Profile, fname string) error {
for _, f := range p.Function {
if strings.Contains(f.Name, fname) {
return nil
}
}
return fmt.Errorf("got %s, want function %q", p.String(), fname)
}
func selfSignedCert(t *testing.T) tls.Certificate {
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("failed to generate private key: %v", err)
}
b, err := x509.MarshalECPrivateKey(privKey)
if err != nil {
t.Fatalf("failed to marshal private key: %v", err)
}
bk := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
tmpl := x509.Certificate{
SerialNumber: big.NewInt(1),
NotBefore: time.Now(),
NotAfter: time.Now().Add(10 * time.Minute),
}
b, err = x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privKey.Public(), privKey)
if err != nil {
t.Fatalf("failed to create cert: %v", err)
}
bc := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: b})
cert, err := tls.X509KeyPair(bc, bk)
if err != nil {
t.Fatalf("failed to create TLS key pair: %v", err)
}
return cert
}
......@@ -123,7 +123,8 @@ var generateReportWrapper = generateReport // For testing purposes.
// greetings prints a brief welcome and some overall profile
// information before accepting interactive commands.
func greetings(p *profile.Profile, ui plugin.UI) {
ropt, err := reportOptions(p, pprofVariables)
numLabelUnits := identifyNumLabelUnits(p, ui)
ropt, err := reportOptions(p, numLabelUnits, pprofVariables)
if err == nil {
ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n"))
}
......
--- contentionz 1 ---
cycles/second = 3201000000
sampling period = 100
ms since reset = 16502830
discarded samples = 0
19490304 27 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
768 1 @ 0xbccc97 0xa42dc7 0xa456e4 0x7fcdc2ff214e
5760 2 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x4e969d 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
569088 1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87f08 0xb8814c 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
2432 1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x7aa74c 0x7ab844 0x7ab914 0x79e9e9 0x79e326 0x4d299e 0x4d4b7b 0x4b7be8 0x4b7ff1 0x4d2dae 0x79e80a
2034816 3 @ 0xbccc97 0xb82f0f 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
--- Memory map: ---
00400000-00fcb000: cppbench_server_main
7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so
7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so
7fcdc272f000-7fcdc28dd000: /libc-2.15.so
7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so
7fcdc2de3000-7fcdc2dea000: /librt-2.15.so
7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so
7fcdc3208000-7fcdc320a000: /libdl-2.15.so
7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so
7fcdc3645000-7fcdc3669000: /ld-2.15.so
7fff86bff000-7fff86c00000: [vdso]
ffffffffff600000-ffffffffff601000: [vsyscall]
--- contentionz 1 ---
cycles/second = 3201000000
sampling period = 100
ms since reset = 16502830
discarded samples = 0
100 10 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
--- Memory map: ---
00400000-00fcb000: cppbench_server_main
7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so
7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so
7fcdc272f000-7fcdc28dd000: /libc-2.15.so
7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so
7fcdc2de3000-7fcdc2dea000: /librt-2.15.so
7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so
7fcdc3208000-7fcdc320a000: /libdl-2.15.so
7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so
7fcdc3645000-7fcdc3669000: /ld-2.15.so
7fff86bff000-7fff86c00000: [vdso]
ffffffffff600000-ffffffffff601000: [vsyscall]
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 149.50ms, 100% of 149.50ms total\l"] }
N1 [label="file3000.src\n32.77ms (21.92%)\nof 149.50ms (100%)" fontsize=20 shape=box tooltip="testdata/file3000.src (149.50ms)" color="#b20000" fillcolor="#edd5d5"]
N2 [label="file1000.src\n51.20ms (34.25%)" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"]
N3 [label="file2000.src\n65.54ms (43.84%)\nof 75.78ms (50.68%)" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"]
N1 [label="file3000.src\n32.77ms (21.92%)\nof 149.50ms (100%)" id="node1" fontsize=20 shape=box tooltip="testdata/file3000.src (149.50ms)" color="#b20000" fillcolor="#edd5d5"]
N2 [label="file1000.src\n51.20ms (34.25%)" id="node2" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"]
N3 [label="file2000.src\n65.54ms (43.84%)\nof 75.78ms (50.68%)" id="node3" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"]
N1 -> N3 [label=" 75.78ms" weight=51 penwidth=3 color="#b22000" tooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)" labeltooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)"]
N1 -> N2 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)" labeltooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)"]
N3 -> N2 [label=" 10.24ms" weight=7 color="#b29775" tooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)" labeltooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)"]
......
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\l"] }
N1 [label="0000000000001000\nline1000\nfile1000.src:1\n40.96ms (27.40%)" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
N2 [label="0000000000003001\nline3000\nfile3000.src:5\n0 of 40.96ms (27.40%)" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
N3 [label="0000000000003001\nline3001\nfile3000.src:3\n0 of 40.96ms (27.40%)" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lActive filters:\l focus=[X1]000\l ignore=[X3]002\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\l"] }
N1 [label="0000000000001000\nline1000\nfile1000.src:1\n40.96ms (27.40%)" id="node1" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
N2 [label="0000000000003001\nline3000\nfile3000.src:5\n0 of 40.96ms (27.40%)" id="node2" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
N3 [label="0000000000003001\nline3001\nfile3000.src:3\n0 of 40.96ms (27.40%)" id="node3" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
N2 -> N3 [label=" 40.96ms\n (inline)" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" labeltooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)"]
N3 -> N1 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" labeltooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)"]
}
positions: instr line
events: cpu(ms)
ob=(1) /path/to/testbinary
fl=(1) testdata/file1000.src
fn=(1) line1000
0x1000 1 1000
* 1 100
ob=(1)
fl=(2) testdata/file2000.src
fn=(2) line2001
+4096 9 10
ob=(1)
fl=(3) testdata/file3000.src
fn=(3) line3002
+4096 2 10
cfl=(2)
cfn=(4) line2000 [1/2]
calls=0 * 4
* * 1000
ob=(1)
fl=(2)
fn=(5) line2000
-4096 4 0
cfl=(2)
cfn=(6) line2001 [2/2]
calls=0 -4096 9
* * 1000
* 4 0
cfl=(2)
cfn=(7) line2001 [1/2]
calls=0 * 9
* * 10
ob=(1)
fl=(2)
fn=(2)
* 9 0
cfl=(1)
cfn=(8) line1000 [1/2]
calls=0 -4096 1
* * 1000
ob=(1)
fl=(3)
fn=(9) line3000
+4096 6 0
cfl=(3)
cfn=(10) line3001 [1/2]
calls=0 +4096 5
* * 1010
ob=(1)
fl=(3)
fn=(11) line3001
* 5 0
cfl=(3)
cfn=(12) line3002 [1/2]
calls=0 * 2
* * 1010
ob=(1)
fl=(3)
fn=(9)
+1 9 0
cfl=(3)
cfn=(13) line3001 [2/2]
calls=0 +1 8
* * 100
ob=(1)
fl=(3)
fn=(11)
* 8 0
cfl=(1)
cfn=(14) line1000 [2/2]
calls=0 -8193 1
* * 100
ob=(1)
fl=(3)
fn=(9)
+1 9 0
cfl=(3)
cfn=(15) line3002 [2/2]
calls=0 +1 5
* * 10
ob=(1)
fl=(3)
fn=(3)
* 5 0
cfl=(2)
cfn=(16) line2000 [2/2]
calls=0 -4098 4
* * 10
Active filters:
focus=[12]00
hide=line[X3]0
Showing nodes accounting for 1.11s, 99.11% of 1.12s total
flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1
0 0% 98.21% 1.01s 90.18% line2000 testdata/file2000.src:4
0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src:9 (inline)
Active filters:
hide=line[X3]0
Showing nodes accounting for 1.11s, 99.11% of 1.12s total
flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1
......
Active filters:
show=[12]00
Showing nodes accounting for 1.11s, 99.11% of 1.12s total
flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1
......
Active filters:
hide=mangled[X3]0
Showing nodes accounting for 1s, 100% of 1s total
flat flat% sum% cum cum%
1s 100% 100% 1s 100% mangled1000 testdata/file1000.src:1
......@@ -2,9 +2,9 @@ Total: 1.12s
ROUTINE ======================== line1000
1.10s 1.10s (flat, cum) 98.21% of Total
1.10s 1.10s 1000: instruction one ;line1000 file1000.src:1
. . 1001: instruction two
. . 1002: instruction three
. . 1003: instruction four
. . 1001: instruction two ;file1000.src:1
. . 1002: instruction three ;file1000.src:2
. . 1003: instruction four ;file1000.src:1
ROUTINE ======================== line3000
10ms 1.12s (flat, cum) 100% of Total
10ms 1.01s 3000: instruction one ;line3000 file3000.src:6
......
......@@ -2,6 +2,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Pprof listing</title>
<style type="text/css">
body {
......@@ -14,17 +15,11 @@ h1 {
.legend {
font-size: 1.25em;
}
.line {
color: #aaaaaa;
.line, .nop, .unimportant {
color: #aaaaaa;
}
.nop {
color: #aaaaaa;
}
.unimportant {
color: #cccccc;
}
.disasmloc {
color: #000000;
.inlinesrc {
color: #000066;
}
.deadsrc {
cursor: pointer;
......@@ -69,39 +64,41 @@ Type: cpu<br>
Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src
<pre onClick="pprof_toggle_asm(event)">
Total: 1.10s 1.10s (flat, cum) 98.21%
<span class=line> 1</span> <span class=deadsrc> 1.10s 1.10s line1 </span><span class=asm> 1.10s 1.10s 1000: instruction one <span class=disasmloc>file1000.src:1</span>
. . 1001: instruction two <span class=disasmloc></span>
. . 1002: instruction three <span class=disasmloc></span>
. . 1003: instruction four <span class=disasmloc></span>
<span class=line> 1</span> <span class=deadsrc> 1.10s 1.10s line1 </span><span class=asm> 1.10s 1.10s 1000: instruction one <span class=unimportant>file1000.src:1</span>
. . 1001: instruction two <span class=unimportant>file1000.src:1</span>
. . 1003: instruction four <span class=unimportant>file1000.src:1</span>
</span>
<span class=line> 2</span> <span class=deadsrc> . . line2 </span><span class=asm> . . 1002: instruction three <span class=unimportant>file1000.src:2</span>
</span>
<span class=line> 2</span> <span class=nop> . . line2 </span>
<span class=line> 3</span> <span class=nop> . . line3 </span>
<span class=line> 4</span> <span class=nop> . . line4 </span>
<span class=line> 5</span> <span class=nop> . . line5 </span>
<span class=line> 6</span> <span class=nop> . . line6 </span>
<span class=line> 3</span> <span class=nop> . . line3 </span>
<span class=line> 4</span> <span class=nop> . . line4 </span>
<span class=line> 5</span> <span class=nop> . . line5 </span>
<span class=line> 6</span> <span class=nop> . . line6 </span>
<span class=line> 7</span> <span class=nop> . . line7 </span>
</pre>
<h1>line3000</h1>testdata/file3000.src
<pre onClick="pprof_toggle_asm(event)">
Total: 10ms 1.12s (flat, cum) 100%
<span class=line> 1</span> <span class=nop> . . line1 </span>
<span class=line> 2</span> <span class=nop> . . line2 </span>
<span class=line> 3</span> <span class=nop> . . line3 </span>
<span class=line> 4</span> <span class=nop> . . line4 </span>
<span class=line> 5</span> <span class=nop> . . line5 </span>
<span class=line> 6</span> <span class=deadsrc> 10ms 1.01s line6 </span><span class=asm> 10ms 1.01s 3000: instruction one <span class=disasmloc>file3000.src:6</span>
<span class=line> 1</span> <span class=nop> . . line1 </span>
<span class=line> 2</span> <span class=nop> . . line2 </span>
<span class=line> 3</span> <span class=nop> . . line3 </span>
<span class=line> 4</span> <span class=nop> . . line4 </span>
<span class=line> 5</span> <span class=nop> . . line5 </span>
<span class=line> 6</span> <span class=deadsrc> 10ms 1.01s line6 </span><span class=asm> 10ms 1.01s 3000: instruction one <span class=unimportant>file3000.src:6</span>
</span>
<span class=line> 7</span> <span class=nop> . . line7 </span>
<span class=line> 8</span> <span class=nop> . . line8 </span>
<span class=line> 9</span> <span class=deadsrc> . 110ms line9 </span><span class=asm> . 100ms 3001: instruction two <span class=disasmloc>file3000.src:9</span>
. 10ms 3002: instruction three <span class=disasmloc>file3000.src:9</span>
. . 3003: instruction four <span class=disasmloc></span>
. . 3004: instruction five <span class=disasmloc></span>
<span class=line> 7</span> <span class=nop> . . line7 </span>
<span class=line> 8</span> <span class=nop> . . line8 </span>
<span class=line> 9</span> <span class=deadsrc> . 110ms line9 </span><span class=asm> . 100ms 3001: instruction two <span class=unimportant>file3000.src:9</span>
. 10ms 3002: instruction three <span class=unimportant>file3000.src:9</span>
. . 3003: instruction four <span class=unimportant></span>
. . 3004: instruction five <span class=unimportant></span>
</span>
<span class=line> 10</span> <span class=nop> . . line0 </span>
<span class=line> 11</span> <span class=nop> . . line1 </span>
<span class=line> 12</span> <span class=nop> . . line2 </span>
<span class=line> 13</span> <span class=nop> . . line3 </span>
<span class=line> 14</span> <span class=nop> . . line4 </span>
<span class=line> 10</span> <span class=nop> . . line0 </span>
<span class=line> 11</span> <span class=nop> . . line1 </span>
<span class=line> 12</span> <span class=nop> . . line2 </span>
<span class=line> 13</span> <span class=nop> . . line3 </span>
<span class=line> 14</span> <span class=nop> . . line4 </span>
</pre>
</body>
......
digraph "testbinary" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.11s, 99.11% of 1.12s total\lDropped 3 nodes (cum <= 0.06s)\l" tooltip="testbinary"] }
N1 [label="line1000\n1s (89.29%)" id="node1" fontsize=24 shape=box tooltip="line1000 (1s)" color="#b20500" fillcolor="#edd6d5"]
N1_0 [label = "key1:tag1\nkey2:tag1" id="N1_0" fontsize=8 shape=box3d tooltip="1s"]
N1 -> N1_0 [label=" 1s" weight=100 tooltip="1s" labeltooltip="1s"]
N2 [label="line3000\n0 of 1.12s (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (1.12s)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line3001\n0 of 1.11s (99.11%)" id="node3" fontsize=8 shape=box tooltip="line3001 (1.11s)" color="#b20000" fillcolor="#edd5d5"]
N4 [label="line1000\n0.10s (8.93%)" id="node4" fontsize=14 shape=box tooltip="line1000 (0.10s)" color="#b28b62" fillcolor="#ede8e2"]
N4_0 [label = "key1:tag2\nkey3:tag2" id="N4_0" fontsize=8 shape=box3d tooltip="0.10s"]
N4 -> N4_0 [label=" 0.10s" weight=100 tooltip="0.10s" labeltooltip="0.10s"]
N5 [label="line3002\n0.01s (0.89%)\nof 1.01s (90.18%)" id="node5" fontsize=10 shape=box tooltip="line3002 (1.01s)" color="#b20500" fillcolor="#edd6d5"]
N6 [label="line2000\n0 of 1s (89.29%)" id="node6" fontsize=8 shape=box tooltip="line2000 (1s)" color="#b20500" fillcolor="#edd6d5"]
N7 [label="line2001\n0 of 1s (89.29%)" id="node7" fontsize=8 shape=box tooltip="line2001 (1s)" color="#b20500" fillcolor="#edd6d5"]
N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 -> line3001 (1.11s)" labeltooltip="line3000 -> line3001 (1.11s)"]
N3 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 -> line3002 (1.01s)" labeltooltip="line3001 -> line3002 (1.01s)"]
N6 -> N7 [label=" 1s\n (inline)" weight=90 penwidth=5 color="#b20500" tooltip="line2000 -> line2001 (1s)" labeltooltip="line2000 -> line2001 (1s)"]
N7 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 -> line1000 (1s)" labeltooltip="line2001 -> line1000 (1s)"]
N5 -> N6 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line3002 -> line2000 (1s)" labeltooltip="line3002 -> line2000 (1s)"]
N3 -> N4 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 -> line1000 (0.10s)" labeltooltip="line3001 -> line1000 (0.10s)"]
}
digraph "testbinary" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.12s, 100% of 1.12s total\l"] }
N1 [label="line1000\nfile1000.src\n1.10s (98.21%)" fontsize=24 shape=box tooltip="line1000 testdata/file1000.src (1.10s)" color="#b20000" fillcolor="#edd5d5"]
N1_0 [label = "key1:tag1\nkey2:tag1" fontsize=8 shape=box3d tooltip="1s"]
subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.12s, 100% of 1.12s total\l" tooltip="testbinary"] }
N1 [label="line1000\n1.10s (98.21%)" id="node1" fontsize=24 shape=box tooltip="line1000 (1.10s)" color="#b20000" fillcolor="#edd5d5"]
N1_0 [label = "key1:tag1\nkey2:tag1" id="N1_0" fontsize=8 shape=box3d tooltip="1s"]
N1 -> N1_0 [label=" 1s" weight=100 tooltip="1s" labeltooltip="1s"]
N1_1 [label = "key1:tag2\nkey3:tag2" fontsize=8 shape=box3d tooltip="0.10s"]
N1_1 [label = "key1:tag2\nkey3:tag2" id="N1_1" fontsize=8 shape=box3d tooltip="0.10s"]
N1 -> N1_1 [label=" 0.10s" weight=100 tooltip="0.10s" labeltooltip="0.10s"]
N2 [label="line3000\nfile3000.src\n0 of 1.12s (100%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (1.12s)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line3001\nfile3000.src\n0 of 1.11s (99.11%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (1.11s)" color="#b20000" fillcolor="#edd5d5"]
N4 [label="line3002\nfile3000.src\n0.01s (0.89%)\nof 1.02s (91.07%)" fontsize=10 shape=box tooltip="line3002 testdata/file3000.src (1.02s)" color="#b20400" fillcolor="#edd6d5"]
N5 [label="line2001\nfile2000.src\n0.01s (0.89%)\nof 1.01s (90.18%)" fontsize=10 shape=box tooltip="line2001 testdata/file2000.src (1.01s)" color="#b20500" fillcolor="#edd6d5"]
N6 [label="line2000\nfile2000.src\n0 of 1.01s (90.18%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (1.01s)" color="#b20500" fillcolor="#edd6d5"]
N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)"]
N6 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)"]
N3 -> N4 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)"]
N4 -> N6 [label=" 1.01s" weight=91 penwidth=5 color="#b20500" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)"]
N5 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)" labeltooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)"]
N3 -> N1 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)"]
N2 [label="line3000\n0 of 1.12s (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (1.12s)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line3001\n0 of 1.11s (99.11%)" id="node3" fontsize=8 shape=box tooltip="line3001 (1.11s)" color="#b20000" fillcolor="#edd5d5"]
N4 [label="line3002\n0.01s (0.89%)\nof 1.02s (91.07%)" id="node4" fontsize=10 shape=box tooltip="line3002 (1.02s)" color="#b20400" fillcolor="#edd6d5"]
N5 [label="line2001\n0.01s (0.89%)\nof 1.01s (90.18%)" id="node5" fontsize=10 shape=box tooltip="line2001 (1.01s)" color="#b20500" fillcolor="#edd6d5"]
N6 [label="line2000\n0 of 1.01s (90.18%)" id="node6" fontsize=8 shape=box tooltip="line2000 (1.01s)" color="#b20500" fillcolor="#edd6d5"]
N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 -> line3001 (1.11s)" labeltooltip="line3000 -> line3001 (1.11s)"]
N6 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line2000 -> line2001 (1.01s)" labeltooltip="line2000 -> line2001 (1.01s)"]
N3 -> N4 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 -> line3002 (1.01s)" labeltooltip="line3001 -> line3002 (1.01s)"]
N4 -> N6 [label=" 1.01s" weight=91 penwidth=5 color="#b20500" tooltip="line3002 -> line2000 (1.01s)" labeltooltip="line3002 -> line2000 (1.01s)"]
N5 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 -> line1000 (1s)" labeltooltip="line2001 -> line1000 (1s)"]
N3 -> N1 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 -> line1000 (0.10s)" labeltooltip="line3001 -> line1000 (0.10s)"]
}
Showing nodes accounting for 1.12s, 100% of 1.12s total
flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src
0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src (inline)
0.01s 0.89% 100% 1.02s 91.07% line3002 testdata/file3000.src (inline)
0 0% 100% 1.01s 90.18% line2000 testdata/file2000.src
0 0% 100% 1.12s 100% line3000 testdata/file3000.src
0 0% 100% 1.11s 99.11% line3001 testdata/file3000.src (inline)
1.10s 98.21% 98.21% 1.10s 98.21% line1000
0.01s 0.89% 99.11% 1.01s 90.18% line2001 (inline)
0.01s 0.89% 100% 1.02s 91.07% line3002 (inline)
0 0% 100% 1.01s 90.18% line2000
0 0% 100% 1.12s 100% line3000
0 0% 100% 1.11s 99.11% line3001 (inline)
......@@ -2,12 +2,12 @@ Showing nodes accounting for 1.12s, 100% of 1.12s total
----------------------------------------------------------+-------------
flat flat% sum% cum cum% calls calls% + context
----------------------------------------------------------+-------------
1.01s 100% | line2000 testdata/file2000.src (inline)
0.01s 0.89% 0.89% 1.01s 90.18% | line2001 testdata/file2000.src
1s 99.01% | line1000 testdata/file1000.src
----------------------------------------------------------+-------------
1.11s 100% | line3000 testdata/file3000.src (inline)
0 0% 0.89% 1.11s 99.11% | line3001 testdata/file3000.src
1.01s 90.99% | line3002 testdata/file3000.src (inline)
0.10s 9.01% | line1000 testdata/file1000.src
1.01s 100% | line2000 (inline)
0.01s 0.89% 0.89% 1.01s 90.18% | line2001
1s 99.01% | line1000
----------------------------------------------------------+-------------
1.11s 100% | line3000 (inline)
0 0% 0.89% 1.11s 99.11% | line3001
1.01s 90.99% | line3002 (inline)
0.10s 9.01% | line1000
----------------------------------------------------------+-------------
key1: Total 1120
1000 (89.29%): tag1
100 ( 8.93%): tag2
10 ( 0.89%): tag3
10 ( 0.89%): tag4
key1: Total 1.1s
1.0s (89.29%): tag1
100.0ms ( 8.93%): tag2
10.0ms ( 0.89%): tag3
10.0ms ( 0.89%): tag4
key2: Total 1020
1010 (99.02%): tag1
10 ( 0.98%): tag2
key2: Total 1.0s
1.0s (99.02%): tag1
10.0ms ( 0.98%): tag2
key3: Total 100
100 ( 100%): tag2
key3: Total 100.0ms
100.0ms ( 100%): tag2
key1: Total 100
100 ( 100%): tag2
key1: Total 100.0ms
100.0ms ( 100%): tag2
key3: Total 100
100 ( 100%): tag2
key3: Total 100.0ms
100.0ms ( 100%): tag2
......@@ -4,29 +4,29 @@ Duration: 10s, Total samples = 1.12s (11.20%)
-----------+-------------------------------------------------------
key1: tag1
key2: tag1
1s line1000 testdata/file1000.src
line2001 testdata/file2000.src
line2000 testdata/file2000.src
line3002 testdata/file3000.src
line3001 testdata/file3000.src
line3000 testdata/file3000.src
1s line1000
line2001
line2000
line3002
line3001
line3000
-----------+-------------------------------------------------------
key1: tag2
key3: tag2
100ms line1000 testdata/file1000.src
line3001 testdata/file3000.src
line3000 testdata/file3000.src
100ms line1000
line3001
line3000
-----------+-------------------------------------------------------
key1: tag3
key2: tag2
10ms line2001 testdata/file2000.src
line2000 testdata/file2000.src
line3002 testdata/file3000.src
line3000 testdata/file3000.src
10ms line2001
line2000
line3002
line3000
-----------+-------------------------------------------------------
key1: tag4
key2: tag1
10ms line3002 testdata/file3000.src
line3001 testdata/file3000.src
line3000 testdata/file3000.src
10ms line3002
line3001
line3000
-----------+-------------------------------------------------------
Active filters:
focus=[24]00
Showing nodes accounting for 62.50MB, 63.37% of 98.63MB total
Dropped 2 nodes (cum <= 4.93MB)
----------------------------------------------------------+-------------
......
Active filters:
focus=[24]00
Showing nodes accounting for 62.50MB, 98.46% of 63.48MB total
Dropped 2 nodes (cum <= 3.17MB)
----------------------------------------------------------+-------------
flat flat% sum% cum cum% calls calls% + context
----------------------------------------------------------+-------------
63.48MB 100% | line3002 testdata/file3000.src
0 0% 0% 63.48MB 100% | line2000 testdata/file2000.src
63.48MB 100% | line2001 testdata/file2000.src (inline)
63.48MB 100% | line3002
0 0% 0% 63.48MB 100% | line2000
63.48MB 100% | line2001 (inline)
----------------------------------------------------------+-------------
63.48MB 100% | line2000 testdata/file2000.src (inline)
62.50MB 98.46% 98.46% 63.48MB 100% | line2001 testdata/file2000.src
63.48MB 100% | line2000 (inline)
62.50MB 98.46% 98.46% 63.48MB 100% | line2001
----------------------------------------------------------+-------------
0 0% 98.46% 63.48MB 100% | line3000 testdata/file3000.src
63.48MB 100% | line3002 testdata/file3000.src (inline)
0 0% 98.46% 63.48MB 100% | line3000
63.48MB 100% | line3002 (inline)
----------------------------------------------------------+-------------
63.48MB 100% | line3000 testdata/file3000.src (inline)
0 0% 98.46% 63.48MB 100% | line3002 testdata/file3000.src
63.48MB 100% | line2000 testdata/file2000.src
63.48MB 100% | line3000 (inline)
0 0% 98.46% 63.48MB 100% | line3002
63.48MB 100% | line2000
----------------------------------------------------------+-------------
Active filters:
focus=[12]00
taghide=[X3]00
Showing nodes accounting for 67.38MB, 68.32% of 98.63MB total
flat flat% sum% cum cum%
62.50MB 63.37% 63.37% 63.48MB 64.36% testdata/file2000.src
4.88MB 4.95% 68.32% 4.88MB 4.95% testdata/file1000.src
0 0% 68.32% 67.38MB 68.32% testdata/file3000.src
Showing nodes accounting for 150, 100% of 150 total
flat flat% sum% cum cum%
80 53.33% 53.33% 130 86.67% line3002 testdata/file3000.src (inline)
40 26.67% 80.00% 50 33.33% line2001 testdata/file2000.src (inline)
30 20.00% 100% 30 20.00% line1000 testdata/file1000.src
0 0% 100% 50 33.33% line2000 testdata/file2000.src
0 0% 100% 150 100% line3000 testdata/file3000.src
0 0% 100% 110 73.33% line3001 testdata/file3000.src (inline)
80 53.33% 53.33% 130 86.67% line3002 (inline)
40 26.67% 80.00% 50 33.33% line2001 (inline)
30 20.00% 100% 30 20.00% line1000
0 0% 100% 50 33.33% line2000
0 0% 100% 150 100% line3000
0 0% 100% 110 73.33% line3001 (inline)
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\l"] }
N1 [label="line2001\nfile2000.src\n62.50MB (63.37%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
NN1_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lActive filters:\l tagfocus=1mb:2gb\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\l"] }
N1 [label="line2001\n62.50MB (63.37%)" id="node1" fontsize=24 shape=box tooltip="line2001 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
NN1_0 [label = "1.56MB" id="NN1_0" fontsize=8 shape=box3d tooltip="62.50MB"]
N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
N2 [label="line3000\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
N3 [label="line2000\nfile2000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
N4 [label="line3002\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
N3 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)"]
N2 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"]
N4 -> N3 [label=" 62.50MB" weight=64 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)"]
N2 [label="line3000\n0 of 62.50MB (63.37%)" id="node2" fontsize=8 shape=box tooltip="line3000 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
N3 [label="line2000\n0 of 62.50MB (63.37%)" id="node3" fontsize=8 shape=box tooltip="line2000 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
N4 [label="line3002\n0 of 62.50MB (63.37%)" id="node4" fontsize=8 shape=box tooltip="line3002 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
N3 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (62.50MB)" labeltooltip="line2000 -> line2001 (62.50MB)"]
N2 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"]
N4 -> N3 [label=" 62.50MB" weight=64 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (62.50MB)" labeltooltip="line3002 -> line2000 (62.50MB)"]
}
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\lDropped 2 nodes (cum <= 4.93MB)\l"] }
N1 [label="line3002\nfile3000.src\n31.25MB (31.68%)\nof 32.23MB (32.67%)" fontsize=24 shape=box tooltip="line3002 testdata/file3000.src (32.23MB)" color="#b23200" fillcolor="#eddcd5"]
NN1_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lActive filters:\l tagfocus=30kb:\l tagignore=1mb:2mb\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\lDropped 2 nodes (cum <= 4.93MB)\l"] }
N1 [label="line3002\n31.25MB (31.68%)\nof 32.23MB (32.67%)" id="node1" fontsize=24 shape=box tooltip="line3002 (32.23MB)" color="#b23200" fillcolor="#eddcd5"]
NN1_0 [label = "400kB" id="NN1_0" fontsize=8 shape=box3d tooltip="31.25MB"]
N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
N2 [label="line3000\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N3 [label="line3001\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N4 [label="line1000\nfile1000.src\n4.88MB (4.95%)" fontsize=15 shape=box tooltip="line1000 testdata/file1000.src (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
NN4_0 [label = "200kB" fontsize=8 shape=box3d tooltip="3.91MB"]
N2 [label="line3000\n0 of 36.13MB (36.63%)" id="node2" fontsize=8 shape=box tooltip="line3000 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N3 [label="line3001\n0 of 36.13MB (36.63%)" id="node3" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N4 [label="line1000\n4.88MB (4.95%)" id="node4" fontsize=15 shape=box tooltip="line1000 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
NN4_0 [label = "200kB" id="NN4_0" fontsize=8 shape=box3d tooltip="3.91MB"]
N4 -> NN4_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"]
N2 -> N3 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"]
N3 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"]
N3 -> N4 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)"]
N1 -> N4 [label=" 0.98MB" color="#b2b0a9" tooltip="line3002 testdata/file3000.src ... line1000 testdata/file1000.src (0.98MB)" labeltooltip="line3002 testdata/file3000.src ... line1000 testdata/file1000.src (0.98MB)" style="dotted" minlen=2]
N2 -> N3 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"]
N3 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"]
N3 -> N4 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 -> line1000 (3.91MB)" labeltooltip="line3001 -> line1000 (3.91MB)"]
N1 -> N4 [label=" 0.98MB" color="#b2b0a9" tooltip="line3002 ... line1000 (0.98MB)" labeltooltip="line3002 ... line1000 (0.98MB)" style="dotted" minlen=2]
}
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\l"] }
N1 [label="line3000\nfile3000.src:4\n0 of 67.38MB (68.32%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"]
N2 [label="line2001\nfile2000.src:2\n62.50MB (63.37%)\nof 63.48MB (64.36%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
NN2_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lActive filters:\l focus=[12]00\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\l"] }
N1 [label="line3000\nfile3000.src:4\n0 of 67.38MB (68.32%)" id="node1" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"]
N2 [label="line2001\nfile2000.src:2\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node2" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
NN2_0 [label = "1.56MB" id="NN2_0" fontsize=8 shape=box3d tooltip="62.50MB"]
N2 -> NN2_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
N3 [label="line1000\nfile1000.src:1\n4.88MB (4.95%)" fontsize=13 shape=box tooltip="line1000 testdata/file1000.src:1 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
NN3_0 [label = "200kB" fontsize=8 shape=box3d tooltip="3.91MB"]
N3 [label="line1000\nfile1000.src:1\n4.88MB (4.95%)" id="node3" fontsize=13 shape=box tooltip="line1000 testdata/file1000.src:1 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
NN3_0 [label = "200kB" id="NN3_0" fontsize=8 shape=box3d tooltip="3.91MB"]
N3 -> NN3_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"]
N4 [label="line3002\nfile3000.src:3\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N5 [label="line3001\nfile3000.src:2\n0 of 4.88MB (4.95%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
N6 [label="line2000\nfile2000.src:3\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N4 [label="line3002\nfile3000.src:3\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N5 [label="line3001\nfile3000.src:2\n0 of 4.88MB (4.95%)" id="node5" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
N6 [label="line2000\nfile2000.src:3\n0 of 63.48MB (64.36%)" id="node6" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N6 -> N2 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)" labeltooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)"]
N4 -> N6 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)" labeltooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)"]
N1 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)" labeltooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)"]
......
bytes: Total 150
80 (53.33%): 400kB
40 (26.67%): 1.56MB
20 (13.33%): 200kB
10 ( 6.67%): 100kB
bytes: Total 98.6MB
62.5MB (63.37%): 1.56MB
31.2MB (31.68%): 400kB
3.9MB ( 3.96%): 200kB
1000.0kB ( 0.99%): 100kB
bytes: Total 150
80 (53.33%): 409600B
40 (26.67%): 1638400B
20 (13.33%): 204800B
10 ( 6.67%): 102400B
bytes: Total 103424000.0B
65536000.0B (63.37%): 1638400B
32768000.0B (31.68%): 409600B
4096000.0B ( 3.96%): 204800B
1024000.0B ( 0.99%): 102400B
Showing nodes accounting for 150, 100% of 150 total
flat flat% sum% cum cum%
80 53.33% 53.33% 130 86.67% line3002 testdata/file3000.src (inline)
40 26.67% 80.00% 50 33.33% line2001 testdata/file2000.src (inline)
30 20.00% 100% 30 20.00% line1000 testdata/file1000.src
0 0% 100% 50 33.33% line2000 testdata/file2000.src
0 0% 100% 150 100% line3000 testdata/file3000.src
0 0% 100% 110 73.33% line3001 testdata/file3000.src (inline)
80 53.33% 53.33% 130 86.67% line3002 (inline)
40 26.67% 80.00% 50 33.33% line2001 (inline)
30 20.00% 100% 30 20.00% line1000
0 0% 100% 50 33.33% line2000
0 0% 100% 150 100% line3000
0 0% 100% 110 73.33% line3001 (inline)
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lActive filters:\l tagshow=[2]00\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"]
N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N4 [label="line2000\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line2000 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N5 [label="line3001\n0 of 36.13MB (36.63%)" id="node5" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (63.48MB)" labeltooltip="line2000 -> line2001 (63.48MB)"]
N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (63.48MB)" labeltooltip="line3002 -> line2000 (63.48MB)"]
N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"]
N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"]
N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"]
}
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
N1 [label="line3002\nfile3000.src\n31.25MB (31.68%)\nof 94.73MB (96.04%)" fontsize=20 shape=box tooltip="line3002 testdata/file3000.src (94.73MB)" color="#b20200" fillcolor="#edd5d5"]
NN1_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lActive filters:\l focus=[234]00\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"]
NN1_0 [label = "400kB" id="NN1_0" fontsize=8 shape=box3d tooltip="31.25MB"]
N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
N2 [label="line3000\nfile3000.src\n0 of 98.63MB (100%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line2001\nfile2000.src\n62.50MB (63.37%)\nof 63.48MB (64.36%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
NN3_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
NN3_0 [label = "1.56MB" id="NN3_0" fontsize=8 shape=box3d tooltip="62.50MB"]
N3 -> NN3_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
N4 [label="line2000\nfile2000.src\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N5 [label="line3001\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)"]
N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" minlen=2]
N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"]
N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"]
N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"]
N4 [label="line2000\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line2000 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N5 [label="line3001\n0 of 36.13MB (36.63%)" id="node5" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (63.48MB)" labeltooltip="line2000 -> line2001 (63.48MB)"]
N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (63.48MB)" labeltooltip="line3002 -> line2000 (63.48MB)" minlen=2]
N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"]
N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"]
N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"]
}
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
N1 [label="line3000\nfile3000.src\n62.50MB (63.37%)\nof 98.63MB (100%)" fontsize=24 shape=box tooltip="line3000 testdata/file3000.src (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
NN1_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: alloc_space\lActive filters:\l hide=line.*1?23?\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
N1 [label="line3000\n62.50MB (63.37%)\nof 98.63MB (100%)" id="node1" fontsize=24 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
NN1_0 [label = "1.56MB" id="NN1_0" fontsize=8 shape=box3d tooltip="62.50MB"]
N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
N2 [label="line3001\nfile3000.src\n31.25MB (31.68%)\nof 36.13MB (36.63%)" fontsize=20 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
NN2_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"]
N2 [label="line3001\n31.25MB (31.68%)\nof 36.13MB (36.63%)" id="node2" fontsize=20 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
NN2_0 [label = "400kB" id="NN2_0" fontsize=8 shape=box3d tooltip="31.25MB"]
N2 -> NN2_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
N1 -> N2 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" minlen=2]
N1 -> N2 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)" minlen=2]
}
bytes: Total 93.8MB
62.5MB (66.67%): 1.56MB
31.2MB (33.33%): 400kB
request: Total 93.8MB
62.5MB (66.67%): 1.56MB
31.2MB (33.33%): 400kB
digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lcomment\lType: inuse_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"]
NN1_0 [label = "16B..64B" id="NN1_0" fontsize=8 shape=box3d tooltip="93.75MB"]
N1 -> NN1_0 [label=" 93.75MB" weight=100 tooltip="93.75MB" labeltooltip="93.75MB"]
NN1_1 [label = "2B..8B" id="NN1_1" fontsize=8 shape=box3d tooltip="93.75MB"]
N1 -> NN1_1 [label=" 93.75MB" weight=100 tooltip="93.75MB" labeltooltip="93.75MB"]
NN1_2 [label = "256B..1.56MB" id="NN1_2" fontsize=8 shape=box3d tooltip="62.50MB"]
N1 -> NN1_2 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
NN1_3 [label = "128B" id="NN1_3" fontsize=8 shape=box3d tooltip="31.25MB"]
N1 -> NN1_3 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
NN3_0 [label = "16B..64B" id="NN3_0" fontsize=8 shape=box3d tooltip="190.43MB"]
N3 -> NN3_0 [label=" 190.43MB" weight=100 tooltip="190.43MB" labeltooltip="190.43MB" style="dotted"]
NN3_1 [label = "2B..8B" id="NN3_1" fontsize=8 shape=box3d tooltip="190.43MB"]
N3 -> NN3_1 [label=" 190.43MB" weight=100 tooltip="190.43MB" labeltooltip="190.43MB" style="dotted"]
NN3_2 [label = "256B..1.56MB" id="NN3_2" fontsize=8 shape=box3d tooltip="125.98MB"]
N3 -> NN3_2 [label=" 125.98MB" weight=100 tooltip="125.98MB" labeltooltip="125.98MB" style="dotted"]
NN3_3 [label = "128B" id="NN3_3" fontsize=8 shape=box3d tooltip="63.48MB"]
N3 -> NN3_3 [label=" 63.48MB" weight=100 tooltip="63.48MB" labeltooltip="63.48MB" style="dotted"]
N4 [label="line2000\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line2000 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
N5 [label="line3001\n0 of 36.13MB (36.63%)" id="node5" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (63.48MB)" labeltooltip="line2000 -> line2001 (63.48MB)"]
N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (63.48MB)" labeltooltip="line3002 -> line2000 (63.48MB)" minlen=2]
N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"]
N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"]
N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"]
}
Build ID: buildid
comment
Type: inuse_space
-----------+-------------------------------------------------------
key1: tag
bytes: 100kB
request: 100kB
1000kB line1000
line2001
line2000
line3002
line3001
line3000
-----------+-------------------------------------------------------
bytes: 200kB
3.91MB line1000
line3001
line3000
-----------+-------------------------------------------------------
key1: tag
bytes: 1.56MB
request: 1.56MB
62.50MB line2001
line2000
line3002
line3000
-----------+-------------------------------------------------------
bytes: 400kB
31.25MB line3002
line3001
line3000
-----------+-------------------------------------------------------
Showing nodes accounting for 1.12s, 100% of 1.12s total
Showing top 5 nodes out of 6
flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000
0.01s 0.89% 99.11% 1.01s 90.18% line2001 (inline)
0.01s 0.89% 100% 1.02s 91.07% line3002 (inline)
0 0% 100% 1.01s 90.18% line2000
0 0% 100% 1.12s 100% line3000
Showing nodes accounting for 1.12s, 100% of 1.12s total
flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src
0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src (inline)
0.01s 0.89% 100% 1.02s 91.07% line3002 testdata/file3000.src (inline)
0 0% 100% 1.01s 90.18% line2000 testdata/file2000.src
0 0% 100% 1.12s 100% line3000 testdata/file3000.src
0 0% 100% 1.11s 99.11% line3001 testdata/file3000.src (inline)
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os/exec"
"regexp"
"sync"
"testing"
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/profile"
"runtime"
)
func TestWebInterface(t *testing.T) {
if runtime.GOOS == "nacl" {
t.Skip("test assumes tcp available")
}
prof := makeFakeProfile()
// Custom http server creator
var server *httptest.Server
serverCreated := make(chan bool)
creator := func(a *plugin.HTTPServerArgs) error {
server = httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if h := a.Handlers[r.URL.Path]; h != nil {
h.ServeHTTP(w, r)
}
}))
serverCreated <- true
return nil
}
// Start server and wait for it to be initialized
go serveWebInterface("unused:1234", prof, &plugin.Options{
Obj: fakeObjTool{},
UI: &stdUI{},
HTTPServer: creator,
})
<-serverCreated
defer server.Close()
haveDot := false
if _, err := exec.LookPath("dot"); err == nil {
haveDot = true
}
type testCase struct {
path string
want []string
needDot bool
}
testcases := []testCase{
{"/", []string{"F1", "F2", "F3", "testbin", "cpu"}, true},
{"/top", []string{`"Name":"F2","InlineLabel":"","Flat":200,"Cum":300,"FlatFormat":"200ms","CumFormat":"300ms"}`}, false},
{"/source?f=" + url.QueryEscape("F[12]"),
[]string{"F1", "F2", "300ms +line1"}, false},
{"/peek?f=" + url.QueryEscape("F[12]"),
[]string{"300ms.*F1", "200ms.*300ms.*F2"}, false},
{"/disasm?f=" + url.QueryEscape("F[12]"),
[]string{"f1:asm", "f2:asm"}, false},
}
for _, c := range testcases {
if c.needDot && !haveDot {
t.Log("skipping", c.path, "since dot (graphviz) does not seem to be installed")
continue
}
res, err := http.Get(server.URL + c.path)
if err != nil {
t.Error("could not fetch", c.path, err)
continue
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error("could not read response", c.path, err)
continue
}
result := string(data)
for _, w := range c.want {
if match, _ := regexp.MatchString(w, result); !match {
t.Errorf("response for %s does not match "+
"expected pattern '%s'; "+
"actual result:\n%s", c.path, w, result)
}
}
}
// Also fetch all the test case URLs in parallel to test thread
// safety when run under the race detector.
var wg sync.WaitGroup
for _, c := range testcases {
if c.needDot && !haveDot {
continue
}
path := server.URL + c.path
for count := 0; count < 2; count++ {
wg.Add(1)
go func() {
http.Get(path)
wg.Done()
}()
}
}
wg.Wait()
}
// Implement fake object file support.
const addrBase = 0x1000
const fakeSource = "testdata/file1000.src"
type fakeObj struct{}
func (f fakeObj) Close() error { return nil }
func (f fakeObj) Name() string { return "testbin" }
func (f fakeObj) Base() uint64 { return 0 }
func (f fakeObj) BuildID() string { return "" }
func (f fakeObj) SourceLine(addr uint64) ([]plugin.Frame, error) {
return nil, fmt.Errorf("SourceLine unimplemented")
}
func (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
return []*plugin.Sym{
{[]string{"F1"}, fakeSource, addrBase, addrBase + 10},
{[]string{"F2"}, fakeSource, addrBase + 10, addrBase + 20},
{[]string{"F3"}, fakeSource, addrBase + 20, addrBase + 30},
}, nil
}
type fakeObjTool struct{}
func (obj fakeObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
return fakeObj{}, nil
}
func (obj fakeObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
return []plugin.Inst{
{Addr: addrBase + 0, Text: "f1:asm", Function: "F1"},
{Addr: addrBase + 10, Text: "f2:asm", Function: "F2"},
{Addr: addrBase + 20, Text: "d3:asm", Function: "F3"},
}, nil
}
func makeFakeProfile() *profile.Profile {
// Three functions: F1, F2, F3 with three lines, 11, 22, 33.
funcs := []*profile.Function{
{ID: 1, Name: "F1", Filename: fakeSource, StartLine: 3},
{ID: 2, Name: "F2", Filename: fakeSource, StartLine: 5},
{ID: 3, Name: "F3", Filename: fakeSource, StartLine: 7},
}
lines := []profile.Line{
{Function: funcs[0], Line: 11},
{Function: funcs[1], Line: 22},
{Function: funcs[2], Line: 33},
}
mapping := []*profile.Mapping{
{
ID: 1,
Start: addrBase,
Limit: addrBase + 10,
Offset: 0,
File: "testbin",
HasFunctions: true,
HasFilenames: true,
HasLineNumbers: true,
},
}
// Three interesting addresses: base+{10,20,30}
locs := []*profile.Location{
{ID: 1, Address: addrBase + 10, Line: lines[0:1], Mapping: mapping[0]},
{ID: 2, Address: addrBase + 20, Line: lines[1:2], Mapping: mapping[0]},
{ID: 3, Address: addrBase + 30, Line: lines[2:3], Mapping: mapping[0]},
}
// Two stack traces.
return &profile.Profile{
PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
Period: 1,
DurationNanos: 10e9,
SampleType: []*profile.ValueType{
{Type: "cpu", Unit: "milliseconds"},
},
Sample: []*profile.Sample{
{
Location: []*profile.Location{locs[2], locs[1], locs[0]},
Value: []int64{100},
},
{
Location: []*profile.Location{locs[1], locs[0]},
Value: []int64{200},
},
},
Location: locs,
Function: funcs,
Mapping: mapping,
}
}
func TestIsLocalHost(t *testing.T) {
for _, s := range []string{"localhost:10000", "[::1]:10000", "127.0.0.1:10000"} {
host, _, err := net.SplitHostPort(s)
if err != nil {
t.Error("unexpected error when splitting", s)
continue
}
if !isLocalhost(host) {
t.Errorf("host %s from %s not considered local", host, s)
}
}
}
......@@ -131,7 +131,7 @@ func GetBuildID(binary io.ReaderAt) ([]byte, error) {
if buildID == nil {
buildID = note.Desc
} else {
return nil, fmt.Errorf("multiple build ids found, don't know which to use!")
return nil, fmt.Errorf("multiple build ids found, don't know which to use")
}
}
}
......@@ -240,17 +240,22 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
}
return start, nil
case elf.ET_DYN:
if offset != 0 {
if loadSegment == nil || loadSegment.Vaddr == 0 {
return start - offset, nil
}
return 0, fmt.Errorf("Don't know how to handle mapping. Offset=%x, vaddr=%x",
offset, loadSegment.Vaddr)
}
// The process mapping information, start = start of virtual address range,
// and offset = offset in the executable file of the start address, tells us
// that a runtime virtual address x maps to a file offset
// fx = x - start + offset.
if loadSegment == nil {
return start, nil
return start - offset, nil
}
return start - loadSegment.Vaddr, nil
// The program header, if not nil, indicates the offset in the file where
// the executable segment is located (loadSegment.Off), and the base virtual
// address where the first byte of the segment is loaded
// (loadSegment.Vaddr). A file offset fx maps to a virtual (symbol) address
// sx = fx - loadSegment.Off + loadSegment.Vaddr.
//
// Thus, a runtime virtual address x maps to a symbol address
// sx = x - start + offset - loadSegment.Off + loadSegment.Vaddr.
return start - offset + loadSegment.Off - loadSegment.Vaddr, nil
}
return 0, fmt.Errorf("Don't know how to handle FileHeader.Type %v", fh.Type)
}
......@@ -62,8 +62,9 @@ func TestGetBase(t *testing.T) {
{"exec chromeos kernel 4", fhExec, kernelHeader, uint64p(0xffffffff81200198), 0x198, 0x100000, 0, 0x7ee00000, false},
{"exec chromeos kernel unremapped", fhExec, kernelHeader, uint64p(0xffffffff810001c8), 0xffffffff834001c8, 0xffffffffc0000000, 0xffffffff834001c8, 0x2400000, false},
{"dyn", fhDyn, nil, nil, 0x200000, 0x300000, 0, 0x200000, false},
{"dyn offset", fhDyn, lsOffset, nil, 0x0, 0x300000, 0, 0xFFFFFFFFFFC00000, false},
{"dyn map", fhDyn, lsOffset, nil, 0x0, 0x300000, 0, 0xFFFFFFFFFFE00000, false},
{"dyn nomap", fhDyn, nil, nil, 0x0, 0x0, 0, 0, false},
{"dyn map+offset", fhDyn, lsOffset, nil, 0x900000, 0xa00000, 0x200000, 0x500000, false},
{"rel", fhRel, nil, nil, 0x2000000, 0x3000000, 0, 0x2000000, false},
{"rel nomap", fhRel, nil, nil, 0x0, ^uint64(0), 0, 0, false},
{"rel offset", fhRel, nil, nil, 0x100000, 0x200000, 0x1, 0, true},
......
......@@ -171,7 +171,7 @@ func createExpectedEdges(parent expectedNode, children ...expectedNode) {
}
}
// createTestCase1 creates a test case that initally looks like:
// createTestCase1 creates a test case that initially looks like:
// 0
// |(5)
// 1
......@@ -255,7 +255,7 @@ func createTestCase2() trimTreeTestcase {
}
}
// createTestCase3 creates an initally empty graph and expects an empty graph
// createTestCase3 creates an initially empty graph and expects an empty graph
// after trimming.
func createTestCase3() trimTreeTestcase {
graph := &Graph{make(Nodes, 0)}
......
digraph "testtitle" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l"] }
subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l" tooltip="testtitle"] }
}
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment