mirror of https://github.com/golang/go.git
cmd/vendor: update github.com/google/pprof
Sync @ fde099a (Oct 26, 2018) Also update misc/nacl/testzip.proto to include new testdata. Change-Id: If41590be9f395a591056e89a417b589c4ba71b1a Reviewed-on: https://go-review.googlesource.com/c/147979 Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
1100df5823
commit
c0a40e4fe5
|
|
@ -50,6 +50,9 @@ go src=..
|
||||||
google
|
google
|
||||||
pprof
|
pprof
|
||||||
internal
|
internal
|
||||||
|
binutils
|
||||||
|
testdata
|
||||||
|
+
|
||||||
driver
|
driver
|
||||||
testdata
|
testdata
|
||||||
+
|
+
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -48,13 +49,14 @@ func (o *Options) internalOptions() *plugin.Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &plugin.Options{
|
return &plugin.Options{
|
||||||
Writer: o.Writer,
|
Writer: o.Writer,
|
||||||
Flagset: o.Flagset,
|
Flagset: o.Flagset,
|
||||||
Fetch: o.Fetch,
|
Fetch: o.Fetch,
|
||||||
Sym: sym,
|
Sym: sym,
|
||||||
Obj: obj,
|
Obj: obj,
|
||||||
UI: o.UI,
|
UI: o.UI,
|
||||||
HTTPServer: httpServer,
|
HTTPServer: httpServer,
|
||||||
|
HTTPTransport: o.HTTPTransport,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,13 +66,14 @@ type HTTPServerArgs plugin.HTTPServerArgs
|
||||||
|
|
||||||
// Options groups all the optional plugins into pprof.
|
// Options groups all the optional plugins into pprof.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Writer Writer
|
Writer Writer
|
||||||
Flagset FlagSet
|
Flagset FlagSet
|
||||||
Fetch Fetcher
|
Fetch Fetcher
|
||||||
Sym Symbolizer
|
Sym Symbolizer
|
||||||
Obj ObjTool
|
Obj ObjTool
|
||||||
UI UI
|
UI UI
|
||||||
HTTPServer func(*HTTPServerArgs) error
|
HTTPServer func(*HTTPServerArgs) error
|
||||||
|
HTTPTransport http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer provides a mechanism to write data under a certain name,
|
// Writer provides a mechanism to write data under a certain name,
|
||||||
|
|
@ -100,12 +103,16 @@ type FlagSet interface {
|
||||||
// single flag
|
// single flag
|
||||||
StringList(name string, def string, usage string) *[]*string
|
StringList(name string, def string, usage string) *[]*string
|
||||||
|
|
||||||
// ExtraUsage returns any additional text that should be
|
// ExtraUsage returns any additional text that should be printed after the
|
||||||
// printed after the standard usage message.
|
// standard usage message. The extra usage message returned includes all text
|
||||||
// The typical use of ExtraUsage is to show any custom flags
|
// added with AddExtraUsage().
|
||||||
// defined by the specific pprof plugins being used.
|
// The typical use of ExtraUsage is to show any custom flags defined by the
|
||||||
|
// specific pprof plugins being used.
|
||||||
ExtraUsage() string
|
ExtraUsage() string
|
||||||
|
|
||||||
|
// AddExtraUsage appends additional text to the end of the extra usage message.
|
||||||
|
AddExtraUsage(eu string)
|
||||||
|
|
||||||
// Parse initializes the flags with their values for this run
|
// Parse initializes the flags with their values for this run
|
||||||
// and returns the non-flag command line arguments.
|
// and returns the non-flag command line arguments.
|
||||||
// If an unknown flag is encountered or there are no arguments,
|
// If an unknown flag is encountered or there are no arguments,
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,14 @@ package binutils
|
||||||
import (
|
import (
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"debug/macho"
|
"debug/macho"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
|
@ -173,12 +176,8 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
|
||||||
b := bu.get()
|
b := bu.get()
|
||||||
|
|
||||||
// Make sure file is a supported executable.
|
// Make sure file is a supported executable.
|
||||||
// The pprof driver uses Open to sniff the difference
|
// This uses magic numbers, mainly to provide better error messages but
|
||||||
// between an executable and a profile.
|
// it should also help speed.
|
||||||
// For now, only ELF is supported.
|
|
||||||
// Could read the first few bytes of the file and
|
|
||||||
// use a table of prefixes if we need to support other
|
|
||||||
// systems at some point.
|
|
||||||
|
|
||||||
if _, err := os.Stat(name); err != nil {
|
if _, err := os.Stat(name); err != nil {
|
||||||
// For testing, do not require file name to exist.
|
// For testing, do not require file name to exist.
|
||||||
|
|
@ -188,21 +187,54 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f, err := b.openELF(name, start, limit, offset); err == nil {
|
// Read the first 4 bytes of the file.
|
||||||
|
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening %s: %v", name, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var header [4]byte
|
||||||
|
if _, err = io.ReadFull(f, header[:]); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading magic number from %s: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
elfMagic := string(header[:])
|
||||||
|
|
||||||
|
// Match against supported file types.
|
||||||
|
if elfMagic == elf.ELFMAG {
|
||||||
|
f, err := b.openELF(name, start, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading ELF file %s: %v", name, err)
|
||||||
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
if f, err := b.openMachO(name, start, limit, offset); err == nil {
|
|
||||||
|
// Mach-O magic numbers can be big or little endian.
|
||||||
|
machoMagicLittle := binary.LittleEndian.Uint32(header[:])
|
||||||
|
machoMagicBig := binary.BigEndian.Uint32(header[:])
|
||||||
|
|
||||||
|
if machoMagicLittle == macho.Magic32 || machoMagicLittle == macho.Magic64 ||
|
||||||
|
machoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {
|
||||||
|
f, err := b.openMachO(name, start, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading Mach-O file %s: %v", name, err)
|
||||||
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unrecognized binary: %s", name)
|
if machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat {
|
||||||
|
f, err := b.openFatMachO(name, start, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading fat Mach-O file %s: %v", name, err)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unrecognized binary format: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
func (b *binrep) openMachOCommon(name string, of *macho.File, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||||
of, err := macho.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing %s: %v", name, err)
|
|
||||||
}
|
|
||||||
defer of.Close()
|
|
||||||
|
|
||||||
// Subtract the load address of the __TEXT section. Usually 0 for shared
|
// Subtract the load address of the __TEXT section. Usually 0 for shared
|
||||||
// libraries or 0x100000000 for executables. You can check this value by
|
// libraries or 0x100000000 for executables. You can check this value by
|
||||||
|
|
@ -225,6 +257,53 @@ func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.Obj
|
||||||
return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
|
return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *binrep) openFatMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||||
|
of, err := macho.OpenFat(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing %s: %v", name, err)
|
||||||
|
}
|
||||||
|
defer of.Close()
|
||||||
|
|
||||||
|
if len(of.Arches) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty fat Mach-O file: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var arch macho.Cpu
|
||||||
|
// Use the host architecture.
|
||||||
|
// TODO: This is not ideal because the host architecture may not be the one
|
||||||
|
// that was profiled. E.g. an amd64 host can profile a 386 program.
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "386":
|
||||||
|
arch = macho.Cpu386
|
||||||
|
case "amd64", "amd64p32":
|
||||||
|
arch = macho.CpuAmd64
|
||||||
|
case "arm", "armbe", "arm64", "arm64be":
|
||||||
|
arch = macho.CpuArm
|
||||||
|
case "ppc":
|
||||||
|
arch = macho.CpuPpc
|
||||||
|
case "ppc64", "ppc64le":
|
||||||
|
arch = macho.CpuPpc64
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported host architecture for %s: %s", name, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
for i := range of.Arches {
|
||||||
|
if of.Arches[i].Cpu == arch {
|
||||||
|
return b.openMachOCommon(name, of.Arches[i].File, start, limit, offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("architecture not found in %s: %s", name, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||||
|
of, err := macho.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing %s: %v", name, err)
|
||||||
|
}
|
||||||
|
defer of.Close()
|
||||||
|
|
||||||
|
return b.openMachOCommon(name, of, start, limit, offset)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
|
||||||
ef, err := elf.Open(name)
|
ef, err := elf.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/pprof/internal/plugin"
|
"github.com/google/pprof/internal/plugin"
|
||||||
|
|
@ -361,3 +362,31 @@ func TestLLVMSymbolizer(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenMalformedELF(t *testing.T) {
|
||||||
|
// Test that opening a malformed ELF file will report an error containing
|
||||||
|
// the word "ELF".
|
||||||
|
bu := &Binutils{}
|
||||||
|
_, err := bu.Open(filepath.Join("testdata", "malformed_elf"), 0, 0, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Open: unexpected success")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "ELF") {
|
||||||
|
t.Errorf("Open: got %v, want error containing 'ELF'", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenMalformedMachO(t *testing.T) {
|
||||||
|
// Test that opening a malformed Mach-O file will report an error containing
|
||||||
|
// the word "Mach-O".
|
||||||
|
bu := &Binutils{}
|
||||||
|
_, err := bu.Open(filepath.Join("testdata", "malformed_macho"), 0, 0, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Open: unexpected success")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "Mach-O") {
|
||||||
|
t.Errorf("Open: got %v, want error containing 'Mach-O'", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
1
src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/malformed_elf
generated
vendored
Normal file
1
src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/malformed_elf
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ELF˙˙˙˙˙˙˙˙
|
||||||
1
src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/malformed_macho
generated
vendored
Normal file
1
src/cmd/vendor/github.com/google/pprof/internal/binutils/testdata/malformed_macho
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
销睨<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
|
@ -45,8 +45,8 @@ type source struct {
|
||||||
func parseFlags(o *plugin.Options) (*source, []string, error) {
|
func parseFlags(o *plugin.Options) (*source, []string, error) {
|
||||||
flag := o.Flagset
|
flag := o.Flagset
|
||||||
// Comparisons.
|
// Comparisons.
|
||||||
flagBase := flag.StringList("base", "", "Source for base profile for profile subtraction")
|
flagDiffBase := flag.StringList("diff_base", "", "Source of base profile for comparison")
|
||||||
flagDiffBase := flag.StringList("diff_base", "", "Source for diff base profile for comparison")
|
flagBase := flag.StringList("base", "", "Source of base profile for profile subtraction")
|
||||||
// Source options.
|
// Source options.
|
||||||
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
|
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
|
||||||
flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
|
flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
|
||||||
|
|
@ -312,7 +312,8 @@ var usageMsgSrc = "\n\n" +
|
||||||
" -buildid Override build id for main binary\n" +
|
" -buildid Override build id for main binary\n" +
|
||||||
" -add_comment Free-form annotation to add to the profile\n" +
|
" -add_comment Free-form annotation to add to the profile\n" +
|
||||||
" Displayed on some reports or with pprof -comments\n" +
|
" Displayed on some reports or with pprof -comments\n" +
|
||||||
" -base source Source of profile to use as baseline\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" +
|
" profile.pb.gz Profile in compressed protobuf format\n" +
|
||||||
" legacy_profile Profile in legacy pprof format\n" +
|
" legacy_profile Profile in legacy pprof format\n" +
|
||||||
" http://host/profile URL for profile handler to retrieve\n" +
|
" http://host/profile URL for profile handler to retrieve\n" +
|
||||||
|
|
|
||||||
|
|
@ -228,17 +228,17 @@ var pprofVariables = variables{
|
||||||
// Output granularity
|
// Output granularity
|
||||||
"functions": &variable{boolKind, "t", "granularity", helpText(
|
"functions": &variable{boolKind, "t", "granularity", helpText(
|
||||||
"Aggregate at the function level.",
|
"Aggregate at the function level.",
|
||||||
"Takes into account the filename/lineno where the function was defined.")},
|
"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."},
|
"files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."},
|
||||||
"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
|
"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
|
||||||
"addresses": &variable{boolKind, "f", "granularity", helpText(
|
"addresses": &variable{boolKind, "f", "granularity", helpText(
|
||||||
"Aggregate at the function level.",
|
"Aggregate at the address level.",
|
||||||
"Includes functions' addresses in the output.")},
|
"Includes functions' addresses in the output.")},
|
||||||
"noinlines": &variable{boolKind, "f", "granularity", helpText(
|
"noinlines": &variable{boolKind, "f", "", helpText(
|
||||||
"Aggregate at the function level.",
|
"Ignore inlines.",
|
||||||
"Attributes inlined functions to their first out-of-line caller.")},
|
|
||||||
"addressnoinlines": &variable{boolKind, "f", "granularity", helpText(
|
|
||||||
"Aggregate at the function level, including functions' addresses in the output.",
|
|
||||||
"Attributes inlined functions to their first out-of-line caller.")},
|
"Attributes inlined functions to their first out-of-line caller.")},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,21 +337,27 @@ func listHelp(c string, redirect bool) string {
|
||||||
|
|
||||||
// browsers returns a list of commands to attempt for web visualization.
|
// browsers returns a list of commands to attempt for web visualization.
|
||||||
func browsers() []string {
|
func browsers() []string {
|
||||||
cmds := []string{"chrome", "google-chrome", "firefox"}
|
var cmds []string
|
||||||
|
if userBrowser := os.Getenv("BROWSER"); userBrowser != "" {
|
||||||
|
cmds = append(cmds, userBrowser)
|
||||||
|
}
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
return append(cmds, "/usr/bin/open")
|
cmds = append(cmds, "/usr/bin/open")
|
||||||
case "windows":
|
case "windows":
|
||||||
return append(cmds, "cmd /c start")
|
cmds = append(cmds, "cmd /c start")
|
||||||
default:
|
default:
|
||||||
userBrowser := os.Getenv("BROWSER")
|
// Commands opening browsers are prioritized over xdg-open, so browser()
|
||||||
if userBrowser != "" {
|
// command can be used on linux to open the .svg file generated by the -web
|
||||||
cmds = append([]string{userBrowser, "sensible-browser"}, cmds...)
|
// command (the .svg file includes embedded javascript so is best viewed in
|
||||||
} else {
|
// a browser).
|
||||||
cmds = append([]string{"sensible-browser"}, cmds...)
|
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 append(cmds, "xdg-open")
|
|
||||||
}
|
}
|
||||||
|
return cmds
|
||||||
}
|
}
|
||||||
|
|
||||||
var kcachegrind = []string{"kcachegrind"}
|
var kcachegrind = []string{"kcachegrind"}
|
||||||
|
|
|
||||||
|
|
@ -152,20 +152,33 @@ func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyCommandOverrides(cmd string, outputFormat int, v variables) variables {
|
func applyCommandOverrides(cmd string, outputFormat int, v variables) variables {
|
||||||
trim, tagfilter, filter := v["trim"].boolValue(), true, true
|
// 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 {
|
switch cmd {
|
||||||
case "callgrind", "kcachegrind":
|
|
||||||
trim = false
|
|
||||||
v.set("addresses", "t")
|
|
||||||
case "disasm", "weblist":
|
case "disasm", "weblist":
|
||||||
trim = false
|
trim = false
|
||||||
v.set("addressnoinlines", "t")
|
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":
|
case "peek":
|
||||||
trim, tagfilter, filter = false, false, false
|
trim = false
|
||||||
case "list":
|
case "list":
|
||||||
v.set("nodecount", "0")
|
trim = false
|
||||||
v.set("lines", "t")
|
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":
|
case "text", "top", "topproto":
|
||||||
if v["nodecount"].intValue() == -1 {
|
if v["nodecount"].intValue() == -1 {
|
||||||
v.set("nodecount", "0")
|
v.set("nodecount", "0")
|
||||||
|
|
@ -176,9 +189,11 @@ func applyCommandOverrides(cmd string, outputFormat int, v variables) variables
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if outputFormat == report.Proto || outputFormat == report.Raw {
|
switch outputFormat {
|
||||||
trim, tagfilter, filter = false, false, false
|
case report.Proto, report.Raw, report.Callgrind:
|
||||||
|
trim = false
|
||||||
v.set("addresses", "t")
|
v.set("addresses", "t")
|
||||||
|
v.set("noinlines", "f")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !trim {
|
if !trim {
|
||||||
|
|
@ -186,43 +201,32 @@ func applyCommandOverrides(cmd string, outputFormat int, v variables) variables
|
||||||
v.set("nodefraction", "0")
|
v.set("nodefraction", "0")
|
||||||
v.set("edgefraction", "0")
|
v.set("edgefraction", "0")
|
||||||
}
|
}
|
||||||
if !tagfilter {
|
|
||||||
v.set("tagfocus", "")
|
|
||||||
v.set("tagignore", "")
|
|
||||||
}
|
|
||||||
if !filter {
|
|
||||||
v.set("focus", "")
|
|
||||||
v.set("ignore", "")
|
|
||||||
v.set("hide", "")
|
|
||||||
v.set("show", "")
|
|
||||||
v.set("show_from", "")
|
|
||||||
}
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func aggregate(prof *profile.Profile, v variables) error {
|
func aggregate(prof *profile.Profile, v variables) error {
|
||||||
var inlines, function, filename, linenumber, address bool
|
var function, filename, linenumber, address bool
|
||||||
|
inlines := !v["noinlines"].boolValue()
|
||||||
switch {
|
switch {
|
||||||
case v["addresses"].boolValue():
|
case v["addresses"].boolValue():
|
||||||
return nil
|
if inlines {
|
||||||
case v["lines"].boolValue():
|
return nil
|
||||||
inlines = true
|
}
|
||||||
function = true
|
|
||||||
filename = true
|
|
||||||
linenumber = true
|
|
||||||
case v["files"].boolValue():
|
|
||||||
inlines = true
|
|
||||||
filename = true
|
|
||||||
case v["functions"].boolValue():
|
|
||||||
inlines = true
|
|
||||||
function = true
|
|
||||||
case v["noinlines"].boolValue():
|
|
||||||
function = true
|
|
||||||
case v["addressnoinlines"].boolValue():
|
|
||||||
function = true
|
function = true
|
||||||
filename = true
|
filename = true
|
||||||
linenumber = true
|
linenumber = true
|
||||||
address = 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:
|
default:
|
||||||
return fmt.Errorf("unexpected granularity")
|
return fmt.Errorf("unexpected granularity")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ func TestParse(t *testing.T) {
|
||||||
flags, source string
|
flags, source string
|
||||||
}{
|
}{
|
||||||
{"text,functions,flat", "cpu"},
|
{"text,functions,flat", "cpu"},
|
||||||
|
{"text,functions,noinlines,flat", "cpu"},
|
||||||
|
{"text,filefunctions,noinlines,flat", "cpu"},
|
||||||
|
{"text,addresses,noinlines,flat", "cpu"},
|
||||||
{"tree,addresses,flat,nodecount=4", "cpusmall"},
|
{"tree,addresses,flat,nodecount=4", "cpusmall"},
|
||||||
{"text,functions,flat,nodecount=5,call_tree", "unknown"},
|
{"text,functions,flat,nodecount=5,call_tree", "unknown"},
|
||||||
{"text,alloc_objects,flat", "heap_alloc"},
|
{"text,alloc_objects,flat", "heap_alloc"},
|
||||||
|
|
@ -63,6 +66,7 @@ func TestParse(t *testing.T) {
|
||||||
{"text,lines,cum,show=[12]00", "cpu"},
|
{"text,lines,cum,show=[12]00", "cpu"},
|
||||||
{"text,lines,cum,hide=line[X3]0,focus=[12]00", "cpu"},
|
{"text,lines,cum,hide=line[X3]0,focus=[12]00", "cpu"},
|
||||||
{"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
|
{"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
|
||||||
|
{"topproto,lines", "cpu"},
|
||||||
{"tree,lines,cum,focus=[24]00", "heap"},
|
{"tree,lines,cum,focus=[24]00", "heap"},
|
||||||
{"tree,relative_percentages,cum,focus=[24]00", "heap"},
|
{"tree,relative_percentages,cum,focus=[24]00", "heap"},
|
||||||
{"tree,lines,cum,show_from=line2", "cpu"},
|
{"tree,lines,cum,show_from=line2", "cpu"},
|
||||||
|
|
@ -92,6 +96,8 @@ func TestParse(t *testing.T) {
|
||||||
{"peek=line.*01", "cpu"},
|
{"peek=line.*01", "cpu"},
|
||||||
{"weblist=line[13],addresses,flat", "cpu"},
|
{"weblist=line[13],addresses,flat", "cpu"},
|
||||||
{"tags,tagfocus=400kb:", "heap_request"},
|
{"tags,tagfocus=400kb:", "heap_request"},
|
||||||
|
{"dot", "longNameFuncs"},
|
||||||
|
{"text", "longNameFuncs"},
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVars := pprofVariables
|
baseVars := pprofVariables
|
||||||
|
|
@ -108,9 +114,6 @@ func TestParse(t *testing.T) {
|
||||||
|
|
||||||
flags := strings.Split(tc.flags, ",")
|
flags := strings.Split(tc.flags, ",")
|
||||||
|
|
||||||
// Skip the output format in the first flag, to output to a proto
|
|
||||||
addFlags(&f, flags[1:])
|
|
||||||
|
|
||||||
// Encode profile into a protobuf and decode it again.
|
// Encode profile into a protobuf and decode it again.
|
||||||
protoTempFile, err := ioutil.TempFile("", "profile_proto")
|
protoTempFile, err := ioutil.TempFile("", "profile_proto")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -123,11 +126,13 @@ func TestParse(t *testing.T) {
|
||||||
if flags[0] == "topproto" {
|
if flags[0] == "topproto" {
|
||||||
f.bools["proto"] = false
|
f.bools["proto"] = false
|
||||||
f.bools["topproto"] = true
|
f.bools["topproto"] = true
|
||||||
|
f.bools["addresses"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// First pprof invocation to save the profile into a profile.proto.
|
// First pprof invocation to save the profile into a profile.proto.
|
||||||
o1 := setDefaults(nil)
|
// Pass in flag set hen setting defaults, because otherwise default
|
||||||
o1.Flagset = f
|
// transport will try to add flags to the default flag set.
|
||||||
|
o1 := setDefaults(&plugin.Options{Flagset: f})
|
||||||
o1.Fetch = testFetcher{}
|
o1.Fetch = testFetcher{}
|
||||||
o1.Sym = testSymbolizer{}
|
o1.Sym = testSymbolizer{}
|
||||||
o1.UI = testUI
|
o1.UI = testUI
|
||||||
|
|
@ -144,28 +149,28 @@ func TestParse(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.Remove(outputTempFile.Name())
|
defer os.Remove(outputTempFile.Name())
|
||||||
defer outputTempFile.Close()
|
defer outputTempFile.Close()
|
||||||
|
|
||||||
|
f = baseFlags()
|
||||||
f.strings["output"] = outputTempFile.Name()
|
f.strings["output"] = outputTempFile.Name()
|
||||||
f.args = []string{protoTempFile.Name()}
|
f.args = []string{protoTempFile.Name()}
|
||||||
|
|
||||||
var solution string
|
delete(f.bools, "proto")
|
||||||
|
addFlags(&f, flags)
|
||||||
|
solution := solutionFilename(tc.source, &f)
|
||||||
// Apply the flags for the second pprof run, and identify name of
|
// Apply the flags for the second pprof run, and identify name of
|
||||||
// the file containing expected results
|
// the file containing expected results
|
||||||
if flags[0] == "topproto" {
|
if flags[0] == "topproto" {
|
||||||
|
addFlags(&f, flags)
|
||||||
solution = solutionFilename(tc.source, &f)
|
solution = solutionFilename(tc.source, &f)
|
||||||
delete(f.bools, "topproto")
|
delete(f.bools, "topproto")
|
||||||
f.bools["text"] = true
|
f.bools["text"] = true
|
||||||
} else {
|
|
||||||
delete(f.bools, "proto")
|
|
||||||
addFlags(&f, flags[:1])
|
|
||||||
solution = solutionFilename(tc.source, &f)
|
|
||||||
}
|
}
|
||||||
// The add_comment flag is not idempotent so only apply it on the first run.
|
|
||||||
delete(f.strings, "add_comment")
|
|
||||||
|
|
||||||
// Second pprof invocation to read the profile from profile.proto
|
// Second pprof invocation to read the profile from profile.proto
|
||||||
// and generate a report.
|
// and generate a report.
|
||||||
o2 := setDefaults(nil)
|
// Pass in flag set hen setting defaults, because otherwise default
|
||||||
o2.Flagset = f
|
// transport will try to add flags to the default flag set.
|
||||||
|
o2 := setDefaults(&plugin.Options{Flagset: f})
|
||||||
o2.Sym = testSymbolizeDemangler{}
|
o2.Sym = testSymbolizeDemangler{}
|
||||||
o2.Obj = new(mockObjTool)
|
o2.Obj = new(mockObjTool)
|
||||||
o2.UI = testUI
|
o2.UI = testUI
|
||||||
|
|
@ -250,7 +255,8 @@ func testSourceURL(port int) string {
|
||||||
func solutionFilename(source string, f *testFlags) string {
|
func solutionFilename(source string, f *testFlags) string {
|
||||||
name := []string{"pprof", strings.TrimPrefix(source, testSourceURL(8000))}
|
name := []string{"pprof", strings.TrimPrefix(source, testSourceURL(8000))}
|
||||||
name = addString(name, f, []string{"flat", "cum"})
|
name = addString(name, f, []string{"flat", "cum"})
|
||||||
name = addString(name, f, []string{"functions", "files", "lines", "addresses"})
|
name = addString(name, f, []string{"functions", "filefunctions", "files", "lines", "addresses"})
|
||||||
|
name = addString(name, f, []string{"noinlines"})
|
||||||
name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"})
|
name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"})
|
||||||
name = addString(name, f, []string{"relative_percentages"})
|
name = addString(name, f, []string{"relative_percentages"})
|
||||||
name = addString(name, f, []string{"seconds"})
|
name = addString(name, f, []string{"seconds"})
|
||||||
|
|
@ -293,6 +299,8 @@ type testFlags struct {
|
||||||
|
|
||||||
func (testFlags) ExtraUsage() string { return "" }
|
func (testFlags) ExtraUsage() string { return "" }
|
||||||
|
|
||||||
|
func (testFlags) AddExtraUsage(eu string) {}
|
||||||
|
|
||||||
func (f testFlags) Bool(s string, d bool, c string) *bool {
|
func (f testFlags) Bool(s string, d bool, c string) *bool {
|
||||||
if b, ok := f.bools[s]; ok {
|
if b, ok := f.bools[s]; ok {
|
||||||
return &b
|
return &b
|
||||||
|
|
@ -436,6 +444,8 @@ func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string
|
||||||
p = contentionProfile()
|
p = contentionProfile()
|
||||||
case "symbolz":
|
case "symbolz":
|
||||||
p = symzProfile()
|
p = symzProfile()
|
||||||
|
case "longNameFuncs":
|
||||||
|
p = longNameFuncsProfile()
|
||||||
default:
|
default:
|
||||||
return nil, "", fmt.Errorf("unexpected source: %s", s)
|
return nil, "", fmt.Errorf("unexpected source: %s", s)
|
||||||
}
|
}
|
||||||
|
|
@ -519,6 +529,83 @@ func fakeDemangler(name string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a profile with function names which should be shortened in
|
||||||
|
// graph and flame views.
|
||||||
|
func longNameFuncsProfile() *profile.Profile {
|
||||||
|
var longNameFuncsM = []*profile.Mapping{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Start: 0x1000,
|
||||||
|
Limit: 0x4000,
|
||||||
|
File: "/path/to/testbinary",
|
||||||
|
HasFunctions: true,
|
||||||
|
HasFilenames: true,
|
||||||
|
HasLineNumbers: true,
|
||||||
|
HasInlineFrames: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var longNameFuncsF = []*profile.Function{
|
||||||
|
{ID: 1, Name: "path/to/package1.object.function1", SystemName: "path/to/package1.object.function1", Filename: "path/to/package1.go"},
|
||||||
|
{ID: 2, Name: "(anonymous namespace)::Bar::Foo", SystemName: "(anonymous namespace)::Bar::Foo", Filename: "a/long/path/to/package2.cc"},
|
||||||
|
{ID: 3, Name: "java.bar.foo.FooBar.run(java.lang.Runnable)", SystemName: "java.bar.foo.FooBar.run(java.lang.Runnable)", Filename: "FooBar.java"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var longNameFuncsL = []*profile.Location{
|
||||||
|
{
|
||||||
|
ID: 1000,
|
||||||
|
Mapping: longNameFuncsM[0],
|
||||||
|
Address: 0x1000,
|
||||||
|
Line: []profile.Line{
|
||||||
|
{Function: longNameFuncsF[0], Line: 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2000,
|
||||||
|
Mapping: longNameFuncsM[0],
|
||||||
|
Address: 0x2000,
|
||||||
|
Line: []profile.Line{
|
||||||
|
{Function: longNameFuncsF[1], Line: 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3000,
|
||||||
|
Mapping: longNameFuncsM[0],
|
||||||
|
Address: 0x3000,
|
||||||
|
Line: []profile.Line{
|
||||||
|
{Function: longNameFuncsF[2], Line: 9},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &profile.Profile{
|
||||||
|
PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
|
||||||
|
Period: 1,
|
||||||
|
DurationNanos: 10e9,
|
||||||
|
SampleType: []*profile.ValueType{
|
||||||
|
{Type: "samples", Unit: "count"},
|
||||||
|
{Type: "cpu", Unit: "milliseconds"},
|
||||||
|
},
|
||||||
|
Sample: []*profile.Sample{
|
||||||
|
{
|
||||||
|
Location: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1], longNameFuncsL[2]},
|
||||||
|
Value: []int64{1000, 1000},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Location: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1]},
|
||||||
|
Value: []int64{100, 100},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Location: []*profile.Location{longNameFuncsL[2]},
|
||||||
|
Value: []int64{10, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Location: longNameFuncsL,
|
||||||
|
Function: longNameFuncsF,
|
||||||
|
Mapping: longNameFuncsM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cpuProfile() *profile.Profile {
|
func cpuProfile() *profile.Profile {
|
||||||
var cpuM = []*profile.Mapping{
|
var cpuM = []*profile.Mapping{
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -57,7 +56,7 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI)
|
p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI, o.HTTPTransport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +122,7 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, *profile.Profile, plugin.MappingSources, plugin.MappingSources, bool, error) {
|
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 := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
var psrc, pbase *profile.Profile
|
var psrc, pbase *profile.Profile
|
||||||
|
|
@ -133,11 +132,11 @@ func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, o
|
||||||
var countsrc, countbase int
|
var countsrc, countbase int
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
psrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui)
|
psrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui, tr)
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui)
|
pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui, tr)
|
||||||
}()
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
save := savesrc || savebase
|
save := savesrc || savebase
|
||||||
|
|
@ -167,7 +166,7 @@ func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, o
|
||||||
// chunkedGrab fetches the profiles described in source and merges them into
|
// chunkedGrab fetches the profiles described in source and merges them into
|
||||||
// a single profile. It fetches a chunk of profiles concurrently, with a maximum
|
// a single profile. It fetches a chunk of profiles concurrently, with a maximum
|
||||||
// chunk size to limit its memory usage.
|
// chunk size to limit its memory usage.
|
||||||
func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) {
|
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
|
const chunkSize = 64
|
||||||
|
|
||||||
var p *profile.Profile
|
var p *profile.Profile
|
||||||
|
|
@ -180,7 +179,7 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo
|
||||||
if end > len(sources) {
|
if end > len(sources) {
|
||||||
end = len(sources)
|
end = len(sources)
|
||||||
}
|
}
|
||||||
chunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui)
|
chunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui, tr)
|
||||||
switch {
|
switch {
|
||||||
case chunkErr != nil:
|
case chunkErr != nil:
|
||||||
return nil, nil, false, 0, chunkErr
|
return nil, nil, false, 0, chunkErr
|
||||||
|
|
@ -204,13 +203,13 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// concurrentGrab fetches multiple profiles concurrently
|
// concurrentGrab fetches multiple profiles concurrently
|
||||||
func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) {
|
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 := sync.WaitGroup{}
|
||||||
wg.Add(len(sources))
|
wg.Add(len(sources))
|
||||||
for i := range sources {
|
for i := range sources {
|
||||||
go func(s *profileSource) {
|
go func(s *profileSource) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui)
|
s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui, tr)
|
||||||
}(&sources[i])
|
}(&sources[i])
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
@ -310,7 +309,7 @@ const testSourceAddress = "pproftest.local"
|
||||||
// grabProfile fetches a profile. Returns the profile, sources for the
|
// grabProfile fetches a profile. Returns the profile, sources for the
|
||||||
// profile mappings, a bool indicating if the profile was fetched
|
// profile mappings, a bool indicating if the profile was fetched
|
||||||
// remotely, and an error.
|
// remotely, and an error.
|
||||||
func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
|
func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
|
||||||
var src string
|
var src string
|
||||||
duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
|
duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
|
||||||
if fetcher != nil {
|
if fetcher != nil {
|
||||||
|
|
@ -321,7 +320,7 @@ func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.Ob
|
||||||
}
|
}
|
||||||
if err != nil || p == nil {
|
if err != nil || p == nil {
|
||||||
// Fetch the profile over HTTP or from a file.
|
// Fetch the profile over HTTP or from a file.
|
||||||
p, src, err = fetch(source, duration, timeout, ui)
|
p, src, err = fetch(source, duration, timeout, ui, tr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +460,7 @@ mapping:
|
||||||
// fetch fetches a profile from source, within the timeout specified,
|
// fetch fetches a profile from source, within the timeout specified,
|
||||||
// producing messages through the ui. It returns the profile and the
|
// producing messages through the ui. It returns the profile and the
|
||||||
// url of the actual source of the profile for remote profiles.
|
// url of the actual source of the profile for remote profiles.
|
||||||
func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *profile.Profile, src string, err error) {
|
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
|
var f io.ReadCloser
|
||||||
|
|
||||||
if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
|
if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
|
||||||
|
|
@ -469,7 +468,7 @@ func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *pro
|
||||||
if duration > 0 {
|
if duration > 0 {
|
||||||
ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
|
ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
|
||||||
}
|
}
|
||||||
f, err = fetchURL(sourceURL, timeout)
|
f, err = fetchURL(sourceURL, timeout, tr)
|
||||||
src = sourceURL
|
src = sourceURL
|
||||||
} else if isPerfFile(source) {
|
} else if isPerfFile(source) {
|
||||||
f, err = convertPerfData(source, ui)
|
f, err = convertPerfData(source, ui)
|
||||||
|
|
@ -484,8 +483,12 @@ func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *pro
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchURL fetches a profile from a URL using HTTP.
|
// fetchURL fetches a profile from a URL using HTTP.
|
||||||
func fetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
|
func fetchURL(source string, timeout time.Duration, tr http.RoundTripper) (io.ReadCloser, error) {
|
||||||
resp, err := httpGet(source, timeout)
|
client := &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: timeout + 5*time.Second,
|
||||||
|
}
|
||||||
|
resp, err := client.Get(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("http fetch: %v", err)
|
return nil, fmt.Errorf("http fetch: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -582,30 +585,3 @@ func adjustURL(source string, duration, timeout time.Duration) (string, time.Dur
|
||||||
u.RawQuery = values.Encode()
|
u.RawQuery = values.Encode()
|
||||||
return u.String(), timeout
|
return u.String(), timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpGet is a wrapper around http.Get; it is defined as a variable
|
|
||||||
// so it can be redefined during for testing.
|
|
||||||
var httpGet = func(source string, timeout time.Duration) (*http.Response, error) {
|
|
||||||
url, err := url.Parse(source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
if url.Scheme == "https+insecure" {
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
url.Scheme = "https"
|
|
||||||
source = url.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
ResponseHeaderTimeout: timeout + 5*time.Second,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return client.Get(source)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -39,6 +39,7 @@ import (
|
||||||
"github.com/google/pprof/internal/plugin"
|
"github.com/google/pprof/internal/plugin"
|
||||||
"github.com/google/pprof/internal/proftest"
|
"github.com/google/pprof/internal/proftest"
|
||||||
"github.com/google/pprof/internal/symbolizer"
|
"github.com/google/pprof/internal/symbolizer"
|
||||||
|
"github.com/google/pprof/internal/transport"
|
||||||
"github.com/google/pprof/profile"
|
"github.com/google/pprof/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -173,12 +174,6 @@ func (testFile) Close() error {
|
||||||
|
|
||||||
func TestFetch(t *testing.T) {
|
func TestFetch(t *testing.T) {
|
||||||
const path = "testdata/"
|
const path = "testdata/"
|
||||||
|
|
||||||
// Intercept http.Get calls from HTTPFetcher.
|
|
||||||
savedHTTPGet := httpGet
|
|
||||||
defer func() { httpGet = savedHTTPGet }()
|
|
||||||
httpGet = stubHTTPGet
|
|
||||||
|
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
source, execName string
|
source, execName string
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +183,7 @@ func TestFetch(t *testing.T) {
|
||||||
{path + "go.nomappings.crash", "/bin/gotest.exe"},
|
{path + "go.nomappings.crash", "/bin/gotest.exe"},
|
||||||
{"http://localhost/profile?file=cppbench.cpu", ""},
|
{"http://localhost/profile?file=cppbench.cpu", ""},
|
||||||
} {
|
} {
|
||||||
p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t})
|
p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t}, &httpTransport{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s: %s", tc.source, err)
|
t.Fatalf("%s: %s", tc.source, err)
|
||||||
}
|
}
|
||||||
|
|
@ -449,8 +444,9 @@ func TestFetchWithBase(t *testing.T) {
|
||||||
f.args = tc.sources
|
f.args = tc.sources
|
||||||
|
|
||||||
o := setDefaults(&plugin.Options{
|
o := setDefaults(&plugin.Options{
|
||||||
UI: &proftest.TestUI{T: t, AllowRx: "Local symbolization failed|Some binary filenames not available"},
|
UI: &proftest.TestUI{T: t, AllowRx: "Local symbolization failed|Some binary filenames not available"},
|
||||||
Flagset: f,
|
Flagset: f,
|
||||||
|
HTTPTransport: transport.New(nil),
|
||||||
})
|
})
|
||||||
src, _, err := parseFlags(o)
|
src, _, err := parseFlags(o)
|
||||||
|
|
||||||
|
|
@ -503,19 +499,14 @@ func mappingSources(key, source string, start uint64) plugin.MappingSources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubHTTPGet intercepts a call to http.Get and rewrites it to use
|
type httpTransport struct{}
|
||||||
// "file://" to get the profile directly from a file.
|
|
||||||
func stubHTTPGet(source string, _ time.Duration) (*http.Response, error) {
|
|
||||||
url, err := url.Parse(source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
values := url.Query()
|
func (tr *httpTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
values := req.URL.Query()
|
||||||
file := values.Get("file")
|
file := values.Get("file")
|
||||||
|
|
||||||
if file == "" {
|
if file == "" {
|
||||||
return nil, fmt.Errorf("want .../file?profile, got %s", source)
|
return nil, fmt.Errorf("want .../file?profile, got %s", req.URL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &http.Transport{}
|
t := &http.Transport{}
|
||||||
|
|
@ -532,7 +523,7 @@ func closedError() string {
|
||||||
return "use of closed"
|
return "use of closed"
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpsInsecure(t *testing.T) {
|
func TestHTTPSInsecure(t *testing.T) {
|
||||||
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
|
||||||
t.Skip("test assumes tcp available")
|
t.Skip("test assumes tcp available")
|
||||||
}
|
}
|
||||||
|
|
@ -553,7 +544,8 @@ func TestHttpsInsecure(t *testing.T) {
|
||||||
pprofVariables = baseVars.makeCopy()
|
pprofVariables = baseVars.makeCopy()
|
||||||
defer func() { pprofVariables = baseVars }()
|
defer func() { pprofVariables = baseVars }()
|
||||||
|
|
||||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{selfSignedCert(t)}}
|
tlsCert, _, _ := selfSignedCert(t, "")
|
||||||
|
tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||||
|
|
||||||
l, err := tls.Listen("tcp", "localhost:0", tlsConfig)
|
l, err := tls.Listen("tcp", "localhost:0", tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -586,8 +578,9 @@ func TestHttpsInsecure(t *testing.T) {
|
||||||
Symbolize: "remote",
|
Symbolize: "remote",
|
||||||
}
|
}
|
||||||
o := &plugin.Options{
|
o := &plugin.Options{
|
||||||
Obj: &binutils.Binutils{},
|
Obj: &binutils.Binutils{},
|
||||||
UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
|
UI: &proftest.TestUI{T: t, AllowRx: "Saved profile in"},
|
||||||
|
HTTPTransport: transport.New(nil),
|
||||||
}
|
}
|
||||||
o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI}
|
o.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI}
|
||||||
p, err := fetchProfiles(s, o)
|
p, err := fetchProfiles(s, o)
|
||||||
|
|
@ -600,7 +593,122 @@ func TestHttpsInsecure(t *testing.T) {
|
||||||
if len(p.Function) == 0 {
|
if len(p.Function) == 0 {
|
||||||
t.Fatalf("fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0", address)
|
t.Fatalf("fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0", address)
|
||||||
}
|
}
|
||||||
if err := checkProfileHasFunction(p, "TestHttpsInsecure"); err != nil {
|
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)
|
t.Fatalf("fetchProfiles(%s) %v", address, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -614,7 +722,10 @@ func checkProfileHasFunction(p *profile.Profile, fname string) error {
|
||||||
return fmt.Errorf("got %s, want function %q", p.String(), fname)
|
return fmt.Errorf("got %s, want function %q", p.String(), fname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selfSignedCert(t *testing.T) tls.Certificate {
|
// 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)
|
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate private key: %v", err)
|
t.Fatalf("failed to generate private key: %v", err)
|
||||||
|
|
@ -629,6 +740,8 @@ func selfSignedCert(t *testing.T) tls.Certificate {
|
||||||
SerialNumber: big.NewInt(1),
|
SerialNumber: big.NewInt(1),
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: time.Now().Add(10 * time.Minute),
|
NotAfter: time.Now().Add(10 * time.Minute),
|
||||||
|
IsCA: true,
|
||||||
|
DNSNames: []string{host},
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err = x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privKey.Public(), privKey)
|
b, err = x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privKey.Public(), privKey)
|
||||||
|
|
@ -641,5 +754,5 @@ func selfSignedCert(t *testing.T) tls.Certificate {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create TLS key pair: %v", err)
|
t.Fatalf("failed to create TLS key pair: %v", err)
|
||||||
}
|
}
|
||||||
return cert
|
return cert, bc, bk
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
|
||||||
v := n.CumValue()
|
v := n.CumValue()
|
||||||
fullName := n.Info.PrintableName()
|
fullName := n.Info.PrintableName()
|
||||||
node := &treeNode{
|
node := &treeNode{
|
||||||
Name: getNodeShortName(fullName),
|
Name: graph.ShortenFunctionName(fullName),
|
||||||
FullName: fullName,
|
FullName: fullName,
|
||||||
Cum: v,
|
Cum: v,
|
||||||
CumFormat: config.FormatValue(v),
|
CumFormat: config.FormatValue(v),
|
||||||
|
|
@ -101,19 +101,3 @@ func (ui *webInterface) flamegraph(w http.ResponseWriter, req *http.Request) {
|
||||||
Nodes: nodeArr,
|
Nodes: nodeArr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNodeShortName builds a short node name from fullName.
|
|
||||||
func getNodeShortName(name string) string {
|
|
||||||
chunks := strings.SplitN(name, "(", 2)
|
|
||||||
head := chunks[0]
|
|
||||||
pathSep := strings.LastIndexByte(head, '/')
|
|
||||||
if pathSep == -1 || pathSep+1 >= len(head) {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
// Check if name is a stdlib package, i.e. doesn't have "." before "/"
|
|
||||||
if dot := strings.IndexByte(head, '.'); dot == -1 || dot > pathSep {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
// Trim package path prefix from node name
|
|
||||||
return name[pathSep+1:]
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package driver
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestGetNodeShortName(t *testing.T) {
|
|
||||||
type testCase struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
}
|
|
||||||
testcases := []testCase{
|
|
||||||
{
|
|
||||||
"root",
|
|
||||||
"root",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"syscall.Syscall",
|
|
||||||
"syscall.Syscall",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"net/http.(*conn).serve",
|
|
||||||
"net/http.(*conn).serve",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"github.com/blah/foo.Foo",
|
|
||||||
"foo.Foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"github.com/blah/foo_bar.(*FooBar).Foo",
|
|
||||||
"foo_bar.(*FooBar).Foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
|
|
||||||
"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
|
|
||||||
"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testcases {
|
|
||||||
name := getNodeShortName(tc.name)
|
|
||||||
if got, want := name, tc.want; got != want {
|
|
||||||
t.Errorf("for %s, got %q, want %q", tc.name, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/google/pprof/internal/plugin"
|
"github.com/google/pprof/internal/plugin"
|
||||||
"github.com/google/pprof/internal/proftest"
|
"github.com/google/pprof/internal/proftest"
|
||||||
"github.com/google/pprof/internal/report"
|
"github.com/google/pprof/internal/report"
|
||||||
|
"github.com/google/pprof/internal/transport"
|
||||||
"github.com/google/pprof/profile"
|
"github.com/google/pprof/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,7 +42,10 @@ func TestShell(t *testing.T) {
|
||||||
|
|
||||||
// Random interleave of independent scripts
|
// Random interleave of independent scripts
|
||||||
pprofVariables = testVariables(savedVariables)
|
pprofVariables = testVariables(savedVariables)
|
||||||
o := setDefaults(nil)
|
|
||||||
|
// 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))
|
o.UI = newUI(t, interleave(script, 0))
|
||||||
if err := interactive(p, o); err != nil {
|
if err := interactive(p, o); err != nil {
|
||||||
t.Error("first attempt:", err)
|
t.Error("first attempt:", err)
|
||||||
|
|
@ -259,12 +263,13 @@ func TestInteractiveCommands(t *testing.T) {
|
||||||
{
|
{
|
||||||
"weblist find -test",
|
"weblist find -test",
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"functions": "false",
|
"functions": "false",
|
||||||
"addressnoinlines": "true",
|
"addresses": "true",
|
||||||
"nodecount": "0",
|
"noinlines": "true",
|
||||||
"cum": "false",
|
"nodecount": "0",
|
||||||
"flat": "true",
|
"cum": "false",
|
||||||
"ignore": "test",
|
"flat": "true",
|
||||||
|
"ignore": "test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -25,6 +24,7 @@ import (
|
||||||
"github.com/google/pprof/internal/binutils"
|
"github.com/google/pprof/internal/binutils"
|
||||||
"github.com/google/pprof/internal/plugin"
|
"github.com/google/pprof/internal/plugin"
|
||||||
"github.com/google/pprof/internal/symbolizer"
|
"github.com/google/pprof/internal/symbolizer"
|
||||||
|
"github.com/google/pprof/internal/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setDefaults returns a new plugin.Options with zero fields sets to
|
// setDefaults returns a new plugin.Options with zero fields sets to
|
||||||
|
|
@ -38,7 +38,7 @@ func setDefaults(o *plugin.Options) *plugin.Options {
|
||||||
d.Writer = oswriter{}
|
d.Writer = oswriter{}
|
||||||
}
|
}
|
||||||
if d.Flagset == nil {
|
if d.Flagset == nil {
|
||||||
d.Flagset = goFlags{}
|
d.Flagset = &GoFlags{}
|
||||||
}
|
}
|
||||||
if d.Obj == nil {
|
if d.Obj == nil {
|
||||||
d.Obj = &binutils.Binutils{}
|
d.Obj = &binutils.Binutils{}
|
||||||
|
|
@ -46,67 +46,15 @@ func setDefaults(o *plugin.Options) *plugin.Options {
|
||||||
if d.UI == nil {
|
if d.UI == nil {
|
||||||
d.UI = &stdUI{r: bufio.NewReader(os.Stdin)}
|
d.UI = &stdUI{r: bufio.NewReader(os.Stdin)}
|
||||||
}
|
}
|
||||||
|
if d.HTTPTransport == nil {
|
||||||
|
d.HTTPTransport = transport.New(d.Flagset)
|
||||||
|
}
|
||||||
if d.Sym == nil {
|
if d.Sym == nil {
|
||||||
d.Sym = &symbolizer.Symbolizer{Obj: d.Obj, UI: d.UI}
|
d.Sym = &symbolizer.Symbolizer{Obj: d.Obj, UI: d.UI, Transport: d.HTTPTransport}
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// goFlags returns a flagset implementation based on the standard flag
|
|
||||||
// package from the Go distribution. It implements the plugin.FlagSet
|
|
||||||
// interface.
|
|
||||||
type goFlags struct{}
|
|
||||||
|
|
||||||
func (goFlags) Bool(o string, d bool, c string) *bool {
|
|
||||||
return flag.Bool(o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) Int(o string, d int, c string) *int {
|
|
||||||
return flag.Int(o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) Float64(o string, d float64, c string) *float64 {
|
|
||||||
return flag.Float64(o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) String(o, d, c string) *string {
|
|
||||||
return flag.String(o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) BoolVar(b *bool, o string, d bool, c string) {
|
|
||||||
flag.BoolVar(b, o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) IntVar(i *int, o string, d int, c string) {
|
|
||||||
flag.IntVar(i, o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) Float64Var(f *float64, o string, d float64, c string) {
|
|
||||||
flag.Float64Var(f, o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) StringVar(s *string, o, d, c string) {
|
|
||||||
flag.StringVar(s, o, d, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) StringList(o, d, c string) *[]*string {
|
|
||||||
return &[]*string{flag.String(o, d, c)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) ExtraUsage() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (goFlags) Parse(usage func()) []string {
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) == 0 {
|
|
||||||
usage()
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
type stdUI struct {
|
type stdUI struct {
|
||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.addresses.noinlines.text
generated
vendored
Normal file
7
src/cmd/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
|
||||||
5
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.filefunctions.noinlines.text
generated
vendored
Normal file
5
src/cmd/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
|
||||||
5
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.flat.functions.noinlines.text
generated
vendored
Normal file
5
src/cmd/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
|
||||||
3
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.cpu.lines.topproto
generated
vendored
Normal file
3
src/cmd/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
|
||||||
9
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.longNameFuncs.dot
generated
vendored
Normal file
9
src/cmd/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
src/cmd/vendor/github.com/google/pprof/internal/driver/testdata/pprof.longNameFuncs.text
generated
vendored
Normal file
5
src/cmd/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
|
||||||
|
|
@ -249,6 +249,21 @@ table tr td {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{$sampleLen := len .SampleTypes}}
|
||||||
|
{{if gt $sampleLen 1}}
|
||||||
|
<div id="sample" class="menu-item">
|
||||||
|
<div class="menu-name">
|
||||||
|
Sample
|
||||||
|
<i class="downArrow"></i>
|
||||||
|
</div>
|
||||||
|
<div class="submenu">
|
||||||
|
{{range .SampleTypes}}
|
||||||
|
<a href="?si={{.}}" id="{{.}}">{{.}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div id="refine" class="menu-item">
|
<div id="refine" class="menu-item">
|
||||||
<div class="menu-name">
|
<div class="menu-name">
|
||||||
Refine
|
Refine
|
||||||
|
|
@ -259,6 +274,7 @@ table tr td {
|
||||||
<a title="{{.Help.ignore}}" href="?" id="ignore">Ignore</a>
|
<a title="{{.Help.ignore}}" href="?" id="ignore">Ignore</a>
|
||||||
<a title="{{.Help.hide}}" href="?" id="hide">Hide</a>
|
<a title="{{.Help.hide}}" href="?" id="hide">Hide</a>
|
||||||
<a title="{{.Help.show}}" href="?" id="show">Show</a>
|
<a title="{{.Help.show}}" href="?" id="show">Show</a>
|
||||||
|
<a title="{{.Help.show_from}}" href="?" id="show-from">Show from</a>
|
||||||
<hr>
|
<hr>
|
||||||
<a title="{{.Help.reset}}" href="?">Reset</a>
|
<a title="{{.Help.reset}}" href="?">Reset</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -718,9 +734,18 @@ function viewer(baseUrl, nodes) {
|
||||||
return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
|
return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSampleIndexLink(id) {
|
||||||
|
const elem = document.getElementById(id);
|
||||||
|
if (elem != null) {
|
||||||
|
setHrefParams(elem, function (params) {
|
||||||
|
params.set("si", id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update id's href to reflect current selection whenever it is
|
// Update id's href to reflect current selection whenever it is
|
||||||
// liable to be followed.
|
// liable to be followed.
|
||||||
function makeLinkDynamic(id) {
|
function makeSearchLinkDynamic(id) {
|
||||||
const elem = document.getElementById(id);
|
const elem = document.getElementById(id);
|
||||||
if (elem == null) return;
|
if (elem == null) return;
|
||||||
|
|
||||||
|
|
@ -730,46 +755,50 @@ function viewer(baseUrl, nodes) {
|
||||||
if (id == 'ignore') param = 'i';
|
if (id == 'ignore') param = 'i';
|
||||||
if (id == 'hide') param = 'h';
|
if (id == 'hide') param = 'h';
|
||||||
if (id == 'show') param = 's';
|
if (id == 'show') param = 's';
|
||||||
|
if (id == 'show-from') param = 'sf';
|
||||||
|
|
||||||
// We update on mouseenter so middle-click/right-click work properly.
|
// We update on mouseenter so middle-click/right-click work properly.
|
||||||
elem.addEventListener('mouseenter', updater);
|
elem.addEventListener('mouseenter', updater);
|
||||||
elem.addEventListener('touchstart', updater);
|
elem.addEventListener('touchstart', updater);
|
||||||
|
|
||||||
function updater() {
|
function updater() {
|
||||||
elem.href = updateUrl(new URL(elem.href), param);
|
// The selection can be in one of two modes: regexp-based or
|
||||||
|
// list-based. Construct regular expression depending on mode.
|
||||||
|
let re = regexpActive
|
||||||
|
? search.value
|
||||||
|
: Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
|
||||||
|
|
||||||
|
setHrefParams(elem, function (params) {
|
||||||
|
if (re != '') {
|
||||||
|
// For focus/show/show-from, forget old parameter. For others, add to re.
|
||||||
|
if (param != 'f' && param != 's' && param != 'sf' && params.has(param)) {
|
||||||
|
const old = params.get(param);
|
||||||
|
if (old != '') {
|
||||||
|
re += '|' + old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.set(param, re);
|
||||||
|
} else {
|
||||||
|
params.delete(param);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update URL to reflect current selection.
|
function setHrefParams(elem, paramSetter) {
|
||||||
function updateUrl(url, param) {
|
let url = new URL(elem.href);
|
||||||
url.hash = '';
|
url.hash = '';
|
||||||
|
|
||||||
// The selection can be in one of two modes: regexp-based or
|
|
||||||
// list-based. Construct regular expression depending on mode.
|
|
||||||
let re = regexpActive
|
|
||||||
? search.value
|
|
||||||
: Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|');
|
|
||||||
|
|
||||||
// Copy params from this page's URL.
|
// Copy params from this page's URL.
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
for (const p of new URLSearchParams(window.location.search)) {
|
for (const p of new URLSearchParams(window.location.search)) {
|
||||||
params.set(p[0], p[1]);
|
params.set(p[0], p[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (re != '') {
|
// Give the params to the setter to modify.
|
||||||
// For focus/show, forget old parameter. For others, add to re.
|
paramSetter(params);
|
||||||
if (param != 'f' && param != 's' && params.has(param)) {
|
|
||||||
const old = params.get(param);
|
|
||||||
if (old != '') {
|
|
||||||
re += '|' + old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params.set(param, re);
|
|
||||||
} else {
|
|
||||||
params.delete(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.toString();
|
elem.href = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTopClick(e) {
|
function handleTopClick(e) {
|
||||||
|
|
@ -803,7 +832,7 @@ function viewer(baseUrl, nodes) {
|
||||||
const enable = (search.value != '' || selected.size != 0);
|
const enable = (search.value != '' || selected.size != 0);
|
||||||
if (buttonsEnabled == enable) return;
|
if (buttonsEnabled == enable) return;
|
||||||
buttonsEnabled = enable;
|
buttonsEnabled = enable;
|
||||||
for (const id of ['focus', 'ignore', 'hide', 'show']) {
|
for (const id of ['focus', 'ignore', 'hide', 'show', 'show-from']) {
|
||||||
const link = document.getElementById(id);
|
const link = document.getElementById(id);
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
link.classList.toggle('disabled', !enable);
|
link.classList.toggle('disabled', !enable);
|
||||||
|
|
@ -825,8 +854,11 @@ function viewer(baseUrl, nodes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids = ['topbtn', 'graphbtn', 'peek', 'list', 'disasm',
|
const ids = ['topbtn', 'graphbtn', 'peek', 'list', 'disasm',
|
||||||
'focus', 'ignore', 'hide', 'show'];
|
'focus', 'ignore', 'hide', 'show', 'show-from'];
|
||||||
ids.forEach(makeLinkDynamic);
|
ids.forEach(makeSearchLinkDynamic);
|
||||||
|
|
||||||
|
const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}];
|
||||||
|
sampleIDs.forEach(setSampleIndexLink);
|
||||||
|
|
||||||
// Bind action to button with specified id.
|
// Bind action to button with specified id.
|
||||||
function addAction(id, action) {
|
function addAction(id, action) {
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,17 @@ func (ec *errorCatcher) PrintErr(args ...interface{}) {
|
||||||
|
|
||||||
// webArgs contains arguments passed to templates in webhtml.go.
|
// webArgs contains arguments passed to templates in webhtml.go.
|
||||||
type webArgs struct {
|
type webArgs struct {
|
||||||
Title string
|
Title string
|
||||||
Errors []string
|
Errors []string
|
||||||
Total int64
|
Total int64
|
||||||
Legend []string
|
SampleTypes []string
|
||||||
Help map[string]string
|
Legend []string
|
||||||
Nodes []string
|
Help map[string]string
|
||||||
HTMLBody template.HTML
|
Nodes []string
|
||||||
TextBody string
|
HTMLBody template.HTML
|
||||||
Top []report.TextItem
|
TextBody string
|
||||||
FlameGraph template.JS
|
Top []report.TextItem
|
||||||
|
FlameGraph template.JS
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
|
func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options) error {
|
||||||
|
|
@ -199,8 +200,10 @@ func openBrowser(url string, o *plugin.Options) {
|
||||||
for _, p := range []struct{ param, key string }{
|
for _, p := range []struct{ param, key string }{
|
||||||
{"f", "focus"},
|
{"f", "focus"},
|
||||||
{"s", "show"},
|
{"s", "show"},
|
||||||
|
{"sf", "show_from"},
|
||||||
{"i", "ignore"},
|
{"i", "ignore"},
|
||||||
{"h", "hide"},
|
{"h", "hide"},
|
||||||
|
{"si", "sample_index"},
|
||||||
} {
|
} {
|
||||||
if v := pprofVariables[p.key].value; v != "" {
|
if v := pprofVariables[p.key].value; v != "" {
|
||||||
q.Set(p.param, v)
|
q.Set(p.param, v)
|
||||||
|
|
@ -230,8 +233,10 @@ func varsFromURL(u *gourl.URL) variables {
|
||||||
vars := pprofVariables.makeCopy()
|
vars := pprofVariables.makeCopy()
|
||||||
vars["focus"].value = u.Query().Get("f")
|
vars["focus"].value = u.Query().Get("f")
|
||||||
vars["show"].value = u.Query().Get("s")
|
vars["show"].value = u.Query().Get("s")
|
||||||
|
vars["show_from"].value = u.Query().Get("sf")
|
||||||
vars["ignore"].value = u.Query().Get("i")
|
vars["ignore"].value = u.Query().Get("i")
|
||||||
vars["hide"].value = u.Query().Get("h")
|
vars["hide"].value = u.Query().Get("h")
|
||||||
|
vars["sample_index"].value = u.Query().Get("si")
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,6 +267,7 @@ func (ui *webInterface) render(w http.ResponseWriter, tmpl string,
|
||||||
data.Title = file + " " + profile
|
data.Title = file + " " + profile
|
||||||
data.Errors = errList
|
data.Errors = errList
|
||||||
data.Total = rpt.Total()
|
data.Total = rpt.Total()
|
||||||
|
data.SampleTypes = sampleTypes(ui.prof)
|
||||||
data.Legend = legend
|
data.Legend = legend
|
||||||
data.Help = ui.help
|
data.Help = ui.help
|
||||||
html := &bytes.Buffer{}
|
html := &bytes.Buffer{}
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,7 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
|
||||||
pageOffsetPpc64 = 0xc000000000000000
|
pageOffsetPpc64 = 0xc000000000000000
|
||||||
)
|
)
|
||||||
|
|
||||||
if start == 0 && offset == 0 &&
|
if start == 0 && offset == 0 && (limit == ^uint64(0) || limit == 0) {
|
||||||
(limit == ^uint64(0) || limit == 0) {
|
|
||||||
// Some tools may introduce a fake mapping that spans the entire
|
// Some tools may introduce a fake mapping that spans the entire
|
||||||
// address space. Assume that the address has already been
|
// address space. Assume that the address has already been
|
||||||
// adjusted, so no additional base adjustment is necessary.
|
// adjusted, so no additional base adjustment is necessary.
|
||||||
|
|
@ -189,9 +188,24 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
|
||||||
switch fh.Type {
|
switch fh.Type {
|
||||||
case elf.ET_EXEC:
|
case elf.ET_EXEC:
|
||||||
if loadSegment == nil {
|
if loadSegment == nil {
|
||||||
// Fixed-address executable, no adjustment.
|
// Assume fixed-address executable and so no adjustment.
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
if stextOffset == nil && start > 0 && start < 0x8000000000000000 {
|
||||||
|
// A regular user-mode executable. Compute the base offset using same
|
||||||
|
// arithmetics as in ET_DYN case below, see the explanation there.
|
||||||
|
// Ideally, the condition would just be "stextOffset == nil" as that
|
||||||
|
// represents the address of _stext symbol in the vmlinux image. Alas,
|
||||||
|
// the caller may skip reading it from the binary (it's expensive to scan
|
||||||
|
// all the symbols) and so it may be nil even for the kernel executable.
|
||||||
|
// So additionally check that the start is within the user-mode half of
|
||||||
|
// the 64-bit address space.
|
||||||
|
return start - offset + loadSegment.Off - loadSegment.Vaddr, nil
|
||||||
|
}
|
||||||
|
// Various kernel heuristics and cases follow.
|
||||||
|
if loadSegment.Vaddr == start-offset {
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
if start == 0 && limit != 0 {
|
if start == 0 && limit != 0 {
|
||||||
// ChromeOS remaps its kernel to 0. Nothing else should come
|
// ChromeOS remaps its kernel to 0. Nothing else should come
|
||||||
// down this path. Empirical values:
|
// down this path. Empirical values:
|
||||||
|
|
@ -202,12 +216,6 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
|
||||||
}
|
}
|
||||||
return -loadSegment.Vaddr, nil
|
return -loadSegment.Vaddr, nil
|
||||||
}
|
}
|
||||||
if loadSegment.Vaddr-loadSegment.Off == start-offset {
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
if loadSegment.Vaddr == start-offset {
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
|
if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
|
||||||
// Some kernels look like:
|
// Some kernels look like:
|
||||||
// VADDR=0xffffffff80200000
|
// VADDR=0xffffffff80200000
|
||||||
|
|
@ -230,7 +238,7 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
|
||||||
// start=0x198 limit=0x2f9fffff offset=0
|
// start=0x198 limit=0x2f9fffff offset=0
|
||||||
// VADDR=0xffffffff81000000
|
// VADDR=0xffffffff81000000
|
||||||
// stextOffset=0xffffffff81000198
|
// stextOffset=0xffffffff81000198
|
||||||
return -(*stextOffset - start), nil
|
return start - *stextOffset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("Don't know how to handle EXEC segment: %v start=0x%x limit=0x%x offset=0x%x", *loadSegment, start, limit, offset)
|
return 0, fmt.Errorf("Don't know how to handle EXEC segment: %v start=0x%x limit=0x%x offset=0x%x", *loadSegment, start, limit, offset)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ func TestGetBase(t *testing.T) {
|
||||||
kernelHeader := &elf.ProgHeader{
|
kernelHeader := &elf.ProgHeader{
|
||||||
Vaddr: 0xffffffff81000000,
|
Vaddr: 0xffffffff81000000,
|
||||||
}
|
}
|
||||||
|
kernelAslrHeader := &elf.ProgHeader{
|
||||||
|
Vaddr: 0xffffffff80200000,
|
||||||
|
Off: 0x1000,
|
||||||
|
}
|
||||||
ppc64KernelHeader := &elf.ProgHeader{
|
ppc64KernelHeader := &elf.ProgHeader{
|
||||||
Vaddr: 0xc000000000000000,
|
Vaddr: 0xc000000000000000,
|
||||||
}
|
}
|
||||||
|
|
@ -51,12 +55,15 @@ func TestGetBase(t *testing.T) {
|
||||||
wanterr bool
|
wanterr bool
|
||||||
}{
|
}{
|
||||||
{"exec", fhExec, nil, nil, 0x400000, 0, 0, 0, false},
|
{"exec", fhExec, nil, nil, 0x400000, 0, 0, 0, false},
|
||||||
{"exec offset", fhExec, lsOffset, nil, 0x400000, 0x800000, 0, 0, false},
|
{"exec offset", fhExec, lsOffset, nil, 0x400000, 0x800000, 0, 0x200000, false},
|
||||||
{"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},
|
{"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},
|
||||||
{"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false},
|
{"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false},
|
||||||
{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},
|
{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},
|
||||||
{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0x0, 0x0, false},
|
{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0x0, 0x0, false},
|
||||||
{"exec kernel ASLR", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0xffffffff81000000, 0x0, false},
|
{"exec kernel ASLR", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0xffffffff81000000, 0x0, false},
|
||||||
|
// TODO(aalexand): Figure out where this test case exactly comes from and
|
||||||
|
// whether it's still relevant.
|
||||||
|
{"exec kernel ASLR 2", fhExec, kernelAslrHeader, nil, 0xffffffff83e00000, 0xfffffffffc3fffff, 0x3c00000, 0x3c00000, false},
|
||||||
{"exec PPC64 kernel", fhExec, ppc64KernelHeader, uint64p(0xc000000000000000), 0xc000000000000000, 0xd00000001a730000, 0x0, 0x0, false},
|
{"exec PPC64 kernel", fhExec, ppc64KernelHeader, uint64p(0xc000000000000000), 0xc000000000000000, 0xd00000001a730000, 0x0, 0x0, false},
|
||||||
{"exec chromeos kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10197, 0, 0x7efffe68, false},
|
{"exec chromeos kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10197, 0, 0x7efffe68, false},
|
||||||
{"exec chromeos kernel 2", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10198, 0, 0x7efffe68, false},
|
{"exec chromeos kernel 2", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10198, 0, 0x7efffe68, false},
|
||||||
|
|
@ -85,7 +92,7 @@ func TestGetBase(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if base != tc.want {
|
if base != tc.want {
|
||||||
t.Errorf("%s: want %x, got %x", tc.label, tc.want, base)
|
t.Errorf("%s: want 0x%x, got 0x%x", tc.label, tc.want, base)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -382,6 +382,7 @@ func dotColor(score float64, isBackground bool) string {
|
||||||
|
|
||||||
func multilinePrintableName(info *NodeInfo) string {
|
func multilinePrintableName(info *NodeInfo) string {
|
||||||
infoCopy := *info
|
infoCopy := *info
|
||||||
|
infoCopy.Name = ShortenFunctionName(infoCopy.Name)
|
||||||
infoCopy.Name = strings.Replace(infoCopy.Name, "::", `\n`, -1)
|
infoCopy.Name = strings.Replace(infoCopy.Name, "::", `\n`, -1)
|
||||||
infoCopy.Name = strings.Replace(infoCopy.Name, ".", `\n`, -1)
|
infoCopy.Name = strings.Replace(infoCopy.Name, ".", `\n`, -1)
|
||||||
if infoCopy.File != "" {
|
if infoCopy.File != "" {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -26,6 +27,12 @@ import (
|
||||||
"github.com/google/pprof/profile"
|
"github.com/google/pprof/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
javaRegExp = regexp.MustCompile(`^(?:[a-z]\w*\.)*([A-Z][\w\$]*\.(?:<init>|[a-z]\w*(?:\$\d+)?))(?:(?:\()|$)`)
|
||||||
|
goRegExp = regexp.MustCompile(`^(?:[\w\-\.]+\/)+(.+)`)
|
||||||
|
cppRegExp = regexp.MustCompile(`^(?:(?:\(anonymous namespace\)::)(\w+$))|(?:(?:\(anonymous namespace\)::)?(?:[_a-zA-Z]\w*\::|)*(_*[A-Z]\w*::~?[_a-zA-Z]\w*)$)`)
|
||||||
|
)
|
||||||
|
|
||||||
// Graph summarizes a performance profile into a format that is
|
// Graph summarizes a performance profile into a format that is
|
||||||
// suitable for visualization.
|
// suitable for visualization.
|
||||||
type Graph struct {
|
type Graph struct {
|
||||||
|
|
@ -420,6 +427,16 @@ func newTree(prof *profile.Profile, o *Options) (g *Graph) {
|
||||||
return selectNodesForGraph(nodes, o.DropNegative)
|
return selectNodesForGraph(nodes, o.DropNegative)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShortenFunctionName returns a shortened version of a function's name.
|
||||||
|
func ShortenFunctionName(f string) string {
|
||||||
|
for _, re := range []*regexp.Regexp{goRegExp, javaRegExp, cppRegExp} {
|
||||||
|
if matches := re.FindStringSubmatch(f); len(matches) >= 2 {
|
||||||
|
return strings.Join(matches[1:], "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
// TrimTree trims a Graph in forest form, keeping only the nodes in kept. This
|
// TrimTree trims a Graph in forest form, keeping only the nodes in kept. This
|
||||||
// will not work correctly if even a single node has multiple parents.
|
// will not work correctly if even a single node has multiple parents.
|
||||||
func (g *Graph) TrimTree(kept NodePtrSet) {
|
func (g *Graph) TrimTree(kept NodePtrSet) {
|
||||||
|
|
@ -512,9 +529,7 @@ func isNegative(n *Node) bool {
|
||||||
|
|
||||||
// CreateNodes creates graph nodes for all locations in a profile. It
|
// CreateNodes creates graph nodes for all locations in a profile. It
|
||||||
// returns set of all nodes, plus a mapping of each location to the
|
// returns set of all nodes, plus a mapping of each location to the
|
||||||
// set of corresponding nodes (one per location.Line). If kept is
|
// set of corresponding nodes (one per location.Line).
|
||||||
// non-nil, only nodes in that set are included; nodes that do not
|
|
||||||
// match are represented as a nil.
|
|
||||||
func CreateNodes(prof *profile.Profile, o *Options) (Nodes, map[uint64]Nodes) {
|
func CreateNodes(prof *profile.Profile, o *Options) (Nodes, map[uint64]Nodes) {
|
||||||
locations := make(map[uint64]Nodes, len(prof.Location))
|
locations := make(map[uint64]Nodes, len(prof.Location))
|
||||||
nm := make(NodeMap, len(prof.Location))
|
nm := make(NodeMap, len(prof.Location))
|
||||||
|
|
@ -564,13 +579,13 @@ func nodeInfo(l *profile.Location, line profile.Line, objfile string, o *Options
|
||||||
if fname := line.Function.Filename; fname != "" {
|
if fname := line.Function.Filename; fname != "" {
|
||||||
ni.File = filepath.Clean(fname)
|
ni.File = filepath.Clean(fname)
|
||||||
}
|
}
|
||||||
if o.ObjNames {
|
|
||||||
ni.Objfile = objfile
|
|
||||||
ni.StartLine = int(line.Function.StartLine)
|
|
||||||
}
|
|
||||||
if o.OrigFnNames {
|
if o.OrigFnNames {
|
||||||
ni.OrigName = line.Function.SystemName
|
ni.OrigName = line.Function.SystemName
|
||||||
}
|
}
|
||||||
|
if o.ObjNames || (ni.Name == "" && ni.OrigName == "") {
|
||||||
|
ni.Objfile = objfile
|
||||||
|
ni.StartLine = int(line.Function.StartLine)
|
||||||
|
}
|
||||||
return ni
|
return ni
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package graph
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/pprof/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func edgeDebugString(edge *Edge) string {
|
func edgeDebugString(edge *Edge) string {
|
||||||
|
|
@ -312,3 +314,158 @@ func TestTrimTree(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nodeTestProfile() *profile.Profile {
|
||||||
|
mappings := []*profile.Mapping{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
File: "symbolized_binary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
File: "unsymbolized_library_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
File: "unsymbolized_library_2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
functions := []*profile.Function{
|
||||||
|
{ID: 1, Name: "symname"},
|
||||||
|
{ID: 2},
|
||||||
|
}
|
||||||
|
locations := []*profile.Location{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Mapping: mappings[0],
|
||||||
|
Line: []profile.Line{
|
||||||
|
{Function: functions[0]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Mapping: mappings[1],
|
||||||
|
Line: []profile.Line{
|
||||||
|
{Function: functions[1]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
Mapping: mappings[2],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &profile.Profile{
|
||||||
|
PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
|
||||||
|
SampleType: []*profile.ValueType{
|
||||||
|
{Type: "type", Unit: "unit"},
|
||||||
|
},
|
||||||
|
Sample: []*profile.Sample{
|
||||||
|
{
|
||||||
|
Location: []*profile.Location{locations[0]},
|
||||||
|
Value: []int64{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Location: []*profile.Location{locations[1]},
|
||||||
|
Value: []int64{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Location: []*profile.Location{locations[2]},
|
||||||
|
Value: []int64{1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Location: locations,
|
||||||
|
Function: functions,
|
||||||
|
Mapping: mappings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that nodes are properly created for a simple profile.
|
||||||
|
func TestCreateNodes(t *testing.T) {
|
||||||
|
testProfile := nodeTestProfile()
|
||||||
|
wantNodeSet := NodeSet{
|
||||||
|
{Name: "symname"}: true,
|
||||||
|
{Objfile: "unsymbolized_library_1"}: true,
|
||||||
|
{Objfile: "unsymbolized_library_2"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, _ := CreateNodes(testProfile, &Options{})
|
||||||
|
if len(nodes) != len(wantNodeSet) {
|
||||||
|
t.Errorf("got %d nodes, want %d", len(nodes), len(wantNodeSet))
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if !wantNodeSet[node.Info] {
|
||||||
|
t.Errorf("unexpected node %v", node.Info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortenFunctionName(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
testcases := []testCase{
|
||||||
|
{
|
||||||
|
"root",
|
||||||
|
"root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"syscall.Syscall",
|
||||||
|
"syscall.Syscall",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"net/http.(*conn).serve",
|
||||||
|
"http.(*conn).serve",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/blahBlah/foo.Foo",
|
||||||
|
"foo.Foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/BlahBlah/foo.Foo",
|
||||||
|
"foo.Foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/blah-blah/foo_bar.(*FooBar).Foo",
|
||||||
|
"foo_bar.(*FooBar).Foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"encoding/json.(*structEncoder).(encoding/json.encode)-fm",
|
||||||
|
"json.(*structEncoder).(encoding/json.encode)-fm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
|
||||||
|
"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"java.util.concurrent.ThreadPoolExecutor$Worker.run",
|
||||||
|
"ThreadPoolExecutor$Worker.run",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"java.bar.foo.FooBar.run(java.lang.Runnable)",
|
||||||
|
"FooBar.run",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"(anonymous namespace)::Bar::Foo",
|
||||||
|
"Bar::Foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"(anonymous namespace)::foo",
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"foo_bar::Foo::bar",
|
||||||
|
"Foo::bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"foo",
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
name := ShortenFunctionName(tc.name)
|
||||||
|
if got, want := name, tc.want; got != want {
|
||||||
|
t.Errorf("ShortenFunctionName(%q) = %q, want %q", tc.name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ type Options struct {
|
||||||
//
|
//
|
||||||
// A common use for a custom HTTPServer is to provide custom
|
// A common use for a custom HTTPServer is to provide custom
|
||||||
// authentication checks.
|
// authentication checks.
|
||||||
HTTPServer func(args *HTTPServerArgs) error
|
HTTPServer func(args *HTTPServerArgs) error
|
||||||
|
HTTPTransport http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer provides a mechanism to write data under a certain name,
|
// Writer provides a mechanism to write data under a certain name,
|
||||||
|
|
@ -71,12 +72,16 @@ type FlagSet interface {
|
||||||
// single flag
|
// single flag
|
||||||
StringList(name string, def string, usage string) *[]*string
|
StringList(name string, def string, usage string) *[]*string
|
||||||
|
|
||||||
// ExtraUsage returns any additional text that should be
|
// ExtraUsage returns any additional text that should be printed after the
|
||||||
// printed after the standard usage message.
|
// standard usage message. The extra usage message returned includes all text
|
||||||
// The typical use of ExtraUsage is to show any custom flags
|
// added with AddExtraUsage().
|
||||||
// defined by the specific pprof plugins being used.
|
// The typical use of ExtraUsage is to show any custom flags defined by the
|
||||||
|
// specific pprof plugins being used.
|
||||||
ExtraUsage() string
|
ExtraUsage() string
|
||||||
|
|
||||||
|
// AddExtraUsage appends additional text to the end of the extra usage message.
|
||||||
|
AddExtraUsage(eu string)
|
||||||
|
|
||||||
// Parse initializes the flags with their values for this run
|
// Parse initializes the flags with their values for this run
|
||||||
// and returns the non-flag command line arguments.
|
// and returns the non-flag command line arguments.
|
||||||
// If an unknown flag is encountered or there are no arguments,
|
// If an unknown flag is encountered or there are no arguments,
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,10 @@ func printTopProto(w io.Writer, rpt *Report) error {
|
||||||
}
|
}
|
||||||
functionMap := make(functionMap)
|
functionMap := make(functionMap)
|
||||||
for i, n := range g.Nodes {
|
for i, n := range g.Nodes {
|
||||||
f := functionMap.FindOrAdd(n.Info)
|
f, added := functionMap.findOrAdd(n.Info)
|
||||||
|
if added {
|
||||||
|
out.Function = append(out.Function, f)
|
||||||
|
}
|
||||||
flat, cum := n.FlatValue(), n.CumValue()
|
flat, cum := n.FlatValue(), n.CumValue()
|
||||||
l := &profile.Location{
|
l := &profile.Location{
|
||||||
ID: uint64(i + 1),
|
ID: uint64(i + 1),
|
||||||
|
|
@ -328,7 +331,6 @@ func printTopProto(w io.Writer, rpt *Report) error {
|
||||||
Location: []*profile.Location{l},
|
Location: []*profile.Location{l},
|
||||||
Value: []int64{int64(cv), int64(fv)},
|
Value: []int64{int64(cv), int64(fv)},
|
||||||
}
|
}
|
||||||
out.Function = append(out.Function, f)
|
|
||||||
out.Location = append(out.Location, l)
|
out.Location = append(out.Location, l)
|
||||||
out.Sample = append(out.Sample, s)
|
out.Sample = append(out.Sample, s)
|
||||||
}
|
}
|
||||||
|
|
@ -338,11 +340,15 @@ func printTopProto(w io.Writer, rpt *Report) error {
|
||||||
|
|
||||||
type functionMap map[string]*profile.Function
|
type functionMap map[string]*profile.Function
|
||||||
|
|
||||||
func (fm functionMap) FindOrAdd(ni graph.NodeInfo) *profile.Function {
|
// findOrAdd takes a node representing a function, adds the function
|
||||||
|
// represented by the node to the map if the function is not already present,
|
||||||
|
// and returns the function the node represents. This also returns a boolean,
|
||||||
|
// which is true if the function was added and false otherwise.
|
||||||
|
func (fm functionMap) findOrAdd(ni graph.NodeInfo) (*profile.Function, bool) {
|
||||||
fName := fmt.Sprintf("%q%q%q%d", ni.Name, ni.OrigName, ni.File, ni.StartLine)
|
fName := fmt.Sprintf("%q%q%q%d", ni.Name, ni.OrigName, ni.File, ni.StartLine)
|
||||||
|
|
||||||
if f := fm[fName]; f != nil {
|
if f := fm[fName]; f != nil {
|
||||||
return f
|
return f, false
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &profile.Function{
|
f := &profile.Function{
|
||||||
|
|
@ -353,7 +359,7 @@ func (fm functionMap) FindOrAdd(ni graph.NodeInfo) *profile.Function {
|
||||||
StartLine: int64(ni.StartLine),
|
StartLine: int64(ni.StartLine),
|
||||||
}
|
}
|
||||||
fm[fName] = f
|
fm[fName] = f
|
||||||
return f
|
return f, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// printAssembly prints an annotated assembly listing.
|
// printAssembly prints an annotated assembly listing.
|
||||||
|
|
@ -361,7 +367,7 @@ func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
|
||||||
return PrintAssembly(w, rpt, obj, -1)
|
return PrintAssembly(w, rpt, obj, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintAssembly prints annotated disasssembly of rpt to w.
|
// PrintAssembly prints annotated disassembly of rpt to w.
|
||||||
func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) error {
|
func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) error {
|
||||||
o := rpt.options
|
o := rpt.options
|
||||||
prof := rpt.prof
|
prof := rpt.prof
|
||||||
|
|
@ -1217,8 +1223,8 @@ func NewDefault(prof *profile.Profile, options Options) *Report {
|
||||||
}
|
}
|
||||||
|
|
||||||
// computeTotal computes the sum of the absolute value of all sample values.
|
// computeTotal computes the sum of the absolute value of all sample values.
|
||||||
// If any samples have the label "pprof::base" with value "true", then the total
|
// If any samples have label indicating they belong to the diff base, then the
|
||||||
// will only include samples with that label.
|
// total will only include samples with that label.
|
||||||
func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {
|
func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {
|
||||||
var div, total, diffDiv, diffTotal int64
|
var div, total, diffDiv, diffTotal int64
|
||||||
for _, sample := range prof.Sample {
|
for _, sample := range prof.Sample {
|
||||||
|
|
@ -1232,7 +1238,7 @@ func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) i
|
||||||
}
|
}
|
||||||
total += v
|
total += v
|
||||||
div += d
|
div += d
|
||||||
if sample.HasLabel("pprof::base", "true") {
|
if sample.DiffBaseSample() {
|
||||||
diffTotal += v
|
diffTotal += v
|
||||||
diffDiv += d
|
diffDiv += d
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -253,16 +253,23 @@ func TestFunctionMap(t *testing.T) {
|
||||||
{Name: "fun2", File: "filename2"},
|
{Name: "fun2", File: "filename2"},
|
||||||
}
|
}
|
||||||
|
|
||||||
want := []profile.Function{
|
want := []struct {
|
||||||
{ID: 1, Name: "fun1"},
|
wantFunction profile.Function
|
||||||
{ID: 2, Name: "fun2", Filename: "filename"},
|
wantAdded bool
|
||||||
{ID: 1, Name: "fun1"},
|
}{
|
||||||
{ID: 3, Name: "fun2", Filename: "filename2"},
|
{profile.Function{ID: 1, Name: "fun1"}, true},
|
||||||
|
{profile.Function{ID: 2, Name: "fun2", Filename: "filename"}, true},
|
||||||
|
{profile.Function{ID: 1, Name: "fun1"}, false},
|
||||||
|
{profile.Function{ID: 3, Name: "fun2", Filename: "filename2"}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range nodes {
|
for i, tc := range nodes {
|
||||||
if got, want := fm.FindOrAdd(tc), want[i]; *got != want {
|
gotFunc, gotAdded := fm.findOrAdd(tc)
|
||||||
t.Errorf("%d: want %v, got %v", i, want, got)
|
if got, want := gotFunc, want[i].wantFunction; *got != want {
|
||||||
|
t.Errorf("%d: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := gotAdded, want[i].wantAdded; got != want {
|
||||||
|
t.Errorf("%d: got %v, want %v", i, got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
package symbolizer
|
package symbolizer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -35,8 +34,9 @@ import (
|
||||||
|
|
||||||
// Symbolizer implements the plugin.Symbolize interface.
|
// Symbolizer implements the plugin.Symbolize interface.
|
||||||
type Symbolizer struct {
|
type Symbolizer struct {
|
||||||
Obj plugin.ObjTool
|
Obj plugin.ObjTool
|
||||||
UI plugin.UI
|
UI plugin.UI
|
||||||
|
Transport http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
// test taps for dependency injection
|
// test taps for dependency injection
|
||||||
|
|
@ -85,7 +85,10 @@ func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *pr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if remote {
|
if remote {
|
||||||
if err = symbolzSymbolize(p, force, sources, postURL, s.UI); err != nil {
|
post := func(source, post string) ([]byte, error) {
|
||||||
|
return postURL(source, post, s.Transport)
|
||||||
|
}
|
||||||
|
if err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {
|
||||||
return err // Ran out of options.
|
return err // Ran out of options.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,25 +98,9 @@ func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *pr
|
||||||
}
|
}
|
||||||
|
|
||||||
// postURL issues a POST to a URL over HTTP.
|
// postURL issues a POST to a URL over HTTP.
|
||||||
func postURL(source, post string) ([]byte, error) {
|
func postURL(source, post string, tr http.RoundTripper) ([]byte, error) {
|
||||||
url, err := url.Parse(source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
if url.Scheme == "https+insecure" {
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
url.Scheme = "https"
|
|
||||||
source = url.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: tr,
|
||||||
TLSClientConfig: tlsConfig,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
|
resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,8 @@ func TestSymbolization(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s := Symbolizer{
|
s := Symbolizer{
|
||||||
mockObjTool{},
|
Obj: mockObjTool{},
|
||||||
&proftest.TestUI{T: t},
|
UI: &proftest.TestUI{T: t},
|
||||||
}
|
}
|
||||||
for i, tc := range []testcase{
|
for i, tc := range []testcase{
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,9 @@ func symbolizeMapping(source string, offset int64, syms func(string, string) ([]
|
||||||
for _, l := range p.Location {
|
for _, l := range p.Location {
|
||||||
if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 {
|
if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 {
|
||||||
// Compensate for normalization.
|
// Compensate for normalization.
|
||||||
addr := int64(l.Address) + offset
|
addr, overflow := adjust(l.Address, offset)
|
||||||
if addr < 0 {
|
if overflow {
|
||||||
return fmt.Errorf("unexpected negative adjusted address, mapping %v source %d, offset %d", l.Mapping, l.Address, offset)
|
return fmt.Errorf("cannot adjust address %d by %d, it would overflow (mapping %v)", l.Address, offset, l.Mapping)
|
||||||
}
|
}
|
||||||
a = append(a, fmt.Sprintf("%#x", addr))
|
a = append(a, fmt.Sprintf("%#x", addr))
|
||||||
}
|
}
|
||||||
|
|
@ -144,15 +144,15 @@ func symbolizeMapping(source string, offset int64, syms func(string, string) ([]
|
||||||
}
|
}
|
||||||
|
|
||||||
if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
|
if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
|
||||||
addr, err := strconv.ParseInt(symbol[1], 0, 64)
|
origAddr, err := strconv.ParseUint(symbol[1], 0, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
|
return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
|
||||||
}
|
}
|
||||||
if addr < 0 {
|
|
||||||
return fmt.Errorf("unexpected negative adjusted address, source %s, offset %d", symbol[1], offset)
|
|
||||||
}
|
|
||||||
// Reapply offset expected by the profile.
|
// Reapply offset expected by the profile.
|
||||||
addr -= offset
|
addr, overflow := adjust(origAddr, -offset)
|
||||||
|
if overflow {
|
||||||
|
return fmt.Errorf("cannot adjust symbolz address %d by %d, it would overflow", origAddr, -offset)
|
||||||
|
}
|
||||||
|
|
||||||
name := symbol[2]
|
name := symbol[2]
|
||||||
fn := functions[name]
|
fn := functions[name]
|
||||||
|
|
@ -166,7 +166,7 @@ func symbolizeMapping(source string, offset int64, syms func(string, string) ([]
|
||||||
p.Function = append(p.Function, fn)
|
p.Function = append(p.Function, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
lines[uint64(addr)] = profile.Line{Function: fn}
|
lines[addr] = profile.Line{Function: fn}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,3 +181,20 @@ func symbolizeMapping(source string, offset int64, syms func(string, string) ([]
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adjust shifts the specified address by the signed offset. It returns the
|
||||||
|
// adjusted address. It signals that the address cannot be adjusted without an
|
||||||
|
// overflow by returning true in the second return value.
|
||||||
|
func adjust(addr uint64, offset int64) (uint64, bool) {
|
||||||
|
adj := uint64(int64(addr) + offset)
|
||||||
|
if offset < 0 {
|
||||||
|
if adj >= addr {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if adj < addr {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return adj, false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package symbolz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -139,3 +140,30 @@ func fetchSymbols(source, post string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return []byte(symbolz), nil
|
return []byte(symbolz), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdjust(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
addr uint64
|
||||||
|
offset int64
|
||||||
|
wantAdj uint64
|
||||||
|
wantOverflow bool
|
||||||
|
}{{math.MaxUint64, 0, math.MaxUint64, false},
|
||||||
|
{math.MaxUint64, 1, 0, true},
|
||||||
|
{math.MaxUint64 - 1, 1, math.MaxUint64, false},
|
||||||
|
{math.MaxUint64 - 1, 2, 0, true},
|
||||||
|
{math.MaxInt64 + 1, math.MaxInt64, math.MaxUint64, false},
|
||||||
|
{0, 0, 0, false},
|
||||||
|
{0, -1, 0, true},
|
||||||
|
{1, -1, 0, false},
|
||||||
|
{2, -1, 1, false},
|
||||||
|
{2, -2, 0, false},
|
||||||
|
{2, -3, 0, true},
|
||||||
|
{-math.MinInt64, math.MinInt64, 0, false},
|
||||||
|
{-math.MinInt64 + 1, math.MinInt64, 1, false},
|
||||||
|
{-math.MinInt64 - 1, math.MinInt64, 0, true},
|
||||||
|
} {
|
||||||
|
if adj, overflow := adjust(tc.addr, tc.offset); adj != tc.wantAdj || overflow != tc.wantOverflow {
|
||||||
|
t.Errorf("adjust(%d, %d) = (%d, %t), want (%d, %t)", tc.addr, tc.offset, adj, overflow, tc.wantAdj, tc.wantOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
131
src/cmd/vendor/github.com/google/pprof/internal/transport/transport.go
generated
vendored
Normal file
131
src/cmd/vendor/github.com/google/pprof/internal/transport/transport.go
generated
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
// 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 transport provides a mechanism to send requests with https cert,
|
||||||
|
// key, and CA.
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/pprof/internal/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type transport struct {
|
||||||
|
cert *string
|
||||||
|
key *string
|
||||||
|
ca *string
|
||||||
|
caCertPool *x509.CertPool
|
||||||
|
certs []tls.Certificate
|
||||||
|
initOnce sync.Once
|
||||||
|
initErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraUsage = ` -tls_cert TLS client certificate file for fetching profile and symbols
|
||||||
|
-tls_key TLS private key file for fetching profile and symbols
|
||||||
|
-tls_ca TLS CA certs file for fetching profile and symbols`
|
||||||
|
|
||||||
|
// New returns a round tripper for making requests with the
|
||||||
|
// specified cert, key, and ca. The flags tls_cert, tls_key, and tls_ca are
|
||||||
|
// added to the flagset to allow a user to specify the cert, key, and ca. If
|
||||||
|
// the flagset is nil, no flags will be added, and users will not be able to
|
||||||
|
// use these flags.
|
||||||
|
func New(flagset plugin.FlagSet) http.RoundTripper {
|
||||||
|
if flagset == nil {
|
||||||
|
return &transport{}
|
||||||
|
}
|
||||||
|
flagset.AddExtraUsage(extraUsage)
|
||||||
|
return &transport{
|
||||||
|
cert: flagset.String("tls_cert", "", "TLS client certificate file for fetching profile and symbols"),
|
||||||
|
key: flagset.String("tls_key", "", "TLS private key file for fetching profile and symbols"),
|
||||||
|
ca: flagset.String("tls_ca", "", "TLS CA certs file for fetching profile and symbols"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize uses the cert, key, and ca to initialize the certs
|
||||||
|
// to use these when making requests.
|
||||||
|
func (tr *transport) initialize() error {
|
||||||
|
var cert, key, ca string
|
||||||
|
if tr.cert != nil {
|
||||||
|
cert = *tr.cert
|
||||||
|
}
|
||||||
|
if tr.key != nil {
|
||||||
|
key = *tr.key
|
||||||
|
}
|
||||||
|
if tr.ca != nil {
|
||||||
|
ca = *tr.ca
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert != "" && key != "" {
|
||||||
|
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not load certificate/key pair specified by -tls_cert and -tls_key: %v", err)
|
||||||
|
}
|
||||||
|
tr.certs = []tls.Certificate{tlsCert}
|
||||||
|
} else if cert == "" && key != "" {
|
||||||
|
return fmt.Errorf("-tls_key is specified, so -tls_cert must also be specified")
|
||||||
|
} else if cert != "" && key == "" {
|
||||||
|
return fmt.Errorf("-tls_cert is specified, so -tls_key must also be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ca != "" {
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
caCert, err := ioutil.ReadFile(ca)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not load CA specified by -tls_ca: %v", err)
|
||||||
|
}
|
||||||
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
|
tr.caCertPool = caCertPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip executes a single HTTP transaction, returning
|
||||||
|
// a Response for the provided Request.
|
||||||
|
func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
tr.initOnce.Do(func() {
|
||||||
|
tr.initErr = tr.initialize()
|
||||||
|
})
|
||||||
|
if tr.initErr != nil {
|
||||||
|
return nil, tr.initErr
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
RootCAs: tr.caCertPool,
|
||||||
|
Certificates: tr.certs,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.URL.Scheme == "https+insecure" {
|
||||||
|
// Make shallow copy of request, and req.URL, so the request's URL can be
|
||||||
|
// modified.
|
||||||
|
r := *req
|
||||||
|
*r.URL = *req.URL
|
||||||
|
req = &r
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
req.URL.Scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
@ -134,7 +134,7 @@ func parseJavaHeader(pType string, b []byte, p *Profile) ([]byte, error) {
|
||||||
}
|
}
|
||||||
case "contention/resolution":
|
case "contention/resolution":
|
||||||
p.SampleType = []*ValueType{
|
p.SampleType = []*ValueType{
|
||||||
{Type: "contentions", Unit: value},
|
{Type: "contentions", Unit: "count"},
|
||||||
{Type: "delay", Unit: value},
|
{Type: "delay", Unit: value},
|
||||||
}
|
}
|
||||||
case "contention/sampling period":
|
case "contention/sampling period":
|
||||||
|
|
|
||||||
|
|
@ -704,6 +704,12 @@ func (s *Sample) HasLabel(key, value string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DiffBaseSample returns true if a sample belongs to the diff base and false
|
||||||
|
// otherwise.
|
||||||
|
func (s *Sample) DiffBaseSample() bool {
|
||||||
|
return s.HasLabel("pprof::base", "true")
|
||||||
|
}
|
||||||
|
|
||||||
// Scale multiplies all sample values in a profile by a constant.
|
// Scale multiplies all sample values in a profile by a constant.
|
||||||
func (p *Profile) Scale(ratio float64) {
|
func (p *Profile) Scale(ratio float64) {
|
||||||
if ratio == 1 {
|
if ratio == 1 {
|
||||||
|
|
|
||||||
|
|
@ -977,6 +977,59 @@ func TestHasLabel(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiffBaseSample(t *testing.T) {
|
||||||
|
var testcases = []struct {
|
||||||
|
desc string
|
||||||
|
labels map[string][]string
|
||||||
|
wantDiffBaseSample bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty label does not have label",
|
||||||
|
labels: map[string][]string{},
|
||||||
|
wantDiffBaseSample: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "label with one key and value, including diff base label",
|
||||||
|
labels: map[string][]string{"pprof::base": {"true"}},
|
||||||
|
wantDiffBaseSample: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "label with one key and value, not including diff base label",
|
||||||
|
labels: map[string][]string{"key": {"value"}},
|
||||||
|
wantDiffBaseSample: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "label with many keys and values, including diff base label",
|
||||||
|
labels: map[string][]string{
|
||||||
|
"pprof::base": {"value2", "true"},
|
||||||
|
"key2": {"true", "value2", "value2"},
|
||||||
|
"key3": {"true", "value2", "value2"},
|
||||||
|
},
|
||||||
|
wantDiffBaseSample: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "label with many keys and values, not including diff base label",
|
||||||
|
labels: map[string][]string{
|
||||||
|
"key1": {"value2", "value1"},
|
||||||
|
"key2": {"value1", "value2", "value2"},
|
||||||
|
"key3": {"value1", "value2", "value2"},
|
||||||
|
},
|
||||||
|
wantDiffBaseSample: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
sample := &Sample{
|
||||||
|
Label: tc.labels,
|
||||||
|
}
|
||||||
|
if gotHasLabel := sample.DiffBaseSample(); gotHasLabel != tc.wantDiffBaseSample {
|
||||||
|
t.Errorf("sample.DiffBaseSample() got %v, want %v", gotHasLabel, tc.wantDiffBaseSample)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
var testcases = []struct {
|
var testcases = []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ PeriodType: contentions count
|
||||||
Period: 100
|
Period: 100
|
||||||
Duration: 1h40
|
Duration: 1h40
|
||||||
Samples:
|
Samples:
|
||||||
contentions/microseconds delay/microseconds
|
contentions/count delay/microseconds
|
||||||
100 100: 1 2
|
100 100: 1 2
|
||||||
100 1400: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 10 17 18 19 20 21 22 23 24 25 26 27 28 29
|
100 1400: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 10 17 18 19 20 21 22 23 24 25 26 27 28 29
|
||||||
200 200: 1 2
|
200 200: 1 2
|
||||||
|
|
|
||||||
|
|
@ -3,94 +3,100 @@
|
||||||
"ignore": "",
|
"ignore": "",
|
||||||
"package": [
|
"package": [
|
||||||
{
|
{
|
||||||
"checksumSHA1": "G9UsR+iruMWxwUefhy+ID+VIFNs=",
|
"checksumSHA1": "tvvU1lZut+OvO+7NOIG3DXojs48=",
|
||||||
"path": "github.com/google/pprof/driver",
|
"path": "github.com/google/pprof/driver",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LzGfApA19baVJIbQEqziWpRS3zE=",
|
"checksumSHA1": "LDRBxfypG0ZI3Nl/mfEIhrU/ae4=",
|
||||||
"path": "github.com/google/pprof/internal/binutils",
|
"path": "github.com/google/pprof/internal/binutils",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "uoKLYk9VTOx2kYV3hU3vOGm4BX8=",
|
"checksumSHA1": "mViiOBlz5l3mIlQE1SxY1IveYBU=",
|
||||||
"path": "github.com/google/pprof/internal/driver",
|
"path": "github.com/google/pprof/internal/driver",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "IhuyU2pFSHhQxzadDBw1nHbcsrY=",
|
"checksumSHA1": "lxGP2FcHBwAiYHup+BNMBis136o=",
|
||||||
"path": "github.com/google/pprof/internal/elfexec",
|
"path": "github.com/google/pprof/internal/elfexec",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "8vah+aXLGpbtn55JR8MkCAEOMrk=",
|
"checksumSHA1": "ejpBQbeYO4XPI6UtiPe4SVaMQqE=",
|
||||||
"path": "github.com/google/pprof/internal/graph",
|
"path": "github.com/google/pprof/internal/graph",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "QPWfnT5pEU2jOOb8l8hpiFzQJ7Q=",
|
"checksumSHA1": "QPWfnT5pEU2jOOb8l8hpiFzQJ7Q=",
|
||||||
"path": "github.com/google/pprof/internal/measurement",
|
"path": "github.com/google/pprof/internal/measurement",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "PWZdFtGfGz/zbQTfvel9737NZdY=",
|
"checksumSHA1": "wMdOybuEcd10antxdOUDUugjmOs=",
|
||||||
"path": "github.com/google/pprof/internal/plugin",
|
"path": "github.com/google/pprof/internal/plugin",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LmDglu/S6vFmgqkxubKDZemFHaY=",
|
"checksumSHA1": "LmDglu/S6vFmgqkxubKDZemFHaY=",
|
||||||
"path": "github.com/google/pprof/internal/proftest",
|
"path": "github.com/google/pprof/internal/proftest",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "qgsLCrPLve6es8A3bA3qv2LPoYk=",
|
"checksumSHA1": "ijtIORD3B7fg+VwzjmXT/33zahM=",
|
||||||
"path": "github.com/google/pprof/internal/report",
|
"path": "github.com/google/pprof/internal/report",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "rWdirHgJi1+TdRwv5v3zjgFKcJA=",
|
"checksumSHA1": "Jjx/GbK8ftMDp0uoqfjTncz0TaQ=",
|
||||||
"path": "github.com/google/pprof/internal/symbolizer",
|
"path": "github.com/google/pprof/internal/symbolizer",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "5lS2AF207MVYyjF+82qHkWK2V64=",
|
"checksumSHA1": "T0WqnYtJKNJYW3qYH15E1HFlmE0=",
|
||||||
"path": "github.com/google/pprof/internal/symbolz",
|
"path": "github.com/google/pprof/internal/symbolz",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "JMf63Fn5hz7JFgz6A2aT9DP/bL0=",
|
"checksumSHA1": "qDNZM9DiplY70UFnEiP9NcsVplg=",
|
||||||
|
"path": "github.com/google/pprof/internal/transport",
|
||||||
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "yFlyOuu4KgPEjXo1rIvl7Sj32Oo=",
|
||||||
"path": "github.com/google/pprof/profile",
|
"path": "github.com/google/pprof/profile",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "xmqfYca88U2c/I4642r3ps9uIRg=",
|
"checksumSHA1": "xmqfYca88U2c/I4642r3ps9uIRg=",
|
||||||
"path": "github.com/google/pprof/third_party/d3",
|
"path": "github.com/google/pprof/third_party/d3",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LzWzD56Trzpq+0hLR00Yw5Gpepw=",
|
"checksumSHA1": "LzWzD56Trzpq+0hLR00Yw5Gpepw=",
|
||||||
"path": "github.com/google/pprof/third_party/d3flamegraph",
|
"path": "github.com/google/pprof/third_party/d3flamegraph",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "738v1E0v0qRW6oAKdCpBEtyVNnY=",
|
"checksumSHA1": "738v1E0v0qRW6oAKdCpBEtyVNnY=",
|
||||||
"path": "github.com/google/pprof/third_party/svgpan",
|
"path": "github.com/google/pprof/third_party/svgpan",
|
||||||
"revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
|
"revision": "fde099a545debf81bf2a96a0ec13d7da2c2a6663",
|
||||||
"revisionTime": "2018-05-30T14:24:47Z"
|
"revisionTime": "2018-10-26T15:26:56Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "J5yI4NzHbondzccJmummyJR/kQQ=",
|
"checksumSHA1": "J5yI4NzHbondzccJmummyJR/kQQ=",
|
||||||
|
|
@ -371,5 +377,5 @@
|
||||||
"revisionTime": "2018-11-05T19:48:08Z"
|
"revisionTime": "2018-11-05T19:48:08Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rootPath": "/cmd"
|
"rootPath": "cmd"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue