internal: support optional filter expression for debug.stacks (#23605)

* internal: support optional filter expression for debug.stacks

* internal/debug: fix string regexp

* internal/debug: support searching for line numbers too
This commit is contained in:
Péter Szilágyi
2021-09-20 16:29:07 +03:00
committed by GitHub
parent 62e3b83af6
commit e28f713ada
4 changed files with 47 additions and 3 deletions

View File

@ -27,6 +27,7 @@ import (
"os"
"os/user"
"path/filepath"
"regexp"
"runtime"
"runtime/debug"
"runtime/pprof"
@ -35,6 +36,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/hashicorp/go-bexpr"
)
// Handler is the global debugging handler.
@ -189,10 +191,44 @@ func (*HandlerT) WriteMemProfile(file string) error {
return writeProfile("heap", file)
}
// Stacks returns a printed representation of the stacks of all goroutines.
func (*HandlerT) Stacks() string {
// Stacks returns a printed representation of the stacks of all goroutines. It
// also permits the following optional filters to be used:
// - filter: boolean expression of packages to filter for
func (*HandlerT) Stacks(filter *string) string {
buf := new(bytes.Buffer)
pprof.Lookup("goroutine").WriteTo(buf, 2)
// If any filtering was requested, execute them now
if filter != nil && len(*filter) > 0 {
expanded := *filter
// The input filter is a logical expression of package names. Transform
// it into a proper boolean expression that can be fed into a parser and
// interpreter:
//
// E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value
expanded = regexp.MustCompile("[:/\\.A-Za-z0-9_-]+").ReplaceAllString(expanded, "`$0` in Value")
expanded = regexp.MustCompile("!(`[:/\\.A-Za-z0-9_-]+`)").ReplaceAllString(expanded, "$1 not")
expanded = strings.Replace(expanded, "||", "or", -1)
expanded = strings.Replace(expanded, "&&", "and", -1)
log.Info("Expanded filter expression", "filter", *filter, "expanded", expanded)
expr, err := bexpr.CreateEvaluator(expanded)
if err != nil {
log.Error("Failed to parse filter expression", "expanded", expanded, "err", err)
return ""
}
// Split the goroutine dump into segments and filter each
dump := buf.String()
buf.Reset()
for _, trace := range strings.Split(dump, "\n\n") {
if ok, _ := expr.Evaluate(map[string]string{"Value": trace}); ok {
buf.WriteString(trace)
buf.WriteString("\n\n")
}
}
}
return buf.String()
}