aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/github.com/google/pprof/profile
diff options
context:
space:
mode:
authorAlberto Donizetti <alb.donizetti@gmail.com>2017-08-20 12:27:32 +0200
committerBrad Fitzpatrick <bradfitz@golang.org>2017-11-02 23:51:45 +0000
commitaec345d638fa624f08b7d758e9e173897edc80e8 (patch)
treed782d951af4f34de34a08c4775a37f869af25b81 /src/cmd/vendor/github.com/google/pprof/profile
parent3039bff9d07ce05dc9af8c155c6929ae5e53a231 (diff)
downloadgo-aec345d638fa624f08b7d758e9e173897edc80e8.tar.xz
cmd/vendor/github.com/google/pprof: refresh from upstream
Update vendored pprof to commit 4fc39a00b6b8c1aad05260f01429ec70e127252c from github.com/google/pprof (2017-11-01). Fixes #19380 Updates #21047 Change-Id: Ib64a94a45209039e5945acbcfa0392790c8ee41e Reviewed-on: https://go-review.googlesource.com/57370 Run-TryBot: Alberto Donizetti <alb.donizetti@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/cmd/vendor/github.com/google/pprof/profile')
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/encode.go40
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/filter.go3
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go5
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/merge.go43
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/profile.go293
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/profile_test.go557
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/proto.go14
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/proto_test.go19
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/prune.go47
-rw-r--r--src/cmd/vendor/github.com/google/pprof/profile/prune_test.go91
10 files changed, 965 insertions, 147 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/encode.go b/src/cmd/vendor/github.com/google/pprof/profile/encode.go
index c64083a400..622319484a 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/encode.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/encode.go
@@ -59,12 +59,19 @@ func (p *Profile) preEncode() {
}
sort.Strings(numKeys)
for _, k := range numKeys {
+ keyX := addString(strings, k)
vs := s.NumLabel[k]
- for _, v := range vs {
+ units := s.NumUnit[k]
+ for i, v := range vs {
+ var unitX int64
+ if len(units) != 0 {
+ unitX = addString(strings, units[i])
+ }
s.labelX = append(s.labelX,
label{
- keyX: addString(strings, k),
- numX: v,
+ keyX: keyX,
+ numX: v,
+ unitX: unitX,
},
)
}
@@ -289,6 +296,7 @@ func (p *Profile) postDecode() error {
for _, s := range p.Sample {
labels := make(map[string][]string, len(s.labelX))
numLabels := make(map[string][]int64, len(s.labelX))
+ numUnits := make(map[string][]string, len(s.labelX))
for _, l := range s.labelX {
var key, value string
key, err = getString(p.stringTable, &l.keyX, err)
@@ -296,6 +304,14 @@ func (p *Profile) postDecode() error {
value, err = getString(p.stringTable, &l.strX, err)
labels[key] = append(labels[key], value)
} else if l.numX != 0 {
+ numValues := numLabels[key]
+ units := numUnits[key]
+ if l.unitX != 0 {
+ var unit string
+ unit, err = getString(p.stringTable, &l.unitX, err)
+ units = padStringArray(units, len(numValues))
+ numUnits[key] = append(units, unit)
+ }
numLabels[key] = append(numLabels[key], l.numX)
}
}
@@ -304,6 +320,12 @@ func (p *Profile) postDecode() error {
}
if len(numLabels) > 0 {
s.NumLabel = numLabels
+ for key, units := range numUnits {
+ if len(units) > 0 {
+ numUnits[key] = padStringArray(units, len(numLabels[key]))
+ }
+ }
+ s.NumUnit = numUnits
}
s.Location = make([]*Location, len(s.locationIDX))
for i, lid := range s.locationIDX {
@@ -340,6 +362,15 @@ func (p *Profile) postDecode() error {
return err
}
+// padStringArray pads arr with enough empty strings to make arr
+// length l when arr's length is less than l.
+func padStringArray(arr []string, l int) []string {
+ if l <= len(arr) {
+ return arr
+ }
+ return append(arr, make([]string, l-len(arr))...)
+}
+
func (p *ValueType) decoder() []decoder {
return valueTypeDecoder
}
@@ -392,6 +423,7 @@ func (p label) encode(b *buffer) {
encodeInt64Opt(b, 1, p.keyX)
encodeInt64Opt(b, 2, p.strX)
encodeInt64Opt(b, 3, p.numX)
+ encodeInt64Opt(b, 4, p.unitX)
}
var labelDecoder = []decoder{
@@ -402,6 +434,8 @@ var labelDecoder = []decoder{
func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).strX) },
// optional int64 num = 3
func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).numX) },
+ // optional int64 num = 4
+ func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).unitX) },
}
func (p *Mapping) decoder() []decoder {
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/filter.go b/src/cmd/vendor/github.com/google/pprof/profile/filter.go
index 85361e8799..f857fdf8f8 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/filter.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/filter.go
@@ -41,10 +41,11 @@ func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp)
}
}
if show != nil {
- hnm = true
l.Line = l.matchedLines(show)
if len(l.Line) == 0 {
hidden[l.ID] = true
+ } else {
+ hnm = true
}
}
}
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go b/src/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go
index 7b40f5d24c..06322e5d2c 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go
@@ -212,7 +212,10 @@ func parseJavaSamples(pType string, b []byte, p *Profile) ([]byte, map[uint64]*L
switch pType {
case "heap":
const javaHeapzSamplingRate = 524288 // 512K
- s.NumLabel = map[string][]int64{"bytes": []int64{s.Value[1] / s.Value[0]}}
+ if s.Value[0] == 0 {
+ return nil, nil, fmt.Errorf("parsing sample %s: second value must be non-zero", line)
+ }
+ s.NumLabel = map[string][]int64{"bytes": {s.Value[1] / s.Value[0]}}
s.Value[0], s.Value[1] = scaleHeapSample(s.Value[0], s.Value[1], javaHeapzSamplingRate)
case "contention":
if period := p.Period; period != 0 {
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/merge.go b/src/cmd/vendor/github.com/google/pprof/profile/merge.go
index 2e9c2cd8af..e00829cc34 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/merge.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/merge.go
@@ -85,6 +85,41 @@ func Merge(srcs []*Profile) (*Profile, error) {
return p, nil
}
+// Normalize normalizes the source profile by multiplying each value in profile by the
+// ratio of the sum of the base profile's values of that sample type to the sum of the
+// source profile's value of that sample type.
+func (p *Profile) Normalize(pb *Profile) error {
+
+ if err := p.compatible(pb); err != nil {
+ return err
+ }
+
+ baseVals := make([]int64, len(p.SampleType))
+ for _, s := range pb.Sample {
+ for i, v := range s.Value {
+ baseVals[i] += v
+ }
+ }
+
+ srcVals := make([]int64, len(p.SampleType))
+ for _, s := range p.Sample {
+ for i, v := range s.Value {
+ srcVals[i] += v
+ }
+ }
+
+ normScale := make([]float64, len(baseVals))
+ for i := range baseVals {
+ if srcVals[i] == 0 {
+ normScale[i] = 0.0
+ } else {
+ normScale[i] = float64(baseVals[i]) / float64(srcVals[i])
+ }
+ }
+ p.ScaleN(normScale)
+ return nil
+}
+
func isZeroSample(s *Sample) bool {
for _, v := range s.Value {
if v != 0 {
@@ -120,6 +155,7 @@ func (pm *profileMerger) mapSample(src *Sample) *Sample {
Value: make([]int64, len(src.Value)),
Label: make(map[string][]string, len(src.Label)),
NumLabel: make(map[string][]int64, len(src.NumLabel)),
+ NumUnit: make(map[string][]string, len(src.NumLabel)),
}
for i, l := range src.Location {
s.Location[i] = pm.mapLocation(l)
@@ -130,9 +166,13 @@ func (pm *profileMerger) mapSample(src *Sample) *Sample {
s.Label[k] = vv
}
for k, v := range src.NumLabel {
+ u := src.NumUnit[k]
vv := make([]int64, len(v))
+ uu := make([]string, len(u))
copy(vv, v)
+ copy(uu, u)
s.NumLabel[k] = vv
+ s.NumUnit[k] = uu
}
// Check memoization table. Must be done on the remapped location to
// account for the remapped mapping. Add current values to the
@@ -165,7 +205,7 @@ func (sample *Sample) key() sampleKey {
numlabels := make([]string, 0, len(sample.NumLabel))
for k, v := range sample.NumLabel {
- numlabels = append(numlabels, fmt.Sprintf("%q%x", k, v))
+ numlabels = append(numlabels, fmt.Sprintf("%q%x%x", k, v, sample.NumUnit[k]))
}
sort.Strings(numlabels)
@@ -432,7 +472,6 @@ func (p *Profile) compatible(pb *Profile) error {
return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
}
}
-
return nil
}
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/profile.go b/src/cmd/vendor/github.com/google/pprof/profile/profile.go
index fb3d4fd4fb..a0f53efe3e 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/profile.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/profile.go
@@ -26,6 +26,7 @@ import (
"regexp"
"sort"
"strings"
+ "sync"
"time"
)
@@ -47,6 +48,10 @@ type Profile struct {
PeriodType *ValueType
Period int64
+ // The following fields are modified during encoding and copying,
+ // so are protected by a Mutex.
+ encodeMu sync.Mutex
+
commentX []int64
dropFramesX int64
keepFramesX int64
@@ -69,6 +74,7 @@ type Sample struct {
Value []int64
Label map[string][]string
NumLabel map[string][]int64
+ NumUnit map[string][]string
locationIDX []uint64
labelX []label
@@ -80,6 +86,8 @@ type label struct {
// Exactly one of the two following values must be set
strX int64
numX int64 // Integer value for this label
+ // can be set if numX has value
+ unitX int64
}
// Mapping corresponds to Profile.Mapping
@@ -296,21 +304,25 @@ func (p *Profile) updateLocationMapping(from, to *Mapping) {
}
}
-// Write writes the profile as a gzip-compressed marshaled protobuf.
-func (p *Profile) Write(w io.Writer) error {
+func serialize(p *Profile) []byte {
+ p.encodeMu.Lock()
p.preEncode()
b := marshal(p)
+ p.encodeMu.Unlock()
+ return b
+}
+
+// Write writes the profile as a gzip-compressed marshaled protobuf.
+func (p *Profile) Write(w io.Writer) error {
zw := gzip.NewWriter(w)
defer zw.Close()
- _, err := zw.Write(b)
+ _, err := zw.Write(serialize(p))
return err
}
// WriteUncompressed writes the profile as a marshaled protobuf.
func (p *Profile) WriteUncompressed(w io.Writer) error {
- p.preEncode()
- b := marshal(p)
- _, err := w.Write(b)
+ _, err := w.Write(serialize(p))
return err
}
@@ -325,8 +337,11 @@ func (p *Profile) CheckValid() error {
return fmt.Errorf("missing sample type information")
}
for _, s := range p.Sample {
+ if s == nil {
+ return fmt.Errorf("profile has nil sample")
+ }
if len(s.Value) != sampleLen {
- return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
+ return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(s.Value), len(p.SampleType))
}
for _, l := range s.Location {
if l == nil {
@@ -339,6 +354,9 @@ func (p *Profile) CheckValid() error {
// Check that there are no duplicate ids
mappings := make(map[uint64]*Mapping, len(p.Mapping))
for _, m := range p.Mapping {
+ if m == nil {
+ return fmt.Errorf("profile has nil mapping")
+ }
if m.ID == 0 {
return fmt.Errorf("found mapping with reserved ID=0")
}
@@ -349,6 +367,9 @@ func (p *Profile) CheckValid() error {
}
functions := make(map[uint64]*Function, len(p.Function))
for _, f := range p.Function {
+ if f == nil {
+ return fmt.Errorf("profile has nil function")
+ }
if f.ID == 0 {
return fmt.Errorf("found function with reserved ID=0")
}
@@ -359,6 +380,9 @@ func (p *Profile) CheckValid() error {
}
locations := make(map[uint64]*Location, len(p.Location))
for _, l := range p.Location {
+ if l == nil {
+ return fmt.Errorf("profile has nil location")
+ }
if l.ID == 0 {
return fmt.Errorf("found location with reserved id=0")
}
@@ -426,6 +450,70 @@ func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address
return p.CheckValid()
}
+// NumLabelUnits returns a map of numeric label keys to the units
+// associated with those keys and a map of those keys to any units
+// that were encountered but not used.
+// Unit for a given key is the first encountered unit for that key. If multiple
+// units are encountered for values paired with a particular key, then the first
+// unit encountered is used and all other units are returned in sorted order
+// in map of ignored units.
+// If no units are encountered for a particular key, the unit is then inferred
+// based on the key.
+func (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {
+ numLabelUnits := map[string]string{}
+ ignoredUnits := map[string]map[string]bool{}
+ encounteredKeys := map[string]bool{}
+
+ // Determine units based on numeric tags for each sample.
+ for _, s := range p.Sample {
+ for k := range s.NumLabel {
+ encounteredKeys[k] = true
+ for _, unit := range s.NumUnit[k] {
+ if unit == "" {
+ continue
+ }
+ if wantUnit, ok := numLabelUnits[k]; !ok {
+ numLabelUnits[k] = unit
+ } else if wantUnit != unit {
+ if v, ok := ignoredUnits[k]; ok {
+ v[unit] = true
+ } else {
+ ignoredUnits[k] = map[string]bool{unit: true}
+ }
+ }
+ }
+ }
+ }
+ // Infer units for keys without any units associated with
+ // numeric tag values.
+ for key := range encounteredKeys {
+ unit := numLabelUnits[key]
+ if unit == "" {
+ switch key {
+ case "alignment", "request":
+ numLabelUnits[key] = "bytes"
+ default:
+ numLabelUnits[key] = key
+ }
+ }
+ }
+
+ // Copy ignored units into more readable format
+ unitsIgnored := make(map[string][]string, len(ignoredUnits))
+ for key, values := range ignoredUnits {
+ units := make([]string, len(values))
+ i := 0
+ for unit := range values {
+ units[i] = unit
+ i++
+ }
+ sort.Strings(units)
+ unitsIgnored[key] = units
+ }
+
+ return numLabelUnits, unitsIgnored
+}
+
// String dumps a text representation of a profile. Intended mainly
// for debugging purposes.
func (p *Profile) String() string {
@@ -455,87 +543,132 @@ func (p *Profile) String() string {
}
ss = append(ss, strings.TrimSpace(sh1))
for _, s := range p.Sample {
- var sv string
- for _, v := range s.Value {
- sv = fmt.Sprintf("%s %10d", sv, v)
- }
- sv = sv + ": "
- for _, l := range s.Location {
- sv = sv + fmt.Sprintf("%d ", l.ID)
- }
- ss = append(ss, sv)
- const labelHeader = " "
- if len(s.Label) > 0 {
- ls := []string{}
- for k, v := range s.Label {
- ls = append(ls, fmt.Sprintf("%s:%v", k, v))
- }
- sort.Strings(ls)
- ss = append(ss, labelHeader+strings.Join(ls, " "))
- }
- if len(s.NumLabel) > 0 {
- ls := []string{}
- for k, v := range s.NumLabel {
- ls = append(ls, fmt.Sprintf("%s:%v", k, v))
- }
- sort.Strings(ls)
- ss = append(ss, labelHeader+strings.Join(ls, " "))
- }
+ ss = append(ss, s.string())
}
ss = append(ss, "Locations")
for _, l := range p.Location {
- locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
- if m := l.Mapping; m != nil {
- locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
- }
- if len(l.Line) == 0 {
- ss = append(ss, locStr)
- }
- for li := range l.Line {
- lnStr := "??"
- if fn := l.Line[li].Function; fn != nil {
- lnStr = fmt.Sprintf("%s %s:%d s=%d",
- fn.Name,
- fn.Filename,
- l.Line[li].Line,
- fn.StartLine)
- if fn.Name != fn.SystemName {
- lnStr = lnStr + "(" + fn.SystemName + ")"
- }
- }
- ss = append(ss, locStr+lnStr)
- // Do not print location details past the first line
- locStr = " "
- }
+ ss = append(ss, l.string())
}
ss = append(ss, "Mappings")
for _, m := range p.Mapping {
- bits := ""
- if m.HasFunctions {
- bits = bits + "[FN]"
- }
- if m.HasFilenames {
- bits = bits + "[FL]"
- }
- if m.HasLineNumbers {
- bits = bits + "[LN]"
- }
- if m.HasInlineFrames {
- bits = bits + "[IN]"
- }
- ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
- m.ID,
- m.Start, m.Limit, m.Offset,
- m.File,
- m.BuildID,
- bits))
+ ss = append(ss, m.string())
}
return strings.Join(ss, "\n") + "\n"
}
+// string dumps a text representation of a mapping. Intended mainly
+// for debugging purposes.
+func (m *Mapping) string() string {
+ bits := ""
+ if m.HasFunctions {
+ bits = bits + "[FN]"
+ }
+ if m.HasFilenames {
+ bits = bits + "[FL]"
+ }
+ if m.HasLineNumbers {
+ bits = bits + "[LN]"
+ }
+ if m.HasInlineFrames {
+ bits = bits + "[IN]"
+ }
+ return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
+ m.ID,
+ m.Start, m.Limit, m.Offset,
+ m.File,
+ m.BuildID,
+ bits)
+}
+
+// string dumps a text representation of a location. Intended mainly
+// for debugging purposes.
+func (l *Location) string() string {
+ ss := []string{}
+ locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
+ if m := l.Mapping; m != nil {
+ locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
+ }
+ if len(l.Line) == 0 {
+ ss = append(ss, locStr)
+ }
+ for li := range l.Line {
+ lnStr := "??"
+ if fn := l.Line[li].Function; fn != nil {
+ lnStr = fmt.Sprintf("%s %s:%d s=%d",
+ fn.Name,
+ fn.Filename,
+ l.Line[li].Line,
+ fn.StartLine)
+ if fn.Name != fn.SystemName {
+ lnStr = lnStr + "(" + fn.SystemName + ")"
+ }
+ }
+ ss = append(ss, locStr+lnStr)
+ // Do not print location details past the first line
+ locStr = " "
+ }
+ return strings.Join(ss, "\n")
+}
+
+// string dumps a text representation of a sample. Intended mainly
+// for debugging purposes.
+func (s *Sample) string() string {
+ ss := []string{}
+ var sv string
+ for _, v := range s.Value {
+ sv = fmt.Sprintf("%s %10d", sv, v)
+ }
+ sv = sv + ": "
+ for _, l := range s.Location {
+ sv = sv + fmt.Sprintf("%d ", l.ID)
+ }
+ ss = append(ss, sv)
+ const labelHeader = " "
+ if len(s.Label) > 0 {
+ ss = append(ss, labelHeader+labelsToString(s.Label))
+ }
+ if len(s.NumLabel) > 0 {
+ ss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))
+ }
+ return strings.Join(ss, "\n")
+}
+
+// labelsToString returns a string representation of a
+// map representing labels.
+func labelsToString(labels map[string][]string) string {
+ ls := []string{}
+ for k, v := range labels {
+ ls = append(ls, fmt.Sprintf("%s:%v", k, v))
+ }
+ sort.Strings(ls)
+ return strings.Join(ls, " ")
+}
+
+// numLablesToString returns a string representation of a map
+// representing numeric labels.
+func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {
+ ls := []string{}
+ for k, v := range numLabels {
+ units := numUnits[k]
+ var labelString string
+ if len(units) == len(v) {
+ values := make([]string, len(v))
+ for i, vv := range v {
+ values[i] = fmt.Sprintf("%d %s", vv, units[i])
+ }
+ labelString = fmt.Sprintf("%s:%v", k, values)
+ } else {
+ labelString = fmt.Sprintf("%s:%v", k, v)
+ }
+ ls = append(ls, labelString)
+ }
+ sort.Strings(ls)
+ return strings.Join(ls, " ")
+}
+
// Scale multiplies all sample values in a profile by a constant.
func (p *Profile) Scale(ratio float64) {
if ratio == 1 {
@@ -596,19 +729,17 @@ func (p *Profile) HasFileLines() bool {
}
// Unsymbolizable returns true if a mapping points to a binary for which
-// locations can't be symbolized in principle, at least now.
+// locations can't be symbolized in principle, at least now. Examples are
+// "[vdso]", [vsyscall]" and some others, see the code.
func (m *Mapping) Unsymbolizable() bool {
name := filepath.Base(m.File)
- return name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") || name == "[heap]" || strings.HasPrefix(m.File, "/dev/dri/")
+ return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
}
// Copy makes a fully independent copy of a profile.
func (p *Profile) Copy() *Profile {
- p.preEncode()
- b := marshal(p)
-
pp := &Profile{}
- if err := unmarshal(b, pp); err != nil {
+ if err := unmarshal(serialize(p), pp); err != nil {
panic(err)
}
if err := pp.postDecode(); err != nil {
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go b/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go
index c2319a6500..bc2ab8bdd1 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go
@@ -19,8 +19,10 @@ import (
"fmt"
"io/ioutil"
"path/filepath"
+ "reflect"
"regexp"
"strings"
+ "sync"
"testing"
"github.com/google/pprof/internal/proftest"
@@ -91,7 +93,6 @@ func TestParse(t *testing.T) {
}
func TestParseError(t *testing.T) {
-
testcases := []string{
"",
"garbage text",
@@ -107,6 +108,63 @@ func TestParseError(t *testing.T) {
}
}
+func TestCheckValid(t *testing.T) {
+ const path = "testdata/java.cpu"
+
+ inbytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ t.Fatalf("failed to read profile file %q: %v", path, err)
+ }
+ p, err := Parse(bytes.NewBuffer(inbytes))
+ if err != nil {
+ t.Fatalf("failed to parse profile %q: %s", path, err)
+ }
+
+ for _, tc := range []struct {
+ mutateFn func(*Profile)
+ wantErr string
+ }{
+ {
+ mutateFn: func(p *Profile) { p.SampleType = nil },
+ wantErr: "missing sample type information",
+ },
+ {
+ mutateFn: func(p *Profile) { p.Sample[0] = nil },
+ wantErr: "profile has nil sample",
+ },
+ {
+ mutateFn: func(p *Profile) { p.Sample[0].Value = append(p.Sample[0].Value, 0) },
+ wantErr: "sample has 3 values vs. 2 types",
+ },
+ {
+ mutateFn: func(p *Profile) { p.Sample[0].Location[0] = nil },
+ wantErr: "sample has nil location",
+ },
+ {
+ mutateFn: func(p *Profile) { p.Location[0] = nil },
+ wantErr: "profile has nil location",
+ },
+ {
+ mutateFn: func(p *Profile) { p.Mapping = append(p.Mapping, nil) },
+ wantErr: "profile has nil mapping",
+ },
+ {
+ mutateFn: func(p *Profile) { p.Function[0] = nil },
+ wantErr: "profile has nil function",
+ },
+ } {
+ t.Run(tc.wantErr, func(t *testing.T) {
+ p := p.Copy()
+ tc.mutateFn(p)
+ if err := p.CheckValid(); err == nil {
+ t.Errorf("CheckValid(): got no error, want error %q", tc.wantErr)
+ } else if !strings.Contains(err.Error(), tc.wantErr) {
+ t.Errorf("CheckValid(): got error %v, want error %q", err, tc.wantErr)
+ }
+ })
+ }
+}
+
// leaveTempfile leaves |b| in a temporary file on disk and returns the
// temp filename. This is useful to recover a profile when the test
// fails.
@@ -217,7 +275,7 @@ var cpuL = []*Location{
},
}
-var testProfile = &Profile{
+var testProfile1 = &Profile{
PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
Period: 1,
DurationNanos: 10e9,
@@ -230,40 +288,181 @@ var testProfile = &Profile{
Location: []*Location{cpuL[0]},
Value: []int64{1000, 1000},
Label: map[string][]string{
- "key1": []string{"tag1"},
- "key2": []string{"tag1"},
+ "key1": {"tag1"},
+ "key2": {"tag1"},
},
},
{
Location: []*Location{cpuL[1], cpuL[0]},
Value: []int64{100, 100},
Label: map[string][]string{
- "key1": []string{"tag2"},
- "key3": []string{"tag2"},
+ "key1": {"tag2"},
+ "key3": {"tag2"},
},
},
{
Location: []*Location{cpuL[2], cpuL[0]},
Value: []int64{10, 10},
Label: map[string][]string{
- "key1": []string{"tag3"},
- "key2": []string{"tag2"},
+ "key1": {"tag3"},
+ "key2": {"tag2"},
},
},
{
Location: []*Location{cpuL[3], cpuL[0]},
Value: []int64{10000, 10000},
Label: map[string][]string{
- "key1": []string{"tag4"},
- "key2": []string{"tag1"},
+ "key1": {"tag4"},
+ "key2": {"tag1"},
},
},
{
Location: []*Location{cpuL[4], cpuL[0]},
Value: []int64{1, 1},
Label: map[string][]string{
- "key1": []string{"tag4"},
- "key2": []string{"tag1"},
+ "key1": {"tag4"},
+ "key2": {"tag1"},
+ },
+ },
+ },
+ Location: cpuL,
+ Function: cpuF,
+ Mapping: cpuM,
+}
+
+var testProfile2 = &Profile{
+ PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
+ Period: 1,
+ DurationNanos: 10e9,
+ SampleType: []*ValueType{
+ {Type: "samples", Unit: "count"},
+ {Type: "cpu", Unit: "milliseconds"},
+ },
+ Sample: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{70, 1000},
+ Label: map[string][]string{
+ "key1": {"tag1"},
+ "key2": {"tag1"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[1], cpuL[0]},
+ Value: []int64{60, 100},
+ Label: map[string][]string{
+ "key1": {"tag2"},
+ "key3": {"tag2"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[2], cpuL[0]},
+ Value: []int64{50, 10},
+ Label: map[string][]string{
+ "key1": {"tag3"},
+ "key2": {"tag2"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[3], cpuL[0]},
+ Value: []int64{40, 10000},
+ Label: map[string][]string{
+ "key1": {"tag4"},
+ "key2": {"tag1"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[4], cpuL[0]},
+ Value: []int64{1, 1},
+ Label: map[string][]string{
+ "key1": {"tag4"},
+ "key2": {"tag1"},
+ },
+ },
+ },
+ Location: cpuL,
+ Function: cpuF,
+ Mapping: cpuM,
+}
+
+var testProfile3 = &Profile{
+ PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
+ Period: 1,
+ DurationNanos: 10e9,
+ SampleType: []*ValueType{
+ {Type: "samples", Unit: "count"},
+ },
+ Sample: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"tag1"},
+ "key2": {"tag1"},
+ },
+ },
+ },
+ Location: cpuL,
+ Function: cpuF,
+ Mapping: cpuM,
+}
+
+var testProfile4 = &Profile{
+ PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
+ Period: 1,
+ DurationNanos: 10e9,
+ SampleType: []*ValueType{
+ {Type: "samples", Unit: "count"},
+ },
+ Sample: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ NumLabel: map[string][]int64{
+ "key1": {10},
+ "key2": {30},
+ },
+ NumUnit: map[string][]string{
+ "key1": {"bytes"},
+ "key2": {"bytes"},
+ },
+ },
+ },
+ Location: cpuL,
+ Function: cpuF,
+ Mapping: cpuM,
+}
+
+var testProfile5 = &Profile{
+ PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
+ Period: 1,
+ DurationNanos: 10e9,
+ SampleType: []*ValueType{
+ {Type: "samples", Unit: "count"},
+ },
+ Sample: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ NumLabel: map[string][]int64{
+ "key1": {10},
+ "key2": {30},
+ },
+ NumUnit: map[string][]string{
+ "key1": {"bytes"},
+ "key2": {"bytes"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ NumLabel: map[string][]int64{
+ "key1": {10},
+ "key2": {30},
+ },
+ NumUnit: map[string][]string{
+ "key1": {"kilobytes"},
+ "key2": {"kilobytes"},
},
},
},
@@ -273,10 +472,10 @@ var testProfile = &Profile{
}
var aggTests = map[string]aggTest{
- "precise": aggTest{true, true, true, true, 5},
- "fileline": aggTest{false, true, true, true, 4},
- "inline_function": aggTest{false, true, false, true, 3},
- "function": aggTest{false, true, false, false, 2},
+ "precise": {true, true, true, true, 5},
+ "fileline": {false, true, true, true, 4},
+ "inline_function": {false, true, false, true, 3},
+ "function": {false, true, false, false, 2},
}
type aggTest struct {
@@ -287,7 +486,7 @@ type aggTest struct {
const totalSamples = int64(11111)
func TestAggregation(t *testing.T) {
- prof := testProfile.Copy()
+ prof := testProfile1.Copy()
for _, resolution := range []string{"precise", "fileline", "inline_function", "function"} {
a := aggTests[resolution]
if !a.precise {
@@ -362,7 +561,7 @@ func checkAggregation(prof *Profile, a *aggTest) error {
// Test merge leaves the main binary in place.
func TestMergeMain(t *testing.T) {
- prof := testProfile.Copy()
+ prof := testProfile1.Copy()
p1, err := Merge([]*Profile{prof})
if err != nil {
t.Fatalf("merge error: %v", err)
@@ -377,7 +576,7 @@ func TestMerge(t *testing.T) {
// -2. Should end up with an empty profile (all samples for a
// location should add up to 0).
- prof := testProfile.Copy()
+ prof := testProfile1.Copy()
p1, err := Merge([]*Profile{prof, prof})
if err != nil {
t.Errorf("merge error: %v", err)
@@ -409,7 +608,7 @@ func TestMergeAll(t *testing.T) {
// Aggregate 10 copies of the profile.
profs := make([]*Profile, 10)
for i := 0; i < 10; i++ {
- profs[i] = testProfile.Copy()
+ profs[i] = testProfile1.Copy()
}
prof, err := Merge(profs)
if err != nil {
@@ -420,7 +619,7 @@ func TestMergeAll(t *testing.T) {
tb := locationHash(s)
samples[tb] = samples[tb] + s.Value[0]
}
- for _, s := range testProfile.Sample {
+ for _, s := range testProfile1.Sample {
tb := locationHash(s)
if samples[tb] != s.Value[0]*10 {
t.Errorf("merge got wrong value at %s : %d instead of %d", tb, samples[tb], s.Value[0]*10)
@@ -428,6 +627,140 @@ func TestMergeAll(t *testing.T) {
}
}
+func TestNumLabelMerge(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ profs []*Profile
+ wantNumLabels []map[string][]int64
+ wantNumUnits []map[string][]string
+ }{
+ {
+ name: "different tag units not merged",
+ profs: []*Profile{testProfile4.Copy(), testProfile5.Copy()},
+ wantNumLabels: []map[string][]int64{
+ {
+ "key1": {10},
+ "key2": {30},
+ },
+ {
+ "key1": {10},
+ "key2": {30},
+ },
+ },
+ wantNumUnits: []map[string][]string{
+ {
+ "key1": {"bytes"},
+ "key2": {"bytes"},
+ },
+ {
+ "key1": {"kilobytes"},
+ "key2": {"kilobytes"},
+ },
+ },
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ prof, err := Merge(tc.profs)
+ if err != nil {
+ t.Errorf("merge error: %v", err)
+ }
+
+ if want, got := len(tc.wantNumLabels), len(prof.Sample); want != got {
+ t.Fatalf("got %d samples, want %d samples", got, want)
+ }
+ for i, wantLabels := range tc.wantNumLabels {
+ numLabels := prof.Sample[i].NumLabel
+ if !reflect.DeepEqual(wantLabels, numLabels) {
+ t.Errorf("got numeric labels %v, want %v", numLabels, wantLabels)
+ }
+
+ wantUnits := tc.wantNumUnits[i]
+ numUnits := prof.Sample[i].NumUnit
+ if !reflect.DeepEqual(wantUnits, numUnits) {
+ t.Errorf("got numeric labels %v, want %v", numUnits, wantUnits)
+ }
+ }
+ })
+ }
+}
+
+func TestNormalizeBySameProfile(t *testing.T) {
+ pb := testProfile1.Copy()
+ p := testProfile1.Copy()
+
+ if err := p.Normalize(pb); err != nil {
+ t.Fatal(err)
+ }
+
+ for i, s := range p.Sample {
+ for j, v := range s.Value {
+ expectedSampleValue := testProfile1.Sample[i].Value[j]
+ if v != expectedSampleValue {
+ t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValue, v)
+ }
+ }
+ }
+}
+
+func TestNormalizeByDifferentProfile(t *testing.T) {
+ p := testProfile1.Copy()
+ pb := testProfile2.Copy()
+
+ if err := p.Normalize(pb); err != nil {
+ t.Fatal(err)
+ }
+
+ expectedSampleValues := [][]int64{
+ {19, 1000},
+ {1, 100},
+ {0, 10},
+ {198, 10000},
+ {0, 1},
+ }
+
+ for i, s := range p.Sample {
+ for j, v := range s.Value {
+ if v != expectedSampleValues[i][j] {
+ t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValues[i][j], v)
+ }
+ }
+ }
+}
+
+func TestNormalizeByMultipleOfSameProfile(t *testing.T) {
+ pb := testProfile1.Copy()
+ for i, s := range pb.Sample {
+ for j, v := range s.Value {
+ pb.Sample[i].Value[j] = 10 * v
+ }
+ }
+
+ p := testProfile1.Copy()
+
+ err := p.Normalize(pb)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for i, s := range p.Sample {
+ for j, v := range s.Value {
+ expectedSampleValue := 10 * testProfile1.Sample[i].Value[j]
+ if v != expectedSampleValue {
+ t.Errorf("For sample %d, value %d, want %d got %d", i, j, expectedSampleValue, v)
+ }
+ }
+ }
+}
+
+func TestNormalizeIncompatibleProfiles(t *testing.T) {
+ p := testProfile1.Copy()
+ pb := testProfile3.Copy()
+
+ if err := p.Normalize(pb); err == nil {
+ t.Errorf("Expected an error")
+ }
+}
+
func TestFilter(t *testing.T) {
// Perform several forms of filtering on the test profile.
@@ -437,12 +770,33 @@ func TestFilter(t *testing.T) {
}
for tx, tc := range []filterTestcase{
- {nil, nil, nil, nil, true, false, false, false},
- {regexp.MustCompile("notfound"), nil, nil, nil, false, false, false, false},
- {nil, regexp.MustCompile("foo.c"), nil, nil, true, true, false, false},
- {nil, nil, regexp.MustCompile("lib.so"), nil, true, false, true, false},
+ {
+ fm: true, // nil focus matches every sample
+ },
+ {
+ focus: regexp.MustCompile("notfound"),
+ },
+ {
+ ignore: regexp.MustCompile("foo.c"),
+ fm: true,
+ im: true,
+ },
+ {
+ hide: regexp.MustCompile("lib.so"),
+ fm: true,
+ hm: true,
+ },
+ {
+ show: regexp.MustCompile("foo.c"),
+ fm: true,
+ hnm: true,
+ },
+ {
+ show: regexp.MustCompile("notfound"),
+ fm: true,
+ },
} {
- prof := *testProfile.Copy()
+ prof := *testProfile1.Copy()
gf, gi, gh, gnh := prof.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
if gf != tc.fm {
t.Errorf("Filter #%d, got fm=%v, want %v", tx, gf, tc.fm)
@@ -488,7 +842,7 @@ func TestTagFilter(t *testing.T) {
{regexp.MustCompile("key1"), nil, true, false, 1},
{nil, regexp.MustCompile("key[12]"), true, true, 1},
} {
- prof := testProfile.Copy()
+ prof := testProfile1.Copy()
gim, gem := prof.FilterTagsByName(tc.include, tc.exclude)
if gim != tc.im {
t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im)
@@ -513,9 +867,152 @@ func locationHash(s *Sample) string {
return tb
}
+func TestNumLabelUnits(t *testing.T) {
+ var tagFilterTests = []struct {
+ desc string
+ tagVals []map[string][]int64
+ tagUnits []map[string][]string
+ wantUnits map[string]string
+ wantIgnoredUnits map[string][]string
+ }{
+ {
+ "One sample, multiple keys, different specified units",
+ []map[string][]int64{{"key1": {131072}, "key2": {128}}},
+ []map[string][]string{{"key1": {"bytes"}, "key2": {"kilobytes"}}},
+ map[string]string{"key1": "bytes", "key2": "kilobytes"},
+ map[string][]string{},
+ },
+ {
+ "One sample, one key with one value, unit specified",
+ []map[string][]int64{{"key1": {8}}},
+ []map[string][]string{{"key1": {"bytes"}}},
+ map[string]string{"key1": "bytes"},
+ map[string][]string{},
+ },
+ {
+ "One sample, one key with one value, empty unit specified",
+ []map[string][]int64{{"key1": {8}}},
+ []map[string][]string{{"key1": {""}}},
+ map[string]string{"key1": "key1"},
+ map[string][]string{},
+ },
+ {
+ "Key bytes, unit not specified",
+ []map[string][]int64{{"bytes": {8}}},
+ []map[string][]string{nil},
+ map[string]string{"bytes": "bytes"},
+ map[string][]string{},
+ },
+ {
+ "One sample, one key with one value, unit not specified",
+ []map[string][]int64{{"kilobytes": {8}}},
+ []map[string][]string{nil},
+ map[string]string{"kilobytes": "kilobytes"},
+ map[string][]string{},
+ },
+ {
+ "Key request, unit not specified",
+ []map[string][]int64{{"request": {8}}},
+ []map[string][]string{nil},
+ map[string]string{"request": "bytes"},
+ map[string][]string{},
+ },
+ {
+ "Key alignment, unit not specified",
+ []map[string][]int64{{"alignment": {8}}},
+ []map[string][]string{nil},
+ map[string]string{"alignment": "bytes"},
+ map[string][]string{},
+ },
+ {
+ "One sample, one key with multiple values and two different units",
+ []map[string][]int64{{"key1": {8, 8}}},
+ []map[string][]string{{"key1": {"bytes", "kilobytes"}}},
+ map[string]string{"key1": "bytes"},
+ map[string][]string{"key1": {"kilobytes"}},
+ },
+ {
+ "One sample, one key with multiple values and three different units",
+ []map[string][]int64{{"key1": {8, 8}}},
+ []map[string][]string{{"key1": {"bytes", "megabytes", "kilobytes"}}},
+ map[string]string{"key1": "bytes"},
+ map[string][]string{"key1": {"kilobytes", "megabytes"}},
+ },
+ {
+ "Two samples, one key, different units specified",
+ []map[string][]int64{{"key1": {8}}, {"key1": {8}}},
+ []map[string][]string{{"key1": {"bytes"}}, {"key1": {"kilobytes"}}},
+ map[string]string{"key1": "bytes"},
+ map[string][]string{"key1": {"kilobytes"}},
+ },
+ {
+ "Keys alignment, request, and bytes have units specified",
+ []map[string][]int64{{
+ "alignment": {8},
+ "request": {8},
+ "bytes": {8},
+ }},
+ []map[string][]string{{
+ "alignment": {"seconds"},
+ "request": {"minutes"},
+ "bytes": {"hours"},
+ }},
+ map[string]string{
+ "alignment": "seconds",
+ "request": "minutes",
+ "bytes": "hours",
+ },
+ map[string][]string{},
+ },
+ }
+ for _, test := range tagFilterTests {
+ p := &Profile{Sample: make([]*Sample, len(test.tagVals))}
+ for i, numLabel := range test.tagVals {
+ s := Sample{
+ NumLabel: numLabel,
+ NumUnit: test.tagUnits[i],
+ }
+ p.Sample[i] = &s
+ }
+ units, ignoredUnits := p.NumLabelUnits()
+ if !reflect.DeepEqual(test.wantUnits, units) {
+ t.Errorf("%s: got %v units, want %v", test.desc, units, test.wantUnits)
+ }
+ if !reflect.DeepEqual(test.wantIgnoredUnits, ignoredUnits) {
+ t.Errorf("%s: got %v ignored units, want %v", test.desc, ignoredUnits, test.wantIgnoredUnits)
+ }
+ }
+}
+
func TestSetMain(t *testing.T) {
- testProfile.massageMappings()
- if testProfile.Mapping[0].File != mainBinary {
- t.Errorf("got %s for main", testProfile.Mapping[0].File)
+ testProfile1.massageMappings()
+ if testProfile1.Mapping[0].File != mainBinary {
+ t.Errorf("got %s for main", testProfile1.Mapping[0].File)
+ }
+}
+
+// parallel runs n copies of fn in parallel.
+func parallel(n int, fn func()) {
+ var wg sync.WaitGroup
+ wg.Add(n)
+ for i := 0; i < n; i++ {
+ go func() {
+ fn()
+ wg.Done()
+ }()
}
+ wg.Wait()
+}
+
+func TestThreadSafety(t *testing.T) {
+ src := testProfile1.Copy()
+ parallel(4, func() { src.Copy() })
+ parallel(4, func() {
+ var b bytes.Buffer
+ src.WriteUncompressed(&b)
+ })
+ parallel(4, func() {
+ var b bytes.Buffer
+ src.Write(&b)
+ })
}
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/proto.go b/src/cmd/vendor/github.com/google/pprof/profile/proto.go
index 01b7f7ae18..e7df33ac2b 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/proto.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/proto.go
@@ -71,7 +71,7 @@ func encodeLength(b *buffer, tag int, len int) {
func encodeUint64(b *buffer, tag int, x uint64) {
// append varint to b.data
- encodeVarint(b, uint64(tag)<<3|0)
+ encodeVarint(b, uint64(tag)<<3)
encodeVarint(b, x)
}
@@ -145,13 +145,6 @@ func encodeStrings(b *buffer, tag int, x []string) {
}
}
-func encodeStringOpt(b *buffer, tag int, x string) {
- if x == "" {
- return
- }
- encodeString(b, tag, x)
-}
-
func encodeBool(b *buffer, tag int, x bool) {
if x {
encodeUint64(b, tag, 1)
@@ -161,10 +154,9 @@ func encodeBool(b *buffer, tag int, x bool) {
}
func encodeBoolOpt(b *buffer, tag int, x bool) {
- if x == false {
- return
+ if x {
+ encodeBool(b, tag, x)
}
- encodeBool(b, tag, x)
}
func encodeMessage(b *buffer, tag int, m message) {
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/proto_test.go b/src/cmd/vendor/github.com/google/pprof/profile/proto_test.go
index d2a351373e..e0832294ac 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/proto_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/proto_test.go
@@ -100,8 +100,8 @@ var all = &Profile{
{
Location: []*Location{testL[0], testL[1], testL[2], testL[1], testL[1]},
Label: map[string][]string{
- "key1": []string{"value1"},
- "key2": []string{"value2"},
+ "key1": {"value1"},
+ "key2": {"value2"},
},
Value: []int64{10, 20},
},
@@ -109,12 +109,19 @@ var all = &Profile{
Location: []*Location{testL[1], testL[2], testL[0], testL[1]},
Value: []int64{30, 40},
Label: map[string][]string{
- "key1": []string{"value1"},
- "key2": []string{"value2"},
+ "key1": {"value1"},
+ "key2": {"value2"},
},
NumLabel: map[string][]int64{
- "key1": []int64{1, 2},
- "key2": []int64{3, 4},
+ "key1": {1, 2},
+ "key2": {3, 4},
+ "bytes": {3, 4},
+ "requests": {1, 1, 3, 4, 5},
+ "alignment": {3, 4},
+ },
+ NumUnit: map[string][]string{
+ "requests": {"", "", "seconds", "", "s"},
+ "alignment": {"kilobytes", "kilobytes"},
},
},
},
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/prune.go b/src/cmd/vendor/github.com/google/pprof/profile/prune.go
index cf9cbb3894..02d21a8184 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/prune.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/prune.go
@@ -22,6 +22,39 @@ import (
"strings"
)
+var (
+ reservedNames = []string{"(anonymous namespace)", "operator()"}
+ bracketRx = func() *regexp.Regexp {
+ var quotedNames []string
+ for _, name := range append(reservedNames, "(") {
+ quotedNames = append(quotedNames, regexp.QuoteMeta(name))
+ }
+ return regexp.MustCompile(strings.Join(quotedNames, "|"))
+ }()
+)
+
+// simplifyFunc does some primitive simplification of function names.
+func simplifyFunc(f string) string {
+ // Account for leading '.' on the PPC ELF v1 ABI.
+ funcName := strings.TrimPrefix(f, ".")
+ // Account for unsimplified names -- try to remove the argument list by trimming
+ // starting from the first '(', but skipping reserved names that have '('.
+ for _, ind := range bracketRx.FindAllStringSubmatchIndex(funcName, -1) {
+ foundReserved := false
+ for _, res := range reservedNames {
+ if funcName[ind[0]:ind[1]] == res {
+ foundReserved = true
+ break
+ }
+ }
+ if !foundReserved {
+ funcName = funcName[:ind[0]]
+ break
+ }
+ }
+ return funcName
+}
+
// Prune removes all nodes beneath a node matching dropRx, and not
// matching keepRx. If the root node of a Sample matches, the sample
// will have an empty stack.
@@ -33,12 +66,7 @@ func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {
var i int
for i = len(loc.Line) - 1; i >= 0; i-- {
if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
- // Account for leading '.' on the PPC ELF v1 ABI.
- funcName := strings.TrimPrefix(fn.Name, ".")
- // Account for unsimplified names -- trim starting from the first '('.
- if index := strings.Index(funcName, "("); index > 0 {
- funcName = funcName[:index]
- }
+ funcName := simplifyFunc(fn.Name)
if dropRx.MatchString(funcName) {
if keepRx == nil || !keepRx.MatchString(funcName) {
break
@@ -126,12 +154,7 @@ func (p *Profile) PruneFrom(dropRx *regexp.Regexp) {
for _, loc := range p.Location {
for i := 0; i < len(loc.Line); i++ {
if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
- // Account for leading '.' on the PPC ELF v1 ABI.
- funcName := strings.TrimPrefix(fn.Name, ".")
- // Account for unsimplified names -- trim starting from the first '('.
- if index := strings.Index(funcName, "("); index > 0 {
- funcName = funcName[:index]
- }
+ funcName := simplifyFunc(fn.Name)
if dropRx.MatchString(funcName) {
// Found matching entry to prune.
pruneBeneath[loc.ID] = true
diff --git a/src/cmd/vendor/github.com/google/pprof/profile/prune_test.go b/src/cmd/vendor/github.com/google/pprof/profile/prune_test.go
index 58fa25ee2d..75d7c6d4f7 100644
--- a/src/cmd/vendor/github.com/google/pprof/profile/prune_test.go
+++ b/src/cmd/vendor/github.com/google/pprof/profile/prune_test.go
@@ -25,6 +25,7 @@ func TestPrune(t *testing.T) {
want string
}{
{in1, out1},
+ {in2, out2},
} {
in := test.in.Copy()
in.RemoveUninteresting()
@@ -50,6 +51,10 @@ var funs = []*Function{
{ID: 4, Name: "fun3", SystemName: "fun3", Filename: "fun.c"},
{ID: 5, Name: "fun4", SystemName: "fun4", Filename: "fun.c"},
{ID: 6, Name: "fun5", SystemName: "fun5", Filename: "fun.c"},
+ {ID: 7, Name: "unsimplified_fun(int)", SystemName: "unsimplified_fun(int)", Filename: "fun.c"},
+ {ID: 8, Name: "Foo::(anonymous namespace)::Test::Bar", SystemName: "Foo::(anonymous namespace)::Test::Bar", Filename: "fun.c"},
+ {ID: 9, Name: "Hello::(anonymous namespace)::World(const Foo::(anonymous namespace)::Test::Bar)", SystemName: "Hello::(anonymous namespace)::World(const Foo::(anonymous namespace)::Test::Bar)", Filename: "fun.c"},
+ {ID: 10, Name: "Foo::operator()(::Bar)", SystemName: "Foo::operator()(::Bar)", Filename: "fun.c"},
}
var locs1 = []*Location{
@@ -137,3 +142,89 @@ Locations
4: 0x0 fun5 fun.c:2 s=0
Mappings
`
+
+var locs2 = []*Location{
+ {
+ ID: 1,
+ Line: []Line{
+ {Function: funs[0], Line: 1},
+ },
+ },
+ {
+ ID: 2,
+ Line: []Line{
+ {Function: funs[6], Line: 1},
+ },
+ },
+ {
+ ID: 3,
+ Line: []Line{
+ {Function: funs[7], Line: 1},
+ },
+ },
+ {
+ ID: 4,
+ Line: []Line{
+ {Function: funs[8], Line: 1},
+ },
+ },
+ {
+ ID: 5,
+ Line: []Line{
+ {Function: funs[9], Line: 1},
+ },
+ },
+}
+
+var in2 = &Profile{
+ PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
+ Period: 1,
+ DurationNanos: 10e9,
+ SampleType: []*ValueType{
+ {Type: "samples", Unit: "count"},
+ {Type: "cpu", Unit: "milliseconds"},
+ },
+ Sample: []*Sample{
+ // Unsimplified name with parameters shouldn't match.
+ {
+ Location: []*Location{locs2[1], locs2[0]},
+ Value: []int64{1, 1},
+ },
+ // .*Foo::.*::Bar.* should (and will be dropped) regardless of the anonymous namespace.
+ {
+ Location: []*Location{locs2[2], locs2[0]},
+ Value: []int64{1, 1},
+ },
+ // .*Foo::.*::Bar.* shouldn't match inside the parameter list.
+ {
+ Location: []*Location{locs2[3], locs2[0]},
+ Value: []int64{1, 1},
+ },
+ // .*operator\(\) should match, regardless of parameters.
+ {
+ Location: []*Location{locs2[4], locs2[0]},
+ Value: []int64{1, 1},
+ },
+ },
+ Location: locs2,
+ Function: funs,
+ DropFrames: `unsimplified_fun\(int\)|.*Foo::.*::Bar.*|.*operator\(\)`,
+}
+
+const out2 = `PeriodType: cpu milliseconds
+Period: 1
+Duration: 10s
+Samples:
+samples/count cpu/milliseconds
+ 1 1: 2 1
+ 1 1: 1
+ 1 1: 4 1
+ 1 1: 1
+Locations
+ 1: 0x0 main main.c:1 s=0
+ 2: 0x0 unsimplified_fun(int) fun.c:1 s=0
+ 3: 0x0 Foo::(anonymous namespace)::Test::Bar fun.c:1 s=0
+ 4: 0x0 Hello::(anonymous namespace)::World(const Foo::(anonymous namespace)::Test::Bar) fun.c:1 s=0
+ 5: 0x0 Foo::operator()(::Bar) fun.c:1 s=0
+Mappings
+`