package driver
import (
+ "errors"
"fmt"
"os"
"strings"
ExecName string
BuildID string
Base []string
+ DiffBase bool
Normalize bool
Seconds int
func parseFlags(o *plugin.Options) (*source, []string, error) {
flag := o.Flagset
// Comparisons.
- flagBase := flag.StringList("base", "", "Source for base profile for comparison")
+ flagBase := flag.StringList("base", "", "Source for base profile for profile subtraction")
+ flagDiffBase := flag.StringList("diff_base", "", "Source for diff base profile for comparison")
// Source options.
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
usageMsgVars)
})
if len(args) == 0 {
- return nil, nil, fmt.Errorf("no profile source specified")
+ return nil, nil, errors.New("no profile source specified")
}
var execName string
return nil, nil, err
}
if cmd != nil && *flagHTTP != "" {
- return nil, nil, fmt.Errorf("-http is not compatible with an output format on the command line")
+ return nil, nil, errors.New("-http is not compatible with an output format on the command line")
}
si := pprofVariables["sample_index"].value
Comment: *flagAddComment,
}
- for _, s := range *flagBase {
- if *s != "" {
- source.Base = append(source.Base, *s)
- }
+ if err := source.addBaseProfiles(*flagBase, *flagDiffBase); err != nil {
+ return nil, nil, err
}
normalize := pprofVariables["normalize"].boolValue()
if normalize && len(source.Base) == 0 {
- return nil, nil, fmt.Errorf("Must have base profile to normalize by")
+ return nil, nil, errors.New("must have base profile to normalize by")
}
source.Normalize = normalize
return source, cmd, nil
}
+// addBaseProfiles adds the list of base profiles or diff base profiles to
+// the source. This function will return an error if both base and diff base
+// profiles are specified.
+func (source *source) addBaseProfiles(flagBase, flagDiffBase []*string) error {
+ base, diffBase := dropEmpty(flagBase), dropEmpty(flagDiffBase)
+ if len(base) > 0 && len(diffBase) > 0 {
+ return errors.New("-base and -diff_base flags cannot both be specified")
+ }
+
+ source.Base = base
+ if len(diffBase) > 0 {
+ source.Base, source.DiffBase = diffBase, true
+ }
+ return nil
+}
+
+// dropEmpty list takes a slice of string pointers, and outputs a slice of
+// non-empty strings associated with the flag.
+func dropEmpty(list []*string) []string {
+ var l []string
+ for _, s := range list {
+ if *s != "" {
+ l = append(l, *s)
+ }
+ }
+ return l
+}
+
// installFlags creates command line flags for pprof variables.
func installFlags(flag plugin.FlagSet) flagsInstalled {
f := flagsInstalled{
for n, b := range bcmd {
if *b {
if cmd != nil {
- return nil, fmt.Errorf("must set at most one output format")
+ return nil, errors.New("must set at most one output format")
}
cmd = []string{n}
}
for n, s := range acmd {
if *s != "" {
if cmd != nil {
- return nil, fmt.Errorf("must set at most one output format")
+ return nil, errors.New("must set at most one output format")
}
cmd = []string{n, *s}
}
// Identify units of numeric tags in profile.
numLabelUnits := identifyNumLabelUnits(p, o.UI)
- vars = applyCommandOverrides(cmd, vars)
+ // Get report output format
+ c := pprofCommands[cmd[0]]
+ if c == nil {
+ panic("unexpected nil command")
+ }
+
+ vars = applyCommandOverrides(cmd[0], c.format, vars)
// Delay focus after configuring report to get percentages on all samples.
relative := vars["relative_percentages"].boolValue()
if err != nil {
return nil, nil, err
}
- c := pprofCommands[cmd[0]]
- if c == nil {
- panic("unexpected nil command")
- }
ropt.OutputFormat = c.format
if len(cmd) == 2 {
s, err := regexp.Compile(cmd[1])
return out.Close()
}
-func applyCommandOverrides(cmd []string, v variables) variables {
+func applyCommandOverrides(cmd string, outputFormat int, v variables) variables {
trim, tagfilter, filter := v["trim"].boolValue(), true, true
- switch cmd[0] {
- case "proto", "raw":
- trim, tagfilter, filter = false, false, false
- v.set("addresses", "t")
+ switch cmd {
case "callgrind", "kcachegrind":
trim = false
v.set("addresses", "t")
trim = false
v.set("addressnoinlines", "t")
case "peek":
- trim, filter = false, false
+ trim, tagfilter, filter = false, false, false
case "list":
v.set("nodecount", "0")
v.set("lines", "t")
v.set("nodecount", "80")
}
}
+
+ if outputFormat == report.Proto || outputFormat == report.Raw {
+ trim, tagfilter, filter = false, false, false
+ v.set("addresses", "t")
+ }
+
if !trim {
v.set("nodecount", "0")
v.set("nodefraction", "0")
floats map[string]float64
strings map[string]string
args []string
- stringLists map[string][]*string
+ stringLists map[string][]string
}
func (testFlags) ExtraUsage() string { return "" }
func (f testFlags) StringList(s, d, c string) *[]*string {
if t, ok := f.stringLists[s]; ok {
- return &t
+ // convert slice of strings to slice of string pointers before returning.
+ tp := make([]*string, len(t))
+ for i, v := range t {
+ tp[i] = &v
+ }
+ return &tp
}
return &[]*string{}
}
}
if pbase != nil {
+ if s.DiffBase {
+ pbase.SetLabel("pprof::base", []string{"true"})
+ }
if s.Normalize {
err := p.Normalize(pbase)
if err != nil {
baseVars := pprofVariables
defer func() { pprofVariables = baseVars }()
+ type WantSample struct {
+ values []int64
+ labels map[string][]string
+ }
+
const path = "testdata/"
type testcase struct {
- desc string
- sources []string
- bases []string
- normalize bool
- expectedSamples [][]int64
+ desc string
+ sources []string
+ bases []string
+ diffBases []string
+ normalize bool
+ wantSamples []WantSample
+ wantErrorMsg string
}
testcases := []testcase{
"not normalized base is same as source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention"},
+ nil,
+ false,
+ nil,
+ "",
+ },
+ {
+ "not normalized base is same as source",
+ []string{path + "cppbench.contention"},
+ []string{path + "cppbench.contention"},
+ nil,
false,
- [][]int64{},
+ nil,
+ "",
},
{
"not normalized single source, multiple base (all profiles same)",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention", path + "cppbench.contention"},
+ nil,
false,
- [][]int64{{-2700, -608881724}, {-100, -23992}, {-200, -179943}, {-100, -17778444}, {-100, -75976}, {-300, -63568134}},
+ []WantSample{
+ {
+ values: []int64{-2700, -608881724},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{-100, -23992},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{-200, -179943},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{-100, -17778444},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{-100, -75976},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{-300, -63568134},
+ labels: map[string][]string{},
+ },
+ },
+ "",
},
{
"not normalized, different base and source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.small.contention"},
+ nil,
false,
- [][]int64{{1700, 608878600}, {100, 23992}, {200, 179943}, {100, 17778444}, {100, 75976}, {300, 63568134}},
+ []WantSample{
+ {
+ values: []int64{1700, 608878600},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{100, 23992},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{200, 179943},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{100, 17778444},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{100, 75976},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{300, 63568134},
+ labels: map[string][]string{},
+ },
+ },
+ "",
},
{
"normalized base is same as source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention"},
+ nil,
true,
- [][]int64{},
+ nil,
+ "",
},
{
"normalized single source, multiple base (all profiles same)",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.contention", path + "cppbench.contention"},
+ nil,
true,
- [][]int64{},
+ nil,
+ "",
},
{
"normalized different base and source",
[]string{path + "cppbench.contention"},
[]string{path + "cppbench.small.contention"},
+ nil,
true,
- [][]int64{{-229, -370}, {28, 0}, {57, 0}, {28, 80}, {28, 0}, {85, 287}},
+ []WantSample{
+ {
+ values: []int64{-229, -370},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{28, 0},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{57, 0},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{28, 80},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{28, 0},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{85, 287},
+ labels: map[string][]string{},
+ },
+ },
+ "",
+ },
+ {
+ "not normalized diff base is same as source",
+ []string{path + "cppbench.contention"},
+ nil,
+ []string{path + "cppbench.contention"},
+ false,
+ []WantSample{
+ {
+ values: []int64{2700, 608881724},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{100, 23992},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{200, 179943},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{100, 17778444},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{100, 75976},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{300, 63568134},
+ labels: map[string][]string{},
+ },
+ {
+ values: []int64{-2700, -608881724},
+ labels: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ values: []int64{-100, -23992},
+ labels: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ values: []int64{-200, -179943},
+ labels: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ values: []int64{-100, -17778444},
+ labels: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ values: []int64{-100, -75976},
+ labels: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ values: []int64{-300, -63568134},
+ labels: map[string][]string{"pprof::base": {"true"}},
+ },
+ },
+ "",
+ },
+ {
+ "diff_base and base both specified",
+ []string{path + "cppbench.contention"},
+ []string{path + "cppbench.contention"},
+ []string{path + "cppbench.contention"},
+ false,
+ nil,
+ "-base and -diff_base flags cannot both be specified",
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
pprofVariables = baseVars.makeCopy()
-
- base := make([]*string, len(tc.bases))
- for i, s := range tc.bases {
- base[i] = &s
- }
-
f := testFlags{
- stringLists: map[string][]*string{
- "base": base,
+ stringLists: map[string][]string{
+ "base": tc.bases,
+ "diff_base": tc.diffBases,
},
bools: map[string]bool{
"normalize": tc.normalize,
})
src, _, err := parseFlags(o)
+ if tc.wantErrorMsg != "" {
+ if err == nil {
+ t.Fatalf("got nil, want error %q", tc.wantErrorMsg)
+ }
+
+ if gotErrMsg := err.Error(); gotErrMsg != tc.wantErrorMsg {
+ t.Fatalf("got error %q, want error %q", gotErrMsg, tc.wantErrorMsg)
+ }
+ return
+ }
+
if err != nil {
- t.Fatalf("%s: %v", tc.desc, err)
+ t.Fatalf("got error %q, want no error", err)
}
p, err := fetchProfiles(src, o)
- pprofVariables = baseVars
+
if err != nil {
- t.Fatal(err)
+ t.Fatalf("got error %q, want no error", err)
}
- if want, got := len(tc.expectedSamples), len(p.Sample); want != got {
- t.Fatalf("want %d samples got %d", want, got)
+ if got, want := len(p.Sample), len(tc.wantSamples); got != want {
+ t.Fatalf("got %d samples want %d", got, want)
}
- if len(p.Sample) > 0 {
- for i, sample := range p.Sample {
- if want, got := len(tc.expectedSamples[i]), len(sample.Value); want != got {
- t.Errorf("want %d values for sample %d, got %d", want, i, got)
- }
- for j, value := range sample.Value {
- if want, got := tc.expectedSamples[i][j], value; want != got {
- t.Errorf("want value of %d for value %d of sample %d, got %d", want, j, i, got)
- }
- }
+ for i, sample := range p.Sample {
+ if !reflect.DeepEqual(tc.wantSamples[i].values, sample.Value) {
+ t.Errorf("for sample %d got values %v, want %v", i, sample.Value, tc.wantSamples[i])
+ }
+ if !reflect.DeepEqual(tc.wantSamples[i].labels, sample.Label) {
+ t.Errorf("for sample %d got labels %v, want %v", i, sample.Label, tc.wantSamples[i].labels)
}
}
})
t.Errorf("failed on %q: %v", tc.input, err)
continue
}
- vars = applyCommandOverrides(cmd, vars)
+
+ // Get report output format
+ c := pprofCommands[cmd[0]]
+ if c == nil {
+ t.Errorf("unexpected nil command")
+ }
+ vars = applyCommandOverrides(cmd[0], c.format, vars)
for n, want := range tc.want {
if got := vars[n].stringValue(); got != want {
s.NumUnit = numUnits
}
+ // Remove label marking samples from the base profiles, so it does not appear
+ // as a nodelet in the graph view.
+ prof.RemoveLabel("pprof::base")
+
formatTag := func(v int64, key string) string {
return measurement.ScaledLabel(v, key, o.OutputUnit)
}
return New(prof, o)
}
-// computeTotal computes the sum of all sample values. This will be
-// used to compute percentages.
+// 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
+// will only include samples with that label.
func computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {
- var div, ret int64
+ var div, total, diffDiv, diffTotal int64
for _, sample := range prof.Sample {
var d, v int64
v = value(sample.Value)
if v < 0 {
v = -v
}
- ret += v
+ total += v
div += d
+ if sample.HasLabel("pprof::base", "true") {
+ diffTotal += v
+ diffDiv += d
+ }
+ }
+ if diffTotal > 0 {
+ total = diffTotal
+ div = diffDiv
}
if div != 0 {
- return ret / div
+ return total / div
}
- return ret
+ return total
}
// Report contains the data and associated routines to extract a
}
}
}
+
+func TestComputeTotal(t *testing.T) {
+ p1 := testProfile.Copy()
+ p1.Sample = []*profile.Sample{
+ {
+ Location: []*profile.Location{testL[0]},
+ Value: []int64{1, 1},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{1, 10},
+ },
+ {
+ Location: []*profile.Location{testL[4], testL[2], testL[0]},
+ Value: []int64{1, 100},
+ },
+ }
+
+ p2 := testProfile.Copy()
+ p2.Sample = []*profile.Sample{
+ {
+ Location: []*profile.Location{testL[0]},
+ Value: []int64{1, 1},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{1, -10},
+ },
+ {
+ Location: []*profile.Location{testL[4], testL[2], testL[0]},
+ Value: []int64{1, 100},
+ },
+ }
+
+ p3 := testProfile.Copy()
+ p3.Sample = []*profile.Sample{
+ {
+ Location: []*profile.Location{testL[0]},
+ Value: []int64{10000, 1},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{-10, 3},
+ Label: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{1000, -10},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{-9000, 3},
+ Label: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{-1, 3},
+ Label: map[string][]string{"pprof::base": {"true"}},
+ },
+ {
+ Location: []*profile.Location{testL[4], testL[2], testL[0]},
+ Value: []int64{100, 100},
+ },
+ {
+ Location: []*profile.Location{testL[2], testL[1], testL[0]},
+ Value: []int64{100, 3},
+ Label: map[string][]string{"pprof::base": {"true"}},
+ },
+ }
+
+ testcases := []struct {
+ desc string
+ prof *profile.Profile
+ value, meanDiv func(v []int64) int64
+ wantTotal int64
+ }{
+ {
+ desc: "no diff base, all positive values, index 1",
+ prof: p1,
+ value: func(v []int64) int64 {
+ return v[0]
+ },
+ wantTotal: 3,
+ },
+ {
+ desc: "no diff base, all positive values, index 2",
+ prof: p1,
+ value: func(v []int64) int64 {
+ return v[1]
+ },
+ wantTotal: 111,
+ },
+ {
+ desc: "no diff base, some negative values",
+ prof: p2,
+ value: func(v []int64) int64 {
+ return v[1]
+ },
+ wantTotal: 111,
+ },
+ {
+ desc: "diff base, some negative values",
+ prof: p3,
+ value: func(v []int64) int64 {
+ return v[0]
+ },
+ wantTotal: 9111,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ if gotTotal := computeTotal(tc.prof, tc.value, tc.meanDiv); gotTotal != tc.wantTotal {
+ t.Errorf("got total %d, want %v", gotTotal, tc.wantTotal)
+ }
+ })
+ }
+}
{"objects", "space"},
{"inuse_objects", "inuse_space"},
{"alloc_objects", "alloc_space"},
+ {"alloc_objects", "alloc_space", "inuse_objects", "inuse_space"}, // Go pprof legacy profiles
}
var contentionzSampleTypes = [][]string{
{"contentions", "delay"},
{[]string{"objects", "space"}, heap, true, "heapzSampleTypes"},
{[]string{"inuse_objects", "inuse_space"}, heap, true, "heapzSampleTypes"},
{[]string{"alloc_objects", "alloc_space"}, heap, true, "heapzSampleTypes"},
+ {[]string{"alloc_objects", "alloc_space", "inuse_objects", "inuse_space"}, heap, true, "heapzSampleTypes"},
{[]string{"contentions", "delay"}, cont, true, "contentionzSampleTypes"},
// False cases
{[]string{"objects"}, heap, false, "heapzSampleTypes"},
{[]string{"objects", "unknown"}, heap, false, "heapzSampleTypes"},
+ {[]string{"inuse_objects", "inuse_space", "alloc_objects", "alloc_space"}, heap, false, "heapzSampleTypes"},
{[]string{"contentions", "delay"}, heap, false, "heapzSampleTypes"},
{[]string{"samples", "cpu"}, heap, false, "heapzSampleTypes"},
{[]string{"samples", "cpu"}, cont, false, "contentionzSampleTypes"},
return strings.Join(ls, " ")
}
+// SetLabel sets the specified key to the specified value for all samples in the
+// profile.
+func (p *Profile) SetLabel(key string, value []string) {
+ for _, sample := range p.Sample {
+ if sample.Label == nil {
+ sample.Label = map[string][]string{key: value}
+ } else {
+ sample.Label[key] = value
+ }
+ }
+}
+
+// RemoveLabel removes all labels associated with the specified key for all
+// samples in the profile.
+func (p *Profile) RemoveLabel(key string) {
+ for _, sample := range p.Sample {
+ delete(sample.Label, key)
+ }
+}
+
+// HasLabel returns true if a sample has a label with indicated key and value.
+func (s *Sample) HasLabel(key, value string) bool {
+ for _, v := range s.Label[key] {
+ if v == value {
+ return true
+ }
+ }
+ return false
+}
+
// Scale multiplies all sample values in a profile by a constant.
func (p *Profile) Scale(ratio float64) {
if ratio == 1 {
wantNumUnits []map[string][]string
}{
{
- name: "different tag units not merged",
+ name: "different label units not merged",
profs: []*Profile{testProfile4.Copy(), testProfile5.Copy()},
wantNumLabels: []map[string][]int64{
{
return tb
}
+func TestHasLabel(t *testing.T) {
+ var testcases = []struct {
+ desc string
+ labels map[string][]string
+ key string
+ value string
+ wantHasLabel bool
+ }{
+ {
+ desc: "empty label does not have label",
+ labels: map[string][]string{},
+ key: "key",
+ value: "value",
+ wantHasLabel: false,
+ },
+ {
+ desc: "label with one key and value has label",
+ labels: map[string][]string{"key": {"value"}},
+ key: "key",
+ value: "value",
+ wantHasLabel: true,
+ },
+ {
+ desc: "label with one key and value does not have label",
+ labels: map[string][]string{"key": {"value"}},
+ key: "key1",
+ value: "value1",
+ wantHasLabel: false,
+ },
+ {
+ desc: "label with many keys and values has label",
+ labels: map[string][]string{
+ "key1": {"value2", "value1"},
+ "key2": {"value1", "value2", "value2"},
+ "key3": {"value1", "value2", "value2"},
+ },
+ key: "key1",
+ value: "value1",
+ wantHasLabel: true,
+ },
+ {
+ desc: "label with many keys and values does not have label",
+ labels: map[string][]string{
+ "key1": {"value2", "value1"},
+ "key2": {"value1", "value2", "value2"},
+ "key3": {"value1", "value2", "value2"},
+ },
+ key: "key5",
+ value: "value5",
+ wantHasLabel: false,
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ sample := &Sample{
+ Label: tc.labels,
+ }
+ if gotHasLabel := sample.HasLabel(tc.key, tc.value); gotHasLabel != tc.wantHasLabel {
+ t.Errorf("sample.HasLabel(%q, %q) got %v, want %v", tc.key, tc.value, gotHasLabel, tc.wantHasLabel)
+ }
+ })
+ }
+}
+
+func TestRemove(t *testing.T) {
+ var testcases = []struct {
+ desc string
+ samples []*Sample
+ removeKey string
+ wantLabels []map[string][]string
+ }{
+ {
+ desc: "some samples have label already",
+ samples: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"value1", "value2", "value3"},
+ "key2": {"value1"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"value2"},
+ },
+ },
+ },
+ removeKey: "key1",
+ wantLabels: []map[string][]string{
+ {},
+ {"key2": {"value1"}},
+ {},
+ },
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ profile := testProfile1.Copy()
+ profile.Sample = tc.samples
+ profile.RemoveLabel(tc.removeKey)
+ if got, want := len(profile.Sample), len(tc.wantLabels); got != want {
+ t.Fatalf("got %v samples, want %v samples", got, want)
+ }
+ for i, sample := range profile.Sample {
+ wantLabels := tc.wantLabels[i]
+ if got, want := len(sample.Label), len(wantLabels); got != want {
+ t.Errorf("got %v label keys for sample %v, want %v", got, i, want)
+ continue
+ }
+ for wantKey, wantValues := range wantLabels {
+ if gotValues, ok := sample.Label[wantKey]; ok {
+ if !reflect.DeepEqual(gotValues, wantValues) {
+ t.Errorf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues)
+ }
+ } else {
+ t.Errorf("for key %s got no values, want %v", wantKey, wantValues)
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestSetLabel(t *testing.T) {
+ var testcases = []struct {
+ desc string
+ samples []*Sample
+ setKey string
+ setVal []string
+ wantLabels []map[string][]string
+ }{
+ {
+ desc: "some samples have label already",
+ samples: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"value1", "value2", "value3"},
+ "key2": {"value1"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"value2"},
+ },
+ },
+ },
+ setKey: "key1",
+ setVal: []string{"value1"},
+ wantLabels: []map[string][]string{
+ {"key1": {"value1"}},
+ {"key1": {"value1"}, "key2": {"value1"}},
+ {"key1": {"value1"}},
+ },
+ },
+ {
+ desc: "no samples have labels",
+ samples: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ },
+ },
+ setKey: "key1",
+ setVal: []string{"value1"},
+ wantLabels: []map[string][]string{
+ {"key1": {"value1"}},
+ },
+ },
+ {
+ desc: "all samples have some labels, but not key being added",
+ samples: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key2": {"value2"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key3": {"value3"},
+ },
+ },
+ },
+ setKey: "key1",
+ setVal: []string{"value1"},
+ wantLabels: []map[string][]string{
+ {"key1": {"value1"}, "key2": {"value2"}},
+ {"key1": {"value1"}, "key3": {"value3"}},
+ },
+ },
+ {
+ desc: "all samples have key being added",
+ samples: []*Sample{
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"value1"},
+ },
+ },
+ {
+ Location: []*Location{cpuL[0]},
+ Value: []int64{1000},
+ Label: map[string][]string{
+ "key1": {"value1"},
+ },
+ },
+ },
+ setKey: "key1",
+ setVal: []string{"value1"},
+ wantLabels: []map[string][]string{
+ {"key1": {"value1"}},
+ {"key1": {"value1"}},
+ },
+ },
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.desc, func(t *testing.T) {
+ profile := testProfile1.Copy()
+ profile.Sample = tc.samples
+ profile.SetLabel(tc.setKey, tc.setVal)
+ if got, want := len(profile.Sample), len(tc.wantLabels); got != want {
+ t.Fatalf("got %v samples, want %v samples", got, want)
+ }
+ for i, sample := range profile.Sample {
+ wantLabels := tc.wantLabels[i]
+ if got, want := len(sample.Label), len(wantLabels); got != want {
+ t.Errorf("got %v label keys for sample %v, want %v", got, i, want)
+ continue
+ }
+ for wantKey, wantValues := range wantLabels {
+ if gotValues, ok := sample.Label[wantKey]; ok {
+ if !reflect.DeepEqual(gotValues, wantValues) {
+ t.Errorf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues)
+ }
+ } else {
+ t.Errorf("for key %s got no values, want %v", wantKey, wantValues)
+ }
+ }
+ }
+ })
+ }
+}
+
func TestNumLabelUnits(t *testing.T) {
var tagFilterTests = []struct {
desc string
{
"checksumSHA1": "G9UsR+iruMWxwUefhy+ID+VIFNs=",
"path": "github.com/google/pprof/driver",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "LzGfApA19baVJIbQEqziWpRS3zE=",
"path": "github.com/google/pprof/internal/binutils",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
- "checksumSHA1": "f7aprpcWR7iZX1PJgKBZrpt++XY=",
+ "checksumSHA1": "uoKLYk9VTOx2kYV3hU3vOGm4BX8=",
"path": "github.com/google/pprof/internal/driver",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "IhuyU2pFSHhQxzadDBw1nHbcsrY=",
"path": "github.com/google/pprof/internal/elfexec",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "8vah+aXLGpbtn55JR8MkCAEOMrk=",
"path": "github.com/google/pprof/internal/graph",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "QPWfnT5pEU2jOOb8l8hpiFzQJ7Q=",
"path": "github.com/google/pprof/internal/measurement",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "PWZdFtGfGz/zbQTfvel9737NZdY=",
"path": "github.com/google/pprof/internal/plugin",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "LmDglu/S6vFmgqkxubKDZemFHaY=",
"path": "github.com/google/pprof/internal/proftest",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
- "checksumSHA1": "gdyWnzbjgwmqJ2EN/WAp+QPu7d0=",
+ "checksumSHA1": "qgsLCrPLve6es8A3bA3qv2LPoYk=",
"path": "github.com/google/pprof/internal/report",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "rWdirHgJi1+TdRwv5v3zjgFKcJA=",
"path": "github.com/google/pprof/internal/symbolizer",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "5lS2AF207MVYyjF+82qHkWK2V64=",
"path": "github.com/google/pprof/internal/symbolz",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
- "checksumSHA1": "RvwtpZ+NVtPRCo4EiFvLFFHpoBo=",
+ "checksumSHA1": "JMf63Fn5hz7JFgz6A2aT9DP/bL0=",
"path": "github.com/google/pprof/profile",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "xmqfYca88U2c/I4642r3ps9uIRg=",
"path": "github.com/google/pprof/third_party/d3",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "LzWzD56Trzpq+0hLR00Yw5Gpepw=",
"path": "github.com/google/pprof/third_party/d3flamegraph",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
"checksumSHA1": "738v1E0v0qRW6oAKdCpBEtyVNnY=",
"path": "github.com/google/pprof/third_party/svgpan",
- "revision": "4d67f66d7c9469639518a80f378434bb7e9156b7",
- "revisionTime": "2018-05-09T15:07:09Z"
+ "revision": "1ddc9e21322e23449cb6709652bf3583969ca167",
+ "revisionTime": "2018-05-30T14:24:47Z"
},
{
- "checksumSHA1": "UDJQBwUTuQYEHHJ/D7nPBv1qNqI=",
+ "checksumSHA1": "J5yI4NzHbondzccJmummyJR/kQQ=",
"path": "github.com/ianlancetaylor/demangle",
- "revision": "4883227f66371e02c4948937d3e2be1664d9be38",
- "revisionTime": "2016-09-27T19:13:59Z"
+ "revision": "fc6590592b44fedfff586c5d94647c090fbd6bac",
+ "revisionTime": "2018-05-24T22:59:00Z"
},
{
"path": "golang.org/x/arch/arm/armasm",