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