271 lines
7.4 KiB
Go
271 lines
7.4 KiB
Go
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package profile
|
|
|
|
// Implements methods to filter samples from profiles.
|
|
|
|
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) {
|
|
focusOrIgnore := make(map[uint64]bool)
|
|
hidden := make(map[uint64]bool)
|
|
for _, l := range p.Location {
|
|
if ignore != nil && l.matchesName(ignore) {
|
|
im = true
|
|
focusOrIgnore[l.ID] = false
|
|
} else if focus == nil || l.matchesName(focus) {
|
|
fm = true
|
|
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
|
|
}
|
|
}
|
|
if show != nil {
|
|
l.Line = l.matchedLines(show)
|
|
if len(l.Line) == 0 {
|
|
hidden[l.ID] = true
|
|
} else {
|
|
hnm = true
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
s = append(s, sample)
|
|
}
|
|
}
|
|
p.Sample = s
|
|
|
|
return
|
|
}
|
|
|
|
// ShowFrom drops all stack frames above the highest matching frame and returns
|
|
// whether a match was found. If showFrom is nil it returns false and does not
|
|
// modify the profile.
|
|
//
|
|
// Example: consider a sample with frames [A, B, C, B], where A is the root.
|
|
// ShowFrom(nil) returns false and has frames [A, B, C, B].
|
|
// ShowFrom(A) returns true and has frames [A, B, C, B].
|
|
// ShowFrom(B) returns true and has frames [B, C, B].
|
|
// ShowFrom(C) returns true and has frames [C, B].
|
|
// ShowFrom(D) returns false and drops the sample because no frames remain.
|
|
func (p *Profile) ShowFrom(showFrom *regexp.Regexp) (matched bool) {
|
|
if showFrom == nil {
|
|
return false
|
|
}
|
|
// showFromLocs stores location IDs that matched ShowFrom.
|
|
showFromLocs := make(map[uint64]bool)
|
|
// Apply to locations.
|
|
for _, loc := range p.Location {
|
|
if filterShowFromLocation(loc, showFrom) {
|
|
showFromLocs[loc.ID] = true
|
|
matched = true
|
|
}
|
|
}
|
|
// For all samples, strip locations after the highest matching one.
|
|
s := make([]*Sample, 0, len(p.Sample))
|
|
for _, sample := range p.Sample {
|
|
for i := len(sample.Location) - 1; i >= 0; i-- {
|
|
if showFromLocs[sample.Location[i].ID] {
|
|
sample.Location = sample.Location[:i+1]
|
|
s = append(s, sample)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
p.Sample = s
|
|
return matched
|
|
}
|
|
|
|
// filterShowFromLocation tests a showFrom regex against a location, removes
|
|
// lines after the last match and returns whether a match was found. If the
|
|
// mapping is matched, then all lines are kept.
|
|
func filterShowFromLocation(loc *Location, showFrom *regexp.Regexp) bool {
|
|
if m := loc.Mapping; m != nil && showFrom.MatchString(m.File) {
|
|
return true
|
|
}
|
|
if i := loc.lastMatchedLineIndex(showFrom); i >= 0 {
|
|
loc.Line = loc.Line[:i+1]
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// lastMatchedLineIndex returns the index of the last line 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
|
|
}
|
|
|
|
// 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.
|
|
func (loc *Location) matchesName(re *regexp.Regexp) bool {
|
|
for _, ln := range loc.Line {
|
|
if fn := ln.Function; fn != nil {
|
|
if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if m := loc.Mapping; m != nil && re.MatchString(m.File) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// unmatchedLines returns the lines in the location that do not match
|
|
// the regular expression.
|
|
func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
|
|
if m := loc.Mapping; m != nil && re.MatchString(m.File) {
|
|
return nil
|
|
}
|
|
var lines []Line
|
|
for _, ln := range loc.Line {
|
|
if fn := ln.Function; fn != nil {
|
|
if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
|
|
continue
|
|
}
|
|
}
|
|
lines = append(lines, ln)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// matchedLines returns the lines in the location that match
|
|
// the regular expression.
|
|
func (loc *Location) matchedLines(re *regexp.Regexp) []Line {
|
|
if m := loc.Mapping; m != nil && re.MatchString(m.File) {
|
|
return loc.Line
|
|
}
|
|
var lines []Line
|
|
for _, ln := range loc.Line {
|
|
if fn := ln.Function; fn != nil {
|
|
if !re.MatchString(fn.Name) && !re.MatchString(fn.Filename) {
|
|
continue
|
|
}
|
|
}
|
|
lines = append(lines, ln)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// focusedAndNotIgnored looks up a slice of ids against a map of
|
|
// focused/ignored locations. The map only contains locations that are
|
|
// explicitly focused or ignored. Returns whether there is at least
|
|
// one focused location but no ignored locations.
|
|
func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool {
|
|
var f bool
|
|
for _, loc := range locs {
|
|
if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore {
|
|
if focus {
|
|
// Found focused location. Must keep searching in case there
|
|
// is an ignored one as well.
|
|
f = true
|
|
} else {
|
|
// Found ignored location. Can return false right away.
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
// TagMatch selects tags for filtering
|
|
type TagMatch func(s *Sample) bool
|
|
|
|
// FilterSamplesByTag removes all samples from the profile, except
|
|
// those that match focus and do not match the ignore regular
|
|
// expression.
|
|
func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {
|
|
samples := make([]*Sample, 0, len(p.Sample))
|
|
for _, s := range p.Sample {
|
|
focused, ignored := true, false
|
|
if focus != nil {
|
|
focused = focus(s)
|
|
}
|
|
if ignore != nil {
|
|
ignored = ignore(s)
|
|
}
|
|
fm = fm || focused
|
|
im = im || ignored
|
|
if focused && !ignored {
|
|
samples = append(samples, s)
|
|
}
|
|
}
|
|
p.Sample = samples
|
|
return
|
|
}
|