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  }