github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/cmd/tendermint/commands/debug/dump.go (about)

     1  package debug
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/spf13/cobra"
    12  	"github.com/spf13/viper"
    13  
    14  	"github.com/ari-anchor/sei-tendermint/config"
    15  	"github.com/ari-anchor/sei-tendermint/libs/cli"
    16  	"github.com/ari-anchor/sei-tendermint/libs/log"
    17  	rpchttp "github.com/ari-anchor/sei-tendermint/rpc/client/http"
    18  )
    19  
    20  func getDumpCmd(logger log.Logger) *cobra.Command {
    21  	cmd := &cobra.Command{
    22  		Use:   "dump [output-directory]",
    23  		Short: "Continuously poll a Tendermint process and dump debugging data into a single location",
    24  		Long: `Continuously poll a Tendermint process and dump debugging data into a single
    25  location at a specified frequency. At each frequency interval, an archived and compressed
    26  file will contain node debugging information including the goroutine and heap profiles
    27  if enabled.`,
    28  		Args: cobra.ExactArgs(1),
    29  		RunE: func(cmd *cobra.Command, args []string) error {
    30  			outDir := args[0]
    31  			if outDir == "" {
    32  				return errors.New("invalid output directory")
    33  			}
    34  			frequency, err := cmd.Flags().GetUint(flagFrequency)
    35  			if err != nil {
    36  				return fmt.Errorf("flag %q not defined: %w", flagFrequency, err)
    37  			}
    38  
    39  			if frequency == 0 {
    40  				return errors.New("frequency must be positive")
    41  			}
    42  
    43  			nodeRPCAddr, err := cmd.Flags().GetString(flagNodeRPCAddr)
    44  			if err != nil {
    45  				return fmt.Errorf("flag %q not defined: %w", flagNodeRPCAddr, err)
    46  			}
    47  
    48  			profAddr, err := cmd.Flags().GetString(flagProfAddr)
    49  			if err != nil {
    50  				return fmt.Errorf("flag %q not defined: %w", flagProfAddr, err)
    51  			}
    52  
    53  			if _, err := os.Stat(outDir); os.IsNotExist(err) {
    54  				if err := os.Mkdir(outDir, os.ModePerm); err != nil {
    55  					return fmt.Errorf("failed to create output directory: %w", err)
    56  				}
    57  			}
    58  
    59  			rpc, err := rpchttp.New(nodeRPCAddr)
    60  			if err != nil {
    61  				return fmt.Errorf("failed to create new http client: %w", err)
    62  			}
    63  
    64  			ctx := cmd.Context()
    65  
    66  			home := viper.GetString(cli.HomeFlag)
    67  			conf := config.DefaultConfig()
    68  			conf = conf.SetRoot(home)
    69  			config.EnsureRoot(conf.RootDir)
    70  
    71  			dumpArgs := dumpDebugDataArgs{
    72  				conf:     conf,
    73  				outDir:   outDir,
    74  				profAddr: profAddr,
    75  			}
    76  			dumpDebugData(ctx, logger, rpc, dumpArgs)
    77  
    78  			ticker := time.NewTicker(time.Duration(frequency) * time.Second)
    79  			for range ticker.C {
    80  				dumpDebugData(ctx, logger, rpc, dumpArgs)
    81  			}
    82  
    83  			return nil
    84  		},
    85  	}
    86  	cmd.Flags().Uint(
    87  		flagFrequency,
    88  		30,
    89  		"the frequency (seconds) in which to poll, aggregate and dump Tendermint debug data",
    90  	)
    91  
    92  	cmd.Flags().String(
    93  		flagProfAddr,
    94  		"",
    95  		"the profiling server address (<host>:<port>)",
    96  	)
    97  
    98  	return cmd
    99  
   100  }
   101  
   102  type dumpDebugDataArgs struct {
   103  	conf     *config.Config
   104  	outDir   string
   105  	profAddr string
   106  }
   107  
   108  func dumpDebugData(ctx context.Context, logger log.Logger, rpc *rpchttp.HTTP, args dumpDebugDataArgs) {
   109  	start := time.Now().UTC()
   110  
   111  	tmpDir, err := os.MkdirTemp(args.outDir, "tendermint_debug_tmp")
   112  	if err != nil {
   113  		logger.Error("failed to create temporary directory", "dir", tmpDir, "error", err)
   114  		return
   115  	}
   116  	defer os.RemoveAll(tmpDir)
   117  
   118  	logger.Info("getting node status...")
   119  	if err := dumpStatus(ctx, rpc, tmpDir, "status.json"); err != nil {
   120  		logger.Error("failed to dump node status", "error", err)
   121  		return
   122  	}
   123  
   124  	logger.Info("getting node network info...")
   125  	if err := dumpNetInfo(ctx, rpc, tmpDir, "net_info.json"); err != nil {
   126  		logger.Error("failed to dump node network info", "error", err)
   127  		return
   128  	}
   129  
   130  	logger.Info("getting node consensus state...")
   131  	if err := dumpConsensusState(ctx, rpc, tmpDir, "consensus_state.json"); err != nil {
   132  		logger.Error("failed to dump node consensus state", "error", err)
   133  		return
   134  	}
   135  
   136  	logger.Info("copying node WAL...")
   137  	if err := copyWAL(args.conf, tmpDir); err != nil {
   138  		logger.Error("failed to copy node WAL", "error", err)
   139  		return
   140  	}
   141  
   142  	if args.profAddr != "" {
   143  		logger.Info("getting node goroutine profile...")
   144  		if err := dumpProfile(tmpDir, args.profAddr, "goroutine", 2); err != nil {
   145  			logger.Error("failed to dump goroutine profile", "error", err)
   146  			return
   147  		}
   148  
   149  		logger.Info("getting node heap profile...")
   150  		if err := dumpProfile(tmpDir, args.profAddr, "heap", 2); err != nil {
   151  			logger.Error("failed to dump heap profile", "error", err)
   152  			return
   153  		}
   154  	}
   155  
   156  	outFile := filepath.Join(args.outDir, fmt.Sprintf("%s.zip", start.Format(time.RFC3339)))
   157  	if err := zipDir(tmpDir, outFile); err != nil {
   158  		logger.Error("failed to create and compress archive", "file", outFile, "error", err)
   159  	}
   160  }