diff options
| author | Alberto Donizetti <alb.donizetti@gmail.com> | 2017-08-20 12:27:32 +0200 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@golang.org> | 2017-11-02 23:51:45 +0000 |
| commit | aec345d638fa624f08b7d758e9e173897edc80e8 (patch) | |
| tree | d782d951af4f34de34a08c4775a37f869af25b81 /src/cmd/vendor/github.com/google/pprof/profile | |
| parent | 3039bff9d07ce05dc9af8c155c6929ae5e53a231 (diff) | |
| download | go-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')
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 +` |
