github.com/MetalBlockchain/subnet-evm@v0.4.9/internal/debug/api.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2016 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 // Package debug interfaces Go runtime debugging facilities. 28 // This package is mostly glue code making these facilities available 29 // through the CLI and RPC subsystem. If you want to use them from Go code, 30 // use package runtime instead. 31 package debug 32 33 import ( 34 "bytes" 35 "errors" 36 "io" 37 "os" 38 "os/user" 39 "path/filepath" 40 "regexp" 41 "runtime" 42 "runtime/debug" 43 "runtime/pprof" 44 "strings" 45 "sync" 46 "time" 47 48 "github.com/ethereum/go-ethereum/log" 49 "github.com/hashicorp/go-bexpr" 50 ) 51 52 // Handler is the global debugging handler. 53 var Handler = new(HandlerT) 54 55 // HandlerT implements the debugging API. 56 // Do not create values of this type, use the one 57 // in the Handler variable instead. 58 type HandlerT struct { 59 mu sync.Mutex 60 cpuW io.WriteCloser 61 cpuFile string 62 traceW io.WriteCloser 63 traceFile string 64 } 65 66 // Verbosity sets the log verbosity ceiling. The verbosity of individual packages 67 // and source files can be raised using Vmodule. 68 func (*HandlerT) Verbosity(level int) { 69 glogger.Verbosity(log.Lvl(level)) 70 } 71 72 // Vmodule sets the log verbosity pattern. See package log for details on the 73 // pattern syntax. 74 func (*HandlerT) Vmodule(pattern string) error { 75 return glogger.Vmodule(pattern) 76 } 77 78 // BacktraceAt sets the log backtrace location. See package log for details on 79 // the pattern syntax. 80 func (*HandlerT) BacktraceAt(location string) error { 81 return glogger.BacktraceAt(location) 82 } 83 84 // MemStats returns detailed runtime memory statistics. 85 func (*HandlerT) MemStats() *runtime.MemStats { 86 s := new(runtime.MemStats) 87 runtime.ReadMemStats(s) 88 return s 89 } 90 91 // GcStats returns GC statistics. 92 func (*HandlerT) GcStats() *debug.GCStats { 93 s := new(debug.GCStats) 94 debug.ReadGCStats(s) 95 return s 96 } 97 98 // CpuProfile turns on CPU profiling for nsec seconds and writes 99 // profile data to file. 100 func (h *HandlerT) CpuProfile(file string, nsec uint) error { 101 if err := h.StartCPUProfile(file); err != nil { 102 return err 103 } 104 time.Sleep(time.Duration(nsec) * time.Second) 105 h.StopCPUProfile() 106 return nil 107 } 108 109 // StartCPUProfile turns on CPU profiling, writing to the given file. 110 func (h *HandlerT) StartCPUProfile(file string) error { 111 h.mu.Lock() 112 defer h.mu.Unlock() 113 if h.cpuW != nil { 114 return errors.New("CPU profiling already in progress") 115 } 116 f, err := os.Create(expandHome(file)) 117 if err != nil { 118 return err 119 } 120 if err := pprof.StartCPUProfile(f); err != nil { 121 f.Close() 122 return err 123 } 124 h.cpuW = f 125 h.cpuFile = file 126 log.Info("CPU profiling started", "dump", h.cpuFile) 127 return nil 128 } 129 130 // StopCPUProfile stops an ongoing CPU profile. 131 func (h *HandlerT) StopCPUProfile() error { 132 h.mu.Lock() 133 defer h.mu.Unlock() 134 pprof.StopCPUProfile() 135 if h.cpuW == nil { 136 return errors.New("CPU profiling not in progress") 137 } 138 log.Info("Done writing CPU profile", "dump", h.cpuFile) 139 h.cpuW.Close() 140 h.cpuW = nil 141 h.cpuFile = "" 142 return nil 143 } 144 145 // GoTrace turns on tracing for nsec seconds and writes 146 // trace data to file. 147 func (h *HandlerT) GoTrace(file string, nsec uint) error { 148 if err := h.StartGoTrace(file); err != nil { 149 return err 150 } 151 time.Sleep(time.Duration(nsec) * time.Second) 152 h.StopGoTrace() 153 return nil 154 } 155 156 // BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to 157 // file. It uses a profile rate of 1 for most accurate information. If a different rate is 158 // desired, set the rate and write the profile manually. 159 func (*HandlerT) BlockProfile(file string, nsec uint) error { 160 runtime.SetBlockProfileRate(1) 161 time.Sleep(time.Duration(nsec) * time.Second) 162 defer runtime.SetBlockProfileRate(0) 163 return writeProfile("block", file) 164 } 165 166 // SetBlockProfileRate sets the rate of goroutine block profile data collection. 167 // rate 0 disables block profiling. 168 func (*HandlerT) SetBlockProfileRate(rate int) { 169 runtime.SetBlockProfileRate(rate) 170 } 171 172 // WriteBlockProfile writes a goroutine blocking profile to the given file. 173 func (*HandlerT) WriteBlockProfile(file string) error { 174 return writeProfile("block", file) 175 } 176 177 // MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file. 178 // It uses a profile rate of 1 for most accurate information. If a different rate is 179 // desired, set the rate and write the profile manually. 180 func (*HandlerT) MutexProfile(file string, nsec uint) error { 181 runtime.SetMutexProfileFraction(1) 182 time.Sleep(time.Duration(nsec) * time.Second) 183 defer runtime.SetMutexProfileFraction(0) 184 return writeProfile("mutex", file) 185 } 186 187 // SetMutexProfileFraction sets the rate of mutex profiling. 188 func (*HandlerT) SetMutexProfileFraction(rate int) { 189 runtime.SetMutexProfileFraction(rate) 190 } 191 192 // WriteMutexProfile writes a goroutine blocking profile to the given file. 193 func (*HandlerT) WriteMutexProfile(file string) error { 194 return writeProfile("mutex", file) 195 } 196 197 // WriteMemProfile writes an allocation profile to the given file. 198 // Note that the profiling rate cannot be set through the API, 199 // it must be set on the command line. 200 func (*HandlerT) WriteMemProfile(file string) error { 201 return writeProfile("heap", file) 202 } 203 204 // Stacks returns a printed representation of the stacks of all goroutines. It 205 // also permits the following optional filters to be used: 206 // - filter: boolean expression of packages to filter for 207 func (*HandlerT) Stacks(filter *string) string { 208 buf := new(bytes.Buffer) 209 pprof.Lookup("goroutine").WriteTo(buf, 2) 210 211 // If any filtering was requested, execute them now 212 if filter != nil && len(*filter) > 0 { 213 expanded := *filter 214 215 // The input filter is a logical expression of package names. Transform 216 // it into a proper boolean expression that can be fed into a parser and 217 // interpreter: 218 // 219 // E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value 220 expanded = regexp.MustCompile(`[:/\.A-Za-z0-9_-]+`).ReplaceAllString(expanded, "`$0` in Value") 221 expanded = regexp.MustCompile("!(`[:/\\.A-Za-z0-9_-]+`)").ReplaceAllString(expanded, "$1 not") 222 expanded = strings.ReplaceAll(expanded, "||", "or") 223 expanded = strings.ReplaceAll(expanded, "&&", "and") 224 log.Info("Expanded filter expression", "filter", *filter, "expanded", expanded) 225 226 expr, err := bexpr.CreateEvaluator(expanded) 227 if err != nil { 228 log.Error("Failed to parse filter expression", "expanded", expanded, "err", err) 229 return "" 230 } 231 // Split the goroutine dump into segments and filter each 232 dump := buf.String() 233 buf.Reset() 234 235 for _, trace := range strings.Split(dump, "\n\n") { 236 if ok, _ := expr.Evaluate(map[string]string{"Value": trace}); ok { 237 buf.WriteString(trace) 238 buf.WriteString("\n\n") 239 } 240 } 241 } 242 return buf.String() 243 } 244 245 // FreeOSMemory forces a garbage collection. 246 func (*HandlerT) FreeOSMemory() { 247 debug.FreeOSMemory() 248 } 249 250 // SetGCPercent sets the garbage collection target percentage. It returns the previous 251 // setting. A negative value disables GC. 252 func (*HandlerT) SetGCPercent(v int) int { 253 return debug.SetGCPercent(v) 254 } 255 256 func writeProfile(name, file string) error { 257 p := pprof.Lookup(name) 258 log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) 259 f, err := os.Create(expandHome(file)) 260 if err != nil { 261 return err 262 } 263 defer f.Close() 264 return p.WriteTo(f, 0) 265 } 266 267 // expands home directory in file paths. 268 // ~someuser/tmp will not be expanded. 269 func expandHome(p string) string { 270 if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 271 home := os.Getenv("HOME") 272 if home == "" { 273 if usr, err := user.Current(); err == nil { 274 home = usr.HomeDir 275 } 276 } 277 if home != "" { 278 p = home + p[1:] 279 } 280 } 281 return filepath.Clean(p) 282 }