github.com/ethereum/go-ethereum@v1.16.1/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/ethereum/go-ethereum/common" 39 "github.com/ethereum/go-ethereum/log" 40 "github.com/hashicorp/go-bexpr" 41 ) 42 43 // Handler is the global debugging handler. 44 var Handler = new(HandlerT) 45 46 // HandlerT implements the debugging API. 47 // Do not create values of this type, use the one 48 // in the Handler variable instead. 49 type HandlerT struct { 50 mu sync.Mutex 51 cpuW io.WriteCloser 52 cpuFile string 53 traceW io.WriteCloser 54 traceFile string 55 } 56 57 // Verbosity sets the log verbosity ceiling. The verbosity of individual packages 58 // and source files can be raised using Vmodule. 59 func (*HandlerT) Verbosity(level int) { 60 glogger.Verbosity(log.FromLegacyLevel(level)) 61 } 62 63 // Vmodule sets the log verbosity pattern. See package log for details on the 64 // pattern syntax. 65 func (*HandlerT) Vmodule(pattern string) error { 66 return glogger.Vmodule(pattern) 67 } 68 69 // MemStats returns detailed runtime memory statistics. 70 func (*HandlerT) MemStats() *runtime.MemStats { 71 s := new(runtime.MemStats) 72 runtime.ReadMemStats(s) 73 return s 74 } 75 76 // GcStats returns GC statistics. 77 func (*HandlerT) GcStats() *debug.GCStats { 78 s := new(debug.GCStats) 79 debug.ReadGCStats(s) 80 return s 81 } 82 83 // CpuProfile turns on CPU profiling for nsec seconds and writes 84 // profile data to file. 85 func (h *HandlerT) CpuProfile(file string, nsec uint) error { 86 if err := h.StartCPUProfile(file); err != nil { 87 return err 88 } 89 time.Sleep(time.Duration(nsec) * time.Second) 90 h.StopCPUProfile() 91 return nil 92 } 93 94 // StartCPUProfile turns on CPU profiling, writing to the given file. 95 func (h *HandlerT) StartCPUProfile(file string) error { 96 h.mu.Lock() 97 defer h.mu.Unlock() 98 if h.cpuW != nil { 99 return errors.New("CPU profiling already in progress") 100 } 101 f, err := os.Create(expandHome(file)) 102 if err != nil { 103 return err 104 } 105 if err := pprof.StartCPUProfile(f); err != nil { 106 f.Close() 107 return err 108 } 109 h.cpuW = f 110 h.cpuFile = file 111 log.Info("CPU profiling started", "dump", h.cpuFile) 112 return nil 113 } 114 115 // StopCPUProfile stops an ongoing CPU profile. 116 func (h *HandlerT) StopCPUProfile() error { 117 h.mu.Lock() 118 defer h.mu.Unlock() 119 pprof.StopCPUProfile() 120 if h.cpuW == nil { 121 return errors.New("CPU profiling not in progress") 122 } 123 log.Info("Done writing CPU profile", "dump", h.cpuFile) 124 h.cpuW.Close() 125 h.cpuW = nil 126 h.cpuFile = "" 127 return nil 128 } 129 130 // GoTrace turns on tracing for nsec seconds and writes 131 // trace data to file. 132 func (h *HandlerT) GoTrace(file string, nsec uint) error { 133 if err := h.StartGoTrace(file); err != nil { 134 return err 135 } 136 time.Sleep(time.Duration(nsec) * time.Second) 137 h.StopGoTrace() 138 return nil 139 } 140 141 // BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to 142 // file. It uses a profile rate of 1 for most accurate information. If a different rate is 143 // desired, set the rate and write the profile manually. 144 func (*HandlerT) BlockProfile(file string, nsec uint) error { 145 runtime.SetBlockProfileRate(1) 146 time.Sleep(time.Duration(nsec) * time.Second) 147 defer runtime.SetBlockProfileRate(0) 148 return writeProfile("block", file) 149 } 150 151 // SetBlockProfileRate sets the rate of goroutine block profile data collection. 152 // rate 0 disables block profiling. 153 func (*HandlerT) SetBlockProfileRate(rate int) { 154 runtime.SetBlockProfileRate(rate) 155 } 156 157 // WriteBlockProfile writes a goroutine blocking profile to the given file. 158 func (*HandlerT) WriteBlockProfile(file string) error { 159 return writeProfile("block", file) 160 } 161 162 // MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file. 163 // It uses a profile rate of 1 for most accurate information. If a different rate is 164 // desired, set the rate and write the profile manually. 165 func (*HandlerT) MutexProfile(file string, nsec uint) error { 166 runtime.SetMutexProfileFraction(1) 167 time.Sleep(time.Duration(nsec) * time.Second) 168 defer runtime.SetMutexProfileFraction(0) 169 return writeProfile("mutex", file) 170 } 171 172 // SetMutexProfileFraction sets the rate of mutex profiling. 173 func (*HandlerT) SetMutexProfileFraction(rate int) { 174 runtime.SetMutexProfileFraction(rate) 175 } 176 177 // WriteMutexProfile writes a goroutine blocking profile to the given file. 178 func (*HandlerT) WriteMutexProfile(file string) error { 179 return writeProfile("mutex", file) 180 } 181 182 // WriteMemProfile writes an allocation profile to the given file. 183 // Note that the profiling rate cannot be set through the API, 184 // it must be set on the command line. 185 func (*HandlerT) WriteMemProfile(file string) error { 186 return writeProfile("heap", file) 187 } 188 189 // Stacks returns a printed representation of the stacks of all goroutines. It 190 // also permits the following optional filters to be used: 191 // - filter: boolean expression of packages to filter for 192 func (*HandlerT) Stacks(filter *string) string { 193 buf := new(bytes.Buffer) 194 pprof.Lookup("goroutine").WriteTo(buf, 2) 195 196 // If any filtering was requested, execute them now 197 if filter != nil && len(*filter) > 0 { 198 expanded := *filter 199 200 // The input filter is a logical expression of package names. Transform 201 // it into a proper boolean expression that can be fed into a parser and 202 // interpreter: 203 // 204 // E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value 205 expanded = regexp.MustCompile(`[:/\.A-Za-z0-9_-]+`).ReplaceAllString(expanded, "`$0` in Value") 206 expanded = regexp.MustCompile("!(`[:/\\.A-Za-z0-9_-]+`)").ReplaceAllString(expanded, "$1 not") 207 expanded = strings.ReplaceAll(expanded, "||", "or") 208 expanded = strings.ReplaceAll(expanded, "&&", "and") 209 log.Info("Expanded filter expression", "filter", *filter, "expanded", expanded) 210 211 expr, err := bexpr.CreateEvaluator(expanded) 212 if err != nil { 213 log.Error("Failed to parse filter expression", "expanded", expanded, "err", err) 214 return "" 215 } 216 // Split the goroutine dump into segments and filter each 217 dump := buf.String() 218 buf.Reset() 219 220 for _, trace := range strings.Split(dump, "\n\n") { 221 if ok, _ := expr.Evaluate(map[string]string{"Value": trace}); ok { 222 buf.WriteString(trace) 223 buf.WriteString("\n\n") 224 } 225 } 226 } 227 return buf.String() 228 } 229 230 // FreeOSMemory forces a garbage collection. 231 func (*HandlerT) FreeOSMemory() { 232 debug.FreeOSMemory() 233 } 234 235 // SetGCPercent sets the garbage collection target percentage. It returns the previous 236 // setting. A negative value disables GC. 237 func (*HandlerT) SetGCPercent(v int) int { 238 return debug.SetGCPercent(v) 239 } 240 241 // SetMemoryLimit sets the GOMEMLIMIT for the process. It returns the previous limit. 242 // Note: 243 // 244 // - The input limit is provided as bytes. A negative input does not adjust the limit 245 // 246 // - A zero limit or a limit that's lower than the amount of memory used by the Go 247 // runtime may cause the garbage collector to run nearly continuously. However, 248 // the application may still make progress. 249 // 250 // - Setting the limit too low will cause Geth to become unresponsive. 251 // 252 // - Geth also allocates memory off-heap, particularly for fastCache and Pebble, 253 // which can be non-trivial (a few gigabytes by default). 254 func (*HandlerT) SetMemoryLimit(limit int64) int64 { 255 log.Info("Setting memory limit", "size", common.PrettyDuration(limit)) 256 return debug.SetMemoryLimit(limit) 257 } 258 259 func writeProfile(name, file string) error { 260 p := pprof.Lookup(name) 261 log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) 262 f, err := os.Create(expandHome(file)) 263 if err != nil { 264 return err 265 } 266 defer f.Close() 267 return p.WriteTo(f, 0) 268 } 269 270 // expands home directory in file paths. 271 // ~someuser/tmp will not be expanded. 272 func expandHome(p string) string { 273 if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 274 home := os.Getenv("HOME") 275 if home == "" { 276 if usr, err := user.Current(); err == nil { 277 home = usr.HomeDir 278 } 279 } 280 if home != "" { 281 p = home + p[1:] 282 } 283 } 284 return filepath.Clean(p) 285 }