Home | History | Annotate | Download | only in driver
      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