Bump google.golang.org/api
This commit is contained in:
358
vendor/github.com/google/pprof/internal/driver/cli.go
generated
vendored
Normal file
358
vendor/github.com/google/pprof/internal/driver/cli.go
generated
vendored
Normal file
@@ -0,0 +1,358 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/internal/binutils"
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
)
|
||||
|
||||
type source struct {
|
||||
Sources []string
|
||||
ExecName string
|
||||
BuildID string
|
||||
Base []string
|
||||
DiffBase bool
|
||||
Normalize bool
|
||||
|
||||
Seconds int
|
||||
Timeout int
|
||||
Symbolize string
|
||||
HTTPHostport string
|
||||
HTTPDisableBrowser bool
|
||||
Comment string
|
||||
}
|
||||
|
||||
// parseFlags parses the command lines through the specified flags package
|
||||
// and returns the source of the profile and optionally the command
|
||||
// for the kind of report to generate (nil for interactive use).
|
||||
func parseFlags(o *plugin.Options) (*source, []string, error) {
|
||||
flag := o.Flagset
|
||||
// Comparisons.
|
||||
flagDiffBase := flag.StringList("diff_base", "", "Source of base profile for comparison")
|
||||
flagBase := flag.StringList("base", "", "Source of base profile for profile subtraction")
|
||||
// Source options.
|
||||
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
|
||||
flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
|
||||
flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile")
|
||||
flagAddComment := flag.String("add_comment", "", "Annotation string to record in the profile")
|
||||
// CPU profile options
|
||||
flagSeconds := flag.Int("seconds", -1, "Length of time for dynamic profiles")
|
||||
// Heap profile options
|
||||
flagInUseSpace := flag.Bool("inuse_space", false, "Display in-use memory size")
|
||||
flagInUseObjects := flag.Bool("inuse_objects", false, "Display in-use object counts")
|
||||
flagAllocSpace := flag.Bool("alloc_space", false, "Display allocated memory size")
|
||||
flagAllocObjects := flag.Bool("alloc_objects", false, "Display allocated object counts")
|
||||
// Contention profile options
|
||||
flagTotalDelay := flag.Bool("total_delay", false, "Display total delay at each region")
|
||||
flagContentions := flag.Bool("contentions", false, "Display number of delays 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")
|
||||
|
||||
flagHTTP := flag.String("http", "", "Present interactive web UI at the specified http host:port")
|
||||
flagNoBrowser := flag.Bool("no_browser", false, "Skip opening a browswer for the interactive web UI")
|
||||
|
||||
// Flags used during command processing
|
||||
installedFlags := installFlags(flag)
|
||||
|
||||
flagCommands := make(map[string]*bool)
|
||||
flagParamCommands := make(map[string]*string)
|
||||
for name, cmd := range pprofCommands {
|
||||
if cmd.hasParam {
|
||||
flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp")
|
||||
} else {
|
||||
flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format")
|
||||
}
|
||||
}
|
||||
|
||||
args := flag.Parse(func() {
|
||||
o.UI.Print(usageMsgHdr +
|
||||
usage(true) +
|
||||
usageMsgSrc +
|
||||
flag.ExtraUsage() +
|
||||
usageMsgVars)
|
||||
})
|
||||
if len(args) == 0 {
|
||||
return nil, nil, errors.New("no profile source specified")
|
||||
}
|
||||
|
||||
var execName string
|
||||
// Recognize first argument as an executable or buildid override.
|
||||
if len(args) > 1 {
|
||||
arg0 := args[0]
|
||||
if file, err := o.Obj.Open(arg0, 0, ^uint64(0), 0); err == nil {
|
||||
file.Close()
|
||||
execName = arg0
|
||||
args = args[1:]
|
||||
} else if *flagBuildID == "" && isBuildID(arg0) {
|
||||
*flagBuildID = arg0
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Report conflicting options
|
||||
if err := updateFlags(installedFlags); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cmd, err := outputFormat(flagCommands, flagParamCommands)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if cmd != nil && *flagHTTP != "" {
|
||||
return nil, nil, errors.New("-http is not compatible with an output format on the command line")
|
||||
}
|
||||
|
||||
if *flagNoBrowser && *flagHTTP == "" {
|
||||
return nil, nil, errors.New("-no_browser only makes sense with -http")
|
||||
}
|
||||
|
||||
si := pprofVariables["sample_index"].value
|
||||
si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI)
|
||||
si = sampleIndex(flagMeanDelay, si, "delay", "-mean_delay", o.UI)
|
||||
si = sampleIndex(flagContentions, si, "contentions", "-contentions", o.UI)
|
||||
si = sampleIndex(flagInUseSpace, si, "inuse_space", "-inuse_space", o.UI)
|
||||
si = sampleIndex(flagInUseObjects, si, "inuse_objects", "-inuse_objects", o.UI)
|
||||
si = sampleIndex(flagAllocSpace, si, "alloc_space", "-alloc_space", o.UI)
|
||||
si = sampleIndex(flagAllocObjects, si, "alloc_objects", "-alloc_objects", o.UI)
|
||||
pprofVariables.set("sample_index", si)
|
||||
|
||||
if *flagMeanDelay {
|
||||
pprofVariables.set("mean", "true")
|
||||
}
|
||||
|
||||
source := &source{
|
||||
Sources: args,
|
||||
ExecName: execName,
|
||||
BuildID: *flagBuildID,
|
||||
Seconds: *flagSeconds,
|
||||
Timeout: *flagTimeout,
|
||||
Symbolize: *flagSymbolize,
|
||||
HTTPHostport: *flagHTTP,
|
||||
HTTPDisableBrowser: *flagNoBrowser,
|
||||
Comment: *flagAddComment,
|
||||
}
|
||||
|
||||
if err := source.addBaseProfiles(*flagBase, *flagDiffBase); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
normalize := pprofVariables["normalize"].boolValue()
|
||||
if normalize && len(source.Base) == 0 {
|
||||
return nil, nil, errors.New("must have base profile to normalize by")
|
||||
}
|
||||
source.Normalize = normalize
|
||||
|
||||
if bu, ok := o.Obj.(*binutils.Binutils); ok {
|
||||
bu.SetTools(*flagTools)
|
||||
}
|
||||
return source, cmd, nil
|
||||
}
|
||||
|
||||
// addBaseProfiles adds the list of base profiles or diff base profiles to
|
||||
// the source. This function will return an error if both base and diff base
|
||||
// profiles are specified.
|
||||
func (source *source) addBaseProfiles(flagBase, flagDiffBase []*string) error {
|
||||
base, diffBase := dropEmpty(flagBase), dropEmpty(flagDiffBase)
|
||||
if len(base) > 0 && len(diffBase) > 0 {
|
||||
return errors.New("-base and -diff_base flags cannot both be specified")
|
||||
}
|
||||
|
||||
source.Base = base
|
||||
if len(diffBase) > 0 {
|
||||
source.Base, source.DiffBase = diffBase, true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dropEmpty list takes a slice of string pointers, and outputs a slice of
|
||||
// non-empty strings associated with the flag.
|
||||
func dropEmpty(list []*string) []string {
|
||||
var l []string
|
||||
for _, s := range list {
|
||||
if *s != "" {
|
||||
l = append(l, *s)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// installFlags creates command line flags for pprof variables.
|
||||
func installFlags(flag plugin.FlagSet) flagsInstalled {
|
||||
f := flagsInstalled{
|
||||
ints: make(map[string]*int),
|
||||
bools: make(map[string]*bool),
|
||||
floats: make(map[string]*float64),
|
||||
strings: make(map[string]*string),
|
||||
}
|
||||
for n, v := range pprofVariables {
|
||||
switch v.kind {
|
||||
case boolKind:
|
||||
if v.group != "" {
|
||||
// Set all radio variables to false to identify conflicts.
|
||||
f.bools[n] = flag.Bool(n, false, v.help)
|
||||
} else {
|
||||
f.bools[n] = flag.Bool(n, v.boolValue(), v.help)
|
||||
}
|
||||
case intKind:
|
||||
f.ints[n] = flag.Int(n, v.intValue(), v.help)
|
||||
case floatKind:
|
||||
f.floats[n] = flag.Float64(n, v.floatValue(), v.help)
|
||||
case stringKind:
|
||||
f.strings[n] = flag.String(n, v.value, v.help)
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// updateFlags updates the pprof variables according to the flags
|
||||
// parsed in the command line.
|
||||
func updateFlags(f flagsInstalled) error {
|
||||
vars := pprofVariables
|
||||
groups := map[string]string{}
|
||||
for n, v := range f.bools {
|
||||
vars.set(n, fmt.Sprint(*v))
|
||||
if *v {
|
||||
g := vars[n].group
|
||||
if g != "" && groups[g] != "" {
|
||||
return fmt.Errorf("conflicting options %q and %q set", n, groups[g])
|
||||
}
|
||||
groups[g] = n
|
||||
}
|
||||
}
|
||||
for n, v := range f.ints {
|
||||
vars.set(n, fmt.Sprint(*v))
|
||||
}
|
||||
for n, v := range f.floats {
|
||||
vars.set(n, fmt.Sprint(*v))
|
||||
}
|
||||
for n, v := range f.strings {
|
||||
vars.set(n, *v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type flagsInstalled struct {
|
||||
ints map[string]*int
|
||||
bools map[string]*bool
|
||||
floats map[string]*float64
|
||||
strings map[string]*string
|
||||
}
|
||||
|
||||
// isBuildID determines if the profile may contain a build ID, by
|
||||
// checking that it is a string of hex digits.
|
||||
func isBuildID(id string) bool {
|
||||
return strings.Trim(id, "0123456789abcdefABCDEF") == ""
|
||||
}
|
||||
|
||||
func sampleIndex(flag *bool, si string, sampleType, option string, ui plugin.UI) string {
|
||||
if *flag {
|
||||
if si == "" {
|
||||
return sampleType
|
||||
}
|
||||
ui.PrintErr("Multiple value selections, ignoring ", option)
|
||||
}
|
||||
return si
|
||||
}
|
||||
|
||||
func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string, err error) {
|
||||
for n, b := range bcmd {
|
||||
if *b {
|
||||
if cmd != nil {
|
||||
return nil, errors.New("must set at most one output format")
|
||||
}
|
||||
cmd = []string{n}
|
||||
}
|
||||
}
|
||||
for n, s := range acmd {
|
||||
if *s != "" {
|
||||
if cmd != nil {
|
||||
return nil, errors.New("must set at most one output format")
|
||||
}
|
||||
cmd = []string{n, *s}
|
||||
}
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
var usageMsgHdr = `usage:
|
||||
|
||||
Produce output in the specified format.
|
||||
|
||||
pprof <format> [options] [binary] <source> ...
|
||||
|
||||
Omit the format to get an interactive shell whose commands can be used
|
||||
to generate various views of a profile
|
||||
|
||||
pprof [options] [binary] <source> ...
|
||||
|
||||
Omit the format and provide the "-http" flag to get an interactive web
|
||||
interface at the specified host:port that can be used to navigate through
|
||||
various views of a profile.
|
||||
|
||||
pprof -http [host]:[port] [options] [binary] <source> ...
|
||||
|
||||
Details:
|
||||
`
|
||||
|
||||
var usageMsgSrc = "\n\n" +
|
||||
" Source options:\n" +
|
||||
" -seconds Duration for time-based profile collection\n" +
|
||||
" -timeout Timeout in seconds for profile collection\n" +
|
||||
" -buildid Override build id for main binary\n" +
|
||||
" -add_comment Free-form annotation to add to the profile\n" +
|
||||
" Displayed on some reports or with pprof -comments\n" +
|
||||
" -diff_base source Source of base profile for comparison\n" +
|
||||
" -base source Source of base profile for profile subtraction\n" +
|
||||
" profile.pb.gz Profile in compressed protobuf format\n" +
|
||||
" legacy_profile Profile in legacy pprof format\n" +
|
||||
" http://host/profile URL for profile handler to retrieve\n" +
|
||||
" -symbolize= Controls source of symbol information\n" +
|
||||
" none Do not attempt symbolization\n" +
|
||||
" local Examine only local binaries\n" +
|
||||
" fastlocal Only get function names from local binaries\n" +
|
||||
" remote Do not examine local binaries\n" +
|
||||
" force Force re-symbolization\n" +
|
||||
" Binary Local path or build id of binary for symbolization\n"
|
||||
|
||||
var usageMsgVars = "\n\n" +
|
||||
" Misc options:\n" +
|
||||
" -http Provide web interface at host:port.\n" +
|
||||
" Host is optional and 'localhost' by default.\n" +
|
||||
" Port is optional and a randomly available port by default.\n" +
|
||||
" -no_browser Skip opening a browser for the interactive web UI.\n" +
|
||||
" -tools Search path for object tools\n" +
|
||||
"\n" +
|
||||
" Legacy convenience options:\n" +
|
||||
" -inuse_space Same as -sample_index=inuse_space\n" +
|
||||
" -inuse_objects Same as -sample_index=inuse_objects\n" +
|
||||
" -alloc_space Same as -sample_index=alloc_space\n" +
|
||||
" -alloc_objects Same as -sample_index=alloc_objects\n" +
|
||||
" -total_delay Same as -sample_index=delay\n" +
|
||||
" -contentions Same as -sample_index=contentions\n" +
|
||||
" -mean_delay Same as -mean -sample_index=delay\n" +
|
||||
"\n" +
|
||||
" Environment Variables:\n" +
|
||||
" PPROF_TMPDIR Location for saved profiles (default $HOME/pprof)\n" +
|
||||
" PPROF_TOOLS Search path for object-level tools\n" +
|
||||
" PPROF_BINARY_PATH Search path for local binary files\n" +
|
||||
" default: $HOME/pprof/binaries\n" +
|
||||
" searches $name, $path, $buildid/$name, $path/$buildid\n" +
|
||||
" * On Windows, %USERPROFILE% is used instead of $HOME"
|
566
vendor/github.com/google/pprof/internal/driver/commands.go
generated
vendored
Normal file
566
vendor/github.com/google/pprof/internal/driver/commands.go
generated
vendored
Normal file
@@ -0,0 +1,566 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/report"
|
||||
)
|
||||
|
||||
// commands describes the commands accepted by pprof.
|
||||
type commands map[string]*command
|
||||
|
||||
// command describes the actions for a pprof command. Includes a
|
||||
// function for command-line completion, the report format to use
|
||||
// during report generation, any postprocessing functions, and whether
|
||||
// the command expects a regexp parameter (typically a function name).
|
||||
type command struct {
|
||||
format int // report format to generate
|
||||
postProcess PostProcessor // postprocessing to run on report
|
||||
visualizer PostProcessor // display output using some callback
|
||||
hasParam bool // collect a parameter from the CLI
|
||||
description string // single-line description text saying what the command does
|
||||
usage string // multi-line help text saying how the command is used
|
||||
}
|
||||
|
||||
// help returns a help string for a command.
|
||||
func (c *command) help(name string) string {
|
||||
message := c.description + "\n"
|
||||
if c.usage != "" {
|
||||
message += " Usage:\n"
|
||||
lines := strings.Split(c.usage, "\n")
|
||||
for _, line := range lines {
|
||||
message += fmt.Sprintf(" %s\n", line)
|
||||
}
|
||||
}
|
||||
return message + "\n"
|
||||
}
|
||||
|
||||
// AddCommand adds an additional command to the set of commands
|
||||
// accepted by pprof. This enables extensions to add new commands for
|
||||
// specialized visualization formats. If the command specified already
|
||||
// exists, it is overwritten.
|
||||
func AddCommand(cmd string, format int, post PostProcessor, desc, usage string) {
|
||||
pprofCommands[cmd] = &command{format, post, nil, false, desc, usage}
|
||||
}
|
||||
|
||||
// SetVariableDefault sets the default value for a pprof
|
||||
// variable. This enables extensions to set their own defaults.
|
||||
func SetVariableDefault(variable, value string) {
|
||||
if v := pprofVariables[variable]; v != nil {
|
||||
v.value = value
|
||||
}
|
||||
}
|
||||
|
||||
// PostProcessor is a function that applies post-processing to the report output
|
||||
type PostProcessor func(input io.Reader, output io.Writer, ui plugin.UI) error
|
||||
|
||||
// interactiveMode is true if pprof is running on interactive mode, reading
|
||||
// commands from its shell.
|
||||
var interactiveMode = false
|
||||
|
||||
// pprofCommands are the report generation commands recognized by pprof.
|
||||
var pprofCommands = commands{
|
||||
// Commands that require no post-processing.
|
||||
"comments": {report.Comments, nil, nil, false, "Output all profile comments", ""},
|
||||
"disasm": {report.Dis, nil, nil, true, "Output assembly listings annotated with samples", listHelp("disasm", true)},
|
||||
"dot": {report.Dot, nil, nil, false, "Outputs a graph in DOT format", reportHelp("dot", false, true)},
|
||||
"list": {report.List, nil, nil, true, "Output annotated source for functions matching regexp", listHelp("list", false)},
|
||||
"peek": {report.Tree, nil, nil, true, "Output callers/callees of functions matching regexp", "peek func_regex\nDisplay callers and callees of functions matching func_regex."},
|
||||
"raw": {report.Raw, nil, nil, false, "Outputs a text representation of the raw profile", ""},
|
||||
"tags": {report.Tags, nil, nil, false, "Outputs all tags in the profile", "tags [tag_regex]* [-ignore_regex]* [>file]\nList tags with key:value matching tag_regex and exclude ignore_regex."},
|
||||
"text": {report.Text, nil, nil, false, "Outputs top entries in text form", reportHelp("text", true, true)},
|
||||
"top": {report.Text, nil, nil, false, "Outputs top entries in text form", reportHelp("top", true, true)},
|
||||
"traces": {report.Traces, nil, nil, false, "Outputs all profile samples in text form", ""},
|
||||
"tree": {report.Tree, nil, nil, false, "Outputs a text rendering of call graph", reportHelp("tree", true, true)},
|
||||
|
||||
// Save binary formats to a file
|
||||
"callgrind": {report.Callgrind, nil, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
|
||||
"proto": {report.Proto, nil, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
|
||||
"topproto": {report.TopProto, nil, awayFromTTY("pb.gz"), false, "Outputs top entries in compressed protobuf format", ""},
|
||||
|
||||
// Generate report in DOT format and postprocess with dot
|
||||
"gif": {report.Dot, invokeDot("gif"), awayFromTTY("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},
|
||||
"pdf": {report.Dot, invokeDot("pdf"), awayFromTTY("pdf"), false, "Outputs a graph in PDF format", reportHelp("pdf", false, true)},
|
||||
"png": {report.Dot, invokeDot("png"), awayFromTTY("png"), false, "Outputs a graph image in PNG format", reportHelp("png", false, true)},
|
||||
"ps": {report.Dot, invokeDot("ps"), awayFromTTY("ps"), false, "Outputs a graph in PS format", reportHelp("ps", false, true)},
|
||||
|
||||
// Save SVG output into a file
|
||||
"svg": {report.Dot, massageDotSVG(), awayFromTTY("svg"), false, "Outputs a graph in SVG format", reportHelp("svg", false, true)},
|
||||
|
||||
// Visualize postprocessed dot output
|
||||
"eog": {report.Dot, invokeDot("svg"), invokeVisualizer("svg", []string{"eog"}), false, "Visualize graph through eog", reportHelp("eog", false, false)},
|
||||
"evince": {report.Dot, invokeDot("pdf"), invokeVisualizer("pdf", []string{"evince"}), false, "Visualize graph through evince", reportHelp("evince", false, false)},
|
||||
"gv": {report.Dot, invokeDot("ps"), invokeVisualizer("ps", []string{"gv --noantialias"}), false, "Visualize graph through gv", reportHelp("gv", false, false)},
|
||||
"web": {report.Dot, massageDotSVG(), invokeVisualizer("svg", browsers()), false, "Visualize graph through web browser", reportHelp("web", false, false)},
|
||||
|
||||
// Visualize callgrind output
|
||||
"kcachegrind": {report.Callgrind, nil, invokeVisualizer("grind", kcachegrind), false, "Visualize report in KCachegrind", reportHelp("kcachegrind", false, false)},
|
||||
|
||||
// Visualize HTML directly generated by report.
|
||||
"weblist": {report.WebList, nil, invokeVisualizer("html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)},
|
||||
}
|
||||
|
||||
// pprofVariables are the configuration parameters that affect the
|
||||
// reported generated by pprof.
|
||||
var pprofVariables = variables{
|
||||
// Filename for file-based output formats, stdout by default.
|
||||
"output": &variable{stringKind, "", "", helpText("Output filename for file-based outputs")},
|
||||
|
||||
// Comparisons.
|
||||
"drop_negative": &variable{boolKind, "f", "", helpText(
|
||||
"Ignore negative differences",
|
||||
"Do not show any locations with values <0.")},
|
||||
|
||||
// Graph handling options.
|
||||
"call_tree": &variable{boolKind, "f", "", helpText(
|
||||
"Create a context-sensitive call tree",
|
||||
"Treat locations reached through different paths as separate.")},
|
||||
|
||||
// Display options.
|
||||
"relative_percentages": &variable{boolKind, "f", "", helpText(
|
||||
"Show percentages relative to focused subgraph",
|
||||
"If unset, percentages are relative to full graph before focusing",
|
||||
"to facilitate comparison with original graph.")},
|
||||
"unit": &variable{stringKind, "minimum", "", helpText(
|
||||
"Measurement units to display",
|
||||
"Scale the sample values to this unit.",
|
||||
"For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
|
||||
"For memory profiles, use megabytes, kilobytes, bytes, etc.",
|
||||
"Using auto will scale each value independently to the most natural unit.")},
|
||||
"compact_labels": &variable{boolKind, "f", "", "Show minimal headers"},
|
||||
"source_path": &variable{stringKind, "", "", "Search path for source files"},
|
||||
"trim_path": &variable{stringKind, "", "", "Path to trim from source paths before search"},
|
||||
|
||||
// Filtering options
|
||||
"nodecount": &variable{intKind, "-1", "", helpText(
|
||||
"Max number of nodes to show",
|
||||
"Uses heuristics to limit the number of locations to be displayed.",
|
||||
"On graphs, dotted edges represent paths through nodes that have been removed.")},
|
||||
"nodefraction": &variable{floatKind, "0.005", "", "Hide nodes below <f>*total"},
|
||||
"edgefraction": &variable{floatKind, "0.001", "", "Hide edges below <f>*total"},
|
||||
"trim": &variable{boolKind, "t", "", helpText(
|
||||
"Honor nodefraction/edgefraction/nodecount defaults",
|
||||
"Set to false to get the full profile, without any trimming.")},
|
||||
"focus": &variable{stringKind, "", "", helpText(
|
||||
"Restricts to samples going through a node matching regexp",
|
||||
"Discard samples that do not include a node matching this regexp.",
|
||||
"Matching includes the function name, filename or object name.")},
|
||||
"ignore": &variable{stringKind, "", "", helpText(
|
||||
"Skips paths going through any nodes matching regexp",
|
||||
"If set, discard samples that include a node matching this regexp.",
|
||||
"Matching includes the function name, filename or object name.")},
|
||||
"prune_from": &variable{stringKind, "", "", helpText(
|
||||
"Drops any functions below the matched frame.",
|
||||
"If set, any frames matching the specified regexp and any frames",
|
||||
"below it will be dropped from each sample.")},
|
||||
"hide": &variable{stringKind, "", "", helpText(
|
||||
"Skips nodes matching regexp",
|
||||
"Discard nodes that match this location.",
|
||||
"Other nodes from samples that include this location will be shown.",
|
||||
"Matching includes the function name, filename or object name.")},
|
||||
"show": &variable{stringKind, "", "", helpText(
|
||||
"Only show nodes matching regexp",
|
||||
"If set, only show nodes that match this location.",
|
||||
"Matching includes the function name, filename or object name.")},
|
||||
"show_from": &variable{stringKind, "", "", helpText(
|
||||
"Drops functions above the highest matched frame.",
|
||||
"If set, all frames above the highest match are dropped from every sample.",
|
||||
"Matching includes the function name, filename or object name.")},
|
||||
"tagfocus": &variable{stringKind, "", "", helpText(
|
||||
"Restricts to samples with tags in range or matched by regexp",
|
||||
"Use name=value syntax to limit the matching to a specific tag.",
|
||||
"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:",
|
||||
"String tag filter examples: foo, foo.*bar, mytag=foo.*bar")},
|
||||
"tagignore": &variable{stringKind, "", "", helpText(
|
||||
"Discard samples with tags in range or matched by regexp",
|
||||
"Use name=value syntax to limit the matching to a specific tag.",
|
||||
"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:",
|
||||
"String tag filter examples: foo, foo.*bar, mytag=foo.*bar")},
|
||||
"tagshow": &variable{stringKind, "", "", helpText(
|
||||
"Only consider tags matching this regexp",
|
||||
"Discard tags that do not match this regexp")},
|
||||
"taghide": &variable{stringKind, "", "", helpText(
|
||||
"Skip tags matching this regexp",
|
||||
"Discard tags that match this regexp")},
|
||||
// Heap profile options
|
||||
"divide_by": &variable{floatKind, "1", "", helpText(
|
||||
"Ratio to divide all samples before visualization",
|
||||
"Divide all samples values by a constant, eg the number of processors or jobs.")},
|
||||
"mean": &variable{boolKind, "f", "", helpText(
|
||||
"Average sample value over first value (count)",
|
||||
"For memory profiles, report average memory per allocation.",
|
||||
"For time-based profiles, report average time per event.")},
|
||||
"sample_index": &variable{stringKind, "", "", helpText(
|
||||
"Sample value to report (0-based index or name)",
|
||||
"Profiles contain multiple values per sample.",
|
||||
"Use sample_index=i to select the ith value (starting at 0).")},
|
||||
"normalize": &variable{boolKind, "f", "", helpText(
|
||||
"Scales profile based on the base profile.")},
|
||||
|
||||
// Data sorting criteria
|
||||
"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")},
|
||||
"cum": &variable{boolKind, "f", "cumulative", helpText("Sort entries based on cumulative weight")},
|
||||
|
||||
// Output granularity
|
||||
"functions": &variable{boolKind, "t", "granularity", helpText(
|
||||
"Aggregate at the function level.",
|
||||
"Ignores the filename where the function was defined.")},
|
||||
"filefunctions": &variable{boolKind, "t", "granularity", helpText(
|
||||
"Aggregate at the function level.",
|
||||
"Takes into account the filename where the function was defined.")},
|
||||
"files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."},
|
||||
"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
|
||||
"addresses": &variable{boolKind, "f", "granularity", helpText(
|
||||
"Aggregate at the address level.",
|
||||
"Includes functions' addresses in the output.")},
|
||||
"noinlines": &variable{boolKind, "f", "", helpText(
|
||||
"Ignore inlines.",
|
||||
"Attributes inlined functions to their first out-of-line caller.")},
|
||||
}
|
||||
|
||||
func helpText(s ...string) string {
|
||||
return strings.Join(s, "\n") + "\n"
|
||||
}
|
||||
|
||||
// usage returns a string describing the pprof commands and variables.
|
||||
// if commandLine is set, the output reflect cli usage.
|
||||
func usage(commandLine bool) string {
|
||||
var prefix string
|
||||
if commandLine {
|
||||
prefix = "-"
|
||||
}
|
||||
fmtHelp := func(c, d string) string {
|
||||
return fmt.Sprintf(" %-16s %s", c, strings.SplitN(d, "\n", 2)[0])
|
||||
}
|
||||
|
||||
var commands []string
|
||||
for name, cmd := range pprofCommands {
|
||||
commands = append(commands, fmtHelp(prefix+name, cmd.description))
|
||||
}
|
||||
sort.Strings(commands)
|
||||
|
||||
var help string
|
||||
if commandLine {
|
||||
help = " Output formats (select at most one):\n"
|
||||
} else {
|
||||
help = " Commands:\n"
|
||||
commands = append(commands, fmtHelp("o/options", "List options and their current values"))
|
||||
commands = append(commands, fmtHelp("quit/exit/^D", "Exit pprof"))
|
||||
}
|
||||
|
||||
help = help + strings.Join(commands, "\n") + "\n\n" +
|
||||
" Options:\n"
|
||||
|
||||
// Print help for variables after sorting them.
|
||||
// Collect radio variables by their group name to print them together.
|
||||
radioOptions := make(map[string][]string)
|
||||
var variables []string
|
||||
for name, vr := range pprofVariables {
|
||||
if vr.group != "" {
|
||||
radioOptions[vr.group] = append(radioOptions[vr.group], name)
|
||||
continue
|
||||
}
|
||||
variables = append(variables, fmtHelp(prefix+name, vr.help))
|
||||
}
|
||||
sort.Strings(variables)
|
||||
|
||||
help = help + strings.Join(variables, "\n") + "\n\n" +
|
||||
" Option groups (only set one per group):\n"
|
||||
|
||||
var radioStrings []string
|
||||
for radio, ops := range radioOptions {
|
||||
sort.Strings(ops)
|
||||
s := []string{fmtHelp(radio, "")}
|
||||
for _, op := range ops {
|
||||
s = append(s, " "+fmtHelp(prefix+op, pprofVariables[op].help))
|
||||
}
|
||||
|
||||
radioStrings = append(radioStrings, strings.Join(s, "\n"))
|
||||
}
|
||||
sort.Strings(radioStrings)
|
||||
return help + strings.Join(radioStrings, "\n")
|
||||
}
|
||||
|
||||
func reportHelp(c string, cum, redirect bool) string {
|
||||
h := []string{
|
||||
c + " [n] [focus_regex]* [-ignore_regex]*",
|
||||
"Include up to n samples",
|
||||
"Include samples matching focus_regex, and exclude ignore_regex.",
|
||||
}
|
||||
if cum {
|
||||
h[0] += " [-cum]"
|
||||
h = append(h, "-cum sorts the output by cumulative weight")
|
||||
}
|
||||
if redirect {
|
||||
h[0] += " >f"
|
||||
h = append(h, "Optionally save the report on the file f")
|
||||
}
|
||||
return strings.Join(h, "\n")
|
||||
}
|
||||
|
||||
func listHelp(c string, redirect bool) string {
|
||||
h := []string{
|
||||
c + "<func_regex|address> [-focus_regex]* [-ignore_regex]*",
|
||||
"Include functions matching func_regex, or including the address specified.",
|
||||
"Include samples matching focus_regex, and exclude ignore_regex.",
|
||||
}
|
||||
if redirect {
|
||||
h[0] += " >f"
|
||||
h = append(h, "Optionally save the report on the file f")
|
||||
}
|
||||
return strings.Join(h, "\n")
|
||||
}
|
||||
|
||||
// browsers returns a list of commands to attempt for web visualization.
|
||||
func browsers() []string {
|
||||
var cmds []string
|
||||
if userBrowser := os.Getenv("BROWSER"); userBrowser != "" {
|
||||
cmds = append(cmds, userBrowser)
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
cmds = append(cmds, "/usr/bin/open")
|
||||
case "windows":
|
||||
cmds = append(cmds, "cmd /c start")
|
||||
default:
|
||||
// Commands opening browsers are prioritized over xdg-open, so browser()
|
||||
// command can be used on linux to open the .svg file generated by the -web
|
||||
// command (the .svg file includes embedded javascript so is best viewed in
|
||||
// a browser).
|
||||
cmds = append(cmds, []string{"chrome", "google-chrome", "chromium", "firefox", "sensible-browser"}...)
|
||||
if os.Getenv("DISPLAY") != "" {
|
||||
// xdg-open is only for use in a desktop environment.
|
||||
cmds = append(cmds, "xdg-open")
|
||||
}
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
var kcachegrind = []string{"kcachegrind"}
|
||||
|
||||
// awayFromTTY saves the output in a file if it would otherwise go to
|
||||
// the terminal screen. This is used to avoid dumping binary data on
|
||||
// the screen.
|
||||
func awayFromTTY(format string) PostProcessor {
|
||||
return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
||||
if output == os.Stdout && (ui.IsTerminal() || interactiveMode) {
|
||||
tempFile, err := newTempFile("", "profile", "."+format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ui.PrintErr("Generating report in ", tempFile.Name())
|
||||
output = tempFile
|
||||
}
|
||||
_, err := io.Copy(output, input)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func invokeDot(format string) PostProcessor {
|
||||
return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
||||
cmd := exec.Command("dot", "-T"+format)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = input, output, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to execute dot. Is Graphviz installed? Error: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// massageDotSVG invokes the dot tool to generate an SVG image and alters
|
||||
// the image to have panning capabilities when viewed in a browser.
|
||||
func massageDotSVG() PostProcessor {
|
||||
generateSVG := invokeDot("svg")
|
||||
return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
||||
baseSVG := new(bytes.Buffer)
|
||||
if err := generateSVG(input, baseSVG, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := output.Write([]byte(massageSVG(baseSVG.String())))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func invokeVisualizer(suffix string, visualizers []string) PostProcessor {
|
||||
return func(input io.Reader, output io.Writer, ui plugin.UI) error {
|
||||
tempFile, err := newTempFile(os.TempDir(), "pprof", "."+suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deferDeleteTempFile(tempFile.Name())
|
||||
if _, err := io.Copy(tempFile, input); err != nil {
|
||||
return err
|
||||
}
|
||||
tempFile.Close()
|
||||
// Try visualizers until one is successful
|
||||
for _, v := range visualizers {
|
||||
// Separate command and arguments for exec.Command.
|
||||
args := strings.Split(v, " ")
|
||||
if len(args) == 0 {
|
||||
continue
|
||||
}
|
||||
viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
|
||||
viewer.Stderr = os.Stderr
|
||||
if err = viewer.Start(); err == nil {
|
||||
// Wait for a second so that the visualizer has a chance to
|
||||
// open the input file. This needs to be done even if we're
|
||||
// waiting for the visualizer as it can be just a wrapper that
|
||||
// spawns a browser tab and returns right away.
|
||||
defer func(t <-chan time.Time) {
|
||||
<-t
|
||||
}(time.After(time.Second))
|
||||
// On interactive mode, let the visualizer run in the background
|
||||
// so other commands can be issued.
|
||||
if !interactiveMode {
|
||||
return viewer.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// variables describe the configuration parameters recognized by pprof.
|
||||
type variables map[string]*variable
|
||||
|
||||
// variable is a single configuration parameter.
|
||||
type variable struct {
|
||||
kind int // How to interpret the value, must be one of the enums below.
|
||||
value string // Effective value. Only values appropriate for the Kind should be set.
|
||||
group string // boolKind variables with the same Group != "" cannot be set simultaneously.
|
||||
help string // Text describing the variable, in multiple lines separated by newline.
|
||||
}
|
||||
|
||||
const (
|
||||
// variable.kind must be one of these variables.
|
||||
boolKind = iota
|
||||
intKind
|
||||
floatKind
|
||||
stringKind
|
||||
)
|
||||
|
||||
// set updates the value of a variable, checking that the value is
|
||||
// suitable for the variable Kind.
|
||||
func (vars variables) set(name, value string) error {
|
||||
v := vars[name]
|
||||
if v == nil {
|
||||
return fmt.Errorf("no variable %s", name)
|
||||
}
|
||||
var err error
|
||||
switch v.kind {
|
||||
case boolKind:
|
||||
var b bool
|
||||
if b, err = stringToBool(value); err == nil {
|
||||
if v.group != "" && !b {
|
||||
err = fmt.Errorf("%q can only be set to true", name)
|
||||
}
|
||||
}
|
||||
case intKind:
|
||||
_, err = strconv.Atoi(value)
|
||||
case floatKind:
|
||||
_, err = strconv.ParseFloat(value, 64)
|
||||
case stringKind:
|
||||
// Remove quotes, particularly useful for empty values.
|
||||
if len(value) > 1 && strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vars[name].value = value
|
||||
if group := vars[name].group; group != "" {
|
||||
for vname, vvar := range vars {
|
||||
if vvar.group == group && vname != name {
|
||||
vvar.value = "f"
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// boolValue returns the value of a boolean variable.
|
||||
func (v *variable) boolValue() bool {
|
||||
b, err := stringToBool(v.value)
|
||||
if err != nil {
|
||||
panic("unexpected value " + v.value + " for bool ")
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// intValue returns the value of an intKind variable.
|
||||
func (v *variable) intValue() int {
|
||||
i, err := strconv.Atoi(v.value)
|
||||
if err != nil {
|
||||
panic("unexpected value " + v.value + " for int ")
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// floatValue returns the value of a Float variable.
|
||||
func (v *variable) floatValue() float64 {
|
||||
f, err := strconv.ParseFloat(v.value, 64)
|
||||
if err != nil {
|
||||
panic("unexpected value " + v.value + " for float ")
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// stringValue returns a canonical representation for a variable.
|
||||
func (v *variable) stringValue() string {
|
||||
switch v.kind {
|
||||
case boolKind:
|
||||
return fmt.Sprint(v.boolValue())
|
||||
case intKind:
|
||||
return fmt.Sprint(v.intValue())
|
||||
case floatKind:
|
||||
return fmt.Sprint(v.floatValue())
|
||||
}
|
||||
return v.value
|
||||
}
|
||||
|
||||
func stringToBool(s string) (bool, error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "true", "t", "yes", "y", "1", "":
|
||||
return true, nil
|
||||
case "false", "f", "no", "n", "0":
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf(`illegal value "%s" for bool variable`, s)
|
||||
}
|
||||
}
|
||||
|
||||
// makeCopy returns a duplicate of a set of shell variables.
|
||||
func (vars variables) makeCopy() variables {
|
||||
varscopy := make(variables, len(vars))
|
||||
for n, v := range vars {
|
||||
vcopy := *v
|
||||
varscopy[n] = &vcopy
|
||||
}
|
||||
return varscopy
|
||||
}
|
330
vendor/github.com/google/pprof/internal/driver/driver.go
generated
vendored
Normal file
330
vendor/github.com/google/pprof/internal/driver/driver.go
generated
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
// 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.
|
||||
|
||||
// Package driver implements the core pprof functionality. It can be
|
||||
// parameterized with a flag implementation, fetch and symbolize
|
||||
// mechanisms.
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/report"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
// PProf acquires a profile, and symbolizes it using a profile
|
||||
// manager. Then it generates a report formatted according to the
|
||||
// options selected through the flags package.
|
||||
func PProf(eo *plugin.Options) error {
|
||||
// Remove any temporary files created during pprof processing.
|
||||
defer cleanupTempFiles()
|
||||
|
||||
o := setDefaults(eo)
|
||||
|
||||
src, cmd, err := parseFlags(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := fetchProfiles(src, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd != nil {
|
||||
return generateReport(p, cmd, pprofVariables, o)
|
||||
}
|
||||
|
||||
if src.HTTPHostport != "" {
|
||||
return serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser)
|
||||
}
|
||||
return interactive(p, o)
|
||||
}
|
||||
|
||||
func generateRawReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) (*command, *report.Report, error) {
|
||||
p = p.Copy() // Prevent modification to the incoming profile.
|
||||
|
||||
// Identify units of numeric tags in profile.
|
||||
numLabelUnits := identifyNumLabelUnits(p, o.UI)
|
||||
|
||||
// Get report output format
|
||||
c := pprofCommands[cmd[0]]
|
||||
if c == nil {
|
||||
panic("unexpected nil command")
|
||||
}
|
||||
|
||||
vars = applyCommandOverrides(cmd[0], c.format, vars)
|
||||
|
||||
// Delay focus after configuring report to get percentages on all samples.
|
||||
relative := vars["relative_percentages"].boolValue()
|
||||
if relative {
|
||||
if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
ropt, err := reportOptions(p, numLabelUnits, vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ropt.OutputFormat = c.format
|
||||
if len(cmd) == 2 {
|
||||
s, err := regexp.Compile(cmd[1])
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
|
||||
}
|
||||
ropt.Symbol = s
|
||||
}
|
||||
|
||||
rpt := report.New(p, ropt)
|
||||
if !relative {
|
||||
if err := applyFocus(p, numLabelUnits, vars, o.UI); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if err := aggregate(p, vars); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c, rpt, nil
|
||||
}
|
||||
|
||||
func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
|
||||
c, rpt, err := generateRawReport(p, cmd, vars, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the report.
|
||||
dst := new(bytes.Buffer)
|
||||
if err := report.Generate(dst, rpt, o.Obj); err != nil {
|
||||
return err
|
||||
}
|
||||
src := dst
|
||||
|
||||
// If necessary, perform any data post-processing.
|
||||
if c.postProcess != nil {
|
||||
dst = new(bytes.Buffer)
|
||||
if err := c.postProcess(src, dst, o.UI); err != nil {
|
||||
return err
|
||||
}
|
||||
src = dst
|
||||
}
|
||||
|
||||
// If no output is specified, use default visualizer.
|
||||
output := vars["output"].value
|
||||
if output == "" {
|
||||
if c.visualizer != nil {
|
||||
return c.visualizer(src, os.Stdout, o.UI)
|
||||
}
|
||||
_, err := src.WriteTo(os.Stdout)
|
||||
return err
|
||||
}
|
||||
|
||||
// Output to specified file.
|
||||
o.UI.PrintErr("Generating report in ", output)
|
||||
out, err := o.Writer.Open(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := src.WriteTo(out); err != nil {
|
||||
out.Close()
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func applyCommandOverrides(cmd string, outputFormat int, v variables) variables {
|
||||
// Some report types override the trim flag to false below. This is to make
|
||||
// sure the default heuristics of excluding insignificant nodes and edges
|
||||
// from the call graph do not apply. One example where it is important is
|
||||
// annotated source or disassembly listing. Those reports run on a specific
|
||||
// function (or functions), but the trimming is applied before the function
|
||||
// data is selected. So, with trimming enabled, the report could end up
|
||||
// showing no data if the specified function is "uninteresting" as far as the
|
||||
// trimming is concerned.
|
||||
trim := v["trim"].boolValue()
|
||||
|
||||
switch cmd {
|
||||
case "disasm", "weblist":
|
||||
trim = false
|
||||
v.set("addresses", "t")
|
||||
// Force the 'noinlines' mode so that source locations for a given address
|
||||
// collapse and there is only one for the given address. Without this
|
||||
// cumulative metrics would be double-counted when annotating the assembly.
|
||||
// This is because the merge is done by address and in case of an inlined
|
||||
// stack each of the inlined entries is a separate callgraph node.
|
||||
v.set("noinlines", "t")
|
||||
case "peek":
|
||||
trim = false
|
||||
case "list":
|
||||
trim = false
|
||||
v.set("lines", "t")
|
||||
// Do not force 'noinlines' to be false so that specifying
|
||||
// "-list foo -noinlines" is supported and works as expected.
|
||||
case "text", "top", "topproto":
|
||||
if v["nodecount"].intValue() == -1 {
|
||||
v.set("nodecount", "0")
|
||||
}
|
||||
default:
|
||||
if v["nodecount"].intValue() == -1 {
|
||||
v.set("nodecount", "80")
|
||||
}
|
||||
}
|
||||
|
||||
switch outputFormat {
|
||||
case report.Proto, report.Raw, report.Callgrind:
|
||||
trim = false
|
||||
v.set("addresses", "t")
|
||||
v.set("noinlines", "f")
|
||||
}
|
||||
|
||||
if !trim {
|
||||
v.set("nodecount", "0")
|
||||
v.set("nodefraction", "0")
|
||||
v.set("edgefraction", "0")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func aggregate(prof *profile.Profile, v variables) error {
|
||||
var function, filename, linenumber, address bool
|
||||
inlines := !v["noinlines"].boolValue()
|
||||
switch {
|
||||
case v["addresses"].boolValue():
|
||||
if inlines {
|
||||
return nil
|
||||
}
|
||||
function = true
|
||||
filename = true
|
||||
linenumber = true
|
||||
address = true
|
||||
case v["lines"].boolValue():
|
||||
function = true
|
||||
filename = true
|
||||
linenumber = true
|
||||
case v["files"].boolValue():
|
||||
filename = true
|
||||
case v["functions"].boolValue():
|
||||
function = true
|
||||
case v["filefunctions"].boolValue():
|
||||
function = true
|
||||
filename = true
|
||||
default:
|
||||
return fmt.Errorf("unexpected granularity")
|
||||
}
|
||||
return prof.Aggregate(inlines, function, filename, linenumber, address)
|
||||
}
|
||||
|
||||
func reportOptions(p *profile.Profile, numLabelUnits map[string]string, vars variables) (*report.Options, error) {
|
||||
si, mean := vars["sample_index"].value, vars["mean"].boolValue()
|
||||
value, meanDiv, sample, err := sampleFormat(p, si, mean)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stype := sample.Type
|
||||
if mean {
|
||||
stype = "mean_" + stype
|
||||
}
|
||||
|
||||
if vars["divide_by"].floatValue() == 0 {
|
||||
return nil, fmt.Errorf("zero divisor specified")
|
||||
}
|
||||
|
||||
var filters []string
|
||||
for _, k := range []string{"focus", "ignore", "hide", "show", "show_from", "tagfocus", "tagignore", "tagshow", "taghide"} {
|
||||
v := vars[k].value
|
||||
if v != "" {
|
||||
filters = append(filters, k+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
ropt := &report.Options{
|
||||
CumSort: vars["cum"].boolValue(),
|
||||
CallTree: vars["call_tree"].boolValue(),
|
||||
DropNegative: vars["drop_negative"].boolValue(),
|
||||
|
||||
CompactLabels: vars["compact_labels"].boolValue(),
|
||||
Ratio: 1 / vars["divide_by"].floatValue(),
|
||||
|
||||
NodeCount: vars["nodecount"].intValue(),
|
||||
NodeFraction: vars["nodefraction"].floatValue(),
|
||||
EdgeFraction: vars["edgefraction"].floatValue(),
|
||||
|
||||
ActiveFilters: filters,
|
||||
NumLabelUnits: numLabelUnits,
|
||||
|
||||
SampleValue: value,
|
||||
SampleMeanDivisor: meanDiv,
|
||||
SampleType: stype,
|
||||
SampleUnit: sample.Unit,
|
||||
|
||||
OutputUnit: vars["unit"].value,
|
||||
|
||||
SourcePath: vars["source_path"].stringValue(),
|
||||
TrimPath: vars["trim_path"].stringValue(),
|
||||
}
|
||||
|
||||
if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
|
||||
ropt.Title = filepath.Base(p.Mapping[0].File)
|
||||
}
|
||||
|
||||
return ropt, nil
|
||||
}
|
||||
|
||||
// identifyNumLabelUnits returns a map of numeric label keys to the units
|
||||
// associated with those keys.
|
||||
func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
|
||||
numLabelUnits, ignoredUnits := p.NumLabelUnits()
|
||||
|
||||
// Print errors for tags with multiple units associated with
|
||||
// a single key.
|
||||
for k, units := range ignoredUnits {
|
||||
ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
|
||||
}
|
||||
return numLabelUnits
|
||||
}
|
||||
|
||||
type sampleValueFunc func([]int64) int64
|
||||
|
||||
// sampleFormat returns a function to extract values out of a profile.Sample,
|
||||
// and the type/units of those values.
|
||||
func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
|
||||
if len(p.SampleType) == 0 {
|
||||
return nil, nil, nil, fmt.Errorf("profile has no samples")
|
||||
}
|
||||
index, err := p.SampleIndexByName(sampleIndex)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
value = valueExtractor(index)
|
||||
if mean {
|
||||
meanDiv = valueExtractor(0)
|
||||
}
|
||||
v = p.SampleType[index]
|
||||
return
|
||||
}
|
||||
|
||||
func valueExtractor(ix int) sampleValueFunc {
|
||||
return func(v []int64) int64 {
|
||||
return v[ix]
|
||||
}
|
||||
}
|
219
vendor/github.com/google/pprof/internal/driver/driver_focus.go
generated
vendored
Normal file
219
vendor/github.com/google/pprof/internal/driver/driver_focus.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/internal/measurement"
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
var tagFilterRangeRx = regexp.MustCompile("([+-]?[[:digit:]]+)([[:alpha:]]+)")
|
||||
|
||||
// applyFocus filters samples based on the focus/ignore options
|
||||
func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variables, ui plugin.UI) error {
|
||||
focus, err := compileRegexOption("focus", v["focus"].value, nil)
|
||||
ignore, err := compileRegexOption("ignore", v["ignore"].value, err)
|
||||
hide, err := compileRegexOption("hide", v["hide"].value, err)
|
||||
show, err := compileRegexOption("show", v["show"].value, err)
|
||||
showfrom, err := compileRegexOption("show_from", v["show_from"].value, err)
|
||||
tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err)
|
||||
tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err)
|
||||
prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fm, im, hm, hnm := prof.FilterSamplesByName(focus, ignore, hide, show)
|
||||
warnNoMatches(focus == nil || fm, "Focus", ui)
|
||||
warnNoMatches(ignore == nil || im, "Ignore", ui)
|
||||
warnNoMatches(hide == nil || hm, "Hide", ui)
|
||||
warnNoMatches(show == nil || hnm, "Show", ui)
|
||||
|
||||
sfm := prof.ShowFrom(showfrom)
|
||||
warnNoMatches(showfrom == nil || sfm, "ShowFrom", ui)
|
||||
|
||||
tfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore)
|
||||
warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui)
|
||||
warnNoMatches(tagignore == nil || tim, "TagIgnore", ui)
|
||||
|
||||
tagshow, err := compileRegexOption("tagshow", v["tagshow"].value, err)
|
||||
taghide, err := compileRegexOption("taghide", v["taghide"].value, err)
|
||||
tns, tnh := prof.FilterTagsByName(tagshow, taghide)
|
||||
warnNoMatches(tagshow == nil || tns, "TagShow", ui)
|
||||
warnNoMatches(tagignore == nil || tnh, "TagHide", ui)
|
||||
|
||||
if prunefrom != nil {
|
||||
prof.PruneFrom(prunefrom)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) {
|
||||
if value == "" || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rx, err := regexp.Compile(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s regexp: %v", name, err)
|
||||
}
|
||||
return rx, nil
|
||||
}
|
||||
|
||||
func compileTagFilter(name, value string, numLabelUnits map[string]string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) {
|
||||
if value == "" || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tagValuePair := strings.SplitN(value, "=", 2)
|
||||
var wantKey string
|
||||
if len(tagValuePair) == 2 {
|
||||
wantKey = tagValuePair[0]
|
||||
value = tagValuePair[1]
|
||||
}
|
||||
|
||||
if numFilter := parseTagFilterRange(value); numFilter != nil {
|
||||
ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp")
|
||||
labelFilter := func(vals []int64, unit string) bool {
|
||||
for _, val := range vals {
|
||||
if numFilter(val, unit) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
numLabelUnit := func(key string) string {
|
||||
return numLabelUnits[key]
|
||||
}
|
||||
if wantKey == "" {
|
||||
return func(s *profile.Sample) bool {
|
||||
for key, vals := range s.NumLabel {
|
||||
if labelFilter(vals, numLabelUnit(key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, nil
|
||||
}
|
||||
return func(s *profile.Sample) bool {
|
||||
if vals, ok := s.NumLabel[wantKey]; ok {
|
||||
return labelFilter(vals, numLabelUnit(wantKey))
|
||||
}
|
||||
return false
|
||||
}, nil
|
||||
}
|
||||
|
||||
var rfx []*regexp.Regexp
|
||||
for _, tagf := range strings.Split(value, ",") {
|
||||
fx, err := regexp.Compile(tagf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s regexp: %v", name, err)
|
||||
}
|
||||
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 {
|
||||
if vals, ok := s.Label[wantKey]; ok {
|
||||
for _, rx := range rfx {
|
||||
for _, val := range vals {
|
||||
if rx.MatchString(val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseTagFilterRange returns a function to checks if a value is
|
||||
// contained on the range described by a string. It can recognize
|
||||
// strings of the form:
|
||||
// "32kb" -- matches values == 32kb
|
||||
// ":64kb" -- matches values <= 64kb
|
||||
// "4mb:" -- matches values >= 4mb
|
||||
// "12kb:64mb" -- matches values between 12kb and 64mb (both included).
|
||||
func parseTagFilterRange(filter string) func(int64, string) bool {
|
||||
ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2)
|
||||
if len(ranges) == 0 {
|
||||
return nil // No ranges were identified
|
||||
}
|
||||
v, err := strconv.ParseInt(ranges[0][1], 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse int %s: %v", ranges[0][1], err))
|
||||
}
|
||||
scaledValue, unit := measurement.Scale(v, ranges[0][2], ranges[0][2])
|
||||
if len(ranges) == 1 {
|
||||
switch match := ranges[0][0]; filter {
|
||||
case match:
|
||||
return func(v int64, u string) bool {
|
||||
sv, su := measurement.Scale(v, u, unit)
|
||||
return su == unit && sv == scaledValue
|
||||
}
|
||||
case match + ":":
|
||||
return func(v int64, u string) bool {
|
||||
sv, su := measurement.Scale(v, u, unit)
|
||||
return su == unit && sv >= scaledValue
|
||||
}
|
||||
case ":" + match:
|
||||
return func(v int64, u string) bool {
|
||||
sv, su := measurement.Scale(v, u, unit)
|
||||
return su == unit && sv <= scaledValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if filter != ranges[0][0]+":"+ranges[1][0] {
|
||||
return nil
|
||||
}
|
||||
if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil {
|
||||
panic(fmt.Errorf("failed to parse int %s: %v", ranges[1][1], err))
|
||||
}
|
||||
scaledValue2, unit2 := measurement.Scale(v, ranges[1][2], unit)
|
||||
if unit != unit2 {
|
||||
return nil
|
||||
}
|
||||
return func(v int64, u string) bool {
|
||||
sv, su := measurement.Scale(v, u, unit)
|
||||
return su == unit && sv >= scaledValue && sv <= scaledValue2
|
||||
}
|
||||
}
|
||||
|
||||
func warnNoMatches(match bool, option string, ui plugin.UI) {
|
||||
if !match {
|
||||
ui.PrintErr(option + " expression matched no samples")
|
||||
}
|
||||
}
|
1621
vendor/github.com/google/pprof/internal/driver/driver_test.go
generated
vendored
Normal file
1621
vendor/github.com/google/pprof/internal/driver/driver_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
587
vendor/github.com/google/pprof/internal/driver/fetch.go
generated
vendored
Normal file
587
vendor/github.com/google/pprof/internal/driver/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,587 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/pprof/internal/measurement"
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
// fetchProfiles fetches and symbolizes the profiles specified by s.
|
||||
// It will merge all the profiles it is able to retrieve, even if
|
||||
// there are some failures. It will return an error if it is unable to
|
||||
// fetch any profiles.
|
||||
func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
|
||||
sources := make([]profileSource, 0, len(s.Sources))
|
||||
for _, src := range s.Sources {
|
||||
sources = append(sources, profileSource{
|
||||
addr: src,
|
||||
source: s,
|
||||
})
|
||||
}
|
||||
|
||||
bases := make([]profileSource, 0, len(s.Base))
|
||||
for _, src := range s.Base {
|
||||
bases = append(bases, profileSource{
|
||||
addr: src,
|
||||
source: s,
|
||||
})
|
||||
}
|
||||
|
||||
p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI, o.HTTPTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pbase != nil {
|
||||
if s.DiffBase {
|
||||
pbase.SetLabel("pprof::base", []string{"true"})
|
||||
}
|
||||
if s.Normalize {
|
||||
err := p.Normalize(pbase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pbase.Scale(-1)
|
||||
p, m, err = combineProfiles([]*profile.Profile{p, pbase}, []plugin.MappingSources{m, mbase})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Symbolize the merged profile.
|
||||
if err := o.Sym.Symbolize(s.Symbolize, m, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.RemoveUninteresting()
|
||||
unsourceMappings(p)
|
||||
|
||||
if s.Comment != "" {
|
||||
p.Comments = append(p.Comments, s.Comment)
|
||||
}
|
||||
|
||||
// Save a copy of the merged profile if there is at least one remote source.
|
||||
if save {
|
||||
dir, err := setTmpDir(o.UI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefix := "pprof."
|
||||
if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
|
||||
prefix += filepath.Base(p.Mapping[0].File) + "."
|
||||
}
|
||||
for _, s := range p.SampleType {
|
||||
prefix += s.Type + "."
|
||||
}
|
||||
|
||||
tempFile, err := newTempFile(dir, prefix, ".pb.gz")
|
||||
if err == nil {
|
||||
if err = p.Write(tempFile); err == nil {
|
||||
o.UI.PrintErr("Saved profile in ", tempFile.Name())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
o.UI.PrintErr("Could not save profile: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.CheckValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*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, tr)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui, tr)
|
||||
}()
|
||||
wg.Wait()
|
||||
save := savesrc || savebase
|
||||
|
||||
if errsrc != nil {
|
||||
return nil, nil, nil, nil, false, fmt.Errorf("problem fetching source profiles: %v", errsrc)
|
||||
}
|
||||
if errbase != nil {
|
||||
return nil, nil, nil, nil, false, fmt.Errorf("problem fetching base profiles: %v,", errbase)
|
||||
}
|
||||
if countsrc == 0 {
|
||||
return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any source profiles")
|
||||
}
|
||||
if countbase == 0 && len(bases) > 0 {
|
||||
return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any base profiles")
|
||||
}
|
||||
if want, got := len(sources), countsrc; want != got {
|
||||
ui.PrintErr(fmt.Sprintf("Fetched %d source profiles out of %d", got, want))
|
||||
}
|
||||
if want, got := len(bases), countbase; want != got {
|
||||
ui.PrintErr(fmt.Sprintf("Fetched %d base profiles out of %d", got, want))
|
||||
}
|
||||
|
||||
return psrc, pbase, msrc, mbase, save, nil
|
||||
}
|
||||
|
||||
// chunkedGrab fetches the profiles described in source and merges them into
|
||||
// a single profile. It fetches a chunk of profiles concurrently, with a maximum
|
||||
// chunk size to limit its memory usage.
|
||||
func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, plugin.MappingSources, bool, int, error) {
|
||||
const chunkSize = 64
|
||||
|
||||
var p *profile.Profile
|
||||
var msrc plugin.MappingSources
|
||||
var save bool
|
||||
var count int
|
||||
|
||||
for start := 0; start < len(sources); start += chunkSize {
|
||||
end := start + chunkSize
|
||||
if end > len(sources) {
|
||||
end = len(sources)
|
||||
}
|
||||
chunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui, tr)
|
||||
switch {
|
||||
case chunkErr != nil:
|
||||
return nil, nil, false, 0, chunkErr
|
||||
case chunkP == nil:
|
||||
continue
|
||||
case p == nil:
|
||||
p, msrc, save, count = chunkP, chunkMsrc, chunkSave, chunkCount
|
||||
default:
|
||||
p, msrc, chunkErr = combineProfiles([]*profile.Profile{p, chunkP}, []plugin.MappingSources{msrc, chunkMsrc})
|
||||
if chunkErr != nil {
|
||||
return nil, nil, false, 0, chunkErr
|
||||
}
|
||||
if chunkSave {
|
||||
save = true
|
||||
}
|
||||
count += chunkCount
|
||||
}
|
||||
}
|
||||
|
||||
return p, msrc, save, count, nil
|
||||
}
|
||||
|
||||
// concurrentGrab fetches multiple profiles concurrently
|
||||
func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, plugin.MappingSources, bool, int, error) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(sources))
|
||||
for i := range sources {
|
||||
go func(s *profileSource) {
|
||||
defer wg.Done()
|
||||
s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui, tr)
|
||||
}(&sources[i])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var save bool
|
||||
profiles := make([]*profile.Profile, 0, len(sources))
|
||||
msrcs := make([]plugin.MappingSources, 0, len(sources))
|
||||
for i := range sources {
|
||||
s := &sources[i]
|
||||
if err := s.err; err != nil {
|
||||
ui.PrintErr(s.addr + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
save = save || s.remote
|
||||
profiles = append(profiles, s.p)
|
||||
msrcs = append(msrcs, s.msrc)
|
||||
*s = profileSource{}
|
||||
}
|
||||
|
||||
if len(profiles) == 0 {
|
||||
return nil, nil, false, 0, nil
|
||||
}
|
||||
|
||||
p, msrc, err := combineProfiles(profiles, msrcs)
|
||||
if err != nil {
|
||||
return nil, nil, false, 0, err
|
||||
}
|
||||
return p, msrc, save, len(profiles), nil
|
||||
}
|
||||
|
||||
func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) (*profile.Profile, plugin.MappingSources, error) {
|
||||
// Merge profiles.
|
||||
if err := measurement.ScaleProfiles(profiles); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
p, err := profile.Merge(profiles)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Combine mapping sources.
|
||||
msrc := make(plugin.MappingSources)
|
||||
for _, ms := range msrcs {
|
||||
for m, s := range ms {
|
||||
msrc[m] = append(msrc[m], s...)
|
||||
}
|
||||
}
|
||||
return p, msrc, nil
|
||||
}
|
||||
|
||||
type profileSource struct {
|
||||
addr string
|
||||
source *source
|
||||
|
||||
p *profile.Profile
|
||||
msrc plugin.MappingSources
|
||||
remote bool
|
||||
err error
|
||||
}
|
||||
|
||||
func homeEnv() string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return "USERPROFILE"
|
||||
case "plan9":
|
||||
return "home"
|
||||
default:
|
||||
return "HOME"
|
||||
}
|
||||
}
|
||||
|
||||
// setTmpDir prepares the directory to use to save profiles retrieved
|
||||
// remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof, and, if
|
||||
// $HOME is not set, falls back to os.TempDir().
|
||||
func setTmpDir(ui plugin.UI) (string, error) {
|
||||
var dirs []string
|
||||
if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" {
|
||||
dirs = append(dirs, profileDir)
|
||||
}
|
||||
if homeDir := os.Getenv(homeEnv()); homeDir != "" {
|
||||
dirs = append(dirs, filepath.Join(homeDir, "pprof"))
|
||||
}
|
||||
dirs = append(dirs, os.TempDir())
|
||||
for _, tmpDir := range dirs {
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
|
||||
continue
|
||||
}
|
||||
return tmpDir, nil
|
||||
}
|
||||
return "", fmt.Errorf("failed to identify temp dir")
|
||||
}
|
||||
|
||||
const testSourceAddress = "pproftest.local"
|
||||
|
||||
// grabProfile fetches a profile. Returns the profile, sources for the
|
||||
// profile mappings, a bool indicating if the profile was fetched
|
||||
// remotely, and an error.
|
||||
func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
|
||||
var src string
|
||||
duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
|
||||
if fetcher != nil {
|
||||
p, src, err = fetcher.Fetch(source, duration, timeout)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil || p == nil {
|
||||
// Fetch the profile over HTTP or from a file.
|
||||
p, src, err = fetch(source, duration, timeout, ui, tr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.CheckValid(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Update the binary locations from command line and paths.
|
||||
locateBinaries(p, s, obj, ui)
|
||||
|
||||
// Collect the source URL for all mappings.
|
||||
if src != "" {
|
||||
msrc = collectMappingSources(p, src)
|
||||
remote = true
|
||||
if strings.HasPrefix(src, "http://"+testSourceAddress) {
|
||||
// Treat test inputs as local to avoid saving
|
||||
// testcase profiles during driver testing.
|
||||
remote = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// collectMappingSources saves the mapping sources of a profile.
|
||||
func collectMappingSources(p *profile.Profile, source string) plugin.MappingSources {
|
||||
ms := plugin.MappingSources{}
|
||||
for _, m := range p.Mapping {
|
||||
src := struct {
|
||||
Source string
|
||||
Start uint64
|
||||
}{
|
||||
source, m.Start,
|
||||
}
|
||||
key := m.BuildID
|
||||
if key == "" {
|
||||
key = m.File
|
||||
}
|
||||
if key == "" {
|
||||
// If there is no build id or source file, use the source as the
|
||||
// mapping file. This will enable remote symbolization for this
|
||||
// mapping, in particular for Go profiles on the legacy format.
|
||||
// The source is reset back to empty string by unsourceMapping
|
||||
// which is called after symbolization is finished.
|
||||
m.File = source
|
||||
key = source
|
||||
}
|
||||
ms[key] = append(ms[key], src)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
// unsourceMappings iterates over the mappings in a profile and replaces file
|
||||
// set to the remote source URL by collectMappingSources back to empty string.
|
||||
func unsourceMappings(p *profile.Profile) {
|
||||
for _, m := range p.Mapping {
|
||||
if m.BuildID == "" {
|
||||
if u, err := url.Parse(m.File); err == nil && u.IsAbs() {
|
||||
m.File = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// locateBinaries searches for binary files listed in the profile and, if found,
|
||||
// updates the profile accordingly.
|
||||
func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) {
|
||||
// Construct search path to examine
|
||||
searchPath := os.Getenv("PPROF_BINARY_PATH")
|
||||
if searchPath == "" {
|
||||
// Use $HOME/pprof/binaries as default directory for local symbolization binaries
|
||||
searchPath = filepath.Join(os.Getenv(homeEnv()), "pprof", "binaries")
|
||||
}
|
||||
mapping:
|
||||
for _, m := range p.Mapping {
|
||||
var baseName string
|
||||
if m.File != "" {
|
||||
baseName = filepath.Base(m.File)
|
||||
}
|
||||
|
||||
for _, path := range filepath.SplitList(searchPath) {
|
||||
var fileNames []string
|
||||
if m.BuildID != "" {
|
||||
fileNames = []string{filepath.Join(path, m.BuildID, baseName)}
|
||||
if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil {
|
||||
fileNames = append(fileNames, matches...)
|
||||
}
|
||||
fileNames = append(fileNames, filepath.Join(path, m.File, m.BuildID)) // perf path format
|
||||
}
|
||||
if m.File != "" {
|
||||
// Try both the basename and the full path, to support the same directory
|
||||
// structure as the perf symfs option.
|
||||
if baseName != "" {
|
||||
fileNames = append(fileNames, filepath.Join(path, baseName))
|
||||
}
|
||||
fileNames = append(fileNames, filepath.Join(path, m.File))
|
||||
}
|
||||
for _, name := range fileNames {
|
||||
if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil {
|
||||
defer f.Close()
|
||||
fileBuildID := f.BuildID()
|
||||
if m.BuildID != "" && m.BuildID != fileBuildID {
|
||||
ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")")
|
||||
} else {
|
||||
m.File = name
|
||||
continue mapping
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p.Mapping) == 0 {
|
||||
// If there are no mappings, add a fake mapping to attempt symbolization.
|
||||
// This is useful for some profiles generated by the golang runtime, which
|
||||
// do not include any mappings. Symbolization with a fake mapping will only
|
||||
// be successful against a non-PIE binary.
|
||||
m := &profile.Mapping{ID: 1}
|
||||
p.Mapping = []*profile.Mapping{m}
|
||||
for _, l := range p.Location {
|
||||
l.Mapping = m
|
||||
}
|
||||
}
|
||||
// Replace executable filename/buildID with the overrides from source.
|
||||
// Assumes the executable is the first Mapping entry.
|
||||
if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" {
|
||||
m := p.Mapping[0]
|
||||
if execName != "" {
|
||||
m.File = execName
|
||||
}
|
||||
if buildID != "" {
|
||||
m.BuildID = buildID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetch fetches a profile from source, within the timeout specified,
|
||||
// producing messages through the ui. It returns the profile and the
|
||||
// url of the actual source of the profile for remote profiles.
|
||||
func fetch(source string, duration, timeout time.Duration, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, src string, err error) {
|
||||
var f io.ReadCloser
|
||||
|
||||
if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
|
||||
ui.Print("Fetching profile over HTTP from " + sourceURL)
|
||||
if duration > 0 {
|
||||
ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
|
||||
}
|
||||
f, err = fetchURL(sourceURL, timeout, tr)
|
||||
src = sourceURL
|
||||
} else if isPerfFile(source) {
|
||||
f, err = convertPerfData(source, ui)
|
||||
} else {
|
||||
f, err = os.Open(source)
|
||||
}
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
p, err = profile.Parse(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// fetchURL fetches a profile from a URL using HTTP.
|
||||
func fetchURL(source string, timeout time.Duration, tr http.RoundTripper) (io.ReadCloser, error) {
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: timeout + 5*time.Second,
|
||||
}
|
||||
resp, err := client.Get(source)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("http fetch: %v", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
defer resp.Body.Close()
|
||||
return nil, statusCodeError(resp)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
func statusCodeError(resp *http.Response) error {
|
||||
if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
|
||||
// error is from pprof endpoint
|
||||
if body, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||
return fmt.Errorf("server response: %s - %s", resp.Status, body)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("server response: %s", resp.Status)
|
||||
}
|
||||
|
||||
// isPerfFile checks if a file is in perf.data format. It also returns false
|
||||
// if it encounters an error during the check.
|
||||
func isPerfFile(path string) bool {
|
||||
sourceFile, openErr := os.Open(path)
|
||||
if openErr != nil {
|
||||
return false
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
// If the file is the output of a perf record command, it should begin
|
||||
// with the string PERFILE2.
|
||||
perfHeader := []byte("PERFILE2")
|
||||
actualHeader := make([]byte, len(perfHeader))
|
||||
if _, readErr := sourceFile.Read(actualHeader); readErr != nil {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(actualHeader, perfHeader)
|
||||
}
|
||||
|
||||
// convertPerfData converts the file at path which should be in perf.data format
|
||||
// using the perf_to_profile tool and returns the file containing the
|
||||
// profile.proto formatted data.
|
||||
func convertPerfData(perfPath string, ui plugin.UI) (*os.File, error) {
|
||||
ui.Print(fmt.Sprintf(
|
||||
"Converting %s to a profile.proto... (May take a few minutes)",
|
||||
perfPath))
|
||||
profile, err := newTempFile(os.TempDir(), "pprof_", ".pb.gz")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deferDeleteTempFile(profile.Name())
|
||||
cmd := exec.Command("perf_to_profile", "-i", perfPath, "-o", profile.Name(), "-f")
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
profile.Close()
|
||||
return nil, fmt.Errorf("failed to convert perf.data file. Try github.com/google/perf_data_converter: %v", err)
|
||||
}
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
// adjustURL validates if a profile source is a URL and returns an
|
||||
// cleaned up URL and the timeout to use for retrieval over HTTP.
|
||||
// If the source cannot be recognized as a URL it returns an empty string.
|
||||
func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
|
||||
u, err := url.Parse(source)
|
||||
if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
|
||||
// Try adding http:// to catch sources of the form hostname:port/path.
|
||||
// url.Parse treats "hostname" as the scheme.
|
||||
u, err = url.Parse("http://" + source)
|
||||
}
|
||||
if err != nil || u.Host == "" {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// Apply duration/timeout overrides to URL.
|
||||
values := u.Query()
|
||||
if duration > 0 {
|
||||
values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
|
||||
} else {
|
||||
if urlSeconds := values.Get("seconds"); urlSeconds != "" {
|
||||
if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
|
||||
duration = time.Duration(us) * time.Second
|
||||
}
|
||||
}
|
||||
}
|
||||
if timeout <= 0 {
|
||||
if duration > 0 {
|
||||
timeout = duration + duration/2
|
||||
} else {
|
||||
timeout = 60 * time.Second
|
||||
}
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
return u.String(), timeout
|
||||
}
|
758
vendor/github.com/google/pprof/internal/driver/fetch_test.go
generated
vendored
Normal file
758
vendor/github.com/google/pprof/internal/driver/fetch_test.go
generated
vendored
Normal file
@@ -0,0 +1,758 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/pprof/internal/binutils"
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/proftest"
|
||||
"github.com/google/pprof/internal/symbolizer"
|
||||
"github.com/google/pprof/internal/transport"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
func TestSymbolizationPath(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("test assumes Unix paths")
|
||||
}
|
||||
|
||||
// Save environment variables to restore after test
|
||||
saveHome := os.Getenv(homeEnv())
|
||||
savePath := os.Getenv("PPROF_BINARY_PATH")
|
||||
|
||||
tempdir, err := ioutil.TempDir("", "home")
|
||||
if err != nil {
|
||||
t.Fatal("creating temp dir: ", err)
|
||||
}
|
||||
defer os.RemoveAll(tempdir)
|
||||
os.MkdirAll(filepath.Join(tempdir, "pprof", "binaries", "abcde10001"), 0700)
|
||||
os.Create(filepath.Join(tempdir, "pprof", "binaries", "abcde10001", "binary"))
|
||||
|
||||
obj := testObj{tempdir}
|
||||
os.Setenv(homeEnv(), tempdir)
|
||||
for _, tc := range []struct {
|
||||
env, file, buildID, want string
|
||||
msgCount int
|
||||
}{
|
||||
{"", "/usr/bin/binary", "", "/usr/bin/binary", 0},
|
||||
{"", "/usr/bin/binary", "fedcb10000", "/usr/bin/binary", 0},
|
||||
{"/usr", "/bin/binary", "", "/usr/bin/binary", 0},
|
||||
{"", "/prod/path/binary", "abcde10001", filepath.Join(tempdir, "pprof/binaries/abcde10001/binary"), 0},
|
||||
{"/alternate/architecture", "/usr/bin/binary", "", "/alternate/architecture/binary", 0},
|
||||
{"/alternate/architecture", "/usr/bin/binary", "abcde10001", "/alternate/architecture/binary", 0},
|
||||
{"/nowhere:/alternate/architecture", "/usr/bin/binary", "fedcb10000", "/usr/bin/binary", 1},
|
||||
{"/nowhere:/alternate/architecture", "/usr/bin/binary", "abcde10002", "/usr/bin/binary", 1},
|
||||
} {
|
||||
os.Setenv("PPROF_BINARY_PATH", tc.env)
|
||||
p := &profile.Profile{
|
||||
Mapping: []*profile.Mapping{
|
||||
{
|
||||
File: tc.file,
|
||||
BuildID: tc.buildID,
|
||||
},
|
||||
},
|
||||
}
|
||||
s := &source{}
|
||||
locateBinaries(p, s, obj, &proftest.TestUI{T: t, Ignore: tc.msgCount})
|
||||
if file := p.Mapping[0].File; file != tc.want {
|
||||
t.Errorf("%s:%s:%s, want %s, got %s", tc.env, tc.file, tc.buildID, tc.want, file)
|
||||
}
|
||||
}
|
||||
os.Setenv(homeEnv(), saveHome)
|
||||
os.Setenv("PPROF_BINARY_PATH", savePath)
|
||||
}
|
||||
|
||||
func TestCollectMappingSources(t *testing.T) {
|
||||
const startAddress uint64 = 0x40000
|
||||
const url = "http://example.com"
|
||||
for _, tc := range []struct {
|
||||
file, buildID string
|
||||
want plugin.MappingSources
|
||||
}{
|
||||
{"/usr/bin/binary", "buildId", mappingSources("buildId", url, startAddress)},
|
||||
{"/usr/bin/binary", "", mappingSources("/usr/bin/binary", url, startAddress)},
|
||||
{"", "", mappingSources(url, url, startAddress)},
|
||||
} {
|
||||
p := &profile.Profile{
|
||||
Mapping: []*profile.Mapping{
|
||||
{
|
||||
File: tc.file,
|
||||
BuildID: tc.buildID,
|
||||
Start: startAddress,
|
||||
},
|
||||
},
|
||||
}
|
||||
got := collectMappingSources(p, url)
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("%s:%s, want %v, got %v", tc.file, tc.buildID, tc.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsourceMappings(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
file, buildID, want string
|
||||
}{
|
||||
{"/usr/bin/binary", "buildId", "/usr/bin/binary"},
|
||||
{"http://example.com", "", ""},
|
||||
} {
|
||||
p := &profile.Profile{
|
||||
Mapping: []*profile.Mapping{
|
||||
{
|
||||
File: tc.file,
|
||||
BuildID: tc.buildID,
|
||||
},
|
||||
},
|
||||
}
|
||||
unsourceMappings(p)
|
||||
if got := p.Mapping[0].File; got != tc.want {
|
||||
t.Errorf("%s:%s, want %s, got %s", tc.file, tc.buildID, tc.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testObj struct {
|
||||
home string
|
||||
}
|
||||
|
||||
func (o testObj) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||
switch file {
|
||||
case "/alternate/architecture/binary":
|
||||
return testFile{file, "abcde10001"}, nil
|
||||
case "/usr/bin/binary":
|
||||
return testFile{file, "fedcb10000"}, nil
|
||||
case filepath.Join(o.home, "pprof/binaries/abcde10001/binary"):
|
||||
return testFile{file, "abcde10001"}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not found: %s", file)
|
||||
}
|
||||
func (testObj) Demangler(_ string) func(names []string) (map[string]string, error) {
|
||||
return func(names []string) (map[string]string, error) { return nil, nil }
|
||||
}
|
||||
func (testObj) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { return nil, nil }
|
||||
|
||||
type testFile struct{ name, buildID string }
|
||||
|
||||
func (f testFile) Name() string { return f.name }
|
||||
func (testFile) Base() uint64 { return 0 }
|
||||
func (f testFile) BuildID() string { return f.buildID }
|
||||
func (testFile) SourceLine(addr uint64) ([]plugin.Frame, error) { return nil, nil }
|
||||
func (testFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { return nil, nil }
|
||||
func (testFile) Close() error { return nil }
|
||||
|
||||
func TestFetch(t *testing.T) {
|
||||
const path = "testdata/"
|
||||
type testcase struct {
|
||||
source, execName string
|
||||
}
|
||||
|
||||
for _, tc := range []testcase{
|
||||
{path + "go.crc32.cpu", ""},
|
||||
{path + "go.nomappings.crash", "/bin/gotest.exe"},
|
||||
{"http://localhost/profile?file=cppbench.cpu", ""},
|
||||
} {
|
||||
p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t}, &httpTransport{})
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %s", tc.source, err)
|
||||
}
|
||||
if len(p.Sample) == 0 {
|
||||
t.Errorf("%s: want non-zero samples", tc.source)
|
||||
}
|
||||
if e := tc.execName; e != "" {
|
||||
switch {
|
||||
case len(p.Mapping) == 0 || p.Mapping[0] == nil:
|
||||
t.Errorf("%s: want mapping[0].execName == %s, got no mappings", tc.source, e)
|
||||
case p.Mapping[0].File != e:
|
||||
t.Errorf("%s: want mapping[0].execName == %s, got %s", tc.source, e, p.Mapping[0].File)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchWithBase(t *testing.T) {
|
||||
baseVars := pprofVariables
|
||||
defer func() { pprofVariables = baseVars }()
|
||||
|
||||
type WantSample struct {
|
||||
values []int64
|
||||
labels map[string][]string
|
||||
}
|
||||
|
||||
const path = "testdata/"
|
||||
type testcase struct {
|
||||
desc string
|
||||
sources []string
|
||||
bases []string
|
||||
diffBases []string
|
||||
normalize bool
|
||||
wantSamples []WantSample
|
||||
wantErrorMsg string
|
||||
}
|
||||
|
||||
testcases := []testcase{
|
||||
{
|
||||
"not normalized base is same as source",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention"},
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"not normalized base is same as source",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention"},
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"not normalized single source, multiple base (all profiles same)",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention", path + "cppbench.contention"},
|
||||
nil,
|
||||
false,
|
||||
[]WantSample{
|
||||
{
|
||||
values: []int64{-2700, -608881724},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{-100, -23992},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{-200, -179943},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{-100, -17778444},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{-100, -75976},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{-300, -63568134},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"not normalized, different base and source",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.small.contention"},
|
||||
nil,
|
||||
false,
|
||||
[]WantSample{
|
||||
{
|
||||
values: []int64{1700, 608878600},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{100, 23992},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{200, 179943},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{100, 17778444},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{100, 75976},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{300, 63568134},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"normalized base is same as source",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention"},
|
||||
nil,
|
||||
true,
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"normalized single source, multiple base (all profiles same)",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention", path + "cppbench.contention"},
|
||||
nil,
|
||||
true,
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"normalized different base and source",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.small.contention"},
|
||||
nil,
|
||||
true,
|
||||
[]WantSample{
|
||||
{
|
||||
values: []int64{-229, -370},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{28, 0},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{57, 0},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{28, 80},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{28, 0},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{85, 287},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"not normalized diff base is same as source",
|
||||
[]string{path + "cppbench.contention"},
|
||||
nil,
|
||||
[]string{path + "cppbench.contention"},
|
||||
false,
|
||||
[]WantSample{
|
||||
{
|
||||
values: []int64{2700, 608881724},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{100, 23992},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{200, 179943},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{100, 17778444},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{100, 75976},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{300, 63568134},
|
||||
labels: map[string][]string{},
|
||||
},
|
||||
{
|
||||
values: []int64{-2700, -608881724},
|
||||
labels: map[string][]string{"pprof::base": {"true"}},
|
||||
},
|
||||
{
|
||||
values: []int64{-100, -23992},
|
||||
labels: map[string][]string{"pprof::base": {"true"}},
|
||||
},
|
||||
{
|
||||
values: []int64{-200, -179943},
|
||||
labels: map[string][]string{"pprof::base": {"true"}},
|
||||
},
|
||||
{
|
||||
values: []int64{-100, -17778444},
|
||||
labels: map[string][]string{"pprof::base": {"true"}},
|
||||
},
|
||||
{
|
||||
values: []int64{-100, -75976},
|
||||
labels: map[string][]string{"pprof::base": {"true"}},
|
||||
},
|
||||
{
|
||||
values: []int64{-300, -63568134},
|
||||
labels: map[string][]string{"pprof::base": {"true"}},
|
||||
},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"diff_base and base both specified",
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention"},
|
||||
[]string{path + "cppbench.contention"},
|
||||
false,
|
||||
nil,
|
||||
"-base and -diff_base flags cannot both be specified",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
pprofVariables = baseVars.makeCopy()
|
||||
f := testFlags{
|
||||
stringLists: map[string][]string{
|
||||
"base": tc.bases,
|
||||
"diff_base": tc.diffBases,
|
||||
},
|
||||
bools: map[string]bool{
|
||||
"normalize": tc.normalize,
|
||||
},
|
||||
}
|
||||
f.args = tc.sources
|
||||
|
||||
o := setDefaults(&plugin.Options{
|
||||
UI: &proftest.TestUI{T: t, AllowRx: "Local symbolization failed|Some binary filenames not available"},
|
||||
Flagset: f,
|
||||
HTTPTransport: transport.New(nil),
|
||||
})
|
||||
src, _, err := parseFlags(o)
|
||||
|
||||
if tc.wantErrorMsg != "" {
|
||||
if err == nil {
|
||||
t.Fatalf("got nil, want error %q", tc.wantErrorMsg)
|
||||
}
|
||||
|
||||
if gotErrMsg := err.Error(); gotErrMsg != tc.wantErrorMsg {
|
||||
t.Fatalf("got error %q, want error %q", gotErrMsg, tc.wantErrorMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("got error %q, want no error", err)
|
||||
}
|
||||
|
||||
p, err := fetchProfiles(src, o)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("got error %q, want no error", err)
|
||||
}
|
||||
|
||||
if got, want := len(p.Sample), len(tc.wantSamples); got != want {
|
||||
t.Fatalf("got %d samples want %d", got, want)
|
||||
}
|
||||
|
||||
for i, sample := range p.Sample {
|
||||
if !reflect.DeepEqual(tc.wantSamples[i].values, sample.Value) {
|
||||
t.Errorf("for sample %d got values %v, want %v", i, sample.Value, tc.wantSamples[i])
|
||||
}
|
||||
if !reflect.DeepEqual(tc.wantSamples[i].labels, sample.Label) {
|
||||
t.Errorf("for sample %d got labels %v, want %v", i, sample.Label, tc.wantSamples[i].labels)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mappingSources creates MappingSources map with a single item.
|
||||
func mappingSources(key, source string, start uint64) plugin.MappingSources {
|
||||
return plugin.MappingSources{
|
||||
key: []struct {
|
||||
Source string
|
||||
Start uint64
|
||||
}{
|
||||
{Source: source, Start: start},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type httpTransport struct{}
|
||||
|
||||
func (tr *httpTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
values := req.URL.Query()
|
||||
file := values.Get("file")
|
||||
|
||||
if file == "" {
|
||||
return nil, fmt.Errorf("want .../file?profile, got %s", req.URL.String())
|
||||
}
|
||||
|
||||
t := &http.Transport{}
|
||||
t.RegisterProtocol("file", http.NewFileTransport(http.Dir("testdata/")))
|
||||
|
||||
c := &http.Client{Transport: t}
|
||||
return c.Get("file:///" + file)
|
||||
}
|
||||
|
||||
func closedError() string {
|
||||
if runtime.GOOS == "plan9" {
|
||||
return "listen hungup"
|
||||
}
|
||||
return "use of closed"
|
||||
}
|
||||
|
||||
func TestHTTPSInsecure(t *testing.T) {
|
||||
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
||||
t.Skip("test assumes tcp available")
|
||||
}
|
||||
saveHome := os.Getenv(homeEnv())
|
||||
tempdir, err := ioutil.TempDir("", "home")
|
||||
if err != nil {
|
||||
t.Fatal("creating temp dir: ", err)
|
||||
}
|
||||
defer os.RemoveAll(tempdir)
|
||||
|
||||
// pprof writes to $HOME/pprof by default which is not necessarily
|
||||
// writeable (e.g. on a Debian buildd) so set $HOME to something we
|
||||
// know we can write to for the duration of the test.
|
||||
os.Setenv(homeEnv(), tempdir)
|
||||
defer os.Setenv(homeEnv(), saveHome)
|
||||
|
||||
baseVars := pprofVariables
|
||||
pprofVariables = baseVars.makeCopy()
|
||||
defer func() { pprofVariables = baseVars }()
|
||||
|
||||
tlsCert, _, _ := selfSignedCert(t, "")
|
||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
|
||||
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, closedError(); !strings.Contains(got.Error(), want) {
|
||||
t.Fatalf("Serve got error %v, want %q", got, want)
|
||||
}
|
||||
}()
|
||||
defer l.Close()
|
||||
|
||||
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/goroutine"
|
||||
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"},
|
||||
HTTPTransport: transport.New(nil),
|
||||
}
|
||||
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"); err != nil {
|
||||
t.Fatalf("fetchProfiles(%s) %v", address, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPSWithServerCertFetch(t *testing.T) {
|
||||
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
||||
t.Skip("test assumes tcp available")
|
||||
}
|
||||
saveHome := os.Getenv(homeEnv())
|
||||
tempdir, err := ioutil.TempDir("", "home")
|
||||
if err != nil {
|
||||
t.Fatal("creating temp dir: ", err)
|
||||
}
|
||||
defer os.RemoveAll(tempdir)
|
||||
|
||||
// pprof writes to $HOME/pprof by default which is not necessarily
|
||||
// writeable (e.g. on a Debian buildd) so set $HOME to something we
|
||||
// know we can write to for the duration of the test.
|
||||
os.Setenv(homeEnv(), tempdir)
|
||||
defer os.Setenv(homeEnv(), saveHome)
|
||||
|
||||
baseVars := pprofVariables
|
||||
pprofVariables = baseVars.makeCopy()
|
||||
defer func() { pprofVariables = baseVars }()
|
||||
|
||||
cert, certBytes, keyBytes := selfSignedCert(t, "localhost")
|
||||
cas := x509.NewCertPool()
|
||||
cas.AppendCertsFromPEM(certBytes)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: cas,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: cas,
|
||||
}
|
||||
|
||||
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, closedError(); !strings.Contains(got.Error(), want) {
|
||||
t.Fatalf("Serve got error %v, want %q", got, want)
|
||||
}
|
||||
}()
|
||||
defer l.Close()
|
||||
|
||||
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()
|
||||
|
||||
// Get port from the address, so request to the server can be made using
|
||||
// the host name specified in certificates.
|
||||
_, portStr, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("cannot get port from URL: %v", err)
|
||||
}
|
||||
address := "https://" + "localhost:" + portStr + "/debug/pprof/goroutine"
|
||||
s := &source{
|
||||
Sources: []string{address},
|
||||
Seconds: 10,
|
||||
Timeout: 10,
|
||||
Symbolize: "remote",
|
||||
}
|
||||
|
||||
certTempFile, err := ioutil.TempFile("", "cert_output")
|
||||
if err != nil {
|
||||
t.Errorf("cannot create cert tempfile: %v", err)
|
||||
}
|
||||
defer os.Remove(certTempFile.Name())
|
||||
defer certTempFile.Close()
|
||||
certTempFile.Write(certBytes)
|
||||
|
||||
keyTempFile, err := ioutil.TempFile("", "key_output")
|
||||
if err != nil {
|
||||
t.Errorf("cannot create key tempfile: %v", err)
|
||||
}
|
||||
defer os.Remove(keyTempFile.Name())
|
||||
defer keyTempFile.Close()
|
||||
keyTempFile.Write(keyBytes)
|
||||
|
||||
f := &testFlags{
|
||||
strings: map[string]string{
|
||||
"tls_cert": certTempFile.Name(),
|
||||
"tls_key": keyTempFile.Name(),
|
||||
"tls_ca": certTempFile.Name(),
|
||||
},
|
||||
}
|
||||
o := &plugin.Options{
|
||||
Obj: &binutils.Binutils{},
|
||||
UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
|
||||
Flagset: f,
|
||||
HTTPTransport: transport.New(f),
|
||||
}
|
||||
|
||||
o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI, Transport: o.HTTPTransport}
|
||||
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, "TestHTTPSWithServerCertFetch"); err != nil {
|
||||
t.Fatalf("fetchProfiles(%s) %v", address, err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// selfSignedCert generates a self-signed certificate, and returns the
|
||||
// generated certificate, and byte arrays containing the certificate and
|
||||
// key associated with the certificate.
|
||||
func selfSignedCert(t *testing.T, host string) (tls.Certificate, []byte, []byte) {
|
||||
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),
|
||||
IsCA: true,
|
||||
DNSNames: []string{host},
|
||||
}
|
||||
|
||||
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, bc, bk
|
||||
}
|
92
vendor/github.com/google/pprof/internal/driver/flags.go
generated
vendored
Normal file
92
vendor/github.com/google/pprof/internal/driver/flags.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2018 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 (
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GoFlags implements the plugin.FlagSet interface.
|
||||
type GoFlags struct {
|
||||
UsageMsgs []string
|
||||
}
|
||||
|
||||
// Bool implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) Bool(o string, d bool, c string) *bool {
|
||||
return flag.Bool(o, d, c)
|
||||
}
|
||||
|
||||
// Int implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) Int(o string, d int, c string) *int {
|
||||
return flag.Int(o, d, c)
|
||||
}
|
||||
|
||||
// Float64 implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) Float64(o string, d float64, c string) *float64 {
|
||||
return flag.Float64(o, d, c)
|
||||
}
|
||||
|
||||
// String implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) String(o, d, c string) *string {
|
||||
return flag.String(o, d, c)
|
||||
}
|
||||
|
||||
// BoolVar implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) BoolVar(b *bool, o string, d bool, c string) {
|
||||
flag.BoolVar(b, o, d, c)
|
||||
}
|
||||
|
||||
// IntVar implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) IntVar(i *int, o string, d int, c string) {
|
||||
flag.IntVar(i, o, d, c)
|
||||
}
|
||||
|
||||
// Float64Var implements the plugin.FlagSet interface.
|
||||
// the value of the flag.
|
||||
func (*GoFlags) Float64Var(f *float64, o string, d float64, c string) {
|
||||
flag.Float64Var(f, o, d, c)
|
||||
}
|
||||
|
||||
// StringVar implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) StringVar(s *string, o, d, c string) {
|
||||
flag.StringVar(s, o, d, c)
|
||||
}
|
||||
|
||||
// StringList implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) StringList(o, d, c string) *[]*string {
|
||||
return &[]*string{flag.String(o, d, c)}
|
||||
}
|
||||
|
||||
// ExtraUsage implements the plugin.FlagSet interface.
|
||||
func (f *GoFlags) ExtraUsage() string {
|
||||
return strings.Join(f.UsageMsgs, "\n")
|
||||
}
|
||||
|
||||
// AddExtraUsage implements the plugin.FlagSet interface.
|
||||
func (f *GoFlags) AddExtraUsage(eu string) {
|
||||
f.UsageMsgs = append(f.UsageMsgs, eu)
|
||||
}
|
||||
|
||||
// Parse implements the plugin.FlagSet interface.
|
||||
func (*GoFlags) Parse(usage func()) []string {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
usage()
|
||||
}
|
||||
return args
|
||||
}
|
103
vendor/github.com/google/pprof/internal/driver/flamegraph.go
generated
vendored
Normal file
103
vendor/github.com/google/pprof/internal/driver/flamegraph.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/internal/graph"
|
||||
"github.com/google/pprof/internal/measurement"
|
||||
"github.com/google/pprof/internal/report"
|
||||
)
|
||||
|
||||
type treeNode struct {
|
||||
Name string `json:"n"`
|
||||
FullName string `json:"f"`
|
||||
Cum int64 `json:"v"`
|
||||
CumFormat string `json:"l"`
|
||||
Percent string `json:"p"`
|
||||
Children []*treeNode `json:"c"`
|
||||
}
|
||||
|
||||
// flamegraph generates a web page containing a flamegraph.
|
||||
func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
|
||||
// Force the call tree so that the graph is a tree.
|
||||
// Also do not trim the tree so that the flame graph contains all functions.
|
||||
rpt, errList := ui.makeReport(w, req, []string{"svg"}, "call_tree", "true", "trim", "false")
|
||||
if rpt == nil {
|
||||
return // error already reported
|
||||
}
|
||||
|
||||
// Generate dot graph.
|
||||
g, config := report.GetDOT(rpt)
|
||||
var nodes []*treeNode
|
||||
nroots := 0
|
||||
rootValue := int64(0)
|
||||
nodeArr := []string{}
|
||||
nodeMap := map[*graph.Node]*treeNode{}
|
||||
// Make all nodes and the map, collect the roots.
|
||||
for _, n := range g.Nodes {
|
||||
v := n.CumValue()
|
||||
fullName := n.Info.PrintableName()
|
||||
node := &treeNode{
|
||||
Name: graph.ShortenFunctionName(fullName),
|
||||
FullName: fullName,
|
||||
Cum: v,
|
||||
CumFormat: config.FormatValue(v),
|
||||
Percent: strings.TrimSpace(measurement.Percentage(v, config.Total)),
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
if len(n.In) == 0 {
|
||||
nodes[nroots], nodes[len(nodes)-1] = nodes[len(nodes)-1], nodes[nroots]
|
||||
nroots++
|
||||
rootValue += v
|
||||
}
|
||||
nodeMap[n] = node
|
||||
// Get all node names into an array.
|
||||
nodeArr = append(nodeArr, n.Info.Name)
|
||||
}
|
||||
// Populate the child links.
|
||||
for _, n := range g.Nodes {
|
||||
node := nodeMap[n]
|
||||
for child := range n.Out {
|
||||
node.Children = append(node.Children, nodeMap[child])
|
||||
}
|
||||
}
|
||||
|
||||
rootNode := &treeNode{
|
||||
Name: "root",
|
||||
FullName: "root",
|
||||
Cum: rootValue,
|
||||
CumFormat: config.FormatValue(rootValue),
|
||||
Percent: strings.TrimSpace(measurement.Percentage(rootValue, config.Total)),
|
||||
Children: nodes[0:nroots],
|
||||
}
|
||||
|
||||
// JSON marshalling flame graph
|
||||
b, err := json.Marshal(rootNode)
|
||||
if err != nil {
|
||||
http.Error(w, "error serializing flame graph", http.StatusInternalServerError)
|
||||
ui.options.UI.PrintErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
ui.render(w, "flamegraph", rpt, errList, config.Labels, webArgs{
|
||||
FlameGraph: template.JS(b),
|
||||
Nodes: nodeArr,
|
||||
})
|
||||
}
|
459
vendor/github.com/google/pprof/internal/driver/interactive.go
generated
vendored
Normal file
459
vendor/github.com/google/pprof/internal/driver/interactive.go
generated
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/report"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
var commentStart = "//:" // Sentinel for comments on options
|
||||
var tailDigitsRE = regexp.MustCompile("[0-9]+$")
|
||||
|
||||
// interactive starts a shell to read pprof commands.
|
||||
func interactive(p *profile.Profile, o *plugin.Options) error {
|
||||
// Enter command processing loop.
|
||||
o.UI.SetAutoComplete(newCompleter(functionNames(p)))
|
||||
pprofVariables.set("compact_labels", "true")
|
||||
pprofVariables["sample_index"].help += fmt.Sprintf("Or use sample_index=name, with name in %v.\n", sampleTypes(p))
|
||||
|
||||
// Do not wait for the visualizer to complete, to allow multiple
|
||||
// graphs to be visualized simultaneously.
|
||||
interactiveMode = true
|
||||
shortcuts := profileShortcuts(p)
|
||||
|
||||
// Get all groups in pprofVariables to allow for clearer error messages.
|
||||
groups := groupOptions(pprofVariables)
|
||||
|
||||
greetings(p, o.UI)
|
||||
for {
|
||||
input, err := o.UI.ReadLine("(pprof) ")
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if input == "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, input := range shortcuts.expand(input) {
|
||||
// Process assignments of the form variable=value
|
||||
if s := strings.SplitN(input, "=", 2); len(s) > 0 {
|
||||
name := strings.TrimSpace(s[0])
|
||||
var value string
|
||||
if len(s) == 2 {
|
||||
value = s[1]
|
||||
if comment := strings.LastIndex(value, commentStart); comment != -1 {
|
||||
value = value[:comment]
|
||||
}
|
||||
value = strings.TrimSpace(value)
|
||||
}
|
||||
if v := pprofVariables[name]; v != nil {
|
||||
if name == "sample_index" {
|
||||
// Error check sample_index=xxx to ensure xxx is a valid sample type.
|
||||
index, err := p.SampleIndexByName(value)
|
||||
if err != nil {
|
||||
o.UI.PrintErr(err)
|
||||
continue
|
||||
}
|
||||
value = p.SampleType[index].Type
|
||||
}
|
||||
if err := pprofVariables.set(name, value); err != nil {
|
||||
o.UI.PrintErr(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Allow group=variable syntax by converting into variable="".
|
||||
if v := pprofVariables[value]; v != nil && v.group == name {
|
||||
if err := pprofVariables.set(value, ""); err != nil {
|
||||
o.UI.PrintErr(err)
|
||||
}
|
||||
continue
|
||||
} else if okValues := groups[name]; okValues != nil {
|
||||
o.UI.PrintErr(fmt.Errorf("unrecognized value for %s: %q. Use one of %s", name, value, strings.Join(okValues, ", ")))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tokens := strings.Fields(input)
|
||||
if len(tokens) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch tokens[0] {
|
||||
case "o", "options":
|
||||
printCurrentOptions(p, o.UI)
|
||||
continue
|
||||
case "exit", "quit":
|
||||
return nil
|
||||
case "help":
|
||||
commandHelp(strings.Join(tokens[1:], " "), o.UI)
|
||||
continue
|
||||
}
|
||||
|
||||
args, vars, err := parseCommandLine(tokens)
|
||||
if err == nil {
|
||||
err = generateReportWrapper(p, args, vars, o)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
o.UI.PrintErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// groupOptions returns a map containing all non-empty groups
|
||||
// mapped to an array of the option names in that group in
|
||||
// sorted order.
|
||||
func groupOptions(vars variables) map[string][]string {
|
||||
groups := make(map[string][]string)
|
||||
for name, option := range vars {
|
||||
group := option.group
|
||||
if group != "" {
|
||||
groups[group] = append(groups[group], name)
|
||||
}
|
||||
}
|
||||
for _, names := range groups {
|
||||
sort.Strings(names)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
var generateReportWrapper = generateReport // For testing purposes.
|
||||
|
||||
// greetings prints a brief welcome and some overall profile
|
||||
// information before accepting interactive commands.
|
||||
func greetings(p *profile.Profile, ui plugin.UI) {
|
||||
numLabelUnits := identifyNumLabelUnits(p, ui)
|
||||
ropt, err := reportOptions(p, numLabelUnits, pprofVariables)
|
||||
if err == nil {
|
||||
rpt := report.New(p, ropt)
|
||||
ui.Print(strings.Join(report.ProfileLabels(rpt), "\n"))
|
||||
if rpt.Total() == 0 && len(p.SampleType) > 1 {
|
||||
ui.Print(`No samples were found with the default sample value type.`)
|
||||
ui.Print(`Try "sample_index" command to analyze different sample values.`, "\n")
|
||||
}
|
||||
}
|
||||
ui.Print(`Entering interactive mode (type "help" for commands, "o" for options)`)
|
||||
}
|
||||
|
||||
// shortcuts represents composite commands that expand into a sequence
|
||||
// of other commands.
|
||||
type shortcuts map[string][]string
|
||||
|
||||
func (a shortcuts) expand(input string) []string {
|
||||
input = strings.TrimSpace(input)
|
||||
if a != nil {
|
||||
if r, ok := a[input]; ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return []string{input}
|
||||
}
|
||||
|
||||
var pprofShortcuts = shortcuts{
|
||||
":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="},
|
||||
}
|
||||
|
||||
// profileShortcuts creates macros for convenience and backward compatibility.
|
||||
func profileShortcuts(p *profile.Profile) shortcuts {
|
||||
s := pprofShortcuts
|
||||
// Add shortcuts for sample types
|
||||
for _, st := range p.SampleType {
|
||||
command := fmt.Sprintf("sample_index=%s", st.Type)
|
||||
s[st.Type] = []string{command}
|
||||
s["total_"+st.Type] = []string{"mean=0", command}
|
||||
s["mean_"+st.Type] = []string{"mean=1", command}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func sampleTypes(p *profile.Profile) []string {
|
||||
types := make([]string, len(p.SampleType))
|
||||
for i, t := range p.SampleType {
|
||||
types[i] = t.Type
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
|
||||
var args []string
|
||||
type groupInfo struct {
|
||||
set string
|
||||
values []string
|
||||
}
|
||||
groups := make(map[string]*groupInfo)
|
||||
for n, o := range pprofVariables {
|
||||
v := o.stringValue()
|
||||
comment := ""
|
||||
if g := o.group; g != "" {
|
||||
gi, ok := groups[g]
|
||||
if !ok {
|
||||
gi = &groupInfo{}
|
||||
groups[g] = gi
|
||||
}
|
||||
if o.boolValue() {
|
||||
gi.set = n
|
||||
}
|
||||
gi.values = append(gi.values, n)
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case n == "sample_index":
|
||||
st := sampleTypes(p)
|
||||
if v == "" {
|
||||
// Apply default (last sample index).
|
||||
v = st[len(st)-1]
|
||||
}
|
||||
// Add comments for all sample types in profile.
|
||||
comment = "[" + strings.Join(st, " | ") + "]"
|
||||
case n == "source_path":
|
||||
continue
|
||||
case n == "nodecount" && v == "-1":
|
||||
comment = "default"
|
||||
case v == "":
|
||||
// Add quotes for empty values.
|
||||
v = `""`
|
||||
}
|
||||
if comment != "" {
|
||||
comment = commentStart + " " + comment
|
||||
}
|
||||
args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment))
|
||||
}
|
||||
for g, vars := range groups {
|
||||
sort.Strings(vars.values)
|
||||
comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]"
|
||||
args = append(args, fmt.Sprintf(" %-25s = %-20s %s", g, vars.set, comment))
|
||||
}
|
||||
sort.Strings(args)
|
||||
ui.Print(strings.Join(args, "\n"))
|
||||
}
|
||||
|
||||
// parseCommandLine parses a command and returns the pprof command to
|
||||
// execute and a set of variables for the report.
|
||||
func parseCommandLine(input []string) ([]string, variables, error) {
|
||||
cmd, args := input[:1], input[1:]
|
||||
name := cmd[0]
|
||||
|
||||
c := pprofCommands[name]
|
||||
if c == nil {
|
||||
// Attempt splitting digits on abbreviated commands (eg top10)
|
||||
if d := tailDigitsRE.FindString(name); d != "" && d != name {
|
||||
name = name[:len(name)-len(d)]
|
||||
cmd[0], args = name, append([]string{d}, args...)
|
||||
c = pprofCommands[name]
|
||||
}
|
||||
}
|
||||
if c == nil {
|
||||
return nil, nil, fmt.Errorf("unrecognized command: %q", name)
|
||||
}
|
||||
|
||||
if c.hasParam {
|
||||
if len(args) == 0 {
|
||||
return nil, nil, fmt.Errorf("command %s requires an argument", name)
|
||||
}
|
||||
cmd = append(cmd, args[0])
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
// Copy the variables as options set in the command line are not persistent.
|
||||
vcopy := pprofVariables.makeCopy()
|
||||
|
||||
var focus, ignore string
|
||||
for i := 0; i < len(args); i++ {
|
||||
t := args[i]
|
||||
if _, err := strconv.ParseInt(t, 10, 32); err == nil {
|
||||
vcopy.set("nodecount", t)
|
||||
continue
|
||||
}
|
||||
switch t[0] {
|
||||
case '>':
|
||||
outputFile := t[1:]
|
||||
if outputFile == "" {
|
||||
i++
|
||||
if i >= len(args) {
|
||||
return nil, nil, fmt.Errorf("unexpected end of line after >")
|
||||
}
|
||||
outputFile = args[i]
|
||||
}
|
||||
vcopy.set("output", outputFile)
|
||||
case '-':
|
||||
if t == "--cum" || t == "-cum" {
|
||||
vcopy.set("cum", "t")
|
||||
continue
|
||||
}
|
||||
ignore = catRegex(ignore, t[1:])
|
||||
default:
|
||||
focus = catRegex(focus, t)
|
||||
}
|
||||
}
|
||||
|
||||
if name == "tags" {
|
||||
updateFocusIgnore(vcopy, "tag", focus, ignore)
|
||||
} else {
|
||||
updateFocusIgnore(vcopy, "", focus, ignore)
|
||||
}
|
||||
|
||||
if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") {
|
||||
vcopy.set("nodecount", "10")
|
||||
}
|
||||
|
||||
return cmd, vcopy, nil
|
||||
}
|
||||
|
||||
func updateFocusIgnore(v variables, prefix, f, i string) {
|
||||
if f != "" {
|
||||
focus := prefix + "focus"
|
||||
v.set(focus, catRegex(v[focus].value, f))
|
||||
}
|
||||
|
||||
if i != "" {
|
||||
ignore := prefix + "ignore"
|
||||
v.set(ignore, catRegex(v[ignore].value, i))
|
||||
}
|
||||
}
|
||||
|
||||
func catRegex(a, b string) string {
|
||||
if a != "" && b != "" {
|
||||
return a + "|" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
|
||||
// commandHelp displays help and usage information for all Commands
|
||||
// and Variables or a specific Command or Variable.
|
||||
func commandHelp(args string, ui plugin.UI) {
|
||||
if args == "" {
|
||||
help := usage(false)
|
||||
help = help + `
|
||||
: Clear focus/ignore/hide/tagfocus/tagignore
|
||||
|
||||
type "help <cmd|option>" for more information
|
||||
`
|
||||
|
||||
ui.Print(help)
|
||||
return
|
||||
}
|
||||
|
||||
if c := pprofCommands[args]; c != nil {
|
||||
ui.Print(c.help(args))
|
||||
return
|
||||
}
|
||||
|
||||
if v := pprofVariables[args]; v != nil {
|
||||
ui.Print(v.help + "\n")
|
||||
return
|
||||
}
|
||||
|
||||
ui.PrintErr("Unknown command: " + args)
|
||||
}
|
||||
|
||||
// newCompleter creates an autocompletion function for a set of commands.
|
||||
func newCompleter(fns []string) func(string) string {
|
||||
return func(line string) string {
|
||||
v := pprofVariables
|
||||
switch tokens := strings.Fields(line); len(tokens) {
|
||||
case 0:
|
||||
// Nothing to complete
|
||||
case 1:
|
||||
// Single token -- complete command name
|
||||
if match := matchVariableOrCommand(v, tokens[0]); match != "" {
|
||||
return match
|
||||
}
|
||||
case 2:
|
||||
if tokens[0] == "help" {
|
||||
if match := matchVariableOrCommand(v, tokens[1]); match != "" {
|
||||
return tokens[0] + " " + match
|
||||
}
|
||||
return line
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
// Multiple tokens -- complete using functions, except for tags
|
||||
if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" {
|
||||
lastTokenIdx := len(tokens) - 1
|
||||
lastToken := tokens[lastTokenIdx]
|
||||
if strings.HasPrefix(lastToken, "-") {
|
||||
lastToken = "-" + functionCompleter(lastToken[1:], fns)
|
||||
} else {
|
||||
lastToken = functionCompleter(lastToken, fns)
|
||||
}
|
||||
return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
}
|
||||
|
||||
// matchVariableOrCommand attempts to match a string token to the prefix of a Command.
|
||||
func matchVariableOrCommand(v variables, token string) string {
|
||||
token = strings.ToLower(token)
|
||||
found := ""
|
||||
for cmd := range pprofCommands {
|
||||
if strings.HasPrefix(cmd, token) {
|
||||
if found != "" {
|
||||
return ""
|
||||
}
|
||||
found = cmd
|
||||
}
|
||||
}
|
||||
for variable := range v {
|
||||
if strings.HasPrefix(variable, token) {
|
||||
if found != "" {
|
||||
return ""
|
||||
}
|
||||
found = variable
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
// functionCompleter replaces provided substring with a function
|
||||
// name retrieved from a profile if a single match exists. Otherwise,
|
||||
// it returns unchanged substring. It defaults to no-op if the profile
|
||||
// is not specified.
|
||||
func functionCompleter(substring string, fns []string) string {
|
||||
found := ""
|
||||
for _, fName := range fns {
|
||||
if strings.Contains(fName, substring) {
|
||||
if found != "" {
|
||||
return substring
|
||||
}
|
||||
found = fName
|
||||
}
|
||||
}
|
||||
if found != "" {
|
||||
return found
|
||||
}
|
||||
return substring
|
||||
}
|
||||
|
||||
func functionNames(p *profile.Profile) []string {
|
||||
var fns []string
|
||||
for _, fn := range p.Function {
|
||||
fns = append(fns, fn.Name)
|
||||
}
|
||||
return fns
|
||||
}
|
316
vendor/github.com/google/pprof/internal/driver/interactive_test.go
generated
vendored
Normal file
316
vendor/github.com/google/pprof/internal/driver/interactive_test.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/proftest"
|
||||
"github.com/google/pprof/internal/report"
|
||||
"github.com/google/pprof/internal/transport"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
func TestShell(t *testing.T) {
|
||||
p := &profile.Profile{}
|
||||
generateReportWrapper = checkValue
|
||||
defer func() { generateReportWrapper = generateReport }()
|
||||
|
||||
// Use test commands and variables to exercise interactive processing
|
||||
var savedCommands commands
|
||||
savedCommands, pprofCommands = pprofCommands, testCommands
|
||||
defer func() { pprofCommands = savedCommands }()
|
||||
|
||||
savedVariables := pprofVariables
|
||||
defer func() { pprofVariables = savedVariables }()
|
||||
|
||||
// Random interleave of independent scripts
|
||||
pprofVariables = testVariables(savedVariables)
|
||||
|
||||
// pass in HTTPTransport when setting defaults, because otherwise default
|
||||
// transport will try to add flags to the default flag set.
|
||||
o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)})
|
||||
o.UI = newUI(t, interleave(script, 0))
|
||||
if err := interactive(p, o); err != nil {
|
||||
t.Error("first attempt:", err)
|
||||
}
|
||||
// Random interleave of independent scripts
|
||||
pprofVariables = testVariables(savedVariables)
|
||||
o.UI = newUI(t, interleave(script, 1))
|
||||
if err := interactive(p, o); err != nil {
|
||||
t.Error("second attempt:", err)
|
||||
}
|
||||
|
||||
// Random interleave of independent scripts with shortcuts
|
||||
pprofVariables = testVariables(savedVariables)
|
||||
var scScript []string
|
||||
pprofShortcuts, scScript = makeShortcuts(interleave(script, 2), 1)
|
||||
o.UI = newUI(t, scScript)
|
||||
if err := interactive(p, o); err != nil {
|
||||
t.Error("first shortcut attempt:", err)
|
||||
}
|
||||
|
||||
// Random interleave of independent scripts with shortcuts
|
||||
pprofVariables = testVariables(savedVariables)
|
||||
pprofShortcuts, scScript = makeShortcuts(interleave(script, 1), 2)
|
||||
o.UI = newUI(t, scScript)
|
||||
if err := interactive(p, o); err != nil {
|
||||
t.Error("second shortcut attempt:", err)
|
||||
}
|
||||
|
||||
// Group with invalid value
|
||||
pprofVariables = testVariables(savedVariables)
|
||||
ui := &proftest.TestUI{
|
||||
T: t,
|
||||
Input: []string{"cumulative=this"},
|
||||
AllowRx: `unrecognized value for cumulative: "this". Use one of cum, flat`,
|
||||
}
|
||||
o.UI = ui
|
||||
if err := interactive(p, o); err != nil {
|
||||
t.Error("invalid group value:", err)
|
||||
}
|
||||
// Confirm error message written out once.
|
||||
if ui.NumAllowRxMatches != 1 {
|
||||
t.Errorf("want error message to be printed 1 time, got %v", ui.NumAllowRxMatches)
|
||||
}
|
||||
// Verify propagation of IO errors
|
||||
pprofVariables = testVariables(savedVariables)
|
||||
o.UI = newUI(t, []string{"**error**"})
|
||||
if err := interactive(p, o); err == nil {
|
||||
t.Error("expected IO error, got nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var testCommands = commands{
|
||||
"check": &command{report.Raw, nil, nil, true, "", ""},
|
||||
}
|
||||
|
||||
func testVariables(base variables) variables {
|
||||
v := base.makeCopy()
|
||||
|
||||
v["b"] = &variable{boolKind, "f", "", ""}
|
||||
v["bb"] = &variable{boolKind, "f", "", ""}
|
||||
v["i"] = &variable{intKind, "0", "", ""}
|
||||
v["ii"] = &variable{intKind, "0", "", ""}
|
||||
v["f"] = &variable{floatKind, "0", "", ""}
|
||||
v["ff"] = &variable{floatKind, "0", "", ""}
|
||||
v["s"] = &variable{stringKind, "", "", ""}
|
||||
v["ss"] = &variable{stringKind, "", "", ""}
|
||||
|
||||
v["ta"] = &variable{boolKind, "f", "radio", ""}
|
||||
v["tb"] = &variable{boolKind, "f", "radio", ""}
|
||||
v["tc"] = &variable{boolKind, "t", "radio", ""}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// script contains sequences of commands to be executed for testing. Commands
|
||||
// are split by semicolon and interleaved randomly, so they must be
|
||||
// independent from each other.
|
||||
var script = []string{
|
||||
"bb=true;bb=false;check bb=false;bb=yes;check bb=true",
|
||||
"b=1;check b=true;b=n;check b=false",
|
||||
"i=-1;i=-2;check i=-2;i=999999;check i=999999",
|
||||
"check ii=0;ii=-1;check ii=-1;ii=100;check ii=100",
|
||||
"f=-1;f=-2.5;check f=-2.5;f=0.0001;check f=0.0001",
|
||||
"check ff=0;ff=-1.01;check ff=-1.01;ff=100;check ff=100",
|
||||
"s=one;s=two;check s=two",
|
||||
"ss=tree;check ss=tree;ss=;check ss;ss=forest;check ss=forest",
|
||||
"ta=true;check ta=true;check tb=false;check tc=false;tb=1;check tb=true;check ta=false;check tc=false;tc=yes;check tb=false;check ta=false;check tc=true",
|
||||
}
|
||||
|
||||
func makeShortcuts(input []string, seed int) (shortcuts, []string) {
|
||||
rand.Seed(int64(seed))
|
||||
|
||||
s := shortcuts{}
|
||||
var output, chunk []string
|
||||
for _, l := range input {
|
||||
chunk = append(chunk, l)
|
||||
switch rand.Intn(3) {
|
||||
case 0:
|
||||
// Create a macro for commands in 'chunk'.
|
||||
macro := fmt.Sprintf("alias%d", len(s))
|
||||
s[macro] = chunk
|
||||
output = append(output, macro)
|
||||
chunk = nil
|
||||
case 1:
|
||||
// Append commands in 'chunk' by themselves.
|
||||
output = append(output, chunk...)
|
||||
chunk = nil
|
||||
case 2:
|
||||
// Accumulate commands into 'chunk'
|
||||
}
|
||||
}
|
||||
output = append(output, chunk...)
|
||||
return s, output
|
||||
}
|
||||
|
||||
func newUI(t *testing.T, input []string) plugin.UI {
|
||||
return &proftest.TestUI{
|
||||
T: t,
|
||||
Input: input,
|
||||
}
|
||||
}
|
||||
|
||||
func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
|
||||
if len(cmd) != 2 {
|
||||
return fmt.Errorf("expected len(cmd)==2, got %v", cmd)
|
||||
}
|
||||
|
||||
input := cmd[1]
|
||||
args := strings.SplitN(input, "=", 2)
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("unexpected empty input")
|
||||
}
|
||||
name, value := args[0], ""
|
||||
if len(args) == 2 {
|
||||
value = args[1]
|
||||
}
|
||||
|
||||
gotv := vars[name]
|
||||
if gotv == nil {
|
||||
return fmt.Errorf("Could not find variable named %s", name)
|
||||
}
|
||||
|
||||
if got := gotv.stringValue(); got != value {
|
||||
return fmt.Errorf("Variable %s, want %s, got %s", name, value, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func interleave(input []string, seed int) []string {
|
||||
var inputs [][]string
|
||||
for _, s := range input {
|
||||
inputs = append(inputs, strings.Split(s, ";"))
|
||||
}
|
||||
rand.Seed(int64(seed))
|
||||
var output []string
|
||||
for len(inputs) > 0 {
|
||||
next := rand.Intn(len(inputs))
|
||||
output = append(output, inputs[next][0])
|
||||
if tail := inputs[next][1:]; len(tail) > 0 {
|
||||
inputs[next] = tail
|
||||
} else {
|
||||
inputs = append(inputs[:next], inputs[next+1:]...)
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func TestInteractiveCommands(t *testing.T) {
|
||||
type interactiveTestcase struct {
|
||||
input string
|
||||
want map[string]string
|
||||
}
|
||||
|
||||
testcases := []interactiveTestcase{
|
||||
{
|
||||
"top 10 --cum focus1 -ignore focus2",
|
||||
map[string]string{
|
||||
"functions": "true",
|
||||
"nodecount": "10",
|
||||
"cum": "true",
|
||||
"focus": "focus1|focus2",
|
||||
"ignore": "ignore",
|
||||
},
|
||||
},
|
||||
{
|
||||
"top10 --cum focus1 -ignore focus2",
|
||||
map[string]string{
|
||||
"functions": "true",
|
||||
"nodecount": "10",
|
||||
"cum": "true",
|
||||
"focus": "focus1|focus2",
|
||||
"ignore": "ignore",
|
||||
},
|
||||
},
|
||||
{
|
||||
"dot",
|
||||
map[string]string{
|
||||
"functions": "true",
|
||||
"nodecount": "80",
|
||||
"cum": "false",
|
||||
},
|
||||
},
|
||||
{
|
||||
"tags -ignore1 -ignore2 focus1 >out",
|
||||
map[string]string{
|
||||
"functions": "true",
|
||||
"nodecount": "80",
|
||||
"cum": "false",
|
||||
"output": "out",
|
||||
"tagfocus": "focus1",
|
||||
"tagignore": "ignore1|ignore2",
|
||||
},
|
||||
},
|
||||
{
|
||||
"weblist find -test",
|
||||
map[string]string{
|
||||
"functions": "false",
|
||||
"addresses": "true",
|
||||
"noinlines": "true",
|
||||
"nodecount": "0",
|
||||
"cum": "false",
|
||||
"flat": "true",
|
||||
"ignore": "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
"callgrind fun -ignore >out",
|
||||
map[string]string{
|
||||
"functions": "false",
|
||||
"addresses": "true",
|
||||
"nodecount": "0",
|
||||
"cum": "false",
|
||||
"flat": "true",
|
||||
"output": "out",
|
||||
},
|
||||
},
|
||||
{
|
||||
"999",
|
||||
nil, // Error
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
cmd, vars, err := parseCommandLine(strings.Fields(tc.input))
|
||||
if tc.want == nil && err != nil {
|
||||
// Error expected
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("failed on %q: %v", tc.input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get report output format
|
||||
c := pprofCommands[cmd[0]]
|
||||
if c == nil {
|
||||
t.Errorf("unexpected nil command")
|
||||
}
|
||||
vars = applyCommandOverrides(cmd[0], c.format, vars)
|
||||
|
||||
for n, want := range tc.want {
|
||||
if got := vars[n].stringValue(); got != want {
|
||||
t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
vendor/github.com/google/pprof/internal/driver/options.go
generated
vendored
Normal file
100
vendor/github.com/google/pprof/internal/driver/options.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/internal/binutils"
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/symbolizer"
|
||||
"github.com/google/pprof/internal/transport"
|
||||
)
|
||||
|
||||
// setDefaults returns a new plugin.Options with zero fields sets to
|
||||
// sensible defaults.
|
||||
func setDefaults(o *plugin.Options) *plugin.Options {
|
||||
d := &plugin.Options{}
|
||||
if o != nil {
|
||||
*d = *o
|
||||
}
|
||||
if d.Writer == nil {
|
||||
d.Writer = oswriter{}
|
||||
}
|
||||
if d.Flagset == nil {
|
||||
d.Flagset = &GoFlags{}
|
||||
}
|
||||
if d.Obj == nil {
|
||||
d.Obj = &binutils.Binutils{}
|
||||
}
|
||||
if d.UI == nil {
|
||||
d.UI = &stdUI{r: bufio.NewReader(os.Stdin)}
|
||||
}
|
||||
if d.HTTPTransport == nil {
|
||||
d.HTTPTransport = transport.New(d.Flagset)
|
||||
}
|
||||
if d.Sym == nil {
|
||||
d.Sym = &symbolizer.Symbolizer{Obj: d.Obj, UI: d.UI, Transport: d.HTTPTransport}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type stdUI struct {
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
func (ui *stdUI) ReadLine(prompt string) (string, error) {
|
||||
os.Stdout.WriteString(prompt)
|
||||
return ui.r.ReadString('\n')
|
||||
}
|
||||
|
||||
func (ui *stdUI) Print(args ...interface{}) {
|
||||
ui.fprint(os.Stderr, args)
|
||||
}
|
||||
|
||||
func (ui *stdUI) PrintErr(args ...interface{}) {
|
||||
ui.fprint(os.Stderr, args)
|
||||
}
|
||||
|
||||
func (ui *stdUI) IsTerminal() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ui *stdUI) WantBrowser() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ui *stdUI) SetAutoComplete(func(string) string) {
|
||||
}
|
||||
|
||||
func (ui *stdUI) fprint(f *os.File, args []interface{}) {
|
||||
text := fmt.Sprint(args...)
|
||||
if !strings.HasSuffix(text, "\n") {
|
||||
text += "\n"
|
||||
}
|
||||
f.WriteString(text)
|
||||
}
|
||||
|
||||
// oswriter implements the Writer interface using a regular file.
|
||||
type oswriter struct{}
|
||||
|
||||
func (oswriter) Open(name string) (io.WriteCloser, error) {
|
||||
f, err := os.Create(name)
|
||||
return f, err
|
||||
}
|
80
vendor/github.com/google/pprof/internal/driver/svg.go
generated
vendored
Normal file
80
vendor/github.com/google/pprof/internal/driver/svg.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/google/pprof/third_party/svgpan"
|
||||
)
|
||||
|
||||
var (
|
||||
viewBox = regexp.MustCompile(`<svg\s*width="[^"]+"\s*height="[^"]+"\s*viewBox="[^"]+"`)
|
||||
graphID = regexp.MustCompile(`<g id="graph\d"`)
|
||||
svgClose = regexp.MustCompile(`</svg>`)
|
||||
)
|
||||
|
||||
// massageSVG enhances the SVG output from DOT to provide better
|
||||
// panning inside a web browser. It uses the svgpan library, which is
|
||||
// embedded into the svgpan.JSSource variable.
|
||||
func massageSVG(svg string) string {
|
||||
// Work around for dot bug which misses quoting some ampersands,
|
||||
// resulting on unparsable SVG.
|
||||
svg = strings.Replace(svg, "&;", "&;", -1)
|
||||
|
||||
// Dot's SVG output is
|
||||
//
|
||||
// <svg width="___" height="___"
|
||||
// viewBox="___" xmlns=...>
|
||||
// <g id="graph0" transform="...">
|
||||
// ...
|
||||
// </g>
|
||||
// </svg>
|
||||
//
|
||||
// Change it to
|
||||
//
|
||||
// <svg width="100%" height="100%"
|
||||
// xmlns=...>
|
||||
|
||||
// <script type="text/ecmascript"><![CDATA[` ..$(svgpan.JSSource)... `]]></script>`
|
||||
// <g id="viewport" transform="translate(0,0)">
|
||||
// <g id="graph0" transform="...">
|
||||
// ...
|
||||
// </g>
|
||||
// </g>
|
||||
// </svg>
|
||||
|
||||
if loc := viewBox.FindStringIndex(svg); loc != nil {
|
||||
svg = svg[:loc[0]] +
|
||||
`<svg width="100%" height="100%"` +
|
||||
svg[loc[1]:]
|
||||
}
|
||||
|
||||
if loc := graphID.FindStringIndex(svg); loc != nil {
|
||||
svg = svg[:loc[0]] +
|
||||
`<script type="text/ecmascript"><![CDATA[` + string(svgpan.JSSource) + `]]></script>` +
|
||||
`<g id="viewport" transform="scale(0.5,0.5) translate(0,0)">` +
|
||||
svg[loc[0]:]
|
||||
}
|
||||
|
||||
if loc := svgClose.FindStringIndex(svg); loc != nil {
|
||||
svg = svg[:loc[0]] +
|
||||
`</g>` +
|
||||
svg[loc[0]:]
|
||||
}
|
||||
|
||||
return svg
|
||||
}
|
54
vendor/github.com/google/pprof/internal/driver/tempfile.go
generated
vendored
Normal file
54
vendor/github.com/google/pprof/internal/driver/tempfile.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// newTempFile returns a new output file in dir with the provided prefix and suffix.
|
||||
func newTempFile(dir, prefix, suffix string) (*os.File, error) {
|
||||
for index := 1; index < 10000; index++ {
|
||||
path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return os.Create(path)
|
||||
}
|
||||
}
|
||||
// Give up
|
||||
return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix)
|
||||
}
|
||||
|
||||
var tempFiles []string
|
||||
var tempFilesMu = sync.Mutex{}
|
||||
|
||||
// deferDeleteTempFile marks a file to be deleted by next call to Cleanup()
|
||||
func deferDeleteTempFile(path string) {
|
||||
tempFilesMu.Lock()
|
||||
tempFiles = append(tempFiles, path)
|
||||
tempFilesMu.Unlock()
|
||||
}
|
||||
|
||||
// cleanupTempFiles removes any temporary files selected for deferred cleaning.
|
||||
func cleanupTempFiles() {
|
||||
tempFilesMu.Lock()
|
||||
for _, f := range tempFiles {
|
||||
os.Remove(f)
|
||||
}
|
||||
tempFiles = nil
|
||||
tempFilesMu.Unlock()
|
||||
}
|
24
vendor/github.com/google/pprof/internal/driver/testdata/cppbench.contention
generated
vendored
Normal file
24
vendor/github.com/google/pprof/internal/driver/testdata/cppbench.contention
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
--- 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]
|
BIN
vendor/github.com/google/pprof/internal/driver/testdata/cppbench.cpu
generated
vendored
Normal file
BIN
vendor/github.com/google/pprof/internal/driver/testdata/cppbench.cpu
generated
vendored
Normal file
Binary file not shown.
19
vendor/github.com/google/pprof/internal/driver/testdata/cppbench.small.contention
generated
vendored
Normal file
19
vendor/github.com/google/pprof/internal/driver/testdata/cppbench.small.contention
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
--- 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]
|
17
vendor/github.com/google/pprof/internal/driver/testdata/file1000.src
generated
vendored
Normal file
17
vendor/github.com/google/pprof/internal/driver/testdata/file1000.src
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
line6
|
||||
line7
|
||||
line8
|
||||
line9
|
||||
line0
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
|
||||
|
17
vendor/github.com/google/pprof/internal/driver/testdata/file2000.src
generated
vendored
Normal file
17
vendor/github.com/google/pprof/internal/driver/testdata/file2000.src
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
line6
|
||||
line7
|
||||
line8
|
||||
line9
|
||||
line0
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
|
||||
|
17
vendor/github.com/google/pprof/internal/driver/testdata/file3000.src
generated
vendored
Normal file
17
vendor/github.com/google/pprof/internal/driver/testdata/file3000.src
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
line6
|
||||
line7
|
||||
line8
|
||||
line9
|
||||
line0
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
|
||||
|
BIN
vendor/github.com/google/pprof/internal/driver/testdata/go.crc32.cpu
generated
vendored
Normal file
BIN
vendor/github.com/google/pprof/internal/driver/testdata/go.crc32.cpu
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/google/pprof/internal/driver/testdata/go.nomappings.crash
generated
vendored
Normal file
BIN
vendor/github.com/google/pprof/internal/driver/testdata/go.nomappings.crash
generated
vendored
Normal file
Binary file not shown.
10
vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot
generated
vendored
Normal file
10
vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.cum.files.dot
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
digraph "unnamed" {
|
||||
node [style=filled fillcolor="#f8f8f8"]
|
||||
subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 149.50ms, 100% of 149.50ms total\l"] }
|
||||
N1 [label="file3000.src\n32.77ms (21.92%)\nof 149.50ms (100%)" id="node1" fontsize=20 shape=box tooltip="testdata/file3000.src (149.50ms)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N2 [label="file1000.src\n51.20ms (34.25%)" id="node2" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"]
|
||||
N3 [label="file2000.src\n65.54ms (43.84%)\nof 75.78ms (50.68%)" id="node3" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"]
|
||||
N1 -> N3 [label=" 75.78ms" weight=51 penwidth=3 color="#b22000" tooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)" labeltooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)"]
|
||||
N1 -> N2 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)" labeltooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)"]
|
||||
N3 -> N2 [label=" 10.24ms" weight=7 color="#b29775" tooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)" labeltooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)"]
|
||||
}
|
9
vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore
generated
vendored
Normal file
9
vendor/github.com/google/pprof/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
digraph "unnamed" {
|
||||
node [style=filled fillcolor="#f8f8f8"]
|
||||
subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lActive filters:\l focus=[X1]000\l ignore=[X3]002\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\l"] }
|
||||
N1 [label="0000000000001000\nline1000\nfile1000.src:1\n40.96ms (27.40%)" id="node1" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
|
||||
N2 [label="0000000000003001\nline3000\nfile3000.src:5\n0 of 40.96ms (27.40%)" id="node2" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
|
||||
N3 [label="0000000000003001\nline3001\nfile3000.src:3\n0 of 40.96ms (27.40%)" id="node3" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
|
||||
N2 -> N3 [label=" 40.96ms\n (inline)" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" labeltooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)"]
|
||||
N3 -> N1 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" labeltooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)"]
|
||||
}
|
99
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.call_tree.callgrind
generated
vendored
Normal file
99
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.call_tree.callgrind
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
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
|
88
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.callgrind
generated
vendored
Normal file
88
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.callgrind
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
positions: instr line
|
||||
events: cpu(ms)
|
||||
|
||||
ob=(1) /path/to/testbinary
|
||||
fl=(1) testdata/file1000.src
|
||||
fn=(1) line1000
|
||||
0x1000 1 1100
|
||||
|
||||
ob=(1)
|
||||
fl=(2) testdata/file2000.src
|
||||
fn=(2) line2001
|
||||
+4096 9 10
|
||||
cfl=(1)
|
||||
cfn=(1)
|
||||
calls=0 * 1
|
||||
* * 1000
|
||||
|
||||
ob=(1)
|
||||
fl=(3) testdata/file3000.src
|
||||
fn=(3) line3002
|
||||
+4096 2 10
|
||||
cfl=(2)
|
||||
cfn=(4) line2000
|
||||
calls=0 * 4
|
||||
* * 1000
|
||||
|
||||
ob=(1)
|
||||
fl=(2)
|
||||
fn=(4)
|
||||
-4096 4 0
|
||||
cfl=(2)
|
||||
cfn=(2)
|
||||
calls=0 -4096 9
|
||||
* * 1010
|
||||
|
||||
ob=(1)
|
||||
fl=(3)
|
||||
fn=(5) line3000
|
||||
+4096 6 0
|
||||
cfl=(3)
|
||||
cfn=(6) line3001
|
||||
calls=0 +4096 5
|
||||
* * 1010
|
||||
|
||||
ob=(1)
|
||||
fl=(3)
|
||||
fn=(6)
|
||||
* 5 0
|
||||
cfl=(3)
|
||||
cfn=(3)
|
||||
calls=0 * 2
|
||||
* * 1010
|
||||
|
||||
ob=(1)
|
||||
fl=(3)
|
||||
fn=(5)
|
||||
+1 9 0
|
||||
cfl=(3)
|
||||
cfn=(6)
|
||||
calls=0 +1 8
|
||||
* * 100
|
||||
|
||||
ob=(1)
|
||||
fl=(3)
|
||||
fn=(6)
|
||||
* 8 0
|
||||
cfl=(1)
|
||||
cfn=(1)
|
||||
calls=0 -8193 1
|
||||
* * 100
|
||||
|
||||
ob=(1)
|
||||
fl=(3)
|
||||
fn=(5)
|
||||
+1 9 0
|
||||
cfl=(3)
|
||||
cfn=(3)
|
||||
calls=0 +1 5
|
||||
* * 10
|
||||
|
||||
ob=(1)
|
||||
fl=(3)
|
||||
fn=(3)
|
||||
* 5 0
|
||||
cfl=(2)
|
||||
cfn=(4)
|
||||
calls=0 -4098 4
|
||||
* * 10
|
1
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments
generated
vendored
Normal file
1
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.comments
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
some-comment
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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)
|
7
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide
generated
vendored
Normal file
7
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.hide
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Active filters:
|
||||
hide=line[X3]0
|
||||
Showing nodes accounting for 1.11s, 99.11% of 1.12s total
|
||||
flat flat% sum% cum cum%
|
||||
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1
|
||||
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)
|
7
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show
generated
vendored
Normal file
7
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.text.show
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Active filters:
|
||||
show=[12]00
|
||||
Showing nodes accounting for 1.11s, 99.11% of 1.12s total
|
||||
flat flat% sum% cum cum%
|
||||
1.10s 98.21% 98.21% 1.10s 98.21% line1000 testdata/file1000.src:1
|
||||
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)
|
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide
generated
vendored
Normal file
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Active filters:
|
||||
hide=mangled[X3]0
|
||||
Showing nodes accounting for 1s, 100% of 1s total
|
||||
flat flat% sum% cum cum%
|
||||
1s 100% 100% 1s 100% mangled1000 testdata/file1000.src:1
|
16
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from
generated
vendored
Normal file
16
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
Active filters:
|
||||
show_from=line2
|
||||
Showing nodes accounting for 1.01s, 90.18% of 1.12s total
|
||||
----------------------------------------------------------+-------------
|
||||
flat flat% sum% cum cum% calls calls% + context
|
||||
----------------------------------------------------------+-------------
|
||||
0 0% 0% 1.01s 90.18% | line2000 testdata/file2000.src:4
|
||||
1.01s 100% | line2001 testdata/file2000.src:9 (inline)
|
||||
----------------------------------------------------------+-------------
|
||||
1.01s 100% | line2000 testdata/file2000.src:4 (inline)
|
||||
0.01s 0.89% 0.89% 1.01s 90.18% | line2001 testdata/file2000.src:9
|
||||
1s 99.01% | line1000 testdata/file1000.src:1
|
||||
----------------------------------------------------------+-------------
|
||||
1s 100% | line2001 testdata/file2000.src:9
|
||||
1s 89.29% 90.18% 1s 89.29% | line1000 testdata/file1000.src:1
|
||||
----------------------------------------------------------+-------------
|
14
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm
generated
vendored
Normal file
14
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.disasm
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
Total: 1.12s
|
||||
ROUTINE ======================== line1000
|
||||
1.10s 1.10s (flat, cum) 98.21% of Total
|
||||
1.10s 1.10s 1000: instruction one ;line1000 file1000.src:1
|
||||
. . 1001: instruction two ;file1000.src:1
|
||||
. . 1002: instruction three ;file1000.src:2
|
||||
. . 1003: instruction four ;file1000.src:1
|
||||
ROUTINE ======================== line3000
|
||||
10ms 1.12s (flat, cum) 100% of Total
|
||||
10ms 1.01s 3000: instruction one ;line3000 file3000.src:6
|
||||
. 100ms 3001: instruction two ;line3000 file3000.src:9
|
||||
. 10ms 3002: instruction three
|
||||
. . 3003: instruction four
|
||||
. . 3004: instruction five
|
7
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.noinlines.text
generated
vendored
Normal file
7
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.noinlines.text
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Showing nodes accounting for 1.12s, 100% of 1.12s total
|
||||
Dropped 1 node (cum <= 0.06s)
|
||||
flat flat% sum% cum cum%
|
||||
1.10s 98.21% 98.21% 1.10s 98.21% 0000000000001000 line1000 testdata/file1000.src:1
|
||||
0.01s 0.89% 99.11% 1.01s 90.18% 0000000000002000 line2000 testdata/file2000.src:4
|
||||
0.01s 0.89% 100% 1.01s 90.18% 0000000000003000 line3000 testdata/file3000.src:6
|
||||
0 0% 100% 0.10s 8.93% 0000000000003001 line3000 testdata/file3000.src:9
|
106
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist
generated
vendored
Normal file
106
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.weblist
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pprof listing</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.legend {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
.line, .nop, .unimportant {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
.inlinesrc {
|
||||
color: #000066;
|
||||
}
|
||||
.deadsrc {
|
||||
cursor: pointer;
|
||||
}
|
||||
.deadsrc:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.livesrc {
|
||||
color: #0000ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.livesrc:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.asm {
|
||||
color: #008800;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function pprof_toggle_asm(e) {
|
||||
var target;
|
||||
if (!e) e = window.event;
|
||||
if (e.target) target = e.target;
|
||||
else if (e.srcElement) target = e.srcElement;
|
||||
|
||||
if (target) {
|
||||
var asm = target.nextSibling;
|
||||
if (asm && asm.className == "asm") {
|
||||
asm.style.display = (asm.style.display == "block" ? "" : "block");
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="legend">File: testbinary<br>
|
||||
Type: cpu<br>
|
||||
Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h2>line1000</h2><p class="filename">testdata/file1000.src</p>
|
||||
<pre onClick="pprof_toggle_asm(event)">
|
||||
Total: 1.10s 1.10s (flat, cum) 98.21%
|
||||
<span class=line> 1</span> <span class=deadsrc> 1.10s 1.10s line1 </span><span class=asm> 1.10s 1.10s 1000: instruction one <span class=unimportant>file1000.src:1</span>
|
||||
. . 1001: instruction two <span class=unimportant>file1000.src:1</span>
|
||||
⋮
|
||||
. . 1003: instruction four <span class=unimportant>file1000.src:1</span>
|
||||
</span>
|
||||
<span class=line> 2</span> <span class=deadsrc> . . line2 </span><span class=asm> . . 1002: instruction three <span class=unimportant>file1000.src:2</span>
|
||||
</span>
|
||||
<span class=line> 3</span> <span class=nop> . . line3 </span>
|
||||
<span class=line> 4</span> <span class=nop> . . line4 </span>
|
||||
<span class=line> 5</span> <span class=nop> . . line5 </span>
|
||||
<span class=line> 6</span> <span class=nop> . . line6 </span>
|
||||
<span class=line> 7</span> <span class=nop> . . line7 </span>
|
||||
</pre>
|
||||
<h2>line3000</h2><p class="filename">testdata/file3000.src</p>
|
||||
<pre onClick="pprof_toggle_asm(event)">
|
||||
Total: 10ms 1.12s (flat, cum) 100%
|
||||
<span class=line> 1</span> <span class=nop> . . line1 </span>
|
||||
<span class=line> 2</span> <span class=nop> . . line2 </span>
|
||||
<span class=line> 3</span> <span class=nop> . . line3 </span>
|
||||
<span class=line> 4</span> <span class=nop> . . line4 </span>
|
||||
<span class=line> 5</span> <span class=nop> . . line5 </span>
|
||||
<span class=line> 6</span> <span class=deadsrc> 10ms 1.01s line6 </span><span class=asm> 10ms 1.01s 3000: instruction one <span class=unimportant>file3000.src:6</span>
|
||||
</span>
|
||||
<span class=line> 7</span> <span class=nop> . . line7 </span>
|
||||
<span class=line> 8</span> <span class=nop> . . line8 </span>
|
||||
<span class=line> 9</span> <span class=deadsrc> . 110ms line9 </span><span class=asm> . 100ms 3001: instruction two <span class=unimportant>file3000.src:9</span>
|
||||
. 10ms 3002: instruction three <span class=unimportant>file3000.src:9</span>
|
||||
. . 3003: instruction four <span class=unimportant></span>
|
||||
. . 3004: instruction five <span class=unimportant></span>
|
||||
</span>
|
||||
<span class=line> 10</span> <span class=nop> . . line0 </span>
|
||||
<span class=line> 11</span> <span class=nop> . . line1 </span>
|
||||
<span class=line> 12</span> <span class=nop> . . line2 </span>
|
||||
<span class=line> 13</span> <span class=nop> . . line3 </span>
|
||||
<span class=line> 14</span> <span class=nop> . . line4 </span>
|
||||
</pre>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.filefunctions.noinlines.text
generated
vendored
Normal file
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.filefunctions.noinlines.text
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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% line2000 testdata/file2000.src
|
||||
0.01s 0.89% 100% 1.12s 100% line3000 testdata/file3000.src
|
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot
generated
vendored
Normal file
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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)"]
|
||||
}
|
20
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot
generated
vendored
Normal file
20
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.dot
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
digraph "testbinary" {
|
||||
node [style=filled fillcolor="#f8f8f8"]
|
||||
subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.12s, 100% of 1.12s total\l" tooltip="testbinary"] }
|
||||
N1 [label="line1000\n1.10s (98.21%)" id="node1" fontsize=24 shape=box tooltip="line1000 (1.10s)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N1_0 [label = "key1:tag1\nkey2:tag1" id="N1_0" fontsize=8 shape=box3d tooltip="1s"]
|
||||
N1 -> N1_0 [label=" 1s" weight=100 tooltip="1s" labeltooltip="1s"]
|
||||
N1_1 [label = "key1:tag2\nkey3:tag2" id="N1_1" fontsize=8 shape=box3d tooltip="0.10s"]
|
||||
N1 -> N1_1 [label=" 0.10s" weight=100 tooltip="0.10s" labeltooltip="0.10s"]
|
||||
N2 [label="line3000\n0 of 1.12s (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (1.12s)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N3 [label="line3001\n0 of 1.11s (99.11%)" id="node3" fontsize=8 shape=box tooltip="line3001 (1.11s)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N4 [label="line3002\n0.01s (0.89%)\nof 1.02s (91.07%)" id="node4" fontsize=10 shape=box tooltip="line3002 (1.02s)" color="#b20400" fillcolor="#edd6d5"]
|
||||
N5 [label="line2001\n0.01s (0.89%)\nof 1.01s (90.18%)" id="node5" fontsize=10 shape=box tooltip="line2001 (1.01s)" color="#b20500" fillcolor="#edd6d5"]
|
||||
N6 [label="line2000\n0 of 1.01s (90.18%)" id="node6" fontsize=8 shape=box tooltip="line2000 (1.01s)" color="#b20500" fillcolor="#edd6d5"]
|
||||
N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 -> line3001 (1.11s)" labeltooltip="line3000 -> line3001 (1.11s)"]
|
||||
N6 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line2000 -> line2001 (1.01s)" labeltooltip="line2000 -> line2001 (1.01s)"]
|
||||
N3 -> N4 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 -> line3002 (1.01s)" labeltooltip="line3001 -> line3002 (1.01s)"]
|
||||
N4 -> N6 [label=" 1.01s" weight=91 penwidth=5 color="#b20500" tooltip="line3002 -> line2000 (1.01s)" labeltooltip="line3002 -> line2000 (1.01s)"]
|
||||
N5 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 -> line1000 (1s)" labeltooltip="line2001 -> line1000 (1s)"]
|
||||
N3 -> N1 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 -> line1000 (0.10s)" labeltooltip="line3001 -> line1000 (0.10s)"]
|
||||
}
|
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.noinlines.text
generated
vendored
Normal file
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.noinlines.text
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
0.01s 0.89% 99.11% 1.01s 90.18% line2000
|
||||
0.01s 0.89% 100% 1.12s 100% line3000
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.text
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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
|
||||
0.01s 0.89% 99.11% 1.01s 90.18% line2001 (inline)
|
||||
0.01s 0.89% 100% 1.02s 91.07% line3002 (inline)
|
||||
0 0% 100% 1.01s 90.18% line2000
|
||||
0 0% 100% 1.12s 100% line3000
|
||||
0 0% 100% 1.11s 99.11% line3001 (inline)
|
3
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.lines.topproto
generated
vendored
Normal file
3
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.lines.topproto
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Showing nodes accounting for 1s, 100% of 1s total
|
||||
flat flat% sum% cum cum%
|
||||
1s 100% 100% 1s 100% mangled1000 testdata/file1000.src:1
|
13
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek
generated
vendored
Normal file
13
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.peek
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
Showing nodes accounting for 1.12s, 100% of 1.12s total
|
||||
----------------------------------------------------------+-------------
|
||||
flat flat% sum% cum cum% calls calls% + context
|
||||
----------------------------------------------------------+-------------
|
||||
1.01s 100% | line2000 (inline)
|
||||
0.01s 0.89% 0.89% 1.01s 90.18% | line2001
|
||||
1s 99.01% | line1000
|
||||
----------------------------------------------------------+-------------
|
||||
1.11s 100% | line3000 (inline)
|
||||
0 0% 0.89% 1.11s 99.11% | line3001
|
||||
1.01s 90.99% | line3002 (inline)
|
||||
0.10s 9.01% | line1000
|
||||
----------------------------------------------------------+-------------
|
13
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags
generated
vendored
Normal file
13
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
key1: Total 1.1s
|
||||
1.0s (89.29%): tag1
|
||||
100.0ms ( 8.93%): tag2
|
||||
10.0ms ( 0.89%): tag3
|
||||
10.0ms ( 0.89%): tag4
|
||||
|
||||
key2: Total 1.0s
|
||||
1.0s (99.02%): tag1
|
||||
10.0ms ( 0.98%): tag2
|
||||
|
||||
key3: Total 100.0ms
|
||||
100.0ms ( 100%): tag2
|
||||
|
6
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore
generated
vendored
Normal file
6
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.tags.focus.ignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
key1: Total 100.0ms
|
||||
100.0ms ( 100%): tag2
|
||||
|
||||
key3: Total 100.0ms
|
||||
100.0ms ( 100%): tag2
|
||||
|
32
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces
generated
vendored
Normal file
32
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.traces
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
File: testbinary
|
||||
Type: cpu
|
||||
Duration: 10s, Total samples = 1.12s (11.20%)
|
||||
-----------+-------------------------------------------------------
|
||||
key1: tag1
|
||||
key2: tag1
|
||||
1s line1000
|
||||
line2001
|
||||
line2000
|
||||
line3002
|
||||
line3001
|
||||
line3000
|
||||
-----------+-------------------------------------------------------
|
||||
key1: tag2
|
||||
key3: tag2
|
||||
100ms line1000
|
||||
line3001
|
||||
line3000
|
||||
-----------+-------------------------------------------------------
|
||||
key1: tag3
|
||||
key2: tag2
|
||||
10ms line2001
|
||||
line2000
|
||||
line3002
|
||||
line3000
|
||||
-----------+-------------------------------------------------------
|
||||
key1: tag4
|
||||
key2: tag1
|
||||
10ms line3002
|
||||
line3001
|
||||
line3000
|
||||
-----------+-------------------------------------------------------
|
17
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpusmall.flat.addresses.tree
generated
vendored
Normal file
17
vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpusmall.flat.addresses.tree
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
Showing nodes accounting for 4s, 100% of 4s total
|
||||
Showing top 4 nodes out of 5
|
||||
----------------------------------------------------------+-------------
|
||||
flat flat% sum% cum cum% calls calls% + context
|
||||
----------------------------------------------------------+-------------
|
||||
1s 100% | 0000000000003000 [testbinary]
|
||||
1s 25.00% 25.00% 1s 25.00% | 0000000000001000 [testbinary]
|
||||
----------------------------------------------------------+-------------
|
||||
1s 25.00% 50.00% 2s 50.00% | 0000000000003000 [testbinary]
|
||||
1s 50.00% | 0000000000001000 [testbinary]
|
||||
----------------------------------------------------------+-------------
|
||||
1s 100% | 0000000000005000 [testbinary]
|
||||
1s 25.00% 75.00% 1s 25.00% | 0000000000004000 [testbinary]
|
||||
----------------------------------------------------------+-------------
|
||||
1s 25.00% 100% 2s 50.00% | 0000000000005000 [testbinary]
|
||||
1s 50.00% | 0000000000004000 [testbinary]
|
||||
----------------------------------------------------------+-------------
|
88
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.callgrind
generated
vendored
Normal file
88
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.callgrind
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
positions: instr line
|
||||
events: inuse_space(MB)
|
||||
|
||||
ob=
|
||||
fl=(1) testdata/file2000.src
|
||||
fn=(1) line2001
|
||||
0x2000 2 62
|
||||
cfl=(2) testdata/file1000.src
|
||||
cfn=(2) line1000
|
||||
calls=0 0x1000 1
|
||||
* * 0
|
||||
|
||||
ob=
|
||||
fl=(3) testdata/file3000.src
|
||||
fn=(3) line3002
|
||||
+4096 3 31
|
||||
cfl=(1)
|
||||
cfn=(4) line2000
|
||||
calls=0 * 3
|
||||
* * 0
|
||||
|
||||
ob=
|
||||
fl=(2)
|
||||
fn=(2)
|
||||
-8192 1 4
|
||||
|
||||
ob=
|
||||
fl=(1)
|
||||
fn=(4)
|
||||
+4096 3 0
|
||||
cfl=(1)
|
||||
cfn=(1)
|
||||
calls=0 +4096 2
|
||||
* * 63
|
||||
|
||||
ob=
|
||||
fl=(3)
|
||||
fn=(5) line3000
|
||||
+4096 4 0
|
||||
cfl=(3)
|
||||
cfn=(6) line3001
|
||||
calls=0 +4096 2
|
||||
* * 32
|
||||
|
||||
ob=
|
||||
fl=(3)
|
||||
fn=(6)
|
||||
* 2 0
|
||||
cfl=(3)
|
||||
cfn=(3)
|
||||
calls=0 * 3
|
||||
* * 32
|
||||
|
||||
ob=
|
||||
fl=(3)
|
||||
fn=(5)
|
||||
+1 4 0
|
||||
cfl=(3)
|
||||
cfn=(6)
|
||||
calls=0 +1 2
|
||||
* * 3
|
||||
|
||||
ob=
|
||||
fl=(3)
|
||||
fn=(6)
|
||||
* 2 0
|
||||
cfl=(2)
|
||||
cfn=(2)
|
||||
calls=0 -8193 1
|
||||
* * 3
|
||||
|
||||
ob=
|
||||
fl=(3)
|
||||
fn=(5)
|
||||
+1 4 0
|
||||
cfl=(3)
|
||||
cfn=(3)
|
||||
calls=0 +1 3
|
||||
* * 62
|
||||
|
||||
ob=
|
||||
fl=(3)
|
||||
fn=(3)
|
||||
* 3 0
|
||||
cfl=(1)
|
||||
cfn=(4)
|
||||
calls=0 -4098 3
|
||||
* * 62
|
2
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.comments
generated
vendored
Normal file
2
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.comments
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
comment
|
||||
#hidden comment
|
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus
generated
vendored
Normal file
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.lines.tree.focus
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
Active filters:
|
||||
focus=[24]00
|
||||
Showing nodes accounting for 62.50MB, 63.37% of 98.63MB total
|
||||
Dropped 2 nodes (cum <= 4.93MB)
|
||||
----------------------------------------------------------+-------------
|
||||
flat flat% sum% cum cum% calls calls% + context
|
||||
----------------------------------------------------------+-------------
|
||||
63.48MB 100% | line3002 testdata/file3000.src:3
|
||||
0 0% 0% 63.48MB 64.36% | line2000 testdata/file2000.src:3
|
||||
63.48MB 100% | line2001 testdata/file2000.src:2 (inline)
|
||||
----------------------------------------------------------+-------------
|
||||
63.48MB 100% | line2000 testdata/file2000.src:3 (inline)
|
||||
62.50MB 63.37% 63.37% 63.48MB 64.36% | line2001 testdata/file2000.src:2
|
||||
----------------------------------------------------------+-------------
|
||||
0 0% 63.37% 63.48MB 64.36% | line3000 testdata/file3000.src:4
|
||||
63.48MB 100% | line3002 testdata/file3000.src:3 (inline)
|
||||
----------------------------------------------------------+-------------
|
||||
63.48MB 100% | line3000 testdata/file3000.src:4 (inline)
|
||||
0 0% 63.37% 63.48MB 64.36% | line3002 testdata/file3000.src:3
|
||||
63.48MB 100% | line2000 testdata/file2000.src:3
|
||||
----------------------------------------------------------+-------------
|
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus
generated
vendored
Normal file
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
Active filters:
|
||||
focus=[24]00
|
||||
Showing nodes accounting for 62.50MB, 98.46% of 63.48MB total
|
||||
Dropped 2 nodes (cum <= 3.17MB)
|
||||
----------------------------------------------------------+-------------
|
||||
flat flat% sum% cum cum% calls calls% + context
|
||||
----------------------------------------------------------+-------------
|
||||
63.48MB 100% | line3002
|
||||
0 0% 0% 63.48MB 100% | line2000
|
||||
63.48MB 100% | line2001 (inline)
|
||||
----------------------------------------------------------+-------------
|
||||
63.48MB 100% | line2000 (inline)
|
||||
62.50MB 98.46% 98.46% 63.48MB 100% | line2001
|
||||
----------------------------------------------------------+-------------
|
||||
0 0% 98.46% 63.48MB 100% | line3000
|
||||
63.48MB 100% | line3002 (inline)
|
||||
----------------------------------------------------------+-------------
|
||||
63.48MB 100% | line3000 (inline)
|
||||
0 0% 98.46% 63.48MB 100% | line3002
|
||||
63.48MB 100% | line2000
|
||||
----------------------------------------------------------+-------------
|
2
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.seconds.text
generated
vendored
Normal file
2
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.seconds.text
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Showing nodes accounting for 0, 0% of 0 total
|
||||
flat flat% sum% cum cum%
|
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text
generated
vendored
Normal file
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Showing nodes accounting for 93.75MB, 95.05% of 98.63MB total
|
||||
Dropped 1 node (cum <= 4.93MB)
|
||||
flat flat% sum% cum cum%
|
||||
62.50MB 63.37% 63.37% 63.48MB 64.36% testdata/file2000.src
|
||||
31.25MB 31.68% 95.05% 98.63MB 100% testdata/file3000.src
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text.focus
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.files.text.focus
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_objects.text
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
Showing nodes accounting for 150, 100% of 150 total
|
||||
flat flat% sum% cum cum%
|
||||
80 53.33% 53.33% 130 86.67% line3002 (inline)
|
||||
40 26.67% 80.00% 50 33.33% line2001 (inline)
|
||||
30 20.00% 100% 30 20.00% line1000
|
||||
0 0% 100% 50 33.33% line2000
|
||||
0 0% 100% 150 100% line3000
|
||||
0 0% 100% 110 73.33% line3001 (inline)
|
13
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus
generated
vendored
Normal file
13
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
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\lActive filters:\l tagfocus=1mb:2gb\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\l"] }
|
||||
N1 [label="line2001\n62.50MB (63.37%)" id="node1" fontsize=24 shape=box tooltip="line2001 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
NN1_0 [label = "1.56MB" id="NN1_0" fontsize=8 shape=box3d tooltip="62.50MB"]
|
||||
N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
|
||||
N2 [label="line3000\n0 of 62.50MB (63.37%)" id="node2" fontsize=8 shape=box tooltip="line3000 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
N3 [label="line2000\n0 of 62.50MB (63.37%)" id="node3" fontsize=8 shape=box tooltip="line2000 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
N4 [label="line3002\n0 of 62.50MB (63.37%)" id="node4" fontsize=8 shape=box tooltip="line3002 (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
N3 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line2000 -> line2001 (62.50MB)" labeltooltip="line2000 -> line2001 (62.50MB)"]
|
||||
N2 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 -> line3002 (62.50MB)" labeltooltip="line3000 -> line3002 (62.50MB)"]
|
||||
N4 -> N3 [label=" 62.50MB" weight=64 penwidth=4 color="#b21600" tooltip="line3002 -> line2000 (62.50MB)" labeltooltip="line3002 -> line2000 (62.50MB)"]
|
||||
}
|
16
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore
generated
vendored
Normal file
16
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
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\lActive filters:\l tagfocus=30kb:\l tagignore=1mb:2mb\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\lDropped 2 nodes (cum <= 4.93MB)\l"] }
|
||||
N1 [label="line3002\n31.25MB (31.68%)\nof 32.23MB (32.67%)" id="node1" fontsize=24 shape=box tooltip="line3002 (32.23MB)" color="#b23200" fillcolor="#eddcd5"]
|
||||
NN1_0 [label = "400kB" id="NN1_0" fontsize=8 shape=box3d tooltip="31.25MB"]
|
||||
N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
|
||||
N2 [label="line3000\n0 of 36.13MB (36.63%)" id="node2" fontsize=8 shape=box tooltip="line3000 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
|
||||
N3 [label="line3001\n0 of 36.13MB (36.63%)" id="node3" fontsize=8 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
|
||||
N4 [label="line1000\n4.88MB (4.95%)" id="node4" fontsize=15 shape=box tooltip="line1000 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
|
||||
NN4_0 [label = "200kB" id="NN4_0" fontsize=8 shape=box3d tooltip="3.91MB"]
|
||||
N4 -> NN4_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"]
|
||||
N2 -> N3 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)"]
|
||||
N3 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 -> line3002 (32.23MB)" labeltooltip="line3001 -> line3002 (32.23MB)"]
|
||||
N3 -> N4 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 -> line1000 (3.91MB)" labeltooltip="line3001 -> line1000 (3.91MB)"]
|
||||
N1 -> N4 [label=" 0.98MB" color="#b2b0a9" tooltip="line3002 ... line1000 (0.98MB)" labeltooltip="line3002 ... line1000 (0.98MB)" style="dotted" minlen=2]
|
||||
}
|
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus
generated
vendored
Normal file
21
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.flat.lines.dot.focus
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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\lActive filters:\l focus=[12]00\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\l"] }
|
||||
N1 [label="line3000\nfile3000.src:4\n0 of 67.38MB (68.32%)" id="node1" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"]
|
||||
N2 [label="line2001\nfile2000.src:2\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node2" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
NN2_0 [label = "1.56MB" id="NN2_0" fontsize=8 shape=box3d tooltip="62.50MB"]
|
||||
N2 -> NN2_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
|
||||
N3 [label="line1000\nfile1000.src:1\n4.88MB (4.95%)" id="node3" fontsize=13 shape=box tooltip="line1000 testdata/file1000.src:1 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
|
||||
NN3_0 [label = "200kB" id="NN3_0" fontsize=8 shape=box3d tooltip="3.91MB"]
|
||||
N3 -> NN3_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"]
|
||||
N4 [label="line3002\nfile3000.src:3\n0 of 63.48MB (64.36%)" id="node4" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
N5 [label="line3001\nfile3000.src:2\n0 of 4.88MB (4.95%)" id="node5" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
|
||||
N6 [label="line2000\nfile2000.src:3\n0 of 63.48MB (64.36%)" id="node6" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
N6 -> N2 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)" labeltooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)"]
|
||||
N4 -> N6 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)" labeltooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)"]
|
||||
N1 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)" labeltooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)"]
|
||||
N1 -> N5 [label=" 4.88MB\n (inline)" weight=5 color="#b2a086" tooltip="line3000 testdata/file3000.src:4 -> line3001 testdata/file3000.src:2 (4.88MB)" labeltooltip="line3000 testdata/file3000.src:4 -> line3001 testdata/file3000.src:2 (4.88MB)"]
|
||||
N5 -> N3 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 testdata/file3000.src:2 -> line1000 testdata/file1000.src:1 (3.91MB)" labeltooltip="line3001 testdata/file3000.src:2 -> line1000 testdata/file1000.src:1 (3.91MB)"]
|
||||
N2 -> N3 [label=" 0.98MB" color="#b2b0a9" tooltip="line2001 testdata/file2000.src:2 -> line1000 testdata/file1000.src:1 (0.98MB)" labeltooltip="line2001 testdata/file2000.src:2 -> line1000 testdata/file1000.src:1 (0.98MB)" minlen=2]
|
||||
N5 -> N4 [label=" 0.98MB\n (inline)" color="#b2b0a9" tooltip="line3001 testdata/file3000.src:2 -> line3002 testdata/file3000.src:3 (0.98MB)" labeltooltip="line3001 testdata/file3000.src:2 -> line3002 testdata/file3000.src:3 (0.98MB)"]
|
||||
}
|
6
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags
generated
vendored
Normal file
6
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bytes: Total 98.6MB
|
||||
62.5MB (63.37%): 1.56MB
|
||||
31.2MB (31.68%): 400kB
|
||||
3.9MB ( 3.96%): 200kB
|
||||
1000.0kB ( 0.99%): 100kB
|
||||
|
6
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit
generated
vendored
Normal file
6
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap.tags.unit
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bytes: Total 103424000.0B
|
||||
65536000.0B (63.37%): 1638400B
|
||||
32768000.0B (31.68%): 409600B
|
||||
4096000.0B ( 3.96%): 204800B
|
||||
1024000.0B ( 0.99%): 102400B
|
||||
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
Showing nodes accounting for 150, 100% of 150 total
|
||||
flat flat% sum% cum cum%
|
||||
80 53.33% 53.33% 130 86.67% line3002 (inline)
|
||||
40 26.67% 80.00% 50 33.33% line2001 (inline)
|
||||
30 20.00% 100% 30 20.00% line1000
|
||||
0 0% 100% 50 33.33% line2000
|
||||
0 0% 100% 150 100% line3000
|
||||
0 0% 100% 110 73.33% line3001 (inline)
|
14
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot
generated
vendored
Normal file
14
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
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)"]
|
||||
}
|
18
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus
generated
vendored
Normal file
18
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
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 focus=[234]00\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
|
||||
N1 [label="line3002\n31.25MB (31.68%)\nof 94.73MB (96.04%)" id="node1" fontsize=20 shape=box tooltip="line3002 (94.73MB)" color="#b20200" fillcolor="#edd5d5"]
|
||||
NN1_0 [label = "400kB" id="NN1_0" fontsize=8 shape=box3d tooltip="31.25MB"]
|
||||
N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
|
||||
N2 [label="line3000\n0 of 98.63MB (100%)" id="node2" fontsize=8 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N3 [label="line2001\n62.50MB (63.37%)\nof 63.48MB (64.36%)" id="node3" fontsize=24 shape=box tooltip="line2001 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
|
||||
NN3_0 [label = "1.56MB" id="NN3_0" fontsize=8 shape=box3d tooltip="62.50MB"]
|
||||
N3 -> NN3_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
|
||||
N4 [label="line2000\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)"]
|
||||
}
|
11
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide
generated
vendored
Normal file
11
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
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 hide=line.*1?23?\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
|
||||
N1 [label="line3000\n62.50MB (63.37%)\nof 98.63MB (100%)" id="node1" fontsize=24 shape=box tooltip="line3000 (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
|
||||
NN1_0 [label = "1.56MB" id="NN1_0" fontsize=8 shape=box3d tooltip="62.50MB"]
|
||||
N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
|
||||
N2 [label="line3001\n31.25MB (31.68%)\nof 36.13MB (36.63%)" id="node2" fontsize=20 shape=box tooltip="line3001 (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
|
||||
NN2_0 [label = "400kB" id="NN2_0" fontsize=8 shape=box3d tooltip="31.25MB"]
|
||||
N2 -> NN2_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
|
||||
N1 -> N2 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 -> line3001 (36.13MB)" labeltooltip="line3000 -> line3001 (36.13MB)" minlen=2]
|
||||
}
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_request.tags.focus
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_request.tags.focus
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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
|
||||
|
30
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_sizetags.dot
generated
vendored
Normal file
30
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_sizetags.dot
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
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)"]
|
||||
}
|
32
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_tags.traces
generated
vendored
Normal file
32
vendor/github.com/google/pprof/internal/driver/testdata/pprof.heap_tags.traces
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
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
|
||||
-----------+-------------------------------------------------------
|
9
vendor/github.com/google/pprof/internal/driver/testdata/pprof.longNameFuncs.dot
generated
vendored
Normal file
9
vendor/github.com/google/pprof/internal/driver/testdata/pprof.longNameFuncs.dot
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
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.11s (11.10%)\lShowing nodes accounting for 1.11s, 100% of 1.11s total\l" tooltip="testbinary"] }
|
||||
N1 [label="package1\nobject\nfunction1\n1.10s (99.10%)" id="node1" fontsize=24 shape=box tooltip="path/to/package1.object.function1 (1.10s)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N2 [label="FooBar\nrun\n0.01s (0.9%)\nof 1.01s (90.99%)" id="node2" fontsize=10 shape=box tooltip="java.bar.foo.FooBar.run(java.lang.Runnable) (1.01s)" color="#b20400" fillcolor="#edd6d5"]
|
||||
N3 [label="Bar\nFoo\n0 of 1.10s (99.10%)" id="node3" fontsize=8 shape=box tooltip="(anonymous namespace)::Bar::Foo (1.10s)" color="#b20000" fillcolor="#edd5d5"]
|
||||
N3 -> N1 [label=" 1.10s" weight=100 penwidth=5 color="#b20000" tooltip="(anonymous namespace)::Bar::Foo -> path/to/package1.object.function1 (1.10s)" labeltooltip="(anonymous namespace)::Bar::Foo -> path/to/package1.object.function1 (1.10s)"]
|
||||
N2 -> N3 [label=" 1s" weight=91 penwidth=5 color="#b20500" tooltip="java.bar.foo.FooBar.run(java.lang.Runnable) -> (anonymous namespace)::Bar::Foo (1s)" labeltooltip="java.bar.foo.FooBar.run(java.lang.Runnable) -> (anonymous namespace)::Bar::Foo (1s)"]
|
||||
}
|
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.longNameFuncs.text
generated
vendored
Normal file
5
vendor/github.com/google/pprof/internal/driver/testdata/pprof.longNameFuncs.text
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Showing nodes accounting for 1.11s, 100% of 1.11s total
|
||||
flat flat% sum% cum cum%
|
||||
1.10s 99.10% 99.10% 1.10s 99.10% path/to/package1.object.function1
|
||||
0.01s 0.9% 100% 1.01s 90.99% java.bar.foo.FooBar.run(java.lang.Runnable)
|
||||
0 0% 100% 1.10s 99.10% (anonymous namespace)::Bar::Foo
|
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text
generated
vendored
Normal file
8
vendor/github.com/google/pprof/internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
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
|
1159
vendor/github.com/google/pprof/internal/driver/webhtml.go
generated
vendored
Normal file
1159
vendor/github.com/google/pprof/internal/driver/webhtml.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
439
vendor/github.com/google/pprof/internal/driver/webui.go
generated
vendored
Normal file
439
vendor/github.com/google/pprof/internal/driver/webui.go
generated
vendored
Normal file
@@ -0,0 +1,439 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
gourl "net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/pprof/internal/graph"
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/report"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
// webInterface holds the state needed for serving a browser based interface.
|
||||
type webInterface struct {
|
||||
prof *profile.Profile
|
||||
options *plugin.Options
|
||||
help map[string]string
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
func makeWebInterface(p *profile.Profile, opt *plugin.Options) *webInterface {
|
||||
templates := template.New("templategroup")
|
||||
addTemplates(templates)
|
||||
report.AddSourceTemplates(templates)
|
||||
return &webInterface{
|
||||
prof: p,
|
||||
options: opt,
|
||||
help: make(map[string]string),
|
||||
templates: templates,
|
||||
}
|
||||
}
|
||||
|
||||
// maxEntries is the maximum number of entries to print for text interfaces.
|
||||
const maxEntries = 50
|
||||
|
||||
// errorCatcher is a UI that captures errors for reporting to the browser.
|
||||
type errorCatcher struct {
|
||||
plugin.UI
|
||||
errors []string
|
||||
}
|
||||
|
||||
func (ec *errorCatcher) PrintErr(args ...interface{}) {
|
||||
ec.errors = append(ec.errors, strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
|
||||
ec.UI.PrintErr(args...)
|
||||
}
|
||||
|
||||
// webArgs contains arguments passed to templates in webhtml.go.
|
||||
type webArgs struct {
|
||||
Title string
|
||||
Errors []string
|
||||
Total int64
|
||||
SampleTypes []string
|
||||
Legend []string
|
||||
Help map[string]string
|
||||
Nodes []string
|
||||
HTMLBody template.HTML
|
||||
TextBody string
|
||||
Top []report.TextItem
|
||||
FlameGraph template.JS
|
||||
}
|
||||
|
||||
func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error {
|
||||
host, port, err := getHostAndPort(hostport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
interactiveMode = true
|
||||
ui := makeWebInterface(p, o)
|
||||
for n, c := range pprofCommands {
|
||||
ui.help[n] = c.description
|
||||
}
|
||||
for n, v := range pprofVariables {
|
||||
ui.help[n] = v.help
|
||||
}
|
||||
ui.help["details"] = "Show information about the profile and this view"
|
||||
ui.help["graph"] = "Display profile as a directed graph"
|
||||
ui.help["reset"] = "Show the entire profile"
|
||||
|
||||
server := o.HTTPServer
|
||||
if server == nil {
|
||||
server = defaultWebServer
|
||||
}
|
||||
args := &plugin.HTTPServerArgs{
|
||||
Hostport: net.JoinHostPort(host, strconv.Itoa(port)),
|
||||
Host: host,
|
||||
Port: port,
|
||||
Handlers: map[string]http.Handler{
|
||||
"/": http.HandlerFunc(ui.dot),
|
||||
"/top": http.HandlerFunc(ui.top),
|
||||
"/disasm": http.HandlerFunc(ui.disasm),
|
||||
"/source": http.HandlerFunc(ui.source),
|
||||
"/peek": http.HandlerFunc(ui.peek),
|
||||
"/flamegraph": http.HandlerFunc(ui.flamegraph),
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://" + args.Hostport
|
||||
|
||||
o.UI.Print("Serving web UI on ", url)
|
||||
|
||||
if o.UI.WantBrowser() && !disableBrowser {
|
||||
go openBrowser(url, o)
|
||||
}
|
||||
return server(args)
|
||||
}
|
||||
|
||||
func getHostAndPort(hostport string) (string, int, error) {
|
||||
host, portStr, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("could not split http address: %v", err)
|
||||
}
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
var port int
|
||||
if portStr == "" {
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(host, "0"))
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("could not generate random port: %v", err)
|
||||
}
|
||||
port = ln.Addr().(*net.TCPAddr).Port
|
||||
err = ln.Close()
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("could not generate random port: %v", err)
|
||||
}
|
||||
} else {
|
||||
port, err = strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("invalid port number: %v", err)
|
||||
}
|
||||
}
|
||||
return host, port, nil
|
||||
}
|
||||
func defaultWebServer(args *plugin.HTTPServerArgs) error {
|
||||
ln, err := net.Listen("tcp", args.Hostport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isLocal := isLocalhost(args.Host)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if isLocal {
|
||||
// Only allow local clients
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil || !isLocalhost(host) {
|
||||
http.Error(w, "permission denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
h := args.Handlers[req.URL.Path]
|
||||
if h == nil {
|
||||
// Fall back to default behavior
|
||||
h = http.DefaultServeMux
|
||||
}
|
||||
h.ServeHTTP(w, req)
|
||||
})
|
||||
|
||||
// We serve the ui at /ui/ and redirect there from the root. This is done
|
||||
// to surface any problems with serving the ui at a non-root early. See:
|
||||
//
|
||||
// https://github.com/google/pprof/pull/348
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ui/", http.StripPrefix("/ui", handler))
|
||||
mux.Handle("/", redirectWithQuery("/ui"))
|
||||
s := &http.Server{Handler: mux}
|
||||
return s.Serve(ln)
|
||||
}
|
||||
|
||||
func redirectWithQuery(path string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
pathWithQuery := &gourl.URL{Path: path, RawQuery: r.URL.RawQuery}
|
||||
http.Redirect(w, r, pathWithQuery.String(), http.StatusTemporaryRedirect)
|
||||
}
|
||||
}
|
||||
|
||||
func isLocalhost(host string) bool {
|
||||
for _, v := range []string{"localhost", "127.0.0.1", "[::1]", "::1"} {
|
||||
if host == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func openBrowser(url string, o *plugin.Options) {
|
||||
// Construct URL.
|
||||
u, _ := gourl.Parse(url)
|
||||
q := u.Query()
|
||||
for _, p := range []struct{ param, key string }{
|
||||
{"f", "focus"},
|
||||
{"s", "show"},
|
||||
{"sf", "show_from"},
|
||||
{"i", "ignore"},
|
||||
{"h", "hide"},
|
||||
{"si", "sample_index"},
|
||||
} {
|
||||
if v := pprofVariables[p.key].value; v != "" {
|
||||
q.Set(p.param, v)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
// Give server a little time to get ready.
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
for _, b := range browsers() {
|
||||
args := strings.Split(b, " ")
|
||||
if len(args) == 0 {
|
||||
continue
|
||||
}
|
||||
viewer := exec.Command(args[0], append(args[1:], u.String())...)
|
||||
viewer.Stderr = os.Stderr
|
||||
if err := viewer.Start(); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// No visualizer succeeded, so just print URL.
|
||||
o.UI.PrintErr(u.String())
|
||||
}
|
||||
|
||||
func varsFromURL(u *gourl.URL) variables {
|
||||
vars := pprofVariables.makeCopy()
|
||||
vars["focus"].value = u.Query().Get("f")
|
||||
vars["show"].value = u.Query().Get("s")
|
||||
vars["show_from"].value = u.Query().Get("sf")
|
||||
vars["ignore"].value = u.Query().Get("i")
|
||||
vars["hide"].value = u.Query().Get("h")
|
||||
vars["sample_index"].value = u.Query().Get("si")
|
||||
return vars
|
||||
}
|
||||
|
||||
// makeReport generates a report for the specified command.
|
||||
func (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,
|
||||
cmd []string, vars ...string) (*report.Report, []string) {
|
||||
v := varsFromURL(req.URL)
|
||||
for i := 0; i+1 < len(vars); i += 2 {
|
||||
v[vars[i]].value = vars[i+1]
|
||||
}
|
||||
catcher := &errorCatcher{UI: ui.options.UI}
|
||||
options := *ui.options
|
||||
options.UI = catcher
|
||||
_, rpt, err := generateRawReport(ui.prof, cmd, v, &options)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
ui.options.UI.PrintErr(err)
|
||||
return nil, nil
|
||||
}
|
||||
return rpt, catcher.errors
|
||||
}
|
||||
|
||||
// render generates html using the named template based on the contents of data.
|
||||
func (ui *webInterface) render(w http.ResponseWriter, tmpl string,
|
||||
rpt *report.Report, errList, legend []string, data webArgs) {
|
||||
file := getFromLegend(legend, "File: ", "unknown")
|
||||
profile := getFromLegend(legend, "Type: ", "unknown")
|
||||
data.Title = file + " " + profile
|
||||
data.Errors = errList
|
||||
data.Total = rpt.Total()
|
||||
data.SampleTypes = sampleTypes(ui.prof)
|
||||
data.Legend = legend
|
||||
data.Help = ui.help
|
||||
html := &bytes.Buffer{}
|
||||
if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil {
|
||||
http.Error(w, "internal template error", http.StatusInternalServerError)
|
||||
ui.options.UI.PrintErr(err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write(html.Bytes())
|
||||
}
|
||||
|
||||
// dot generates a web page containing an svg diagram.
|
||||
func (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {
|
||||
rpt, errList := ui.makeReport(w, req, []string{"svg"})
|
||||
if rpt == nil {
|
||||
return // error already reported
|
||||
}
|
||||
|
||||
// Generate dot graph.
|
||||
g, config := report.GetDOT(rpt)
|
||||
legend := config.Labels
|
||||
config.Labels = nil
|
||||
dot := &bytes.Buffer{}
|
||||
graph.ComposeDot(dot, g, &graph.DotAttributes{}, config)
|
||||
|
||||
// Convert to svg.
|
||||
svg, err := dotToSvg(dot.Bytes())
|
||||
if err != nil {
|
||||
http.Error(w, "Could not execute dot; may need to install graphviz.",
|
||||
http.StatusNotImplemented)
|
||||
ui.options.UI.PrintErr("Failed to execute dot. Is Graphviz installed?\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get all node names into an array.
|
||||
nodes := []string{""} // dot starts with node numbered 1
|
||||
for _, n := range g.Nodes {
|
||||
nodes = append(nodes, n.Info.Name)
|
||||
}
|
||||
|
||||
ui.render(w, "graph", rpt, errList, legend, webArgs{
|
||||
HTMLBody: template.HTML(string(svg)),
|
||||
Nodes: nodes,
|
||||
})
|
||||
}
|
||||
|
||||
func dotToSvg(dot []byte) ([]byte, error) {
|
||||
cmd := exec.Command("dot", "-Tsvg")
|
||||
out := &bytes.Buffer{}
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(dot), out, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fix dot bug related to unquoted amperands.
|
||||
svg := bytes.Replace(out.Bytes(), []byte("&;"), []byte("&;"), -1)
|
||||
|
||||
// Cleanup for embedding by dropping stuff before the <svg> start.
|
||||
if pos := bytes.Index(svg, []byte("<svg")); pos >= 0 {
|
||||
svg = svg[pos:]
|
||||
}
|
||||
return svg, nil
|
||||
}
|
||||
|
||||
func (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {
|
||||
rpt, errList := ui.makeReport(w, req, []string{"top"}, "nodecount", "500")
|
||||
if rpt == nil {
|
||||
return // error already reported
|
||||
}
|
||||
top, legend := report.TextItems(rpt)
|
||||
var nodes []string
|
||||
for _, item := range top {
|
||||
nodes = append(nodes, item.Name)
|
||||
}
|
||||
|
||||
ui.render(w, "top", rpt, errList, legend, webArgs{
|
||||
Top: top,
|
||||
Nodes: nodes,
|
||||
})
|
||||
}
|
||||
|
||||
// disasm generates a web page containing disassembly.
|
||||
func (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {
|
||||
args := []string{"disasm", req.URL.Query().Get("f")}
|
||||
rpt, errList := ui.makeReport(w, req, args)
|
||||
if rpt == nil {
|
||||
return // error already reported
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
if err := report.PrintAssembly(out, rpt, ui.options.Obj, maxEntries); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
ui.options.UI.PrintErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
legend := report.ProfileLabels(rpt)
|
||||
ui.render(w, "plaintext", rpt, errList, legend, webArgs{
|
||||
TextBody: out.String(),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// source generates a web page containing source code annotated with profile
|
||||
// data.
|
||||
func (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {
|
||||
args := []string{"weblist", req.URL.Query().Get("f")}
|
||||
rpt, errList := ui.makeReport(w, req, args)
|
||||
if rpt == nil {
|
||||
return // error already reported
|
||||
}
|
||||
|
||||
// Generate source listing.
|
||||
var body bytes.Buffer
|
||||
if err := report.PrintWebList(&body, rpt, ui.options.Obj, maxEntries); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
ui.options.UI.PrintErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
legend := report.ProfileLabels(rpt)
|
||||
ui.render(w, "sourcelisting", rpt, errList, legend, webArgs{
|
||||
HTMLBody: template.HTML(body.String()),
|
||||
})
|
||||
}
|
||||
|
||||
// peek generates a web page listing callers/callers.
|
||||
func (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {
|
||||
args := []string{"peek", req.URL.Query().Get("f")}
|
||||
rpt, errList := ui.makeReport(w, req, args, "lines", "t")
|
||||
if rpt == nil {
|
||||
return // error already reported
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
if err := report.Generate(out, rpt, ui.options.Obj); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
ui.options.UI.PrintErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
legend := report.ProfileLabels(rpt)
|
||||
ui.render(w, "plaintext", rpt, errList, legend, webArgs{
|
||||
TextBody: out.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// getFromLegend returns the suffix of an entry in legend that starts
|
||||
// with param. It returns def if no such entry is found.
|
||||
func getFromLegend(legend []string, param, def string) string {
|
||||
for _, s := range legend {
|
||||
if strings.HasPrefix(s, param) {
|
||||
return s[len(param):]
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
285
vendor/github.com/google/pprof/internal/driver/webui_test.go
generated
vendored
Normal file
285
vendor/github.com/google/pprof/internal/driver/webui_test.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
// 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"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/pprof/internal/plugin"
|
||||
"github.com/google/pprof/internal/proftest"
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
func TestWebInterface(t *testing.T) {
|
||||
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
||||
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: &proftest.TestUI{},
|
||||
HTTPServer: creator,
|
||||
}, false)
|
||||
<-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},
|
||||
{"/flamegraph", []string{"File: testbin", "\"n\":\"root\"", "\"n\":\"F1\"", "var flamegraph = function", "function hierarchy"}, 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() {
|
||||
defer wg.Done()
|
||||
res, err := http.Get(path)
|
||||
if err != nil {
|
||||
t.Error("could not fetch", c.path, err)
|
||||
return
|
||||
}
|
||||
if _, err = ioutil.ReadAll(res.Body); err != nil {
|
||||
t.Error("could not read response", c.path, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
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{
|
||||
{
|
||||
Name: []string{"F1"}, File: fakeSource,
|
||||
Start: addrBase, End: addrBase + 10,
|
||||
},
|
||||
{
|
||||
Name: []string{"F2"}, File: fakeSource,
|
||||
Start: addrBase + 10, End: addrBase + 20,
|
||||
},
|
||||
{
|
||||
Name: []string{"F3"}, File: fakeSource,
|
||||
Start: addrBase + 20, End: 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 TestGetHostAndPort(t *testing.T) {
|
||||
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
||||
t.Skip("test assumes tcp available")
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
hostport string
|
||||
wantHost string
|
||||
wantPort int
|
||||
wantRandomPort bool
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{":", "localhost", 0, true},
|
||||
{":4681", "localhost", 4681, false},
|
||||
{"localhost:4681", "localhost", 4681, false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
host, port, err := getHostAndPort(tc.hostport)
|
||||
if err != nil {
|
||||
t.Errorf("could not get host and port for %q: %v", tc.hostport, err)
|
||||
}
|
||||
if got, want := host, tc.wantHost; got != want {
|
||||
t.Errorf("for %s, got host %s, want %s", tc.hostport, got, want)
|
||||
continue
|
||||
}
|
||||
if !tc.wantRandomPort {
|
||||
if got, want := port, tc.wantPort; got != want {
|
||||
t.Errorf("for %s, got port %d, want %d", tc.hostport, got, want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user