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 driver 16 17 import ( 18 "fmt" 19 "regexp" 20 "strconv" 21 "strings" 22 23 "github.com/google/pprof/internal/measurement" 24 "github.com/google/pprof/internal/plugin" 25 "github.com/google/pprof/profile" 26 ) 27 28 var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)") 29 30 // applyFocus filters samples based on the focus/ignore options 31 func applyFocus(prof *profile.Profile, numLabelUnits map[string]string, v variables, ui plugin.UI) error { 32 focus, err := compileRegexOption("focus", v["focus"].value, nil) 33 ignore, err := compileRegexOption("ignore", v["ignore"].value, err) 34 hide, err := compileRegexOption("hide", v["hide"].value, err) 35 show, err := compileRegexOption("show", v["show"].value, err) 36 tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, numLabelUnits, ui, err) 37 tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, numLabelUnits, ui, err) 38 prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err) 39 if err != nil { 40 return err 41 } 42 43 fm, im, hm, hnm := prof.FilterSamplesByName(focus, ignore, hide, show) 44 warnNoMatches(focus == nil || fm, "Focus", ui) 45 warnNoMatches(ignore == nil || im, "Ignore", ui) 46 warnNoMatches(hide == nil || hm, "Hide", ui) 47 warnNoMatches(show == nil || hnm, "Show", ui) 48 49 tfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore) 50 warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui) 51 warnNoMatches(tagignore == nil || tim, "TagIgnore", ui) 52 53 tagshow, err := compileRegexOption("tagshow", v["tagshow"].value, err) 54 taghide, err := compileRegexOption("taghide", v["taghide"].value, err) 55 tns, tnh := prof.FilterTagsByName(tagshow, taghide) 56 warnNoMatches(tagshow == nil || tns, "TagShow", ui) 57 warnNoMatches(tagignore == nil || tnh, "TagHide", ui) 58 59 if prunefrom != nil { 60 prof.PruneFrom(prunefrom) 61 } 62 return err 63 } 64 65 func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) { 66 if value == "" || err != nil { 67 return nil, err 68 } 69 rx, err := regexp.Compile(value) 70 if err != nil { 71 return nil, fmt.Errorf("parsing %s regexp: %v", name, err) 72 } 73 return rx, nil 74 } 75 76 func compileTagFilter(name, value string, numLabelUnits map[string]string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) { 77 if value == "" || err != nil { 78 return nil, err 79 } 80 81 tagValuePair := strings.SplitN(value, "=", 2) 82 var wantKey string 83 if len(tagValuePair) == 2 { 84 wantKey = tagValuePair[0] 85 value = tagValuePair[1] 86 } 87 88 if numFilter := parseTagFilterRange(value); numFilter != nil { 89 ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp") 90 labelFilter := func(vals []int64, unit string) bool { 91 for _, val := range vals { 92 if numFilter(val, unit) { 93 return true 94 } 95 } 96 return false 97 } 98 numLabelUnit := func(key string) string { 99 return numLabelUnits[key] 100 } 101 if wantKey == "" { 102 return func(s *profile.Sample) bool { 103 for key, vals := range s.NumLabel { 104 if labelFilter(vals, numLabelUnit(key)) { 105 return true 106 } 107 } 108 return false 109 }, nil 110 } 111 return func(s *profile.Sample) bool { 112 if vals, ok := s.NumLabel[wantKey]; ok { 113 return labelFilter(vals, numLabelUnit(wantKey)) 114 } 115 return false 116 }, nil 117 } 118 119 var rfx []*regexp.Regexp 120 for _, tagf := range strings.Split(value, ",") { 121 fx, err := regexp.Compile(tagf) 122 if err != nil { 123 return nil, fmt.Errorf("parsing %s regexp: %v", name, err) 124 } 125 rfx = append(rfx, fx) 126 } 127 if wantKey == "" { 128 return func(s *profile.Sample) bool { 129 matchedrx: 130 for _, rx := range rfx { 131 for key, vals := range s.Label { 132 for _, val := range vals { 133 // TODO: Match against val, not key:val in future 134 if rx.MatchString(key + ":" + val) { 135 continue matchedrx 136 } 137 } 138 } 139 return false 140 } 141 return true 142 }, nil 143 } 144 return func(s *profile.Sample) bool { 145 if vals, ok := s.Label[wantKey]; ok { 146 for _, rx := range rfx { 147 for _, val := range vals { 148 if rx.MatchString(val) { 149 return true 150 } 151 } 152 } 153 } 154 return false 155 }, nil 156 } 157 158 // parseTagFilterRange returns a function to checks if a value is 159 // contained on the range described by a string. It can recognize 160 // strings of the form: 161 // "32kb" -- matches values == 32kb 162 // ":64kb" -- matches values <= 64kb 163 // "4mb:" -- matches values >= 4mb 164 // "12kb:64mb" -- matches values between 12kb and 64mb (both included). 165 func parseTagFilterRange(filter string) func(int64, string) bool { 166 ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2) 167 if len(ranges) == 0 { 168 return nil // No ranges were identified 169 } 170 v, err := strconv.ParseInt(ranges[0][1], 10, 64) 171 if err != nil { 172 panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err)) 173 } 174 scaledValue, unit := measurement.Scale(v, ranges[0][2], ranges[0][2]) 175 if len(ranges) == 1 { 176 switch match := ranges[0][0]; filter { 177 case match: 178 return func(v int64, u string) bool { 179 sv, su := measurement.Scale(v, u, unit) 180 return su == unit && sv == scaledValue 181 } 182 case match + ":": 183 return func(v int64, u string) bool { 184 sv, su := measurement.Scale(v, u, unit) 185 return su == unit && sv >= scaledValue 186 } 187 case ":" + match: 188 return func(v int64, u string) bool { 189 sv, su := measurement.Scale(v, u, unit) 190 return su == unit && sv <= scaledValue 191 } 192 } 193 return nil 194 } 195 if filter != ranges[0][0]+":"+ranges[1][0] { 196 return nil 197 } 198 if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil { 199 panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err)) 200 } 201 scaledValue2, unit2 := measurement.Scale(v, ranges[1][2], unit) 202 if unit != unit2 { 203 return nil 204 } 205 return func(v int64, u string) bool { 206 sv, su := measurement.Scale(v, u, unit) 207 return su == unit && sv >= scaledValue && sv <= scaledValue2 208 } 209 } 210 211 func warnNoMatches(match bool, option string, ui plugin.UI) { 212 if !match { 213 ui.PrintErr(option + " expression matched no samples") 214 } 215 } 216