good morning!!!!

Skip to content
Snippets Groups Projects
debug.go 4.96 KiB
Newer Older
  • Learn to ignore specific revisions
  • Ferran Borreguero's avatar
    Ferran Borreguero committed
    package main
    
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    // Based on https://github.com/hashicorp/nomad/blob/main/command/operator_debug.go
    
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    import (
    
    	"archive/tar"
    	"compress/gzip"
    	"context"
    	"encoding/hex"
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    	"fmt"
    
    	"io"
    	"io/ioutil"
    	"os"
    	"os/signal"
    	"path/filepath"
    	"strings"
    	"syscall"
    	"time"
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    
    	"github.com/ethereum/go-ethereum/command/flagset"
    
    	"github.com/ethereum/go-ethereum/command/server/proto"
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    )
    
    type DebugCommand struct {
    	*Meta2
    
    
    	seconds uint64
    	output  string
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    }
    
    // Help implements the cli.Command interface
    func (d *DebugCommand) Help() string {
    	return `Usage: bor debug
    
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
      Build an archive containing Bor pprof traces
    
      ` + d.Flags().Help()
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    }
    
    func (d *DebugCommand) Flags() *flagset.Flagset {
    
    	flags := d.NewFlagSet("debug")
    
    	flags.Uint64Flag(&flagset.Uint64Flag{
    		Name:  "seconds",
    		Usage: "seconds to trace",
    		Value: &d.seconds,
    	})
    	flags.StringFlag(&flagset.StringFlag{
    		Name:  "output",
    		Value: &d.output,
    		Usage: "Output directory",
    	})
    
    	return flags
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    }
    
    // Synopsis implements the cli.Command interface
    func (d *DebugCommand) Synopsis() string {
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    	return "Build an archive containing Bor pprof traces"
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    }
    
    // Run implements the cli.Command interface
    func (d *DebugCommand) Run(args []string) int {
    	flags := d.Flags()
    	if err := flags.Parse(args); err != nil {
    		d.UI.Error(err.Error())
    		return 1
    	}
    
    	clt, err := d.BorConn()
    	if err != nil {
    		d.UI.Error(err.Error())
    		return 1
    	}
    
    
    	stamped := "bor-debug-" + time.Now().UTC().Format("2006-01-02-150405Z")
    
    	// Create the output directory
    	var tmp string
    	if d.output != "" {
    		// User specified output directory
    		tmp = filepath.Join(d.output, stamped)
    		_, err := os.Stat(tmp)
    		if !os.IsNotExist(err) {
    			d.UI.Error("Output directory already exists")
    			return 1
    		}
    	} else {
    		// Generate temp directory
    		tmp, err = ioutil.TempDir(os.TempDir(), stamped)
    		if err != nil {
    			d.UI.Error(fmt.Sprintf("Error creating tmp directory: %s", err.Error()))
    			return 1
    		}
    		defer os.RemoveAll(tmp)
    	}
    
    	d.UI.Output("Starting debugger...")
    	d.UI.Output("")
    
    	// ensure destine folder exists
    	if err := os.MkdirAll(tmp, os.ModePerm); err != nil {
    		d.UI.Error(fmt.Sprintf("failed to create parent directory: %v", err))
    		return 1
    	}
    
    	pprofProfile := func(ctx context.Context, profile string, filename string) error {
    		req := &proto.PprofRequest{
    			Seconds: int64(d.seconds),
    		}
    		switch profile {
    		case "cpu":
    			req.Type = proto.PprofRequest_CPU
    		case "trace":
    			req.Type = proto.PprofRequest_TRACE
    		default:
    			req.Type = proto.PprofRequest_LOOKUP
    			req.Profile = profile
    		}
    		resp, err := clt.Pprof(ctx, req)
    		if err != nil {
    			return err
    		}
    		// write file
    		raw, err := hex.DecodeString(resp.Payload)
    		if err != nil {
    			return err
    		}
    		if err := ioutil.WriteFile(filepath.Join(tmp, filename+".prof"), raw, 0755); err != nil {
    			return err
    		}
    		return nil
    	}
    
    	ctx, cancelFn := context.WithCancel(context.Background())
    	trapSignal(cancelFn)
    
    	profiles := map[string]string{
    		"heap":  "heap",
    		"cpu":   "cpu",
    		"trace": "trace",
    	}
    	for profile, filename := range profiles {
    		if err := pprofProfile(ctx, profile, filename); err != nil {
    			d.UI.Error(fmt.Sprintf("Error creating profile '%s': %v", profile, err))
    			return 1
    		}
    	}
    
    	// Exit before archive if output directory was specified
    	if d.output != "" {
    		d.UI.Output(fmt.Sprintf("Created debug directory: %s", tmp))
    		return 0
    	}
    
    	// Create archive tarball
    	archiveFile := stamped + ".tar.gz"
    	if err = tarCZF(archiveFile, tmp, stamped); err != nil {
    		d.UI.Error(fmt.Sprintf("Error creating archive: %s", err.Error()))
    		return 1
    	}
    
    	d.UI.Output(fmt.Sprintf("Created debug archive: %s", archiveFile))
    
    Ferran Borreguero's avatar
    Ferran Borreguero committed
    	return 0
    }
    
    
    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
    	})
    }