package cli import ( "archive/tar" "compress/gzip" "fmt" "io" "os" "os/signal" "path/filepath" "strings" "syscall" "time" "github.com/mitchellh/cli" "github.com/ethereum/go-ethereum/internal/cli/server/proto" grpc_net_conn "github.com/JekaMas/go-grpc-net-conn" "google.golang.org/grpc" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" ) // DebugCommand is the command to group the peers commands type DebugCommand struct { UI cli.Ui } // MarkDown implements cli.MarkDown interface func (d *DebugCommand) MarkDown() string { examples := []string{ "## Examples", "By default it creates a tar.gz file with the output:", CodeBlock([]string{ "$ bor debug", "Starting debugger...\n", "Created debug archive: bor-debug-2021-10-26-073819Z.tar.gz", }), "Send the output to a specific directory:", CodeBlock([]string{ "$ bor debug --output data", "Starting debugger...\n", "Created debug directory: data/bor-debug-2021-10-26-075437Z", }), } items := []string{ "# Debug", "The ```bor debug``` command takes a debug dump of the running client.", "- [```bor debug pprof```](./debug_pprof.md): Dumps bor pprof traces.", "- [```bor debug block ```](./debug_block.md): Dumps bor block traces.", } items = append(items, examples...) return strings.Join(items, "\n\n") } // Help implements the cli.Command interface func (c *DebugCommand) Help() string { return `Usage: bor debug This command takes a debug dump of the running client. Get the pprof traces: $ bor debug pprof Get the block traces: $ bor debug block ` } // Synopsis implements the cli.Command interface func (c *DebugCommand) Synopsis() string { return "Get traces of the running client" } // Run implements the cli.Command interface func (c *DebugCommand) Run(args []string) int { return cli.RunResultHelp } type debugEnv struct { output string prefix string name string dst string } func (d *debugEnv) init() error { d.name = d.prefix + time.Now().UTC().Format("2006-01-02-150405Z") var err error // Create the output directory var tmp string if d.output != "" { // User specified output directory tmp = filepath.Join(d.output, d.name) _, err := os.Stat(tmp) if !os.IsNotExist(err) { return fmt.Errorf("output directory already exists") } } else { // Generate temp directory tmp, err = os.MkdirTemp(os.TempDir(), d.name) if err != nil { return fmt.Errorf("error creating tmp directory: %s", err.Error()) } } // ensure destine folder exists if err := os.MkdirAll(tmp, os.ModePerm); err != nil { return fmt.Errorf("failed to create parent directory: %v", err) } d.dst = tmp return nil } func (d *debugEnv) tarName() string { return d.name + ".tar.gz" } func (d *debugEnv) finish() error { // Exit before archive if output directory was specified if d.output != "" { return nil } // Create archive tarball archiveFile := d.tarName() if err := tarCZF(archiveFile, d.dst, d.name); err != nil { return fmt.Errorf("error creating archive: %s", err.Error()) } return nil } type debugStream interface { Recv() (*proto.DebugFileResponse, error) grpc.ClientStream } func (d *debugEnv) writeFromStream(name string, stream debugStream) error { // wait for open request msg, err := stream.Recv() if err != nil { return err } if _, ok := msg.Event.(*proto.DebugFileResponse_Open_); !ok { return fmt.Errorf("expected open message") } // create the stream conn := &grpc_net_conn.Conn[*proto.DebugFileResponse_Input, *proto.DebugFileResponse_Input]{ Stream: stream, Response: &proto.DebugFileResponse_Input{}, Decode: grpc_net_conn.SimpleDecoder(func(msg *proto.DebugFileResponse_Input) *[]byte { return &msg.Data }), } file, err := os.OpenFile(filepath.Join(d.dst, name), os.O_RDWR|os.O_CREATE, 0644) if err != nil { return err } defer file.Close() if _, err := io.Copy(file, conn); err != nil { return err } return nil } func (d *debugEnv) writeJSON(name string, msg protoreflect.ProtoMessage) error { m := protojson.MarshalOptions{} data, err := m.Marshal(msg) if err != nil { return err } if err := os.WriteFile(filepath.Join(d.dst, name), data, 0600); err != nil { return fmt.Errorf("failed to write status: %v", err) } return nil } func trapSignal(cancel func()) { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) go func() { <-sigCh cancel() }() } func tarCZF(archive string, src, target string) error { // ensure the src actually exists before trying to tar it if _, err := os.Stat(src); err != nil { return fmt.Errorf("unable to tar files - %v", err.Error()) } // create the archive fh, err := os.Create(archive) if err != nil { return err } defer fh.Close() zz := gzip.NewWriter(fh) defer zz.Close() tw := tar.NewWriter(zz) defer tw.Close() // tar return filepath.Walk(src, func(file string, fi os.FileInfo, err error) error { // return on any error if err != nil { return err } if !fi.Mode().IsRegular() { return nil } header, err := tar.FileInfoHeader(fi, fi.Name()) if err != nil { return err } // remove leading path to the src, so files are relative to the archive path := strings.ReplaceAll(file, src, "") if target != "" { path = filepath.Join([]string{target, path}...) } path = strings.TrimPrefix(path, string(filepath.Separator)) header.Name = path if err := tw.WriteHeader(header); err != nil { return err } // copy the file contents f, err := os.Open(file) if err != nil { return err } if _, err := io.Copy(tw, f); err != nil { return err } f.Close() return nil }) }