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