github.com/prysmaticlabs/prysm@v1.4.4/shared/debug/debug.go (about) 1 // Package debug defines useful profiling utils that came originally with go-ethereum. 2 // Copyright 2016 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 package debug 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "io" 24 "net/http" 25 _ "net/http/pprof" // required to serve pprof http endpoints. 26 "os" 27 "os/user" 28 "path/filepath" 29 "runtime" 30 "runtime/debug" 31 "runtime/pprof" 32 "runtime/trace" 33 "strings" 34 "sync" 35 "time" 36 37 "github.com/fjl/memsize/memsizeui" 38 log "github.com/sirupsen/logrus" 39 "github.com/urfave/cli/v2" 40 ) 41 42 // Handler is the global debugging handler. 43 var Handler = new(HandlerT) 44 45 // Memsize is the memsizeui Handler(?). 46 var Memsize memsizeui.Handler 47 var ( 48 // PProfFlag to enable pprof HTTP server. 49 PProfFlag = &cli.BoolFlag{ 50 Name: "pprof", 51 Usage: "Enable the pprof HTTP server", 52 } 53 // PProfPortFlag to specify HTTP server listening port. 54 PProfPortFlag = &cli.IntFlag{ 55 Name: "pprofport", 56 Usage: "pprof HTTP server listening port", 57 Value: 6060, 58 } 59 // PProfAddrFlag to specify HTTP server address. 60 PProfAddrFlag = &cli.StringFlag{ 61 Name: "pprofaddr", 62 Usage: "pprof HTTP server listening interface", 63 Value: "127.0.0.1", 64 } 65 // MemProfileRateFlag to specify the mem profiling rate. 66 MemProfileRateFlag = &cli.IntFlag{ 67 Name: "memprofilerate", 68 Usage: "Turn on memory profiling with the given rate", 69 Value: runtime.MemProfileRate, 70 } 71 // MutexProfileFractionFlag to specify the mutex profiling rate. 72 MutexProfileFractionFlag = &cli.IntFlag{ 73 Name: "mutexprofilefraction", 74 Usage: "Turn on mutex profiling with the given rate", 75 } 76 // BlockProfileRateFlag to specify the block profiling rate. 77 BlockProfileRateFlag = &cli.IntFlag{ 78 Name: "blockprofilerate", 79 Usage: "Turn on block profiling with the given rate", 80 } 81 // CPUProfileFlag to specify where to write the CPU profile. 82 CPUProfileFlag = &cli.StringFlag{ 83 Name: "cpuprofile", 84 Usage: "Write CPU profile to the given file", 85 } 86 // TraceFlag to specify where to write the trace execution profile. 87 TraceFlag = &cli.StringFlag{ 88 Name: "trace", 89 Usage: "Write execution trace to the given file", 90 } 91 ) 92 93 // HandlerT implements the debugging API. 94 // Do not create values of this type, use the one 95 // in the Handler variable instead. 96 type HandlerT struct { 97 mu sync.Mutex 98 cpuW io.WriteCloser 99 cpuFile string 100 traceW io.WriteCloser 101 traceFile string 102 } 103 104 // MemStats returns detailed runtime memory statistics. 105 func (*HandlerT) MemStats() *runtime.MemStats { 106 s := new(runtime.MemStats) 107 runtime.ReadMemStats(s) 108 return s 109 } 110 111 // GcStats returns GC statistics. 112 func (*HandlerT) GcStats() *debug.GCStats { 113 s := new(debug.GCStats) 114 debug.ReadGCStats(s) 115 return s 116 } 117 118 // CPUProfile turns on CPU profiling for nsec seconds and writes 119 // profile data to file. 120 func (h *HandlerT) CPUProfile(file string, nsec uint) error { 121 if err := h.StartCPUProfile(file); err != nil { 122 return err 123 } 124 time.Sleep(time.Duration(nsec) * time.Second) 125 return h.StopCPUProfile() 126 } 127 128 // StartCPUProfile turns on CPU profiling, writing to the given file. 129 func (h *HandlerT) StartCPUProfile(file string) error { 130 h.mu.Lock() 131 defer h.mu.Unlock() 132 if h.cpuW != nil { 133 return errors.New("CPU profiling already in progress") 134 } 135 f, err := os.Create(expandHome(file)) 136 if err != nil { 137 return err 138 } 139 if err := pprof.StartCPUProfile(f); err != nil { 140 if err := f.Close(); err != nil { 141 log.Errorf("Failed to close file: %v", err) 142 } 143 return err 144 } 145 h.cpuW = f 146 h.cpuFile = file 147 log.Info("CPU profiling started", " dump ", h.cpuFile) 148 return nil 149 } 150 151 // StopCPUProfile stops an ongoing CPU profile. 152 func (h *HandlerT) StopCPUProfile() error { 153 h.mu.Lock() 154 defer h.mu.Unlock() 155 pprof.StopCPUProfile() 156 if h.cpuW == nil { 157 return errors.New("CPU profiling not in progress") 158 } 159 log.Info("Done writing CPU profile", " dump ", h.cpuFile) 160 if err := h.cpuW.Close(); err != nil { 161 return err 162 } 163 h.cpuW = nil 164 h.cpuFile = "" 165 return nil 166 } 167 168 // GoTrace turns on tracing for nsec seconds and writes 169 // trace data to file. 170 func (h *HandlerT) GoTrace(file string, nsec uint) error { 171 if err := h.StartGoTrace(file); err != nil { 172 return err 173 } 174 time.Sleep(time.Duration(nsec) * time.Second) 175 return h.StopGoTrace() 176 } 177 178 // StartGoTrace turns on tracing, writing to the given file. 179 func (h *HandlerT) StartGoTrace(file string) error { 180 h.mu.Lock() 181 defer h.mu.Unlock() 182 if h.traceW != nil { 183 return errors.New("trace already in progress") 184 } 185 f, err := os.Create(expandHome(file)) 186 if err != nil { 187 return err 188 } 189 if err := trace.Start(f); err != nil { 190 if err := f.Close(); err != nil { 191 log.Errorf("Failed to close file: %v", err) 192 } 193 return err 194 } 195 h.traceW = f 196 h.traceFile = file 197 log.Info("Go tracing started", "dump", h.traceFile) 198 return nil 199 } 200 201 // StopGoTrace stops an ongoing trace. 202 func (h *HandlerT) StopGoTrace() error { 203 h.mu.Lock() 204 defer h.mu.Unlock() 205 trace.Stop() 206 if h.traceW == nil { 207 return errors.New("trace not in progress") 208 } 209 log.Info("Done writing Go trace", "dump", h.traceFile) 210 if err := h.traceW.Close(); err != nil { 211 return err 212 } 213 h.traceW = nil 214 h.traceFile = "" 215 return nil 216 } 217 218 // BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to 219 // file. It uses a profile rate of 1 for most accurate information. If a different rate is 220 // desired, set the rate and write the profile manually. 221 func (*HandlerT) BlockProfile(file string, nsec uint) error { 222 runtime.SetBlockProfileRate(1) 223 time.Sleep(time.Duration(nsec) * time.Second) 224 defer runtime.SetBlockProfileRate(0) 225 return writeProfile("block", file) 226 } 227 228 // SetBlockProfileRate sets the rate of goroutine block profile data collection. 229 // rate 0 disables block profiling. 230 func (*HandlerT) SetBlockProfileRate(rate int) { 231 runtime.SetBlockProfileRate(rate) 232 } 233 234 // WriteBlockProfile writes a goroutine blocking profile to the given file. 235 func (*HandlerT) WriteBlockProfile(file string) error { 236 return writeProfile("block", file) 237 } 238 239 // MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file. 240 // It uses a profile rate of 1 for most accurate information. If a different rate is 241 // desired, set the rate and write the profile manually. 242 func (*HandlerT) MutexProfile(file string, nsec uint) error { 243 runtime.SetMutexProfileFraction(1) 244 time.Sleep(time.Duration(nsec) * time.Second) 245 defer runtime.SetMutexProfileFraction(0) 246 return writeProfile("mutex", file) 247 } 248 249 // SetMutexProfileFraction sets the rate of mutex profiling. 250 func (*HandlerT) SetMutexProfileFraction(rate int) { 251 runtime.SetMutexProfileFraction(rate) 252 } 253 254 // WriteMutexProfile writes a goroutine blocking profile to the given file. 255 func (*HandlerT) WriteMutexProfile(file string) error { 256 return writeProfile("mutex", file) 257 } 258 259 // WriteMemProfile writes an allocation profile to the given file. 260 // Note that the profiling rate cannot be set through the API, 261 // it must be set on the command line. 262 func (*HandlerT) WriteMemProfile(file string) error { 263 return writeProfile("heap", file) 264 } 265 266 // Stacks returns a printed representation of the stacks of all goroutines. 267 func (*HandlerT) Stacks() string { 268 buf := new(bytes.Buffer) 269 if err := pprof.Lookup("goroutine").WriteTo(buf, 2); err != nil { 270 log.Errorf("Failed to write pprof goroutine stacks: %v", err) 271 } 272 return buf.String() 273 } 274 275 // FreeOSMemory returns unused memory to the OS. 276 func (*HandlerT) FreeOSMemory() { 277 debug.FreeOSMemory() 278 } 279 280 // SetGCPercent sets the garbage collection target percentage. It returns the previous 281 // setting. A negative value disables GC. 282 func (*HandlerT) SetGCPercent(v int) int { 283 return debug.SetGCPercent(v) 284 } 285 286 func writeProfile(name, file string) error { 287 p := pprof.Lookup(name) 288 log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) 289 f, err := os.Create(expandHome(file)) 290 if err != nil { 291 return err 292 } 293 defer func() { 294 if err := f.Close(); err != nil { 295 log.WithError(err).Error("Failed to close pprof profile file.") 296 } 297 }() 298 return p.WriteTo(f, 0) 299 } 300 301 // expands home directory in file paths. 302 // ~someuser/tmp will not be expanded. 303 func expandHome(p string) string { 304 if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 305 home := os.Getenv("HOME") 306 if home == "" { 307 if usr, err := user.Current(); err == nil { 308 home = usr.HomeDir 309 } 310 } 311 if home != "" { 312 p = home + p[1:] 313 } 314 } 315 return filepath.Clean(p) 316 } 317 318 // Debug setup and exit functions. 319 320 // Setup initializes profiling based on the CLI flags. 321 // It should be called as early as possible in the program. 322 func Setup(ctx *cli.Context) error { 323 // profiling, tracing 324 runtime.MemProfileRate = ctx.Int(MemProfileRateFlag.Name) 325 if ctx.IsSet(BlockProfileRateFlag.Name) { 326 runtime.SetBlockProfileRate(ctx.Int(BlockProfileRateFlag.Name)) 327 } 328 if ctx.IsSet(MutexProfileFractionFlag.Name) { 329 runtime.SetMutexProfileFraction(ctx.Int(MutexProfileFractionFlag.Name)) 330 } 331 if traceFile := ctx.String(TraceFlag.Name); traceFile != "" { 332 if err := Handler.StartGoTrace(TraceFlag.Name); err != nil { 333 return err 334 } 335 } 336 if cpuFile := ctx.String(CPUProfileFlag.Name); cpuFile != "" { 337 if err := Handler.StartCPUProfile(cpuFile); err != nil { 338 return err 339 } 340 } 341 342 // pprof server 343 if ctx.Bool(PProfFlag.Name) { 344 address := fmt.Sprintf("%s:%d", ctx.String(PProfAddrFlag.Name), ctx.Int(PProfPortFlag.Name)) 345 startPProf(address) 346 } 347 return nil 348 } 349 350 func startPProf(address string) { 351 http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) 352 log.WithField("addr", fmt.Sprintf("http://%s/debug/pprof", address)).Info("Starting pprof server") 353 go func() { 354 if err := http.ListenAndServe(address, nil); err != nil { 355 log.Error("Failure in running pprof server", "err", err) 356 } 357 }() 358 } 359 360 // Exit stops all running profiles, flushing their output to the 361 // respective file. 362 func Exit(ctx *cli.Context) { 363 if traceFile := ctx.String(TraceFlag.Name); traceFile != "" { 364 if err := Handler.StopGoTrace(); err != nil { 365 log.Errorf("Failed to stop go tracing: %v", err) 366 } 367 } 368 if cpuFile := ctx.String(CPUProfileFlag.Name); cpuFile != "" { 369 if err := Handler.StopCPUProfile(); err != nil { 370 log.Errorf("Failed to stop CPU profiling: %v", err) 371 } 372 } 373 }