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