github.com/amazechain/amc@v0.1.3/internal/debug/api.go (about)

     1  // Copyright 2023 The AmazeChain Authors
     2  // This file is part of the AmazeChain library.
     3  //
     4  // The AmazeChain 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 AmazeChain 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 AmazeChain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package debug
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"github.com/amazechain/amc/log"
    23  	rpc "github.com/amazechain/amc/modules/rpc/jsonrpc"
    24  	"github.com/hashicorp/go-bexpr"
    25  	"io"
    26  	"os"
    27  	"os/user"
    28  	"path/filepath"
    29  	"regexp"
    30  	"runtime"
    31  	"runtime/debug"
    32  	"runtime/pprof"
    33  	"strings"
    34  	"sync"
    35  	"time"
    36  )
    37  
    38  // Handler is the global debugging handler.
    39  var Handler = new(HandlerT)
    40  
    41  // APIs return the collection of RPC services the tracer package offers.
    42  func APIs() []rpc.API {
    43  	// Append all the local APIs and return
    44  	return []rpc.API{
    45  		{
    46  			Namespace: "debug",
    47  			Service:   Handler,
    48  		},
    49  	}
    50  }
    51  
    52  // HandlerT implements the debugging API.
    53  // Do not create values of this type, use the one
    54  // in the Handler variable instead.
    55  type HandlerT struct {
    56  	mu        sync.Mutex
    57  	cpuW      io.WriteCloser
    58  	cpuFile   string
    59  	traceW    io.WriteCloser
    60  	traceFile string
    61  }
    62  
    63  // MemStats returns detailed runtime memory statistics.
    64  func (*HandlerT) MemStats() *runtime.MemStats {
    65  	s := new(runtime.MemStats)
    66  	runtime.ReadMemStats(s)
    67  	return s
    68  }
    69  
    70  // GcStats returns GC statistics.
    71  func (*HandlerT) GcStats() *debug.GCStats {
    72  	s := new(debug.GCStats)
    73  	debug.ReadGCStats(s)
    74  	return s
    75  }
    76  
    77  // CpuProfile turns on CPU profiling for nsec seconds and writes
    78  // profile data to file.
    79  func (h *HandlerT) CpuProfile(file string, nsec uint) error {
    80  	if err := h.StartCPUProfile(file); err != nil {
    81  		return err
    82  	}
    83  	time.Sleep(time.Duration(nsec) * time.Second)
    84  	h.StopCPUProfile()
    85  	return nil
    86  }
    87  
    88  // StartCPUProfile turns on CPU profiling, writing to the given file.
    89  func (h *HandlerT) StartCPUProfile(file string) error {
    90  	h.mu.Lock()
    91  	defer h.mu.Unlock()
    92  	if h.cpuW != nil {
    93  		return errors.New("CPU profiling already in progress")
    94  	}
    95  	f, err := os.Create(expandHome(file))
    96  	if err != nil {
    97  		return err
    98  	}
    99  	if err := pprof.StartCPUProfile(f); err != nil {
   100  		f.Close()
   101  		return err
   102  	}
   103  	h.cpuW = f
   104  	h.cpuFile = file
   105  	log.Info("CPU profiling started", "dump", h.cpuFile)
   106  	return nil
   107  }
   108  
   109  // StopCPUProfile stops an ongoing CPU profile.
   110  func (h *HandlerT) StopCPUProfile() error {
   111  	h.mu.Lock()
   112  	defer h.mu.Unlock()
   113  	pprof.StopCPUProfile()
   114  	if h.cpuW == nil {
   115  		return errors.New("CPU profiling not in progress")
   116  	}
   117  	log.Info("Done writing CPU profile", "dump", h.cpuFile)
   118  	h.cpuW.Close()
   119  	h.cpuW = nil
   120  	h.cpuFile = ""
   121  	return nil
   122  }
   123  
   124  // GoTrace turns on tracing for nsec seconds and writes
   125  // trace data to file.
   126  func (h *HandlerT) GoTrace(file string, nsec uint) error {
   127  	if err := h.StartGoTrace(file); err != nil {
   128  		return err
   129  	}
   130  	time.Sleep(time.Duration(nsec) * time.Second)
   131  	h.StopGoTrace()
   132  	return nil
   133  }
   134  
   135  // BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
   136  // file. It uses a profile rate of 1 for most accurate information. If a different rate is
   137  // desired, set the rate and write the profile manually.
   138  func (*HandlerT) BlockProfile(file string, nsec uint) error {
   139  	runtime.SetBlockProfileRate(1)
   140  	time.Sleep(time.Duration(nsec) * time.Second)
   141  	defer runtime.SetBlockProfileRate(0)
   142  	return writeProfile("block", file)
   143  }
   144  
   145  // SetBlockProfileRate sets the rate of goroutine block profile data collection.
   146  // rate 0 disables block profiling.
   147  func (*HandlerT) SetBlockProfileRate(rate int) {
   148  	runtime.SetBlockProfileRate(rate)
   149  }
   150  
   151  // WriteBlockProfile writes a goroutine blocking profile to the given file.
   152  func (*HandlerT) WriteBlockProfile(file string) error {
   153  	return writeProfile("block", file)
   154  }
   155  
   156  // MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file.
   157  // It uses a profile rate of 1 for most accurate information. If a different rate is
   158  // desired, set the rate and write the profile manually.
   159  func (*HandlerT) MutexProfile(file string, nsec uint) error {
   160  	runtime.SetMutexProfileFraction(1)
   161  	time.Sleep(time.Duration(nsec) * time.Second)
   162  	defer runtime.SetMutexProfileFraction(0)
   163  	return writeProfile("mutex", file)
   164  }
   165  
   166  // SetMutexProfileFraction sets the rate of mutex profiling.
   167  func (*HandlerT) SetMutexProfileFraction(rate int) {
   168  	runtime.SetMutexProfileFraction(rate)
   169  }
   170  
   171  // WriteMutexProfile writes a goroutine blocking profile to the given file.
   172  func (*HandlerT) WriteMutexProfile(file string) error {
   173  	return writeProfile("mutex", file)
   174  }
   175  
   176  // WriteMemProfile writes an allocation profile to the given file.
   177  // Note that the profiling rate cannot be set through the API,
   178  // it must be set on the command line.
   179  func (*HandlerT) WriteMemProfile(file string) error {
   180  	return writeProfile("heap", file)
   181  }
   182  
   183  // Stacks returns a printed representation of the stacks of all goroutines. It
   184  // also permits the following optional filters to be used:
   185  //   - filter: boolean expression of packages to filter for
   186  func (*HandlerT) Stacks(filter *string) string {
   187  	buf := new(bytes.Buffer)
   188  	pprof.Lookup("goroutine").WriteTo(buf, 2)
   189  
   190  	// If any filtering was requested, execute them now
   191  	if filter != nil && len(*filter) > 0 {
   192  		expanded := *filter
   193  
   194  		// The input filter is a logical expression of package names. Transform
   195  		// it into a proper boolean expression that can be fed into a parser and
   196  		// interpreter:
   197  		//
   198  		// E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value
   199  		expanded = regexp.MustCompile(`[:/\.A-Za-z0-9_-]+`).ReplaceAllString(expanded, "`$0` in Value")
   200  		expanded = regexp.MustCompile("!(`[:/\\.A-Za-z0-9_-]+`)").ReplaceAllString(expanded, "$1 not")
   201  		expanded = strings.ReplaceAll(expanded, "||", "or")
   202  		expanded = strings.ReplaceAll(expanded, "&&", "and")
   203  		log.Info("Expanded filter expression", "filter", *filter, "expanded", expanded)
   204  
   205  		expr, err := bexpr.CreateEvaluator(expanded)
   206  		if err != nil {
   207  			log.Error("Failed to parse filter expression", "expanded", expanded, "err", err)
   208  			return ""
   209  		}
   210  		// Split the goroutine dump into segments and filter each
   211  		dump := buf.String()
   212  		buf.Reset()
   213  
   214  		for _, trace := range strings.Split(dump, "\n\n") {
   215  			if ok, _ := expr.Evaluate(map[string]string{"Value": trace}); ok {
   216  				buf.WriteString(trace)
   217  				buf.WriteString("\n\n")
   218  			}
   219  		}
   220  	}
   221  	return buf.String()
   222  }
   223  
   224  // FreeOSMemory forces a garbage collection.
   225  func (*HandlerT) FreeOSMemory() {
   226  	debug.FreeOSMemory()
   227  }
   228  
   229  // SetGCPercent sets the garbage collection target percentage. It returns the previous
   230  // setting. A negative value disables GC.
   231  func (*HandlerT) SetGCPercent(v int) int {
   232  	return debug.SetGCPercent(v)
   233  }
   234  
   235  func writeProfile(name, file string) error {
   236  	p := pprof.Lookup(name)
   237  	log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file)
   238  	f, err := os.Create(expandHome(file))
   239  	if err != nil {
   240  		return err
   241  	}
   242  	defer f.Close()
   243  	return p.WriteTo(f, 0)
   244  }
   245  
   246  // expands home directory in file paths.
   247  // ~someuser/tmp will not be expanded.
   248  func expandHome(p string) string {
   249  	if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
   250  		home := os.Getenv("HOME")
   251  		if home == "" {
   252  			if usr, err := user.Current(); err == nil {
   253  				home = usr.HomeDir
   254  			}
   255  		}
   256  		if home != "" {
   257  			p = home + p[1:]
   258  		}
   259  	}
   260  	return filepath.Clean(p)
   261  }