@ -27,6 +27,7 @@ import (
"os"
"os"
"os/user"
"os/user"
"path/filepath"
"path/filepath"
"regexp"
"runtime"
"runtime"
"runtime/debug"
"runtime/debug"
"runtime/pprof"
"runtime/pprof"
@ -35,6 +36,7 @@ import (
"time"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/hashicorp/go-bexpr"
)
)
// Handler is the global debugging handler.
// Handler is the global debugging handler.
@ -189,10 +191,44 @@ func (*HandlerT) WriteMemProfile(file string) error {
return writeProfile ( "heap" , file )
return writeProfile ( "heap" , file )
}
}
// Stacks returns a printed representation of the stacks of all goroutines.
// Stacks returns a printed representation of the stacks of all goroutines. It
func ( * HandlerT ) Stacks ( ) string {
// 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 )
buf := new ( bytes . Buffer )
pprof . Lookup ( "goroutine" ) . WriteTo ( buf , 2 )
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 ( )
return buf . String ( )
}
}