Source file src/cmd/vendor/github.com/google/pprof/profile/filter.go

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package profile
    16  
    17  // Implements methods to filter samples from profiles.
    18  
    19  import "regexp"
    20  
    21  // FilterSamplesByName filters the samples in a profile and only keeps
    22  // samples where at least one frame matches focus but none match ignore.
    23  // Returns true is the corresponding regexp matched at least one sample.
    24  func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) (fm, im, hm, hnm bool) {
    25  	if focus == nil && ignore == nil && hide == nil && show == nil {
    26  		fm = true // Missing focus implies a match
    27  		return
    28  	}
    29  	focusOrIgnore := make(map[uint64]bool)
    30  	hidden := make(map[uint64]bool)
    31  	for _, l := range p.Location {
    32  		if ignore != nil && l.matchesName(ignore) {
    33  			im = true
    34  			focusOrIgnore[l.ID] = false
    35  		} else if focus == nil || l.matchesName(focus) {
    36  			fm = true
    37  			focusOrIgnore[l.ID] = true
    38  		}
    39  
    40  		if hide != nil && l.matchesName(hide) {
    41  			hm = true
    42  			l.Line = l.unmatchedLines(hide)
    43  			if len(l.Line) == 0 {
    44  				hidden[l.ID] = true
    45  			}
    46  		}
    47  		if show != nil {
    48  			l.Line = l.matchedLines(show)
    49  			if len(l.Line) == 0 {
    50  				hidden[l.ID] = true
    51  			} else {
    52  				hnm = true
    53  			}
    54  		}
    55  	}
    56  
    57  	s := make([]*Sample, 0, len(p.Sample))
    58  	for _, sample := range p.Sample {
    59  		if focusedAndNotIgnored(sample.Location, focusOrIgnore) {
    60  			if len(hidden) > 0 {
    61  				var locs []*Location
    62  				for _, loc := range sample.Location {
    63  					if !hidden[loc.ID] {
    64  						locs = append(locs, loc)
    65  					}
    66  				}
    67  				if len(locs) == 0 {
    68  					// Remove sample with no locations (by not adding it to s).
    69  					continue
    70  				}
    71  				sample.Location = locs
    72  			}
    73  			s = append(s, sample)
    74  		}
    75  	}
    76  	p.Sample = s
    77  
    78  	return
    79  }
    80  
    81  // ShowFrom drops all stack frames above the highest matching frame and returns
    82  // whether a match was found. If showFrom is nil it returns false and does not
    83  // modify the profile.
    84  //
    85  // Example: consider a sample with frames [A, B, C, B], where A is the root.
    86  // ShowFrom(nil) returns false and has frames [A, B, C, B].
    87  // ShowFrom(A) returns true and has frames [A, B, C, B].
    88  // ShowFrom(B) returns true and has frames [B, C, B].
    89  // ShowFrom(C) returns true and has frames [C, B].
    90  // ShowFrom(D) returns false and drops the sample because no frames remain.
    91  func (p *Profile) ShowFrom(showFrom *regexp.Regexp) (matched bool) {
    92  	if showFrom == nil {
    93  		return false
    94  	}
    95  	// showFromLocs stores location IDs that matched ShowFrom.
    96  	showFromLocs := make(map[uint64]bool)
    97  	// Apply to locations.
    98  	for _, loc := range p.Location {
    99  		if filterShowFromLocation(loc, showFrom) {
   100  			showFromLocs[loc.ID] = true
   101  			matched = true
   102  		}
   103  	}
   104  	// For all samples, strip locations after the highest matching one.
   105  	s := make([]*Sample, 0, len(p.Sample))
   106  	for _, sample := range p.Sample {
   107  		for i := len(sample.Location) - 1; i >= 0; i-- {
   108  			if showFromLocs[sample.Location[i].ID] {
   109  				sample.Location = sample.Location[:i+1]
   110  				s = append(s, sample)
   111  				break
   112  			}
   113  		}
   114  	}
   115  	p.Sample = s
   116  	return matched
   117  }
   118  
   119  // filterShowFromLocation tests a showFrom regex against a location, removes
   120  // lines after the last match and returns whether a match was found. If the
   121  // mapping is matched, then all lines are kept.
   122  func filterShowFromLocation(loc *Location, showFrom *regexp.Regexp) bool {
   123  	if m := loc.Mapping; m != nil && showFrom.MatchString(m.File) {
   124  		return true
   125  	}
   126  	if i := loc.lastMatchedLineIndex(showFrom); i >= 0 {
   127  		loc.Line = loc.Line[:i+1]
   128  		return true
   129  	}
   130  	return false
   131  }
   132  
   133  // lastMatchedLineIndex returns the index of the last line that matches a regex,
   134  // or -1 if no match is found.
   135  func (loc *Location) lastMatchedLineIndex(re *regexp.Regexp) int {
   136  	for i := len(loc.Line) - 1; i >= 0; i-- {
   137  		if fn := loc.Line[i].Function; fn != nil {
   138  			if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
   139  				return i
   140  			}
   141  		}
   142  	}
   143  	return -1
   144  }
   145  
   146  // FilterTagsByName filters the tags in a profile and only keeps
   147  // tags that match show and not hide.
   148  func (p *Profile) FilterTagsByName(show, hide *regexp.Regexp) (sm, hm bool) {
   149  	matchRemove := func(name string) bool {
   150  		matchShow := show == nil || show.MatchString(name)
   151  		matchHide := hide != nil && hide.MatchString(name)
   152  
   153  		if matchShow {
   154  			sm = true
   155  		}
   156  		if matchHide {
   157  			hm = true
   158  		}
   159  		return !matchShow || matchHide
   160  	}
   161  	for _, s := range p.Sample {
   162  		for lab := range s.Label {
   163  			if matchRemove(lab) {
   164  				delete(s.Label, lab)
   165  			}
   166  		}
   167  		for lab := range s.NumLabel {
   168  			if matchRemove(lab) {
   169  				delete(s.NumLabel, lab)
   170  			}
   171  		}
   172  	}
   173  	return
   174  }
   175  
   176  // matchesName returns whether the location matches the regular
   177  // expression. It checks any available function names, file names, and
   178  // mapping object filename.
   179  func (loc *Location) matchesName(re *regexp.Regexp) bool {
   180  	for _, ln := range loc.Line {
   181  		if fn := ln.Function; fn != nil {
   182  			if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
   183  				return true
   184  			}
   185  		}
   186  	}
   187  	if m := loc.Mapping; m != nil && re.MatchString(m.File) {
   188  		return true
   189  	}
   190  	return false
   191  }
   192  
   193  // unmatchedLines returns the lines in the location that do not match
   194  // the regular expression.
   195  func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
   196  	if m := loc.Mapping; m != nil && re.MatchString(m.File) {
   197  		return nil
   198  	}
   199  	var lines []Line
   200  	for _, ln := range loc.Line {
   201  		if fn := ln.Function; fn != nil {
   202  			if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
   203  				continue
   204  			}
   205  		}
   206  		lines = append(lines, ln)
   207  	}
   208  	return lines
   209  }
   210  
   211  // matchedLines returns the lines in the location that match
   212  // the regular expression.
   213  func (loc *Location) matchedLines(re *regexp.Regexp) []Line {
   214  	if m := loc.Mapping; m != nil && re.MatchString(m.File) {
   215  		return loc.Line
   216  	}
   217  	var lines []Line
   218  	for _, ln := range loc.Line {
   219  		if fn := ln.Function; fn != nil {
   220  			if !re.MatchString(fn.Name) && !re.MatchString(fn.Filename) {
   221  				continue
   222  			}
   223  		}
   224  		lines = append(lines, ln)
   225  	}
   226  	return lines
   227  }
   228  
   229  // focusedAndNotIgnored looks up a slice of ids against a map of
   230  // focused/ignored locations. The map only contains locations that are
   231  // explicitly focused or ignored. Returns whether there is at least
   232  // one focused location but no ignored locations.
   233  func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool {
   234  	var f bool
   235  	for _, loc := range locs {
   236  		if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore {
   237  			if focus {
   238  				// Found focused location. Must keep searching in case there
   239  				// is an ignored one as well.
   240  				f = true
   241  			} else {
   242  				// Found ignored location. Can return false right away.
   243  				return false
   244  			}
   245  		}
   246  	}
   247  	return f
   248  }
   249  
   250  // TagMatch selects tags for filtering
   251  type TagMatch func(s *Sample) bool
   252  
   253  // FilterSamplesByTag removes all samples from the profile, except
   254  // those that match focus and do not match the ignore regular
   255  // expression.
   256  func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {
   257  	samples := make([]*Sample, 0, len(p.Sample))
   258  	for _, s := range p.Sample {
   259  		focused, ignored := true, false
   260  		if focus != nil {
   261  			focused = focus(s)
   262  		}
   263  		if ignore != nil {
   264  			ignored = ignore(s)
   265  		}
   266  		fm = fm || focused
   267  		im = im || ignored
   268  		if focused && !ignored {
   269  			samples = append(samples, s)
   270  		}
   271  	}
   272  	p.Sample = samples
   273  	return
   274  }
   275  

View as plain text