mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
274 lines
5.7 KiB
Go
274 lines
5.7 KiB
Go
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 <number>```](./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 <subcommand>
|
|
|
|
This command takes a debug dump of the running client.
|
|
|
|
Get the pprof traces:
|
|
|
|
$ bor debug pprof <enode>
|
|
|
|
Get the block traces:
|
|
|
|
$ bor debug block <number>`
|
|
}
|
|
|
|
// 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
|
|
})
|
|
}
|