Source file src/cmd/vendor/github.com/google/pprof/profile/prune.go

     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  // Implements methods to remove frames from profiles.
    16  
    17  package profile
    18  
    19  import (
    20  	"fmt"
    21  	"regexp"
    22  	"slices"
    23  	"strings"
    24  )
    25  
    26  var (
    27  	reservedNames = []string{"(anonymous namespace)", "operator()"}
    28  	bracketRx     = func() *regexp.Regexp {
    29  		var quotedNames []string
    30  		for _, name := range append(reservedNames, "(") {
    31  			quotedNames = append(quotedNames, regexp.QuoteMeta(name))
    32  		}
    33  		return regexp.MustCompile(strings.Join(quotedNames, "|"))
    34  	}()
    35  )
    36  
    37  // simplifyFunc does some primitive simplification of function names.
    38  func simplifyFunc(f string) string {
    39  	// Account for leading '.' on the PPC ELF v1 ABI.
    40  	funcName := strings.TrimPrefix(f, ".")
    41  	// Account for unsimplified names -- try  to remove the argument list by trimming
    42  	// starting from the first '(', but skipping reserved names that have '('.
    43  	for _, ind := range bracketRx.FindAllStringSubmatchIndex(funcName, -1) {
    44  		foundReserved := slices.Contains(reservedNames, funcName[ind[0]:ind[1]])
    45  		if !foundReserved {
    46  			funcName = funcName[:ind[0]]
    47  			break
    48  		}
    49  	}
    50  	return funcName
    51  }
    52  
    53  // Prune removes all nodes beneath a node matching dropRx, and not
    54  // matching keepRx. If the root node of a Sample matches, the sample
    55  // will have an empty stack.
    56  func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {
    57  	prune := make(map[uint64]bool)
    58  	pruneBeneath := make(map[uint64]bool)
    59  
    60  	// simplifyFunc can be expensive, so cache results.
    61  	// Note that the same function name can be encountered many times due
    62  	// different lines and addresses in the same function.
    63  	pruneCache := map[string]bool{} // Map from function to whether or not to prune
    64  	pruneFromHere := func(s string) bool {
    65  		if r, ok := pruneCache[s]; ok {
    66  			return r
    67  		}
    68  		funcName := simplifyFunc(s)
    69  		if dropRx.MatchString(funcName) {
    70  			if keepRx == nil || !keepRx.MatchString(funcName) {
    71  				pruneCache[s] = true
    72  				return true
    73  			}
    74  		}
    75  		pruneCache[s] = false
    76  		return false
    77  	}
    78  
    79  	for _, loc := range p.Location {
    80  		var i int
    81  		for i = len(loc.Line) - 1; i >= 0; i-- {
    82  			if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
    83  				if pruneFromHere(fn.Name) {
    84  					break
    85  				}
    86  			}
    87  		}
    88  
    89  		if i >= 0 {
    90  			// Found matching entry to prune.
    91  			pruneBeneath[loc.ID] = true
    92  
    93  			// Remove the matching location.
    94  			if i == len(loc.Line)-1 {
    95  				// Matched the top entry: prune the whole location.
    96  				prune[loc.ID] = true
    97  			} else {
    98  				loc.Line = loc.Line[i+1:]
    99  			}
   100  		}
   101  	}
   102  
   103  	// Prune locs from each Sample
   104  	for _, sample := range p.Sample {
   105  		// Scan from the root to the leaves to find the prune location.
   106  		// Do not prune frames before the first user frame, to avoid
   107  		// pruning everything.
   108  		foundUser := false
   109  		for i := len(sample.Location) - 1; i >= 0; i-- {
   110  			id := sample.Location[i].ID
   111  			if !prune[id] && !pruneBeneath[id] {
   112  				foundUser = true
   113  				continue
   114  			}
   115  			if !foundUser {
   116  				continue
   117  			}
   118  			if prune[id] {
   119  				sample.Location = sample.Location[i+1:]
   120  				break
   121  			}
   122  			if pruneBeneath[id] {
   123  				sample.Location = sample.Location[i:]
   124  				break
   125  			}
   126  		}
   127  	}
   128  }
   129  
   130  // RemoveUninteresting prunes and elides profiles using built-in
   131  // tables of uninteresting function names.
   132  func (p *Profile) RemoveUninteresting() error {
   133  	var keep, drop *regexp.Regexp
   134  	var err error
   135  
   136  	if p.DropFrames != "" {
   137  		if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil {
   138  			return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err)
   139  		}
   140  		if p.KeepFrames != "" {
   141  			if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil {
   142  				return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err)
   143  			}
   144  		}
   145  		p.Prune(drop, keep)
   146  	}
   147  	return nil
   148  }
   149  
   150  // PruneFrom removes all nodes beneath the lowest node matching dropRx, not including itself.
   151  //
   152  // Please see the example below to understand this method as well as
   153  // the difference from Prune method.
   154  //
   155  // A sample contains Location of [A,B,C,B,D] where D is the top frame and there's no inline.
   156  //
   157  // PruneFrom(A) returns [A,B,C,B,D] because there's no node beneath A.
   158  // Prune(A, nil) returns [B,C,B,D] by removing A itself.
   159  //
   160  // PruneFrom(B) returns [B,C,B,D] by removing all nodes beneath the first B when scanning from the bottom.
   161  // Prune(B, nil) returns [D] because a matching node is found by scanning from the root.
   162  func (p *Profile) PruneFrom(dropRx *regexp.Regexp) {
   163  	pruneBeneath := make(map[uint64]bool)
   164  
   165  	for _, loc := range p.Location {
   166  		for i := 0; i < len(loc.Line); i++ {
   167  			if fn := loc.Line[i].Function; fn != nil && fn.Name != "" {
   168  				funcName := simplifyFunc(fn.Name)
   169  				if dropRx.MatchString(funcName) {
   170  					// Found matching entry to prune.
   171  					pruneBeneath[loc.ID] = true
   172  					loc.Line = loc.Line[i:]
   173  					break
   174  				}
   175  			}
   176  		}
   177  	}
   178  
   179  	// Prune locs from each Sample
   180  	for _, sample := range p.Sample {
   181  		// Scan from the bottom leaf to the root to find the prune location.
   182  		for i, loc := range sample.Location {
   183  			if pruneBeneath[loc.ID] {
   184  				sample.Location = sample.Location[i:]
   185  				break
   186  			}
   187  		}
   188  	}
   189  }
   190  

View as plain text