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

     1  package cli
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/signal"
    11  	"path/filepath"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/mitchellh/cli"
    17  
    18  	"github.com/ethereum/go-ethereum/internal/cli/server/proto"
    19  
    20  	grpc_net_conn "github.com/JekaMas/go-grpc-net-conn"
    21  	"google.golang.org/grpc"
    22  	"google.golang.org/protobuf/encoding/protojson"
    23  	"google.golang.org/protobuf/reflect/protoreflect"
    24  )
    25  
    26  // DebugCommand is the command to group the peers commands
    27  type DebugCommand struct {
    28  	UI cli.Ui
    29  }
    30  
    31  // MarkDown implements cli.MarkDown interface
    32  func (d *DebugCommand) MarkDown() string {
    33  	examples := []string{
    34  		"## Examples",
    35  		"By default it creates a tar.gz file with the output:",
    36  		CodeBlock([]string{
    37  			"$ bor debug",
    38  			"Starting debugger...\n",
    39  			"Created debug archive: bor-debug-2021-10-26-073819Z.tar.gz",
    40  		}),
    41  		"Send the output to a specific directory:",
    42  		CodeBlock([]string{
    43  			"$ bor debug --output data",
    44  			"Starting debugger...\n",
    45  			"Created debug directory: data/bor-debug-2021-10-26-075437Z",
    46  		}),
    47  	}
    48  
    49  	items := []string{
    50  		"# Debug",
    51  		"The ```bor debug``` command takes a debug dump of the running client.",
    52  		"- [```bor debug pprof```](./debug_pprof.md): Dumps bor pprof traces.",
    53  		"- [```bor debug block <number>```](./debug_block.md): Dumps bor block traces.",
    54  	}
    55  	items = append(items, examples...)
    56  
    57  	return strings.Join(items, "\n\n")
    58  }
    59  
    60  // Help implements the cli.Command interface
    61  func (c *DebugCommand) Help() string {
    62  	return `Usage: bor debug <subcommand>
    63  
    64    This command takes a debug dump of the running client. 
    65  	
    66  	Get the pprof traces:
    67  
    68  		$ bor debug pprof <enode>
    69  
    70  	Get the block traces:
    71  
    72  		$ bor debug block <number>`
    73  }
    74  
    75  // Synopsis implements the cli.Command interface
    76  func (c *DebugCommand) Synopsis() string {
    77  	return "Get traces of the running client"
    78  }
    79  
    80  // Run implements the cli.Command interface
    81  func (c *DebugCommand) Run(args []string) int {
    82  	return cli.RunResultHelp
    83  }
    84  
    85  type debugEnv struct {
    86  	output string
    87  	prefix string
    88  
    89  	name string
    90  	dst  string
    91  }
    92  
    93  func (d *debugEnv) init() error {
    94  	d.name = d.prefix + time.Now().UTC().Format("2006-01-02-150405Z")
    95  
    96  	var err error
    97  
    98  	// Create the output directory
    99  	var tmp string
   100  	if d.output != "" {
   101  		// User specified output directory
   102  		tmp = filepath.Join(d.output, d.name)
   103  		_, err := os.Stat(tmp)
   104  
   105  		if !os.IsNotExist(err) {
   106  			return fmt.Errorf("output directory already exists")
   107  		}
   108  	} else {
   109  		// Generate temp directory
   110  		tmp, err = ioutil.TempDir(os.TempDir(), d.name)
   111  		if err != nil {
   112  			return fmt.Errorf("error creating tmp directory: %s", err.Error())
   113  		}
   114  	}
   115  
   116  	// ensure destine folder exists
   117  	if err := os.MkdirAll(tmp, os.ModePerm); err != nil {
   118  		return fmt.Errorf("failed to create parent directory: %v", err)
   119  	}
   120  
   121  	d.dst = tmp
   122  
   123  	return nil
   124  }
   125  
   126  func (d *debugEnv) tarName() string {
   127  	return d.name + ".tar.gz"
   128  }
   129  
   130  func (d *debugEnv) finish() error {
   131  	// Exit before archive if output directory was specified
   132  	if d.output != "" {
   133  		return nil
   134  	}
   135  
   136  	// Create archive tarball
   137  	archiveFile := d.tarName()
   138  	if err := tarCZF(archiveFile, d.dst, d.name); err != nil {
   139  		return fmt.Errorf("error creating archive: %s", err.Error())
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  type debugStream interface {
   146  	Recv() (*proto.DebugFileResponse, error)
   147  	grpc.ClientStream
   148  }
   149  
   150  func (d *debugEnv) writeFromStream(name string, stream debugStream) error {
   151  	// wait for open request
   152  	msg, err := stream.Recv()
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	if _, ok := msg.Event.(*proto.DebugFileResponse_Open_); !ok {
   158  		return fmt.Errorf("expected open message")
   159  	}
   160  
   161  	// create the stream
   162  	conn := &grpc_net_conn.Conn[*proto.DebugFileResponse_Input, *proto.DebugFileResponse_Input]{
   163  		Stream:   stream,
   164  		Response: &proto.DebugFileResponse_Input{},
   165  		Decode: grpc_net_conn.SimpleDecoder(func(msg *proto.DebugFileResponse_Input) *[]byte {
   166  			return &msg.Data
   167  		}),
   168  	}
   169  
   170  	file, err := os.OpenFile(filepath.Join(d.dst, name), os.O_RDWR|os.O_CREATE, 0644)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer file.Close()
   175  
   176  	if _, err := io.Copy(file, conn); err != nil {
   177  		return err
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  func (d *debugEnv) writeJSON(name string, msg protoreflect.ProtoMessage) error {
   184  	m := protojson.MarshalOptions{}
   185  	data, err := m.Marshal(msg)
   186  
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	if err := ioutil.WriteFile(filepath.Join(d.dst, name), data, 0600); err != nil {
   192  		return fmt.Errorf("failed to write status: %v", err)
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func trapSignal(cancel func()) {
   199  	sigCh := make(chan os.Signal, 1)
   200  	signal.Notify(sigCh,
   201  		syscall.SIGHUP,
   202  		syscall.SIGINT,
   203  		syscall.SIGTERM,
   204  		syscall.SIGQUIT)
   205  
   206  	go func() {
   207  		<-sigCh
   208  		cancel()
   209  	}()
   210  }
   211  
   212  func tarCZF(archive string, src, target string) error {
   213  	// ensure the src actually exists before trying to tar it
   214  	if _, err := os.Stat(src); err != nil {
   215  		return fmt.Errorf("unable to tar files - %v", err.Error())
   216  	}
   217  
   218  	// create the archive
   219  	fh, err := os.Create(archive)
   220  	if err != nil {
   221  		return err
   222  	}
   223  	defer fh.Close()
   224  
   225  	zz := gzip.NewWriter(fh)
   226  	defer zz.Close()
   227  
   228  	tw := tar.NewWriter(zz)
   229  	defer tw.Close()
   230  
   231  	// tar
   232  	return filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
   233  		// return on any error
   234  		if err != nil {
   235  			return err
   236  		}
   237  		if !fi.Mode().IsRegular() {
   238  			return nil
   239  		}
   240  
   241  		header, err := tar.FileInfoHeader(fi, fi.Name())
   242  		if err != nil {
   243  			return err
   244  		}
   245  
   246  		// remove leading path to the src, so files are relative to the archive
   247  		path := strings.ReplaceAll(file, src, "")
   248  		if target != "" {
   249  			path = filepath.Join([]string{target, path}...)
   250  		}
   251  		path = strings.TrimPrefix(path, string(filepath.Separator))
   252  
   253  		header.Name = path
   254  
   255  		if err := tw.WriteHeader(header); err != nil {
   256  			return err
   257  		}
   258  
   259  		// copy the file contents
   260  		f, err := os.Open(file)
   261  		if err != nil {
   262  			return err
   263  		}
   264  
   265  		if _, err := io.Copy(tw, f); err != nil {
   266  			return err
   267  		}
   268  
   269  		f.Close()
   270  		return nil
   271  	})
   272  }