From e28f713ada67af2380bd7052cb85b2e255a62a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 20 Sep 2021 16:29:07 +0300 Subject: [PATCH] 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 --- go.mod | 1 + go.sum | 6 ++++++ internal/debug/api.go | 40 +++++++++++++++++++++++++++++++++++-- internal/web3ext/web3ext.go | 3 ++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ce19afc850..b63368085a 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/google/uuid v1.1.5 github.com/gorilla/websocket v1.4.2 github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 + github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.2.0 diff --git a/go.sum b/go.sum index 3d8a04236f..0890b8c5e0 100644 --- a/go.sum +++ b/go.sum @@ -216,6 +216,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= @@ -307,6 +309,10 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= diff --git a/internal/debug/api.go b/internal/debug/api.go index efd8626776..b1f815085c 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -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() } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 927dba1897..fe15cb0509 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -278,7 +278,8 @@ web3._extend({ new web3._extend.Method({ name: 'stacks', call: 'debug_stacks', - params: 0, + params: 1, + inputFormatter: [null], outputFormatter: console.log }), new web3._extend.Method({