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  }