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 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. 91func (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. 122func 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. 135func (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. 148func (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. 179func (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. 195func (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. 213func (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. 233func 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 251type 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. 256func (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