@@ -27,6 +27,7 @@ import (
27
27
"os"
28
28
"os/user"
29
29
"path/filepath"
30
+ "regexp"
30
31
"runtime"
31
32
"runtime/debug"
32
33
"runtime/pprof"
@@ -35,6 +36,7 @@ import (
35
36
"time"
36
37
37
38
"github.com/ethereum/go-ethereum/log"
39
+ "github.com/hashicorp/go-bexpr"
38
40
)
39
41
40
42
// Handler is the global debugging handler.
@@ -189,10 +191,44 @@ func (*HandlerT) WriteMemProfile(file string) error {
189
191
return writeProfile ("heap" , file )
190
192
}
191
193
192
- // Stacks returns a printed representation of the stacks of all goroutines.
193
- func (* HandlerT ) Stacks () string {
194
+ // Stacks returns a printed representation of the stacks of all goroutines. It
195
+ // also permits the following optional filters to be used:
196
+ // - filter: boolean expression of packages to filter for
197
+ func (* HandlerT ) Stacks (filter * string ) string {
194
198
buf := new (bytes.Buffer )
195
199
pprof .Lookup ("goroutine" ).WriteTo (buf , 2 )
200
+
201
+ // If any filtering was requested, execute them now
202
+ if filter != nil && len (* filter ) > 0 {
203
+ expanded := * filter
204
+
205
+ // The input filter is a logical expression of package names. Transform
206
+ // it into a proper boolean expression that can be fed into a parser and
207
+ // interpreter:
208
+ //
209
+ // E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value
210
+ expanded = regexp .MustCompile ("[:/\\ .A-Za-z0-9_-]+" ).ReplaceAllString (expanded , "`$0` in Value" )
211
+ expanded = regexp .MustCompile ("!(`[:/\\ .A-Za-z0-9_-]+`)" ).ReplaceAllString (expanded , "$1 not" )
212
+ expanded = strings .Replace (expanded , "||" , "or" , - 1 )
213
+ expanded = strings .Replace (expanded , "&&" , "and" , - 1 )
214
+ log .Info ("Expanded filter expression" , "filter" , * filter , "expanded" , expanded )
215
+
216
+ expr , err := bexpr .CreateEvaluator (expanded )
217
+ if err != nil {
218
+ log .Error ("Failed to parse filter expression" , "expanded" , expanded , "err" , err )
219
+ return ""
220
+ }
221
+ // Split the goroutine dump into segments and filter each
222
+ dump := buf .String ()
223
+ buf .Reset ()
224
+
225
+ for _ , trace := range strings .Split (dump , "\n \n " ) {
226
+ if ok , _ := expr .Evaluate (map [string ]string {"Value" : trace }); ok {
227
+ buf .WriteString (trace )
228
+ buf .WriteString ("\n \n " )
229
+ }
230
+ }
231
+ }
196
232
return buf .String ()
197
233
}
198
234
0 commit comments