github.com/klaytn/klaytn@v1.12.1/api/debug/api.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 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 // 18 // This file is derived from internal/debug/api.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package debug 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "net/http" 28 "os" 29 "os/user" 30 "path/filepath" 31 "runtime" 32 "runtime/debug" 33 "runtime/pprof" 34 "strings" 35 "sync" 36 "time" 37 38 "github.com/klaytn/klaytn/log" 39 "github.com/klaytn/klaytn/metrics/exp" 40 "github.com/klaytn/klaytn/params" 41 "github.com/rcrowley/go-metrics" 42 ) 43 44 // Handler is the global debugging handler. 45 var ( 46 Handler = new(HandlerT) 47 logger = log.NewModuleLogger(log.APIDebug) 48 ) 49 50 // HandlerT implements the debugging API. 51 // Do not create values of this type, use the one 52 // in the Handler variable instead. 53 type HandlerT struct { 54 mu sync.Mutex 55 cpuW io.WriteCloser 56 cpuFile string 57 memFile string 58 traceW io.WriteCloser 59 traceFile string 60 61 // For the pprof http server 62 handlerInited bool 63 pprofServer *http.Server 64 65 logDir string // log directory path 66 vmLogFile *os.File // a file descriptor of the vmlog output file 67 } 68 69 // Verbosity sets the log verbosity ceiling. The verbosity of individual packages 70 // and source files can be raised using Vmodule. 71 func (*HandlerT) Verbosity(level int) error { 72 return log.ChangeGlobalLogLevel(glogger, log.Lvl(level)) 73 } 74 75 // VerbosityByName sets the verbosity of log module with given name. 76 // Please note that VerbosityByName only works with zapLogger. 77 func (*HandlerT) VerbosityByName(mn string, level int) error { 78 return log.ChangeLogLevelWithName(mn, log.Lvl(level)) 79 } 80 81 // VerbosityByID sets the verbosity of log module with given ModuleID. 82 // Please note that VerbosityByID only works with zapLogger. 83 func (*HandlerT) VerbosityByID(mi int, level int) error { 84 return log.ChangeLogLevelWithID(log.ModuleID(mi), log.Lvl(level)) 85 } 86 87 // Vmodule sets the log verbosity pattern. See package log for details on the 88 // pattern syntax. 89 func (*HandlerT) Vmodule(pattern string) error { 90 return glogger.Vmodule(pattern) 91 } 92 93 // BacktraceAt sets the log backtrace location. See package log for details on 94 // the pattern syntax. 95 func (*HandlerT) BacktraceAt(location string) error { 96 return glogger.BacktraceAt(location) 97 } 98 99 // MemStats returns detailed runtime memory statistics. 100 func (*HandlerT) MemStats() *runtime.MemStats { 101 s := new(runtime.MemStats) 102 runtime.ReadMemStats(s) 103 return s 104 } 105 106 // GcStats returns GC statistics. 107 func (*HandlerT) GcStats() *debug.GCStats { 108 s := new(debug.GCStats) 109 debug.ReadGCStats(s) 110 return s 111 } 112 113 // StartPProf starts the pprof server. 114 func (h *HandlerT) StartPProf(ptrAddr *string, ptrPort *int) error { 115 // Set the default server address and port if they are not set 116 var ( 117 address string 118 port int 119 ) 120 if ptrAddr == nil || *ptrAddr == "" { 121 address = pprofAddrFlag.Value 122 } else { 123 address = *ptrAddr 124 } 125 126 if ptrPort == nil || *ptrPort == 0 { 127 port = pprofPortFlag.Value 128 } else { 129 port = *ptrPort 130 } 131 132 h.mu.Lock() 133 defer h.mu.Unlock() 134 135 if h.pprofServer != nil { 136 return errors.New("pprof server is already running") 137 } 138 139 serverAddr := fmt.Sprintf("%s:%d", address, port) 140 httpServer := &http.Server{Addr: serverAddr} 141 142 if !h.handlerInited { 143 // Hook go-metrics into expvar on any /debug/metrics request, load all vars 144 // from the registry into expvar, and execute regular expvar handler. 145 exp.Exp(metrics.DefaultRegistry) 146 http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) 147 h.handlerInited = true 148 } 149 150 logger.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", serverAddr)) 151 go func(handle *HandlerT) { 152 if err := httpServer.ListenAndServe(); err != nil { 153 if err == http.ErrServerClosed { 154 logger.Info("pprof server is closed") 155 } else { 156 logger.Error("Failure in running pprof server", "err", err) 157 } 158 } 159 h.mu.Lock() 160 h.pprofServer = nil 161 h.mu.Unlock() 162 }(h) 163 164 h.pprofServer = httpServer 165 166 return nil 167 } 168 169 // StopPProf stops the pprof server. 170 func (h *HandlerT) StopPProf() error { 171 h.mu.Lock() 172 defer h.mu.Unlock() 173 174 if h.pprofServer == nil { 175 return errors.New("pprof server is not running") 176 } 177 178 logger.Info("Shutting down pprof server") 179 h.pprofServer.Close() 180 181 return nil 182 } 183 184 // IsPProfRunning returns true if the pprof HTTP server is running and false otherwise. 185 func (h *HandlerT) IsPProfRunning() bool { 186 h.mu.Lock() 187 defer h.mu.Unlock() 188 return h.pprofServer != nil 189 } 190 191 // CpuProfile turns on CPU profiling for nsec seconds and writes 192 // profile data to file. 193 func (h *HandlerT) CpuProfile(file string, nsec uint) error { 194 if err := h.StartCPUProfile(file); err != nil { 195 return err 196 } 197 time.Sleep(time.Duration(nsec) * time.Second) 198 h.StopCPUProfile() 199 return nil 200 } 201 202 // StartCPUProfile turns on CPU profiling, writing to the given file. 203 func (h *HandlerT) StartCPUProfile(file string) error { 204 h.mu.Lock() 205 defer h.mu.Unlock() 206 if h.cpuW != nil { 207 return errors.New("CPU profiling already in progress") 208 } 209 f, err := os.Create(expandHome(file)) 210 if err != nil { 211 return err 212 } 213 if err := pprof.StartCPUProfile(f); err != nil { 214 f.Close() 215 return err 216 } 217 h.cpuW = f 218 h.cpuFile = file 219 logger.Info("CPU profiling started", "dump", h.cpuFile) 220 return nil 221 } 222 223 // StopCPUProfile stops an ongoing CPU profile. 224 func (h *HandlerT) StopCPUProfile() error { 225 h.mu.Lock() 226 defer h.mu.Unlock() 227 pprof.StopCPUProfile() 228 if h.cpuW == nil { 229 return errors.New("CPU profiling not in progress") 230 } 231 logger.Info("Done writing CPU profile", "dump", h.cpuFile) 232 h.cpuW.Close() 233 h.cpuW = nil 234 h.cpuFile = "" 235 return nil 236 } 237 238 // GoTrace turns on tracing for nsec seconds and writes 239 // trace data to file. 240 func (h *HandlerT) GoTrace(file string, nsec uint) error { 241 if err := h.StartGoTrace(file); err != nil { 242 return err 243 } 244 time.Sleep(time.Duration(nsec) * time.Second) 245 h.StopGoTrace() 246 return nil 247 } 248 249 // BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to 250 // file. It uses a profile rate of 1 for most accurate information. If a different rate is 251 // desired, set the rate and write the profile manually. 252 func (*HandlerT) BlockProfile(file string, nsec uint) error { 253 runtime.SetBlockProfileRate(1) 254 time.Sleep(time.Duration(nsec) * time.Second) 255 defer runtime.SetBlockProfileRate(0) 256 return writeProfile("block", file) 257 } 258 259 // SetBlockProfileRate sets the rate of goroutine block profile data collection. 260 // rate 0 disables block profiling. 261 func (*HandlerT) SetBlockProfileRate(rate int) { 262 runtime.SetBlockProfileRate(rate) 263 } 264 265 // WriteBlockProfile writes a goroutine blocking profile to the given file. 266 func (*HandlerT) WriteBlockProfile(file string) error { 267 return writeProfile("block", file) 268 } 269 270 // MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file. 271 // It uses a profile rate of 1 for most accurate information. If a different rate is 272 // desired, set the rate and write the profile manually. 273 func (*HandlerT) MutexProfile(file string, nsec uint) error { 274 runtime.SetMutexProfileFraction(1) 275 time.Sleep(time.Duration(nsec) * time.Second) 276 defer runtime.SetMutexProfileFraction(0) 277 return writeProfile("mutex", file) 278 } 279 280 // SetMutexProfileFraction sets the rate of mutex profiling. 281 func (*HandlerT) SetMutexProfileFraction(rate int) { 282 runtime.SetMutexProfileFraction(rate) 283 } 284 285 // WriteMutexProfile writes a goroutine blocking profile to the given file. 286 func (*HandlerT) WriteMutexProfile(file string) error { 287 return writeProfile("mutex", file) 288 } 289 290 // WriteMemProfile writes an allocation profile to the given file. 291 // Note that the profiling rate cannot be set through the API, 292 // it must be set on the command line. 293 func (*HandlerT) WriteMemProfile(file string) error { 294 return writeProfile("heap", file) 295 } 296 297 // Stacks returns a printed representation of the stacks of all goroutines. 298 func (*HandlerT) Stacks() string { 299 buf := make([]byte, 1024*1024) 300 buf = buf[:runtime.Stack(buf, true)] 301 return string(buf) 302 } 303 304 // FreeOSMemory returns unused memory to the OS. 305 func (*HandlerT) FreeOSMemory() { 306 debug.FreeOSMemory() 307 } 308 309 // SetGCPercent sets the garbage collection target percentage. It returns the previous 310 // setting. A negative value disables GC. 311 func (*HandlerT) SetGCPercent(v int) int { 312 return debug.SetGCPercent(v) 313 } 314 315 func writeProfile(name, file string) error { 316 p := pprof.Lookup(name) 317 logger.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) 318 f, err := os.Create(expandHome(file)) 319 if err != nil { 320 return err 321 } 322 defer f.Close() 323 return p.WriteTo(f, 0) 324 } 325 326 // expands home directory in file paths. 327 // ~someuser/tmp will not be expanded. 328 func expandHome(p string) string { 329 if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 330 home := os.Getenv("HOME") 331 if home == "" { 332 if usr, err := user.Current(); err == nil { 333 home = usr.HomeDir 334 } 335 } 336 if home != "" { 337 p = home + p[1:] 338 } 339 } 340 return filepath.Clean(p) 341 } 342 343 // WriteVMLog writes msg to a vmlog output file. 344 func (h *HandlerT) WriteVMLog(msg string) { 345 h.mu.Lock() 346 defer h.mu.Unlock() 347 if h.vmLogFile != nil { 348 if _, err := h.vmLogFile.WriteString(msg + "\n"); err != nil { 349 // Since vmlog is a debugging feature, write failure can be treated as a warning. 350 logger.Warn("Failed to write to a vmlog file", "msg", msg, "err", err) 351 } 352 } 353 } 354 355 // openVMLogFile opens a file for vmlog output as the append mode. 356 func (h *HandlerT) openVMLogFile() { 357 var err error 358 filename := filepath.Join(h.logDir, "vm.log") 359 Handler.vmLogFile, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) 360 if err != nil { 361 logger.Warn("Failed to open a file", "filename", filename, "err", err) 362 } 363 } 364 365 func vmLogTargetToString(target int) string { 366 switch target { 367 case 0: 368 return "no output" 369 case params.VMLogToFile: 370 return "file" 371 case params.VMLogToStdout: 372 return "stdout" 373 case params.VMLogToAll: 374 return "both file and stdout" 375 default: 376 return "" 377 } 378 } 379 380 // SetVMLogTarget sets the output target of vmlog. 381 func (h *HandlerT) SetVMLogTarget(target int) (string, error) { 382 if target < 0 || target > params.VMLogToAll { 383 return vmLogTargetToString(params.VMLogTarget), fmt.Errorf("target should be between 0 and %d", params.VMLogToAll) 384 } 385 386 h.mu.Lock() 387 defer h.mu.Unlock() 388 if (target & params.VMLogToFile) != 0 { 389 if h.vmLogFile == nil { 390 h.openVMLogFile() 391 } 392 } else { 393 if h.vmLogFile != nil { 394 if err := Handler.vmLogFile.Close(); err != nil { 395 logger.Warn("Failed to close the vmlog file", "err", err) 396 } 397 Handler.vmLogFile = nil 398 } 399 } 400 401 params.VMLogTarget = target 402 return vmLogTargetToString(target), nil 403 }