Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ShowFrom and HideFrom filters to profile filter lib #361

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/driver/driver_focus.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variab
return err
}

fm, im, hm, hnm := prof.FilterSamplesByName(focus, ignore, hide, show)
fm, im, _, _, hnm, hm := prof.FilterSamplesByName(focus, ignore, nil, nil, show, hide)
warnNoMatches(focus == nil || fm, "Focus", ui)
warnNoMatches(ignore == nil || im, "Ignore", ui)
warnNoMatches(hide == nil || hm, "Hide", ui)
Expand Down
198 changes: 170 additions & 28 deletions profile/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ import "regexp"
// FilterSamplesByName filters the samples in a profile and only keeps
// samples where at least one frame matches focus but none match ignore.
// Returns true is the corresponding regexp matched at least one sample.
func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) (fm, im, hm, hnm bool) {
func (p *Profile) FilterSamplesByName(focus, ignore, showFrom, hideFrom, show, hide *regexp.Regexp) (fm, im, sfm, hfm, sm, hm bool) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has become too complicated now. I would make it a separate function, something like ShowHideFrom and put it to prune.go, next to PruneFrom, since the functionality is rather related to pruning. Having the function separate would make the code and the tests more focused and easier to read.

if focus == nil && ignore == nil && showFrom == nil && hideFrom == nil && show == nil && hide == nil {
fm = true
return
}

focusOrIgnore := make(map[uint64]bool)
hidden := make(map[uint64]bool)
// showFromLocs stores location IDs that matched ShowFrom.
showFromLocs := make(map[uint64]bool)
// hideFromLocs stores location IDs that matched HideFrom.
hideFromLocs := make(map[uint64]bool)

for _, l := range p.Location {
if ignore != nil && l.matchesName(ignore) {
im = true
Expand All @@ -33,47 +43,158 @@ func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp)
focusOrIgnore[l.ID] = true
}

if hide != nil && l.matchesName(hide) {
hm = true
l.Line = l.unmatchedLines(hide)
if len(l.Line) == 0 {
hidden[l.ID] = true
drop, sfml, hfml, sml, hml := filterLocation(l, showFrom, hideFrom, show, hide)
if drop {
hidden[l.ID] = true
}
if sfml {
sfm = true
showFromLocs[l.ID] = true
}
if hfml {
hfm = true
hideFromLocs[l.ID] = true
}
sm = sm || sml
hm = hm || hml
}

mayHideFrames := len(hidden) != 0 || showFrom != nil || len(hideFromLocs) != 0

filteredSamples := make([]*Sample, 0, len(p.Sample))
for _, sample := range p.Sample {
if !focusedAndNotIgnored(sample.Location, focusOrIgnore) {
continue
}

if !mayHideFrames {
filteredSamples = append(filteredSamples, sample)
continue
}

var pathIndex int
if hideFrom != nil {
if i := lastLocationIndex(sample.Location, hideFromLocs); i >= 0 {
pathIndex = i
}
}
if show != nil {
l.Line = l.matchedLines(show)
if len(l.Line) == 0 {
hidden[l.ID] = true

var lastPathIndex = len(sample.Location)
if showFrom != nil {
if i := lastLocationIndex(sample.Location, showFromLocs); i >= 0 {
lastPathIndex = i + 1
} else {
hnm = true
lastPathIndex = 0
}
}

var filteredPath []*Location
for ; pathIndex < lastPathIndex; pathIndex++ {
location := sample.Location[pathIndex]
if !hidden[location.ID] {
filteredPath = append(filteredPath, location)
}
}

if len(filteredPath) != 0 {
sample.Location = filteredPath
filteredSamples = append(filteredSamples, sample)
}
}
p.Sample = filteredSamples
return
}

// filterLocation prunes a location's lines based on filters and returns
// whether each filter was matched, and whether or not to drop the location.
func filterLocation(location *Location, showFrom, hideFrom, show, hide *regexp.Regexp) (drop, sfml, hfml, sml, hml bool) {
if len(location.Line) == 0 {
return filterEmptyLocation(location, showFrom, hideFrom, show, hide)
}

s := make([]*Sample, 0, len(p.Sample))
for _, sample := range p.Sample {
if focusedAndNotIgnored(sample.Location, focusOrIgnore) {
if len(hidden) > 0 {
var locs []*Location
for _, loc := range sample.Location {
if !hidden[loc.ID] {
locs = append(locs, loc)
}
}
if len(locs) == 0 {
// Remove sample with no locations (by not adding it to s).
continue
}
sample.Location = locs
// TODO: These filter regexes are tested twice, first to determine the return
// values, and again to prune the lines. This happens because we want to test
// each filter against all lines before any pruning is done, to avoid missing
// any matches that were removed by a previous filter.
if show != nil && location.matchesName(show) {
sml = true
}
if hide != nil && location.matchesName(hide) {
hml = true
}

showFromIndex := len(location.Line)
if showFrom != nil {
if location.matchesMapping(showFrom) {
sfml = true
} else if i := location.lastMatchedLineIndex(showFrom); i >= 0 {
sfml = true
showFromIndex = i + 1
}
}

hideFromIndex := 0
if hideFrom != nil {
if location.matchesMapping(hideFrom) {
hfml = true
hideFromIndex = showFromIndex
} else if i := location.lastMatchedLineIndex(hideFrom); i >= 0 {
hfml = true
hideFromIndex = i + 1
if hideFromIndex > showFromIndex {
hideFromIndex = showFromIndex
}
s = append(s, sample)
}
}
p.Sample = s

location.Line = location.Line[hideFromIndex:showFromIndex]
if show != nil {
location.Line = location.matchedLines(show)
}
if hide != nil {
location.Line = location.unmatchedLines(hide)
}

drop = len(location.Line) == 0
return
}

// filterEmptyLocation handles the special case of locations with zero lines.
// the filters are only tested against the mapping, and the location is only
// dropped if explicitly matched by the filters.
func filterEmptyLocation(location *Location, showFrom, hideFrom, show, hide *regexp.Regexp) (drop, sfml, hfml, sml, hml bool) {
if showFrom != nil && location.matchesMapping(showFrom) {
sfml = true
}
if hideFrom != nil && location.matchesMapping(hideFrom) {
drop = true
hfml = true
}
if show != nil {
if location.matchesMapping(show) {
sml = true
} else {
drop = true
}
}
if hide != nil && location.matchesMapping(hide) {
drop = true
hml = true
}
return
}

// lastLocationIndex returns the index of the last location who's ID is in the
// map.
func lastLocationIndex(path []*Location, matchedIDs map[uint64]bool) int {
for i := len(path) - 1; i >= 0; i-- {
if matchedIDs[path[i].ID] {
return i
}
}
return -1
}

// 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) {
Expand Down Expand Up @@ -121,6 +242,27 @@ func (loc *Location) matchesName(re *regexp.Regexp) bool {
return false
}

// matchesMapping returns whether a regex matches the location's mapping.
func (loc *Location) matchesMapping(re *regexp.Regexp) bool {
if m := loc.Mapping; m != nil && re != nil && re.MatchString(m.File) {
return true
}
return false
}

// lastMatchedLineIndex returns the last line index that matches a regex, or
// -1 if no match is found.
func (loc *Location) lastMatchedLineIndex(re *regexp.Regexp) int {
for i := len(loc.Line) - 1; i >= 0; i-- {
if fn := loc.Line[i].Function; fn != nil {
if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
return i
}
}
}
return -1
}

// unmatchedLines returns the lines in the location that do not match
// the regular expression.
func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
Expand Down
Loading