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). Want to contribute? Great! First, read this page (including the small print at the end).
### Before you contribute ### Before you contribute
Before we can use your code, you must sign the Before we can use your code, you must sign the
[Google Individual Contributor License Agreement] [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
(https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the (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 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 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 ...@@ -17,11 +17,11 @@ possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on. frustration later on.
### Code reviews ### 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 ### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the Contributions made by corporations are covered by a different agreement than the one above,
[Software Grant and Corporate Contributor License Agreement] the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
(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 # Introduction
pprof is a tool for visualization and analysis of profiling data. 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). ...@@ -24,7 +27,7 @@ them through the use of the native binutils tools (addr2line and nm).
Prerequisites: 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 Follow [these instructions](http://golang.org/doc/code.html) to install the
go tool and set up GOPATH. go tool and set up GOPATH.
...@@ -35,6 +38,10 @@ To build and install it, use the `go get` tool. ...@@ -35,6 +38,10 @@ To build and install it, use the `go get` tool.
go get github.com/google/pprof 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 # Basic usage
pprof can read a profile from a file or directly from a server via http. 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. ...@@ -70,12 +77,28 @@ This will open a simple shell that takes pprof commands to generate reports.
Type 'help' for available commands/options. 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 ## Using pprof with Linux Perf
pprof can read `perf.data` files generated by the 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_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 ## Further documentation
......
...@@ -128,9 +128,11 @@ size of 6MB. ...@@ -128,9 +128,11 @@ size of 6MB.
Labels can be string-based or numeric. They are represented by the Label 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 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 value. For numeric labels, the measurement unit can be specified in the profile.
of the numeric value. So for the previous example, the samples would have labels If no unit is specified and the key is "request" or "alignment",
{“bytes”, 2097152} and {“bytes”, 4194304}. 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 ## Keep and drop expressions
......
...@@ -29,7 +29,40 @@ location. pprof is agnostic to the profile semantics, so other uses are ...@@ -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 possible. The interpretation of the reports generated by pprof depends on the
semantics defined by the source of the profile. 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 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 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 ...@@ -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 descendants. Samples that include a location multiple times (eg for recursive
functions) are counted only once per location. functions) are counted only once per location.
The basic usage of pprof is ## Options
pprof <format> [options] source
Where *format* selects the nature of the report, and *options* configure the *options* configure the contents of a report. Each option has a value,
contents of the report. Each option has a value, which can be boolean, numeric, which can be boolean, numeric, or strings. While only one format can
or strings. While only one format can be specified, most options can be selected be specified, most options can be selected independently of each
independently of each other. other.
Some common pprof options are: Some common pprof options are:
...@@ -74,10 +105,56 @@ number of values - 1) or the name of the sample value. ...@@ -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 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 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 visualization. The `unit=` option will force the use of a specific unit. For
example, `sample_index=sec` will force any time values to be reported in example, `unit=sec` will force any time values to be reported in
seconds. pprof recognizes most common time and memory size units. 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 ## Text reports
pprof text reports show the location hierarchy in text format. pprof text reports show the location hierarchy in text format.
......
...@@ -29,10 +29,10 @@ import ( ...@@ -29,10 +29,10 @@ import (
// manager. Then it generates a report formatted according to the // manager. Then it generates a report formatted according to the
// options selected through the flags package. // options selected through the flags package.
func PProf(o *Options) error { 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 var obj plugin.ObjTool
if o.Obj != nil { if o.Obj != nil {
obj = &internalObjTool{o.Obj} obj = &internalObjTool{o.Obj}
...@@ -273,9 +273,9 @@ type internalSymbolizer struct { ...@@ -273,9 +273,9 @@ type internalSymbolizer struct {
} }
func (s *internalSymbolizer) Symbolize(mode string, srcs plugin.MappingSources, prof *profile.Profile) error { func (s *internalSymbolizer) Symbolize(mode string, srcs plugin.MappingSources, prof *profile.Profile) error {
isrcs := plugin.MappingSources{} isrcs := MappingSources{}
for m, s := range srcs { for m, s := range srcs {
isrcs[m] = s isrcs[m] = s
} }
return s.Symbolize(mode, isrcs, prof) return s.Symbolizer.Symbolize(mode, isrcs, prof)
} }
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/plugin"
) )
...@@ -36,6 +37,7 @@ const ( ...@@ -36,6 +37,7 @@ const (
// addr2Liner is a connection to an addr2line command for obtaining // addr2Liner is a connection to an addr2line command for obtaining
// address and line number information from a binary. // address and line number information from a binary.
type addr2Liner struct { type addr2Liner struct {
mu sync.Mutex
rw lineReaderWriter rw lineReaderWriter
base uint64 base uint64
...@@ -170,9 +172,10 @@ func (d *addr2Liner) readFrame() (plugin.Frame, bool) { ...@@ -170,9 +172,10 @@ func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
Line: linenumber}, false Line: linenumber}, false
} }
// addrInfo returns the stack frame information for a specific program func (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) {
// address. It returns nil if the address could not be identified. d.mu.Lock()
func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) { defer d.mu.Unlock()
if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil { if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil {
return nil, err return nil, err
} }
...@@ -201,6 +204,16 @@ func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) { ...@@ -201,6 +204,16 @@ func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
stack = append(stack, frame) 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. // Get better name from nm if possible.
if len(stack) > 0 && d.nm != nil { if len(stack) > 0 && d.nm != nil {
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/plugin"
) )
...@@ -32,6 +33,7 @@ const ( ...@@ -32,6 +33,7 @@ const (
// llvmSymbolizer is a connection to an llvm-symbolizer command for // llvmSymbolizer is a connection to an llvm-symbolizer command for
// obtaining address and line number information from a binary. // obtaining address and line number information from a binary.
type llvmSymbolizer struct { type llvmSymbolizer struct {
sync.Mutex
filename string filename string
rw lineReaderWriter rw lineReaderWriter
base uint64 base uint64
...@@ -150,6 +152,9 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) { ...@@ -150,6 +152,9 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
// addrInfo returns the stack frame information for a specific program // addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified. // address. It returns nil if the address could not be identified.
func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) { 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 { if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil {
return nil, err return nil, err
} }
......
...@@ -48,22 +48,23 @@ func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) { ...@@ -48,22 +48,23 @@ func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
if cmd == "" { if cmd == "" {
cmd = defaultNM cmd = defaultNM
} }
a := &addr2LinerNM{
m: []symbolInfo{},
}
var b bytes.Buffer var b bytes.Buffer
c := exec.Command(cmd, "-n", file) c := exec.Command(cmd, "-n", file)
c.Stdout = &b c.Stdout = &b
if err := c.Run(); err != nil { if err := c.Run(); err != nil {
return nil, err 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. // Parse nm output and populate symbol map.
// Skip lines we fail to parse. // Skip lines we fail to parse.
buf := bufio.NewReader(&b) buf := bufio.NewReader(nm)
for { for {
line, err := buf.ReadString('\n') line, err := buf.ReadString('\n')
if line == "" && err != nil { if line == "" && err != nil {
......
...@@ -24,14 +24,21 @@ import ( ...@@ -24,14 +24,21 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync"
"github.com/google/pprof/internal/elfexec" "github.com/google/pprof/internal/elfexec"
"github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/plugin"
) )
// A Binutils implements plugin.ObjTool by invoking the GNU binutils. // A Binutils implements plugin.ObjTool by invoking the GNU binutils.
// SetConfig must be called before any of the other methods.
type Binutils struct { 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. // Commands to invoke.
llvmSymbolizer string llvmSymbolizer string
llvmSymbolizerFound bool llvmSymbolizerFound bool
...@@ -47,11 +54,38 @@ type Binutils struct { ...@@ -47,11 +54,38 @@ type Binutils struct {
fast bool 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 // SetFastSymbolization sets a toggle that makes binutils use fast
// symbolization (using nm), which is much faster than addr2line but // symbolization (using nm), which is much faster than addr2line but
// provides only symbol name information (no file/line). // provides only symbol name information (no file/line).
func (b *Binutils) SetFastSymbolization(fast bool) { func (bu *Binutils) SetFastSymbolization(fast bool) {
b.fast = fast bu.update(func(r *binrep) { r.fast = fast })
} }
// SetTools processes the contents of the tools option. It // SetTools processes the contents of the tools option. It
...@@ -59,7 +93,11 @@ func (b *Binutils) SetFastSymbolization(fast bool) { ...@@ -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 // 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 // tool named t. If t is not specified, the path is searched for all
// tools. // 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 collect paths per tool; Key "" contains the default.
paths := make(map[string][]string) paths := make(map[string][]string)
for _, t := range strings.Split(config, ",") { for _, t := range strings.Split(config, ",") {
...@@ -91,11 +129,8 @@ func findExe(cmd string, paths []string) (string, bool) { ...@@ -91,11 +129,8 @@ func findExe(cmd string, paths []string) (string, bool) {
// Disasm returns the assembly instructions for the specified address range // Disasm returns the assembly instructions for the specified address range
// of a binary. // of a binary.
func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { func (bu *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
if b.addr2line == "" { b := bu.get()
// Update the command invocations if not initialized.
b.SetTools("")
}
cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l", cmd := exec.Command(b.objdump, "-d", "-C", "--no-show-raw-insn", "-l",
fmt.Sprintf("--start-address=%#x", start), fmt.Sprintf("--start-address=%#x", start),
fmt.Sprintf("--stop-address=%#x", end), fmt.Sprintf("--stop-address=%#x", end),
...@@ -109,11 +144,8 @@ func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) ...@@ -109,11 +144,8 @@ func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error)
} }
// Open satisfies the plugin.ObjTool interface. // Open satisfies the plugin.ObjTool interface.
func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) { func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
if b.addr2line == "" { b := bu.get()
// Update the command invocations if not initialized.
b.SetTools("")
}
// Make sure file is a supported executable. // Make sure file is a supported executable.
// The pprof driver uses Open to sniff the difference // 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 ...@@ -140,7 +172,7 @@ func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFil
return nil, fmt.Errorf("unrecognized binary: %s", name) 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) of, err := macho.Open(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("Parsing %s: %v", name, err) 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 ...@@ -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 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) ef, err := elf.Open(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("Parsing %s: %v", name, err) 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 ...@@ -202,7 +234,7 @@ func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.Obj
// file implements the binutils.ObjFile interface. // file implements the binutils.ObjFile interface.
type file struct { type file struct {
b *Binutils b *binrep
name string name string
base uint64 base uint64
buildID string buildID string
...@@ -263,22 +295,27 @@ func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) { ...@@ -263,22 +295,27 @@ func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
// information). It can be slow for large binaries with debug // information). It can be slow for large binaries with debug
// information. // information.
type fileAddr2Line struct { type fileAddr2Line struct {
once sync.Once
file file
addr2liner *addr2Liner addr2liner *addr2Liner
llvmSymbolizer *llvmSymbolizer llvmSymbolizer *llvmSymbolizer
} }
func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) { func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
f.once.Do(f.init)
if f.llvmSymbolizer != nil { if f.llvmSymbolizer != nil {
return f.llvmSymbolizer.addrInfo(addr) return f.llvmSymbolizer.addrInfo(addr)
} }
if f.addr2liner != nil { if f.addr2liner != nil {
return f.addr2liner.addrInfo(addr) 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 { if llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base); err == nil {
f.llvmSymbolizer = llvmSymbolizer f.llvmSymbolizer = llvmSymbolizer
return f.llvmSymbolizer.addrInfo(addr) return
} }
if addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil { 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) { ...@@ -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 { if nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {
f.addr2liner.nm = nm f.addr2liner.nm = nm
} }
return f.addr2liner.addrInfo(addr)
} }
return nil, fmt.Errorf("could not find local addr2liner")
} }
func (f *fileAddr2Line) Close() error { func (f *fileAddr2Line) Close() error {
if f.llvmSymbolizer != nil {
f.llvmSymbolizer.rw.close()
f.llvmSymbolizer = nil
}
if f.addr2liner != nil { if f.addr2liner != nil {
f.addr2liner.rw.close() f.addr2liner.rw.close()
f.addr2liner = nil f.addr2liner = nil
......
...@@ -15,7 +15,13 @@ ...@@ -15,7 +15,13 @@
package binutils package binutils
import ( import (
"bytes"
"fmt" "fmt"
"math"
"path/filepath"
"reflect"
"regexp"
"runtime"
"testing" "testing"
"github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/plugin"
...@@ -37,7 +43,7 @@ func functionName(level int) (name string) { ...@@ -37,7 +43,7 @@ func functionName(level int) (name string) {
func TestAddr2Liner(t *testing.T) { func TestAddr2Liner(t *testing.T) {
const offset = 0x500 const offset = 0x500
a := addr2Liner{&mockAddr2liner{}, offset, nil} a := addr2Liner{rw: &mockAddr2liner{}, base: offset}
for i := 1; i < 8; i++ { for i := 1; i < 8; i++ {
addr := i*0x1000 + offset addr := i*0x1000 + offset
s, err := a.addrInfo(uint64(addr)) s, err := a.addrInfo(uint64(addr))
...@@ -112,24 +118,23 @@ func (a *mockAddr2liner) close() { ...@@ -112,24 +118,23 @@ func (a *mockAddr2liner) close() {
} }
func TestAddr2LinerLookup(t *testing.T) { func TestAddr2LinerLookup(t *testing.T) {
oddSizedMap := addr2LinerNM{ const oddSizedData = `
m: []symbolInfo{ 00001000 T 0x1000
{0x1000, "0x1000"}, 00002000 T 0x2000
{0x2000, "0x2000"}, 00003000 T 0x3000
{0x3000, "0x3000"}, `
}, const evenSizedData = `
} 0000000000001000 T 0x1000
evenSizedMap := addr2LinerNM{ 0000000000002000 T 0x2000
m: []symbolInfo{ 0000000000003000 T 0x3000
{0x1000, "0x1000"}, 0000000000004000 T 0x4000
{0x2000, "0x2000"}, `
{0x3000, "0x3000"}, for _, d := range []string{oddSizedData, evenSizedData} {
{0x4000, "0x4000"}, a, err := parseAddr2LinerNM(0, bytes.NewBufferString(d))
}, if err != nil {
} t.Errorf("nm parse error: %v", err)
for _, a := range []*addr2LinerNM{ continue
&oddSizedMap, &evenSizedMap, }
} {
for address, want := range map[uint64]string{ for address, want := range map[uint64]string{
0x1000: "0x1000", 0x1000: "0x1000",
0x1001: "0x1000", 0x1001: "0x1000",
...@@ -141,6 +146,11 @@ func TestAddr2LinerLookup(t *testing.T) { ...@@ -141,6 +146,11 @@ func TestAddr2LinerLookup(t *testing.T) {
t.Errorf("%x: got %v, want %s", address, got, want) 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 { ...@@ -150,3 +160,116 @@ func checkAddress(got []plugin.Frame, address uint64, want string) bool {
} }
return got[0].Func == want 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) { ...@@ -73,7 +73,7 @@ func TestFindSymbols(t *testing.T) {
func checkSymbol(got []*plugin.Sym, want []plugin.Sym) error { func checkSymbol(got []*plugin.Sym, want []plugin.Sym) error {
if len(got) != len(want) { 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 { for i, g := range got {
...@@ -134,8 +134,6 @@ func TestFunctionAssembly(t *testing.T) { ...@@ -134,8 +134,6 @@ func TestFunctionAssembly(t *testing.T) {
}, },
} }
const objdump = "testdata/wrapper/objdump"
for _, tc := range testcases { for _, tc := range testcases {
insts, err := disassemble([]byte(tc.asm)) insts, err := disassemble([]byte(tc.asm))
if err != nil { 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 ( ...@@ -24,14 +24,17 @@ import (
) )
type source struct { type source struct {
Sources []string Sources []string
ExecName string ExecName string
BuildID string BuildID string
Base []string Base []string
Normalize bool
Seconds int
Timeout int Seconds int
Symbolize string Timeout int
Symbolize string
HTTPHostport string
Comment string
} }
// Parse parses the command lines through the specified flags package // Parse parses the command lines through the specified flags package
...@@ -41,9 +44,11 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { ...@@ -41,9 +44,11 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
flag := o.Flagset flag := o.Flagset
// Comparisons. // Comparisons.
flagBase := flag.StringList("base", "", "Source for base profile for comparison") flagBase := flag.StringList("base", "", "Source for base profile for comparison")
// Internal options. // Source options.
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization") flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
flagBuildID := flag.String("buildid", "", "Override build id for first mapping") 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 // CPU profile options
flagSeconds := flag.Int("seconds", -1, "Length of time for dynamic profiles") flagSeconds := flag.Int("seconds", -1, "Length of time for dynamic profiles")
// Heap profile options // Heap profile options
...@@ -57,7 +62,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { ...@@ -57,7 +62,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
flagMeanDelay := flag.Bool("mean_delay", false, "Display mean delay at each region") 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") 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 // Flags used during command processing
installedFlags := installFlags(flag) installedFlags := installFlags(flag)
...@@ -106,6 +111,9 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { ...@@ -106,6 +111,9 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
if err != nil { if err != nil {
return nil, nil, err 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 := pprofVariables["sample_index"].value
si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI) si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI)
...@@ -122,12 +130,14 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { ...@@ -122,12 +130,14 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
} }
source := &source{ source := &source{
Sources: args, Sources: args,
ExecName: execName, ExecName: execName,
BuildID: *flagBuildID, BuildID: *flagBuildID,
Seconds: *flagSeconds, Seconds: *flagSeconds,
Timeout: *flagTimeout, Timeout: *flagTimeout,
Symbolize: *flagSymbolize, Symbolize: *flagSymbolize,
HTTPHostport: *flagHTTP,
Comment: *flagAddComment,
} }
for _, s := range *flagBase { for _, s := range *flagBase {
...@@ -136,6 +146,12 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { ...@@ -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 { if bu, ok := o.Obj.(*binutils.Binutils); ok {
bu.SetTools(*flagTools) bu.SetTools(*flagTools)
} }
...@@ -240,13 +256,33 @@ func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string, ...@@ -240,13 +256,33 @@ func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string,
return cmd, nil 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" + var usageMsgSrc = "\n\n" +
" Source options:\n" + " Source options:\n" +
" -seconds Duration for time-based profile collection\n" + " -seconds Duration for time-based profile collection\n" +
" -timeout Timeout in seconds for profile collection\n" + " -timeout Timeout in seconds for profile collection\n" +
" -buildid Override build id for main binary\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" + " -base source Source of profile to use as baseline\n" +
" profile.pb.gz Profile in compressed protobuf format\n" + " profile.pb.gz Profile in compressed protobuf format\n" +
" legacy_profile Profile in legacy pprof format\n" + " legacy_profile Profile in legacy pprof format\n" +
...@@ -261,7 +297,19 @@ var usageMsgSrc = "\n\n" + ...@@ -261,7 +297,19 @@ var usageMsgSrc = "\n\n" +
var usageMsgVars = "\n\n" + var usageMsgVars = "\n\n" +
" Misc options:\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" + "\n" +
" Environment Variables:\n" + " Environment Variables:\n" +
" PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" + " PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" +
......
...@@ -139,10 +139,10 @@ var pprofVariables = variables{ ...@@ -139,10 +139,10 @@ var pprofVariables = variables{
// Comparisons. // Comparisons.
"positive_percentages": &variable{boolKind, "f", "", helpText( "positive_percentages": &variable{boolKind, "f", "", helpText(
"Ignore negative samples when computing percentages", "Ignore negative samples when computing percentages",
" Do not count negative samples when computing the total value", "Do not count negative samples when computing the total value",
" of the profile, used to compute percentages. If set, and the -base", "of the profile, used to compute percentages. If set, and the -base",
" option is used, percentages reported will be computed against the", "option is used, percentages reported will be computed against the",
" main profile, ignoring the base profile.")}, "main profile, ignoring the base profile.")},
// Graph handling options. // Graph handling options.
"call_tree": &variable{boolKind, "f", "", helpText( "call_tree": &variable{boolKind, "f", "", helpText(
...@@ -157,9 +157,9 @@ var pprofVariables = variables{ ...@@ -157,9 +157,9 @@ var pprofVariables = variables{
"unit": &variable{stringKind, "minimum", "", helpText( "unit": &variable{stringKind, "minimum", "", helpText(
"Measurement units to display", "Measurement units to display",
"Scale the sample values to this unit.", "Scale the sample values to this unit.",
" For time-based profiles, use seconds, milliseconds, nanoseconds, etc.", "For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
" For memory profiles, use megabytes, kilobytes, bytes, etc.", "For memory profiles, use megabytes, kilobytes, bytes, etc.",
" auto will scale each value independently to the most natural unit.")}, "Using auto will scale each value independently to the most natural unit.")},
"compact_labels": &variable{boolKind, "f", "", "Show minimal headers"}, "compact_labels": &variable{boolKind, "f", "", "Show minimal headers"},
"source_path": &variable{stringKind, "", "", "Search path for source files"}, "source_path": &variable{stringKind, "", "", "Search path for source files"},
...@@ -195,11 +195,15 @@ var pprofVariables = variables{ ...@@ -195,11 +195,15 @@ var pprofVariables = variables{
"If set, only show nodes that match this location.", "If set, only show nodes that match this location.",
"Matching includes the function name, filename or object name.")}, "Matching includes the function name, filename or object name.")},
"tagfocus": &variable{stringKind, "", "", helpText( "tagfocus": &variable{stringKind, "", "", helpText(
"Restrict to samples with tags in range or matched by regexp", "Restricts to samples with tags in range or matched by regexp",
"Discard samples that do not 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")},
"tagignore": &variable{stringKind, "", "", helpText( "tagignore": &variable{stringKind, "", "", helpText(
"Discard samples with tags in range or matched by regexp", "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( "tagshow": &variable{stringKind, "", "", helpText(
"Only consider tags matching this regexp", "Only consider tags matching this regexp",
"Discard tags that do not match this regexp")}, "Discard tags that do not match this regexp")},
...@@ -218,6 +222,8 @@ var pprofVariables = variables{ ...@@ -218,6 +222,8 @@ var pprofVariables = variables{
"Sample value to report (0-based index or name)", "Sample value to report (0-based index or name)",
"Profiles contain multiple values per sample.", "Profiles contain multiple values per sample.",
"Use sample_index=i to select the ith value (starting at 0).")}, "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 // Data sorting criteria
"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")}, "flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")},
...@@ -227,9 +233,6 @@ var pprofVariables = variables{ ...@@ -227,9 +233,6 @@ var pprofVariables = variables{
"functions": &variable{boolKind, "t", "granularity", helpText( "functions": &variable{boolKind, "t", "granularity", helpText(
"Aggregate at the function level.", "Aggregate at the function level.",
"Takes into account the filename/lineno where the function was defined.")}, "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."}, "files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."},
"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."}, "lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
"addresses": &variable{boolKind, "f", "granularity", helpText( "addresses": &variable{boolKind, "f", "granularity", helpText(
...@@ -266,7 +269,7 @@ func usage(commandLine bool) string { ...@@ -266,7 +269,7 @@ func usage(commandLine bool) string {
var help string var help string
if commandLine { if commandLine {
help = " Output formats (select only one):\n" help = " Output formats (select at most one):\n"
} else { } else {
help = " Commands:\n" help = " Commands:\n"
commands = append(commands, fmtHelp("o/options", "List options and their current values")) commands = append(commands, fmtHelp("o/options", "List options and their current values"))
...@@ -471,7 +474,7 @@ func (vars variables) set(name, value string) error { ...@@ -471,7 +474,7 @@ func (vars variables) set(name, value string) error {
case boolKind: case boolKind:
var b bool var b bool
if b, err = stringToBool(value); err == nil { 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) err = fmt.Errorf("%q can only be set to true", name)
} }
} }
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/report" "github.com/google/pprof/internal/report"
...@@ -52,24 +53,30 @@ func PProf(eo *plugin.Options) error { ...@@ -52,24 +53,30 @@ func PProf(eo *plugin.Options) error {
return generateReport(p, cmd, pprofVariables, o) return generateReport(p, cmd, pprofVariables, o)
} }
if src.HTTPHostport != "" {
return serveWebInterface(src.HTTPHostport, p, o)
}
return interactive(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. 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) vars = applyCommandOverrides(cmd, vars)
// Delay focus after configuring report to get percentages on all samples. // Delay focus after configuring report to get percentages on all samples.
relative := vars["relative_percentages"].boolValue() relative := vars["relative_percentages"].boolValue()
if relative { if relative {
if err := applyFocus(p, vars, o.UI); err != nil { if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
return err return nil, nil, err
} }
} }
ropt, err := reportOptions(p, vars) ropt, err := reportOptions(p, numLabelUnits, vars)
if err != nil { if err != nil {
return err return nil, nil, err
} }
c := pprofCommands[cmd[0]] c := pprofCommands[cmd[0]]
if c == nil { if c == nil {
...@@ -79,18 +86,27 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin. ...@@ -79,18 +86,27 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.
if len(cmd) == 2 { if len(cmd) == 2 {
s, err := regexp.Compile(cmd[1]) s, err := regexp.Compile(cmd[1])
if err != nil { 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 ropt.Symbol = s
} }
rpt := report.New(p, ropt) rpt := report.New(p, ropt)
if !relative { if !relative {
if err := applyFocus(p, vars, o.UI); err != nil { if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
return err return nil, nil, err
} }
} }
if err := aggregate(p, vars); err != nil { 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 return err
} }
...@@ -160,20 +176,20 @@ func applyCommandOverrides(cmd []string, v variables) variables { ...@@ -160,20 +176,20 @@ func applyCommandOverrides(cmd []string, v variables) variables {
v.set("nodecount", "80") v.set("nodecount", "80")
} }
} }
if trim == false { if !trim {
v.set("nodecount", "0") v.set("nodecount", "0")
v.set("nodefraction", "0") v.set("nodefraction", "0")
v.set("edgefraction", "0") v.set("edgefraction", "0")
} }
if focus == false { if !focus {
v.set("focus", "") v.set("focus", "")
v.set("ignore", "") v.set("ignore", "")
} }
if tagfocus == false { if !tagfocus {
v.set("tagfocus", "") v.set("tagfocus", "")
v.set("tagignore", "") v.set("tagignore", "")
} }
if hide == false { if !hide {
v.set("hide", "") v.set("hide", "")
v.set("show", "") v.set("show", "")
} }
...@@ -196,25 +212,20 @@ func aggregate(prof *profile.Profile, v variables) error { ...@@ -196,25 +212,20 @@ func aggregate(prof *profile.Profile, v variables) error {
case v["functions"].boolValue(): case v["functions"].boolValue():
inlines = true inlines = true
function = true function = true
filename = true
case v["noinlines"].boolValue(): case v["noinlines"].boolValue():
function = true function = true
filename = true
case v["addressnoinlines"].boolValue(): case v["addressnoinlines"].boolValue():
function = true function = true
filename = true filename = true
linenumber = true linenumber = true
address = true address = true
case v["functionnameonly"].boolValue():
inlines = true
function = true
default: default:
return fmt.Errorf("unexpected granularity") return fmt.Errorf("unexpected granularity")
} }
return prof.Aggregate(inlines, function, filename, linenumber, address) 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() si, mean := vars["sample_index"].value, vars["mean"].boolValue()
value, meanDiv, sample, err := sampleFormat(p, si, mean) value, meanDiv, sample, err := sampleFormat(p, si, mean)
if err != nil { if err != nil {
...@@ -230,6 +241,14 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) ...@@ -230,6 +241,14 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
return nil, fmt.Errorf("zero divisor specified") 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{ ropt := &report.Options{
CumSort: vars["cum"].boolValue(), CumSort: vars["cum"].boolValue(),
CallTree: vars["call_tree"].boolValue(), CallTree: vars["call_tree"].boolValue(),
...@@ -243,6 +262,9 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) ...@@ -243,6 +262,9 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
NodeFraction: vars["nodefraction"].floatValue(), NodeFraction: vars["nodefraction"].floatValue(),
EdgeFraction: vars["edgefraction"].floatValue(), EdgeFraction: vars["edgefraction"].floatValue(),
ActiveFilters: filters,
NumLabelUnits: numLabelUnits,
SampleValue: value, SampleValue: value,
SampleMeanDivisor: meanDiv, SampleMeanDivisor: meanDiv,
SampleType: stype, SampleType: stype,
...@@ -260,6 +282,19 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) ...@@ -260,6 +282,19 @@ func reportOptions(p *profile.Profile, vars variables) (*report.Options, error)
return ropt, nil 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 type sampleValueFunc func([]int64) int64
// sampleFormat returns a function to extract values out of a profile.Sample, // sampleFormat returns a function to extract values out of a profile.Sample,
......
...@@ -28,13 +28,13 @@ import ( ...@@ -28,13 +28,13 @@ import (
var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)") var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)")
// applyFocus filters samples based on the focus/ignore options // 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) focus, err := compileRegexOption("focus", v["focus"].value, nil)
ignore, err := compileRegexOption("ignore", v["ignore"].value, err) ignore, err := compileRegexOption("ignore", v["ignore"].value, err)
hide, err := compileRegexOption("hide", v["hide"].value, err) hide, err := compileRegexOption("hide", v["hide"].value, err)
show, err := compileRegexOption("show", v["show"].value, err) show, err := compileRegexOption("show", v["show"].value, err)
tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, ui, err) tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err)
tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, ui, err) tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err)
prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err) prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err)
if err != nil { if err != nil {
return err return err
...@@ -59,7 +59,7 @@ func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error { ...@@ -59,7 +59,7 @@ func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error {
if prunefrom != nil { if prunefrom != nil {
prof.PruneFrom(prunefrom) prof.PruneFrom(prunefrom)
} }
return nil return err
} }
func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) { func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) {
...@@ -73,23 +73,49 @@ 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 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 { if value == "" || err != nil {
return nil, err 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 { if numFilter := parseTagFilterRange(value); numFilter != nil {
ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp") ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp")
return func(s *profile.Sample) bool { labelFilter := func(vals []int64, unit string) bool {
for key, vals := range s.NumLabel { for _, val := range vals {
for _, val := range vals { if numFilter(val, unit) {
if numFilter(val, key) { 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 true
} }
} }
return false
}, nil
}
return func(s *profile.Sample) bool {
if vals, ok := s.NumLabel[wantKey]; ok {
return labelFilter(vals, numLabelUnit(wantKey))
} }
return false return false
}, nil }, nil
} }
var rfx []*regexp.Regexp var rfx []*regexp.Regexp
for _, tagf := range strings.Split(value, ",") { for _, tagf := range strings.Split(value, ",") {
fx, err := regexp.Compile(tagf) fx, err := regexp.Compile(tagf)
...@@ -98,19 +124,34 @@ func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profil ...@@ -98,19 +124,34 @@ func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profil
} }
rfx = append(rfx, fx) 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 { return func(s *profile.Sample) bool {
matchedrx: if vals, ok := s.Label[wantKey]; ok {
for _, rx := range rfx { for _, rx := range rfx {
for key, vals := range s.Label {
for _, val := range vals { for _, val := range vals {
if rx.MatchString(key + ":" + val) { if rx.MatchString(val) {
continue matchedrx return true
} }
} }
} }
return false
} }
return true return false
}, nil }, nil
} }
......
...@@ -41,39 +41,52 @@ import ( ...@@ -41,39 +41,52 @@ import (
// there are some failures. It will return an error if it is unable to // there are some failures. It will return an error if it is unable to
// fetch any profiles. // fetch any profiles.
func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) { 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 { for _, src := range s.Sources {
sources = append(sources, profileSource{ sources = append(sources, profileSource{
addr: src, addr: src,
source: s, source: s,
scale: 1,
}) })
} }
bases := make([]profileSource, 0, len(s.Base))
for _, src := range s.Base { for _, src := range s.Base {
sources = append(sources, profileSource{ bases = append(bases, profileSource{
addr: src, addr: src,
source: s, 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 { if err != nil {
return nil, err return nil, err
} }
if cnt == 0 {
return nil, fmt.Errorf("failed to fetch any profiles") if pbase != nil {
} if s.Normalize {
if want, got := len(sources), cnt; want != got { err := p.Normalize(pbase)
o.UI.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", got, want)) 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. // 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 return nil, err
} }
p.RemoveUninteresting() p.RemoveUninteresting()
unsourceMappings(p) 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. // Save a copy of the merged profile if there is at least one remote source.
if save { if save {
dir, err := setTmpDir(o.UI) dir, err := setTmpDir(o.UI)
...@@ -107,6 +120,47 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) { ...@@ -107,6 +120,47 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
return p, nil 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 // chunkedGrab fetches the profiles described in source and merges them into
// a single profile. It fetches a chunk of profiles concurrently, with a maximum // a single profile. It fetches a chunk of profiles concurrently, with a maximum
// chunk size to limit its memory usage. // chunk size to limit its memory usage.
...@@ -142,6 +196,7 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo ...@@ -142,6 +196,7 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo
count += chunkCount count += chunkCount
} }
} }
return p, msrc, save, count, nil return p, msrc, save, count, nil
} }
...@@ -152,7 +207,7 @@ func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.Ob ...@@ -152,7 +207,7 @@ func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.Ob
for i := range sources { for i := range sources {
go func(s *profileSource) { go func(s *profileSource) {
defer wg.Done() 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]) }(&sources[i])
} }
wg.Wait() wg.Wait()
...@@ -207,7 +262,6 @@ func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) ...@@ -207,7 +262,6 @@ func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources)
type profileSource struct { type profileSource struct {
addr string addr string
source *source source *source
scale float64
p *profile.Profile p *profile.Profile
msrc plugin.MappingSources msrc plugin.MappingSources
...@@ -227,12 +281,18 @@ func homeEnv() string { ...@@ -227,12 +281,18 @@ func homeEnv() string {
} }
// setTmpDir prepares the directory to use to save profiles retrieved // 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) { func setTmpDir(ui plugin.UI) (string, error) {
var dirs []string
if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" { 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 { if err := os.MkdirAll(tmpDir, 0755); err != nil {
ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error()) ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
continue continue
...@@ -242,10 +302,12 @@ func setTmpDir(ui plugin.UI) (string, error) { ...@@ -242,10 +302,12 @@ func setTmpDir(ui plugin.UI) (string, error) {
return "", fmt.Errorf("failed to identify temp dir") return "", fmt.Errorf("failed to identify temp dir")
} }
const testSourceAddress = "pproftest.local"
// grabProfile fetches a profile. Returns the profile, sources for the // grabProfile fetches a profile. Returns the profile, sources for the
// profile mappings, a bool indicating if the profile was fetched // profile mappings, a bool indicating if the profile was fetched
// remotely, and an error. // 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 var src string
duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
if fetcher != nil { if fetcher != nil {
...@@ -266,9 +328,6 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher ...@@ -266,9 +328,6 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher
return return
} }
// Apply local changes to the profile.
p.Scale(scale)
// Update the binary locations from command line and paths. // Update the binary locations from command line and paths.
locateBinaries(p, s, obj, ui) locateBinaries(p, s, obj, ui)
...@@ -276,6 +335,11 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher ...@@ -276,6 +335,11 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher
if src != "" { if src != "" {
msrc = collectMappingSources(p, src) msrc = collectMappingSources(p, src)
remote = true remote = true
if strings.HasPrefix(src, "http://"+testSourceAddress) {
// Treat test inputs as local to avoid saving
// testcase profiles during driver testing.
remote = false
}
} }
return return
} }
...@@ -366,20 +430,20 @@ mapping: ...@@ -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. // Replace executable filename/buildID with the overrides from source.
// Assumes the executable is the first Mapping entry. // Assumes the executable is the first Mapping entry.
if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" { 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] m := p.Mapping[0]
if execName != "" { if execName != "" {
m.File = execName m.File = execName
......
...@@ -15,8 +15,15 @@ ...@@ -15,8 +15,15 @@
package driver package driver
import ( import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
...@@ -24,11 +31,14 @@ import ( ...@@ -24,11 +31,14 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
"strings"
"testing" "testing"
"time" "time"
"github.com/google/pprof/internal/binutils"
"github.com/google/pprof/internal/plugin" "github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/proftest" "github.com/google/pprof/internal/proftest"
"github.com/google/pprof/internal/symbolizer"
"github.com/google/pprof/profile" "github.com/google/pprof/profile"
) )
...@@ -165,6 +175,8 @@ func TestFetch(t *testing.T) { ...@@ -165,6 +175,8 @@ func TestFetch(t *testing.T) {
const path = "testdata/" const path = "testdata/"
// Intercept http.Get calls from HTTPFetcher. // Intercept http.Get calls from HTTPFetcher.
savedHTTPGet := httpGet
defer func() { httpGet = savedHTTPGet }()
httpGet = stubHTTPGet httpGet = stubHTTPGet
type testcase struct { type testcase struct {
...@@ -176,7 +188,7 @@ func TestFetch(t *testing.T) { ...@@ -176,7 +188,7 @@ func TestFetch(t *testing.T) {
{path + "go.nomappings.crash", "/bin/gotest.exe"}, {path + "go.nomappings.crash", "/bin/gotest.exe"},
{"http://localhost/profile?file=cppbench.cpu", ""}, {"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 { if err != nil {
t.Fatalf("%s: %s", tc.source, err) t.Fatalf("%s: %s", tc.source, err)
} }
...@@ -194,6 +206,117 @@ func TestFetch(t *testing.T) { ...@@ -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. // mappingSources creates MappingSources map with a single item.
func mappingSources(key, source string, start uint64) plugin.MappingSources { func mappingSources(key, source string, start uint64) plugin.MappingSources {
return plugin.MappingSources{ return plugin.MappingSources{
...@@ -227,3 +350,123 @@ func stubHTTPGet(source string, _ time.Duration) (*http.Response, error) { ...@@ -227,3 +350,123 @@ func stubHTTPGet(source string, _ time.Duration) (*http.Response, error) {
c := &http.Client{Transport: t} c := &http.Client{Transport: t}
return c.Get("file:///" + file) 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. ...@@ -123,7 +123,8 @@ var generateReportWrapper = generateReport // For testing purposes.
// greetings prints a brief welcome and some overall profile // greetings prints a brief welcome and some overall profile
// information before accepting interactive commands. // information before accepting interactive commands.
func greetings(p *profile.Profile, ui plugin.UI) { 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 { if err == nil {
ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n")) 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" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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"] 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%)" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"] 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%)" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"] 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 -> 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)"] 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)"] 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" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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%)" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"] 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%)" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (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%)" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (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)"] 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)"] 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 Showing nodes accounting for 1.11s, 99.11% of 1.12s total
flat flat% sum% cum cum% flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1 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 Showing nodes accounting for 1.11s, 99.11% of 1.12s total
flat flat% sum% cum cum% flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1 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 Showing nodes accounting for 1s, 100% of 1s total
flat flat% sum% cum cum% flat flat% sum% cum cum%
1s 100% 100% 1s 100% mangled1000 testdata/file1000.src:1 1s 100% 100% 1s 100% mangled1000 testdata/file1000.src:1
...@@ -2,9 +2,9 @@ Total: 1.12s ...@@ -2,9 +2,9 @@ Total: 1.12s
ROUTINE ======================== line1000 ROUTINE ======================== line1000
1.10s 1.10s (flat, cum) 98.21% of Total 1.10s 1.10s (flat, cum) 98.21% of Total
1.10s 1.10s 1000: instruction one ;line1000 file1000.src:1 1.10s 1.10s 1000: instruction one ;line1000 file1000.src:1
. . 1001: instruction two . . 1001: instruction two ;file1000.src:1
. . 1002: instruction three . . 1002: instruction three ;file1000.src:2
. . 1003: instruction four . . 1003: instruction four ;file1000.src:1
ROUTINE ======================== line3000 ROUTINE ======================== line3000
10ms 1.12s (flat, cum) 100% of Total 10ms 1.12s (flat, cum) 100% of Total
10ms 1.01s 3000: instruction one ;line3000 file3000.src:6 10ms 1.01s 3000: instruction one ;line3000 file3000.src:6
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8">
<title>Pprof listing</title> <title>Pprof listing</title>
<style type="text/css"> <style type="text/css">
body { body {
...@@ -14,17 +15,11 @@ h1 { ...@@ -14,17 +15,11 @@ h1 {
.legend { .legend {
font-size: 1.25em; font-size: 1.25em;
} }
.line { .line, .nop, .unimportant {
color: #aaaaaa; color: #aaaaaa;
} }
.nop { .inlinesrc {
color: #aaaaaa; color: #000066;
}
.unimportant {
color: #cccccc;
}
.disasmloc {
color: #000000;
} }
.deadsrc { .deadsrc {
cursor: pointer; cursor: pointer;
...@@ -69,39 +64,41 @@ Type: cpu<br> ...@@ -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 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)"> <pre onClick="pprof_toggle_asm(event)">
Total: 1.10s 1.10s (flat, cum) 98.21% 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> <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=disasmloc></span> . . 1001: instruction two <span class=unimportant>file1000.src:1</span>
. . 1002: instruction three <span class=disasmloc></span>
. . 1003: instruction four <span class=disasmloc></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>
<span class=line> 2</span> <span class=nop> . . line2 </span> <span class=line> 3</span> <span class=nop> . . line3 </span>
<span class=line> 3</span> <span class=nop> . . line3 </span> <span class=line> 4</span> <span class=nop> . . line4 </span>
<span class=line> 4</span> <span class=nop> . . line4 </span> <span class=line> 5</span> <span class=nop> . . line5 </span>
<span class=line> 5</span> <span class=nop> . . line5 </span> <span class=line> 6</span> <span class=nop> . . line6 </span>
<span class=line> 6</span> <span class=nop> . . line6 </span> <span class=line> 7</span> <span class=nop> . . line7 </span>
</pre> </pre>
<h1>line3000</h1>testdata/file3000.src <h1>line3000</h1>testdata/file3000.src
<pre onClick="pprof_toggle_asm(event)"> <pre onClick="pprof_toggle_asm(event)">
Total: 10ms 1.12s (flat, cum) 100% Total: 10ms 1.12s (flat, cum) 100%
<span class=line> 1</span> <span class=nop> . . line1 </span> <span class=line> 1</span> <span class=nop> . . line1 </span>
<span class=line> 2</span> <span class=nop> . . line2 </span> <span class=line> 2</span> <span class=nop> . . line2 </span>
<span class=line> 3</span> <span class=nop> . . line3 </span> <span class=line> 3</span> <span class=nop> . . line3 </span>
<span class=line> 4</span> <span class=nop> . . line4 </span> <span class=line> 4</span> <span class=nop> . . line4 </span>
<span class=line> 5</span> <span class=nop> . . line5 </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> 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>
<span class=line> 7</span> <span class=nop> . . line7 </span> <span class=line> 7</span> <span class=nop> . . line7 </span>
<span class=line> 8</span> <span class=nop> . . line8 </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> <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=disasmloc>file3000.src:9</span> . 10ms 3002: instruction three <span class=unimportant>file3000.src:9</span>
. . 3003: instruction four <span class=disasmloc></span> . . 3003: instruction four <span class=unimportant></span>
. . 3004: instruction five <span class=disasmloc></span> . . 3004: instruction five <span class=unimportant></span>
</span> </span>
<span class=line> 10</span> <span class=nop> . . line0 </span> <span class=line> 10</span> <span class=nop> . . line0 </span>
<span class=line> 11</span> <span class=nop> . . line1 </span> <span class=line> 11</span> <span class=nop> . . line1 </span>
<span class=line> 12</span> <span class=nop> . . line2 </span> <span class=line> 12</span> <span class=nop> . . line2 </span>
<span class=line> 13</span> <span class=nop> . . line3 </span> <span class=line> 13</span> <span class=nop> . . line3 </span>
<span class=line> 14</span> <span class=nop> . . line4 </span> <span class=line> 14</span> <span class=nop> . . line4 </span>
</pre> </pre>
</body> </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" { digraph "testbinary" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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\nfile1000.src\n1.10s (98.21%)" fontsize=24 shape=box tooltip="line1000 testdata/file1000.src (1.10s)" color="#b20000" fillcolor="#edd5d5"] 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" fontsize=8 shape=box3d tooltip="1s"] 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 -> 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"] 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"] 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\nfile3000.src\n0 of 1.11s (99.11%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (1.11s)" 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\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"] 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\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"] 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\nfile2000.src\n0 of 1.01s (90.18%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (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 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)"] 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 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)"] 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 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (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 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (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 testdata/file2000.src -> line1000 testdata/file1000.src (1s)" labeltooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)"] 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 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)"] 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 Showing nodes accounting for 1.12s, 100% of 1.12s total
flat flat% sum% cum cum% flat flat% sum% cum cum%
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src 1.10s 98.21% 98.21% 1.10s 98.21% line1000
0.01s 0.89% 99.11% 1.01s 90.18% line2001 testdata/file2000.src (inline) 0.01s 0.89% 99.11% 1.01s 90.18% line2001 (inline)
0.01s 0.89% 100% 1.02s 91.07% line3002 testdata/file3000.src (inline) 0.01s 0.89% 100% 1.02s 91.07% line3002 (inline)
0 0% 100% 1.01s 90.18% line2000 testdata/file2000.src 0 0% 100% 1.01s 90.18% line2000
0 0% 100% 1.12s 100% line3000 testdata/file3000.src 0 0% 100% 1.12s 100% line3000
0 0% 100% 1.11s 99.11% line3001 testdata/file3000.src (inline) 0 0% 100% 1.11s 99.11% line3001 (inline)
...@@ -2,12 +2,12 @@ Showing nodes accounting for 1.12s, 100% of 1.12s total ...@@ -2,12 +2,12 @@ Showing nodes accounting for 1.12s, 100% of 1.12s total
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
flat flat% sum% cum cum% calls calls% + context flat flat% sum% cum cum% calls calls% + context
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
1.01s 100% | line2000 testdata/file2000.src (inline) 1.01s 100% | line2000 (inline)
0.01s 0.89% 0.89% 1.01s 90.18% | line2001 testdata/file2000.src 0.01s 0.89% 0.89% 1.01s 90.18% | line2001
1s 99.01% | line1000 testdata/file1000.src 1s 99.01% | line1000
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
1.11s 100% | line3000 testdata/file3000.src (inline) 1.11s 100% | line3000 (inline)
0 0% 0.89% 1.11s 99.11% | line3001 testdata/file3000.src 0 0% 0.89% 1.11s 99.11% | line3001
1.01s 90.99% | line3002 testdata/file3000.src (inline) 1.01s 90.99% | line3002 (inline)
0.10s 9.01% | line1000 testdata/file1000.src 0.10s 9.01% | line1000
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
key1: Total 1120 key1: Total 1.1s
1000 (89.29%): tag1 1.0s (89.29%): tag1
100 ( 8.93%): tag2 100.0ms ( 8.93%): tag2
10 ( 0.89%): tag3 10.0ms ( 0.89%): tag3
10 ( 0.89%): tag4 10.0ms ( 0.89%): tag4
key2: Total 1020 key2: Total 1.0s
1010 (99.02%): tag1 1.0s (99.02%): tag1
10 ( 0.98%): tag2 10.0ms ( 0.98%): tag2
key3: Total 100 key3: Total 100.0ms
100 ( 100%): tag2 100.0ms ( 100%): tag2
key1: Total 100 key1: Total 100.0ms
100 ( 100%): tag2 100.0ms ( 100%): tag2
key3: Total 100 key3: Total 100.0ms
100 ( 100%): tag2 100.0ms ( 100%): tag2
...@@ -4,29 +4,29 @@ Duration: 10s, Total samples = 1.12s (11.20%) ...@@ -4,29 +4,29 @@ Duration: 10s, Total samples = 1.12s (11.20%)
-----------+------------------------------------------------------- -----------+-------------------------------------------------------
key1: tag1 key1: tag1
key2: tag1 key2: tag1
1s line1000 testdata/file1000.src 1s line1000
line2001 testdata/file2000.src line2001
line2000 testdata/file2000.src line2000
line3002 testdata/file3000.src line3002
line3001 testdata/file3000.src line3001
line3000 testdata/file3000.src line3000
-----------+------------------------------------------------------- -----------+-------------------------------------------------------
key1: tag2 key1: tag2
key3: tag2 key3: tag2
100ms line1000 testdata/file1000.src 100ms line1000
line3001 testdata/file3000.src line3001
line3000 testdata/file3000.src line3000
-----------+------------------------------------------------------- -----------+-------------------------------------------------------
key1: tag3 key1: tag3
key2: tag2 key2: tag2
10ms line2001 testdata/file2000.src 10ms line2001
line2000 testdata/file2000.src line2000
line3002 testdata/file3000.src line3002
line3000 testdata/file3000.src line3000
-----------+------------------------------------------------------- -----------+-------------------------------------------------------
key1: tag4 key1: tag4
key2: tag1 key2: tag1
10ms line3002 testdata/file3000.src 10ms line3002
line3001 testdata/file3000.src line3001
line3000 testdata/file3000.src line3000
-----------+------------------------------------------------------- -----------+-------------------------------------------------------
Active filters:
focus=[24]00
Showing nodes accounting for 62.50MB, 63.37% of 98.63MB total Showing nodes accounting for 62.50MB, 63.37% of 98.63MB total
Dropped 2 nodes (cum <= 4.93MB) Dropped 2 nodes (cum <= 4.93MB)
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
......
Active filters:
focus=[24]00
Showing nodes accounting for 62.50MB, 98.46% of 63.48MB total Showing nodes accounting for 62.50MB, 98.46% of 63.48MB total
Dropped 2 nodes (cum <= 3.17MB) Dropped 2 nodes (cum <= 3.17MB)
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
flat flat% sum% cum cum% calls calls% + context flat flat% sum% cum cum% calls calls% + context
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
63.48MB 100% | line3002 testdata/file3000.src 63.48MB 100% | line3002
0 0% 0% 63.48MB 100% | line2000 testdata/file2000.src 0 0% 0% 63.48MB 100% | line2000
63.48MB 100% | line2001 testdata/file2000.src (inline) 63.48MB 100% | line2001 (inline)
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
63.48MB 100% | line2000 testdata/file2000.src (inline) 63.48MB 100% | line2000 (inline)
62.50MB 98.46% 98.46% 63.48MB 100% | line2001 testdata/file2000.src 62.50MB 98.46% 98.46% 63.48MB 100% | line2001
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
0 0% 98.46% 63.48MB 100% | line3000 testdata/file3000.src 0 0% 98.46% 63.48MB 100% | line3000
63.48MB 100% | line3002 testdata/file3000.src (inline) 63.48MB 100% | line3002 (inline)
----------------------------------------------------------+------------- ----------------------------------------------------------+-------------
63.48MB 100% | line3000 testdata/file3000.src (inline) 63.48MB 100% | line3000 (inline)
0 0% 98.46% 63.48MB 100% | line3002 testdata/file3000.src 0 0% 98.46% 63.48MB 100% | line3002
63.48MB 100% | line2000 testdata/file2000.src 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 Showing nodes accounting for 150, 100% of 150 total
flat flat% sum% cum cum% flat flat% sum% cum cum%
80 53.33% 53.33% 130 86.67% line3002 testdata/file3000.src (inline) 80 53.33% 53.33% 130 86.67% line3002 (inline)
40 26.67% 80.00% 50 33.33% line2001 testdata/file2000.src (inline) 40 26.67% 80.00% 50 33.33% line2001 (inline)
30 20.00% 100% 30 20.00% line1000 testdata/file1000.src 30 20.00% 100% 30 20.00% line1000
0 0% 100% 50 33.33% line2000 testdata/file2000.src 0 0% 100% 50 33.33% line2000
0 0% 100% 150 100% line3000 testdata/file3000.src 0 0% 100% 150 100% line3000
0 0% 100% 110 73.33% line3001 testdata/file3000.src (inline) 0 0% 100% 110 73.33% line3001 (inline)
digraph "unnamed" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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\nfile2000.src\n62.50MB (63.37%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"] 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" fontsize=8 shape=box3d tooltip="62.50MB"] 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"] 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"] 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\nfile2000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (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\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src (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 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)"] 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 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (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 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (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" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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\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"] 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" fontsize=8 shape=box3d tooltip="31.25MB"] 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"] 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"] 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\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (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\nfile1000.src\n4.88MB (4.95%)" fontsize=15 shape=box tooltip="line1000 testdata/file1000.src (4.88MB)" color="#b2a086" fillcolor="#edeae7"] 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" fontsize=8 shape=box3d tooltip="3.91MB"] 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"] 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)"] 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 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"] 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 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)"] 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 testdata/file3000.src ... line1000 testdata/file1000.src (0.98MB)" labeltooltip="line3002 testdata/file3000.src ... line1000 testdata/file1000.src (0.98MB)" style="dotted" minlen=2] N1 -> N4 [label=" 0.98MB" color="#b2b0a9" tooltip="line3002 ... line1000 (0.98MB)" labeltooltip="line3002 ... line1000 (0.98MB)" style="dotted" minlen=2]
} }
digraph "unnamed" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"] 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%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] 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" fontsize=8 shape=box3d tooltip="62.50MB"] 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"] 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"] 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" fontsize=8 shape=box3d tooltip="3.91MB"] 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"] 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"] 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%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"] 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%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"] 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)"] 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)"] 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)"] 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 bytes: Total 98.6MB
80 (53.33%): 400kB 62.5MB (63.37%): 1.56MB
40 (26.67%): 1.56MB 31.2MB (31.68%): 400kB
20 (13.33%): 200kB 3.9MB ( 3.96%): 200kB
10 ( 6.67%): 100kB 1000.0kB ( 0.99%): 100kB
bytes: Total 150 bytes: Total 103424000.0B
80 (53.33%): 409600B 65536000.0B (63.37%): 1638400B
40 (26.67%): 1638400B 32768000.0B (31.68%): 409600B
20 (13.33%): 204800B 4096000.0B ( 3.96%): 204800B
10 ( 6.67%): 102400B 1024000.0B ( 0.99%): 102400B
Showing nodes accounting for 150, 100% of 150 total Showing nodes accounting for 150, 100% of 150 total
flat flat% sum% cum cum% flat flat% sum% cum cum%
80 53.33% 53.33% 130 86.67% line3002 testdata/file3000.src (inline) 80 53.33% 53.33% 130 86.67% line3002 (inline)
40 26.67% 80.00% 50 33.33% line2001 testdata/file2000.src (inline) 40 26.67% 80.00% 50 33.33% line2001 (inline)
30 20.00% 100% 30 20.00% line1000 testdata/file1000.src 30 20.00% 100% 30 20.00% line1000
0 0% 100% 50 33.33% line2000 testdata/file2000.src 0 0% 100% 50 33.33% line2000
0 0% 100% 150 100% line3000 testdata/file3000.src 0 0% 100% 150 100% line3000
0 0% 100% 110 73.33% line3001 testdata/file3000.src (inline) 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" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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\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"] 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" fontsize=8 shape=box3d tooltip="31.25MB"] 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"] 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"] 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\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"] 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" fontsize=8 shape=box3d tooltip="62.50MB"] 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"] 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"] 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\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"] 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 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)"] 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 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" minlen=2] 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 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"] 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 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"] 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 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"] 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" { digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"] 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"] } 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\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"] 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" fontsize=8 shape=box3d tooltip="62.50MB"] 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"] 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"] 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" fontsize=8 shape=box3d tooltip="31.25MB"] 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"] 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) { ...@@ -131,7 +131,7 @@ func GetBuildID(binary io.ReaderAt) ([]byte, error) {
if buildID == nil { if buildID == nil {
buildID = note.Desc buildID = note.Desc
} else { } 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 ...@@ -240,17 +240,22 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
} }
return start, nil return start, nil
case elf.ET_DYN: case elf.ET_DYN:
if offset != 0 { // The process mapping information, start = start of virtual address range,
if loadSegment == nil || loadSegment.Vaddr == 0 { // and offset = offset in the executable file of the start address, tells us
return start - offset, nil // that a runtime virtual address x maps to a file offset
} // fx = x - start + offset.
return 0, fmt.Errorf("Don't know how to handle mapping. Offset=%x, vaddr=%x",
offset, loadSegment.Vaddr)
}
if loadSegment == nil { 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) return 0, fmt.Errorf("Don't know how to handle FileHeader.Type %v", fh.Type)
} }
...@@ -62,8 +62,9 @@ func TestGetBase(t *testing.T) { ...@@ -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 4", fhExec, kernelHeader, uint64p(0xffffffff81200198), 0x198, 0x100000, 0, 0x7ee00000, false},
{"exec chromeos kernel unremapped", fhExec, kernelHeader, uint64p(0xffffffff810001c8), 0xffffffff834001c8, 0xffffffffc0000000, 0xffffffff834001c8, 0x2400000, false}, {"exec chromeos kernel unremapped", fhExec, kernelHeader, uint64p(0xffffffff810001c8), 0xffffffff834001c8, 0xffffffffc0000000, 0xffffffff834001c8, 0x2400000, false},
{"dyn", fhDyn, nil, nil, 0x200000, 0x300000, 0, 0x200000, 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 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", fhRel, nil, nil, 0x2000000, 0x3000000, 0, 0x2000000, false},
{"rel nomap", fhRel, nil, nil, 0x0, ^uint64(0), 0, 0, false}, {"rel nomap", fhRel, nil, nil, 0x0, ^uint64(0), 0, 0, false},
{"rel offset", fhRel, nil, nil, 0x100000, 0x200000, 0x1, 0, true}, {"rel offset", fhRel, nil, nil, 0x100000, 0x200000, 0x1, 0, true},
......
...@@ -171,7 +171,7 @@ func createExpectedEdges(parent expectedNode, children ...expectedNode) { ...@@ -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 // 0
// |(5) // |(5)
// 1 // 1
...@@ -255,7 +255,7 @@ func createTestCase2() trimTreeTestcase { ...@@ -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. // after trimming.
func createTestCase3() trimTreeTestcase { func createTestCase3() trimTreeTestcase {
graph := &Graph{make(Nodes, 0)} graph := &Graph{make(Nodes, 0)}
......
digraph "testtitle" { digraph "testtitle" {
node [style=filled fillcolor="#f8f8f8"] 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