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  }