diff --git a/internal/driver/commands.go b/internal/driver/commands.go index 73f2d89f..32d5c667 100644 --- a/internal/driver/commands.go +++ b/internal/driver/commands.go @@ -199,7 +199,12 @@ var pprofVariables = variables{ "tagignore": &variable{stringKind, "", "", helpText( "Discard samples with tags in range or matched by regexp", "Discard samples that do include a node with a tag matching this regexp.")}, - + "tagshow": &variable{stringKind, "", "", helpText( + "Only consider tags matching this regexp", + "Discard tags that do not match this regexp")}, + "taghide": &variable{stringKind, "", "", helpText( + "Skip tags matching this regexp", + "Discard tags that match this regexp")}, // Heap profile options "divide_by": &variable{floatKind, "1", "", helpText( "Ratio to divide all samples before visualization", diff --git a/internal/driver/driver_focus.go b/internal/driver/driver_focus.go index f0ced087..c60ad815 100644 --- a/internal/driver/driver_focus.go +++ b/internal/driver/driver_focus.go @@ -50,6 +50,12 @@ func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error { warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui) warnNoMatches(tagignore == nil || tim, "TagIgnore", ui) + tagshow, err := compileRegexOption("tagshow", v["tagshow"].value, err) + taghide, err := compileRegexOption("taghide", v["taghide"].value, err) + tns, tnh := prof.FilterTagsByName(tagshow, taghide) + warnNoMatches(tagshow == nil || tns, "TagShow", ui) + warnNoMatches(tagignore == nil || tnh, "TagHide", ui) + if prunefrom != nil { prof.PruneFrom(prunefrom) } diff --git a/internal/symbolizer/symbolizer.go b/internal/symbolizer/symbolizer.go index 27aa1393..712a8f5a 100644 --- a/internal/symbolizer/symbolizer.go +++ b/internal/symbolizer/symbolizer.go @@ -292,7 +292,7 @@ func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force b missingBinaries = true continue } - + // Skip well-known system mappings name := filepath.Base(m.File) if name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") { diff --git a/profile/filter.go b/profile/filter.go index f79595eb..85361e87 100644 --- a/profile/filter.go +++ b/profile/filter.go @@ -73,6 +73,36 @@ func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) return } +// FilterTagsByName filters the tags in a profile and only keeps +// tags that match show and not hide. +func (p *Profile) FilterTagsByName(show, hide *regexp.Regexp) (sm, hm bool) { + matchRemove := func(name string) bool { + matchShow := show == nil || show.MatchString(name) + matchHide := hide != nil && hide.MatchString(name) + + if matchShow { + sm = true + } + if matchHide { + hm = true + } + return !matchShow || matchHide + } + for _, s := range p.Sample { + for lab := range s.Label { + if matchRemove(lab) { + delete(s.Label, lab) + } + } + for lab := range s.NumLabel { + if matchRemove(lab) { + delete(s.NumLabel, lab) + } + } + } + return +} + // matchesName returns whether the location matches the regular // expression. It checks any available function names, file names, and // mapping object filename. diff --git a/profile/profile_test.go b/profile/profile_test.go index da9b9cb6..f4d80175 100644 --- a/profile/profile_test.go +++ b/profile/profile_test.go @@ -507,6 +507,49 @@ func TestFilter(t *testing.T) { } } +func TestTagFilter(t *testing.T) { + // Perform several forms of tag filtering on the test profile. + + type filterTestcase struct { + include, exclude *regexp.Regexp + im, em bool + count int + } + + countTags := func (p *Profile) map[string]bool { + tags := make(map[string]bool) + + for _, s := range p.Sample { + for l := range s.Label { + tags[l] = true + } + for l := range s.NumLabel { + tags[l] = true + } + } + return tags + } + + for tx, tc := range []filterTestcase{ + {nil, nil, true, false, 3}, + {regexp.MustCompile("notfound"), nil, false, false, 0}, + {regexp.MustCompile("key1"), nil, true, false, 1}, + {nil, regexp.MustCompile("key[12]"), true, true, 1}, + } { + prof := testProfile.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) + } + if gem != tc.em { + t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em) + } + if tags := countTags(prof) ; len(tags) != tc.count { + t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count) + } + } +} + // locationHash constructs a string to use as a hashkey for a sample, based on its locations func locationHash(s *Sample) string { var tb string