github.com/number571/tendermint@v0.34.11-gost/cmd/tendermint/commands/debug/kill.go (about)

     1  package debug
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strconv"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/spf13/cobra"
    15  	"github.com/spf13/viper"
    16  
    17  	cfg "github.com/number571/tendermint/config"
    18  	"github.com/number571/tendermint/libs/cli"
    19  	rpchttp "github.com/number571/tendermint/rpc/client/http"
    20  )
    21  
    22  var killCmd = &cobra.Command{
    23  	Use:   "kill [pid] [compressed-output-file]",
    24  	Short: "Kill a Tendermint process while aggregating and packaging debugging data",
    25  	Long: `Kill a Tendermint process while also aggregating Tendermint process data
    26  such as the latest node state, including consensus and networking state,
    27  go-routine state, and the node's WAL and config information. This aggregated data
    28  is packaged into a compressed archive.
    29  
    30  Example:
    31  $ tendermint debug kill 34255 /path/to/tm-debug.zip`,
    32  	Args: cobra.ExactArgs(2),
    33  	RunE: killCmdHandler,
    34  }
    35  
    36  func killCmdHandler(cmd *cobra.Command, args []string) error {
    37  	pid, err := strconv.ParseUint(args[0], 10, 64)
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	outFile := args[1]
    43  	if outFile == "" {
    44  		return errors.New("invalid output file")
    45  	}
    46  
    47  	rpc, err := rpchttp.New(nodeRPCAddr)
    48  	if err != nil {
    49  		return fmt.Errorf("failed to create new http client: %w", err)
    50  	}
    51  
    52  	home := viper.GetString(cli.HomeFlag)
    53  	conf := cfg.DefaultConfig()
    54  	conf = conf.SetRoot(home)
    55  	cfg.EnsureRoot(conf.RootDir)
    56  
    57  	// Create a temporary directory which will contain all the state dumps and
    58  	// relevant files and directories that will be compressed into a file.
    59  	tmpDir, err := ioutil.TempDir(os.TempDir(), "tendermint_debug_tmp")
    60  	if err != nil {
    61  		return fmt.Errorf("failed to create temporary directory: %w", err)
    62  	}
    63  	defer os.RemoveAll(tmpDir)
    64  
    65  	logger.Info("getting node status...")
    66  	if err := dumpStatus(rpc, tmpDir, "status.json"); err != nil {
    67  		return err
    68  	}
    69  
    70  	logger.Info("getting node network info...")
    71  	if err := dumpNetInfo(rpc, tmpDir, "net_info.json"); err != nil {
    72  		return err
    73  	}
    74  
    75  	logger.Info("getting node consensus state...")
    76  	if err := dumpConsensusState(rpc, tmpDir, "consensus_state.json"); err != nil {
    77  		return err
    78  	}
    79  
    80  	logger.Info("copying node WAL...")
    81  	if err := copyWAL(conf, tmpDir); err != nil {
    82  		if !os.IsNotExist(err) {
    83  			return err
    84  		}
    85  
    86  		logger.Info("node WAL does not exist; continuing...")
    87  	}
    88  
    89  	logger.Info("copying node configuration...")
    90  	if err := copyConfig(home, tmpDir); err != nil {
    91  		return err
    92  	}
    93  
    94  	logger.Info("killing Tendermint process")
    95  	if err := killProc(pid, tmpDir); err != nil {
    96  		return err
    97  	}
    98  
    99  	logger.Info("archiving and compressing debug directory...")
   100  	return zipDir(tmpDir, outFile)
   101  }
   102  
   103  // killProc attempts to kill the Tendermint process with a given PID with an
   104  // ABORT signal which should result in a goroutine stacktrace. The PID's STDERR
   105  // is tailed and piped to a file under the directory dir. An error is returned
   106  // if the output file cannot be created or the tail command cannot be started.
   107  // An error is not returned if any subsequent syscall fails.
   108  func killProc(pid uint64, dir string) error {
   109  	// pipe STDERR output from tailing the Tendermint process to a file
   110  	//
   111  	// NOTE: This will only work on UNIX systems.
   112  	cmd := exec.Command("tail", "-f", fmt.Sprintf("/proc/%d/fd/2", pid)) // nolint: gosec
   113  
   114  	outFile, err := os.Create(filepath.Join(dir, "stacktrace.out"))
   115  	if err != nil {
   116  		return err
   117  	}
   118  	defer outFile.Close()
   119  
   120  	cmd.Stdout = outFile
   121  	cmd.Stderr = outFile
   122  
   123  	if err := cmd.Start(); err != nil {
   124  		return err
   125  	}
   126  
   127  	// kill the underlying Tendermint process and subsequent tailing process
   128  	go func() {
   129  		// Killing the Tendermint process with the '-ABRT|-6' signal will result in
   130  		// a goroutine stacktrace.
   131  		p, err := os.FindProcess(int(pid))
   132  		if err != nil {
   133  			fmt.Fprintf(os.Stderr, "failed to find PID to kill Tendermint process: %s", err)
   134  		} else if err = p.Signal(syscall.SIGABRT); err != nil {
   135  			fmt.Fprintf(os.Stderr, "failed to kill Tendermint process: %s", err)
   136  		}
   137  
   138  		// allow some time to allow the Tendermint process to be killed
   139  		//
   140  		// TODO: We should 'wait' for a kill to succeed (e.g. poll for PID until it
   141  		// cannot be found). Regardless, this should be ample time.
   142  		time.Sleep(5 * time.Second)
   143  
   144  		if err := cmd.Process.Kill(); err != nil {
   145  			fmt.Fprintf(os.Stderr, "failed to kill Tendermint process output redirection: %s", err)
   146  		}
   147  	}()
   148  
   149  	if err := cmd.Wait(); err != nil {
   150  		// only return an error not invoked by a manual kill
   151  		if _, ok := err.(*exec.ExitError); !ok {
   152  			return err
   153  		}
   154  	}
   155  
   156  	return nil
   157  }