github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/internal/cli/debug_pprof.go (about)

     1  package cli
     2  
     3  // Based on https://github.com/hashicorp/nomad/blob/main/command/operator_debug.go
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"google.golang.org/grpc"
    11  
    12  	"github.com/ethereum/go-ethereum/internal/cli/flagset"
    13  	"github.com/ethereum/go-ethereum/internal/cli/server/proto"
    14  )
    15  
    16  type DebugPprofCommand struct {
    17  	*Meta2
    18  
    19  	seconds   uint64
    20  	output    string
    21  	skiptrace bool
    22  }
    23  
    24  func (p *DebugPprofCommand) MarkDown() string {
    25  	items := []string{
    26  		"# Debug Pprof",
    27  		"The ```debug pprof <enode>``` command will create an archive containing bor pprof traces.",
    28  		p.Flags().MarkDown(),
    29  	}
    30  
    31  	return strings.Join(items, "\n\n")
    32  }
    33  
    34  // Help implements the cli.Command interface
    35  func (d *DebugPprofCommand) Help() string {
    36  	return `Usage: bor debug
    37  
    38    Build an archive containing Bor pprof traces
    39  
    40    ` + d.Flags().Help()
    41  }
    42  
    43  func (d *DebugPprofCommand) Flags() *flagset.Flagset {
    44  	flags := d.NewFlagSet("debug")
    45  
    46  	flags.Uint64Flag(&flagset.Uint64Flag{
    47  		Name:    "seconds",
    48  		Usage:   "seconds to profile",
    49  		Value:   &d.seconds,
    50  		Default: 2,
    51  	})
    52  	flags.StringFlag(&flagset.StringFlag{
    53  		Name:  "output",
    54  		Value: &d.output,
    55  		Usage: "Output directory",
    56  	})
    57  
    58  	// Trace profiles can be expensive and take too much size (for grpc).
    59  	// This flag will help in making it optional.
    60  	flags.BoolFlag(&flagset.BoolFlag{
    61  		Name:    "skiptrace",
    62  		Value:   &d.skiptrace,
    63  		Usage:   "Skip running the trace",
    64  		Default: false,
    65  	})
    66  
    67  	return flags
    68  }
    69  
    70  // Synopsis implements the cli.Command interface
    71  func (d *DebugPprofCommand) Synopsis() string {
    72  	return "Build an archive containing Bor pprof traces"
    73  }
    74  
    75  // Run implements the cli.Command interface
    76  func (d *DebugPprofCommand) Run(args []string) int {
    77  	flags := d.Flags()
    78  	if err := flags.Parse(args); err != nil {
    79  		d.UI.Error(err.Error())
    80  		return 1
    81  	}
    82  
    83  	clt, err := d.BorConn()
    84  	if err != nil {
    85  		d.UI.Error(err.Error())
    86  		return 1
    87  	}
    88  
    89  	dEnv := &debugEnv{
    90  		output: d.output,
    91  		prefix: "bor-debug-",
    92  	}
    93  	if err := dEnv.init(); err != nil {
    94  		d.UI.Error(err.Error())
    95  		return 1
    96  	}
    97  
    98  	d.UI.Output("Starting debugger...")
    99  	d.UI.Output("")
   100  
   101  	pprofProfile := func(ctx context.Context, profile string, filename string) error {
   102  		req := &proto.DebugPprofRequest{
   103  			Seconds: int64(d.seconds),
   104  		}
   105  
   106  		switch profile {
   107  		case "cpu":
   108  			req.Type = proto.DebugPprofRequest_CPU
   109  		case "trace":
   110  			req.Type = proto.DebugPprofRequest_TRACE
   111  		default:
   112  			req.Type = proto.DebugPprofRequest_LOOKUP
   113  			req.Profile = profile
   114  		}
   115  
   116  		stream, err := clt.DebugPprof(ctx, req, grpc.MaxCallRecvMsgSize(1024*1024*1024))
   117  
   118  		if err != nil {
   119  			return err
   120  		}
   121  
   122  		if err := dEnv.writeFromStream(filename+".prof", stream); err != nil {
   123  			return err
   124  		}
   125  
   126  		return nil
   127  	}
   128  
   129  	ctx, cancelFn := context.WithCancel(context.Background())
   130  	trapSignal(cancelFn)
   131  
   132  	// Only take cpu and heap profiles by default
   133  	profiles := map[string]string{
   134  		"heap":  "heap",
   135  		"cpu":   "cpu",
   136  		"mutex": "mutex",
   137  	}
   138  
   139  	if !d.skiptrace {
   140  		profiles["trace"] = "trace"
   141  	}
   142  
   143  	for profile, filename := range profiles {
   144  		if err := pprofProfile(ctx, profile, filename); err != nil {
   145  			d.UI.Error(fmt.Sprintf("Error creating profile '%s': %v", profile, err))
   146  			return 1
   147  		}
   148  	}
   149  
   150  	// append the status
   151  	{
   152  		statusResp, err := clt.Status(ctx, &proto.StatusRequest{})
   153  		if err != nil {
   154  			d.UI.Output(fmt.Sprintf("Failed to get status: %v", err))
   155  			return 1
   156  		}
   157  		if err := dEnv.writeJSON("status.json", statusResp); err != nil {
   158  			d.UI.Error(err.Error())
   159  			return 1
   160  		}
   161  	}
   162  
   163  	if err := dEnv.finish(); err != nil {
   164  		d.UI.Error(err.Error())
   165  		return 1
   166  	}
   167  
   168  	if d.output != "" {
   169  		d.UI.Output(fmt.Sprintf("Created debug directory: %s", dEnv.dst))
   170  	} else {
   171  		d.UI.Output(fmt.Sprintf("Created debug archive: %s", dEnv.tarName()))
   172  	}
   173  
   174  	return 0
   175  }