github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/internal/cli/debug.go (about) 1 package cli 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/signal" 11 "path/filepath" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/mitchellh/cli" 17 18 "github.com/ethereum/go-ethereum/internal/cli/server/proto" 19 20 grpc_net_conn "github.com/JekaMas/go-grpc-net-conn" 21 "google.golang.org/grpc" 22 "google.golang.org/protobuf/encoding/protojson" 23 "google.golang.org/protobuf/reflect/protoreflect" 24 ) 25 26 // DebugCommand is the command to group the peers commands 27 type DebugCommand struct { 28 UI cli.Ui 29 } 30 31 // MarkDown implements cli.MarkDown interface 32 func (d *DebugCommand) MarkDown() string { 33 examples := []string{ 34 "## Examples", 35 "By default it creates a tar.gz file with the output:", 36 CodeBlock([]string{ 37 "$ bor debug", 38 "Starting debugger...\n", 39 "Created debug archive: bor-debug-2021-10-26-073819Z.tar.gz", 40 }), 41 "Send the output to a specific directory:", 42 CodeBlock([]string{ 43 "$ bor debug --output data", 44 "Starting debugger...\n", 45 "Created debug directory: data/bor-debug-2021-10-26-075437Z", 46 }), 47 } 48 49 items := []string{ 50 "# Debug", 51 "The ```bor debug``` command takes a debug dump of the running client.", 52 "- [```bor debug pprof```](./debug_pprof.md): Dumps bor pprof traces.", 53 "- [```bor debug block <number>```](./debug_block.md): Dumps bor block traces.", 54 } 55 items = append(items, examples...) 56 57 return strings.Join(items, "\n\n") 58 } 59 60 // Help implements the cli.Command interface 61 func (c *DebugCommand) Help() string { 62 return `Usage: bor debug <subcommand> 63 64 This command takes a debug dump of the running client. 65 66 Get the pprof traces: 67 68 $ bor debug pprof <enode> 69 70 Get the block traces: 71 72 $ bor debug block <number>` 73 } 74 75 // Synopsis implements the cli.Command interface 76 func (c *DebugCommand) Synopsis() string { 77 return "Get traces of the running client" 78 } 79 80 // Run implements the cli.Command interface 81 func (c *DebugCommand) Run(args []string) int { 82 return cli.RunResultHelp 83 } 84 85 type debugEnv struct { 86 output string 87 prefix string 88 89 name string 90 dst string 91 } 92 93 func (d *debugEnv) init() error { 94 d.name = d.prefix + time.Now().UTC().Format("2006-01-02-150405Z") 95 96 var err error 97 98 // Create the output directory 99 var tmp string 100 if d.output != "" { 101 // User specified output directory 102 tmp = filepath.Join(d.output, d.name) 103 _, err := os.Stat(tmp) 104 105 if !os.IsNotExist(err) { 106 return fmt.Errorf("output directory already exists") 107 } 108 } else { 109 // Generate temp directory 110 tmp, err = ioutil.TempDir(os.TempDir(), d.name) 111 if err != nil { 112 return fmt.Errorf("error creating tmp directory: %s", err.Error()) 113 } 114 } 115 116 // ensure destine folder exists 117 if err := os.MkdirAll(tmp, os.ModePerm); err != nil { 118 return fmt.Errorf("failed to create parent directory: %v", err) 119 } 120 121 d.dst = tmp 122 123 return nil 124 } 125 126 func (d *debugEnv) tarName() string { 127 return d.name + ".tar.gz" 128 } 129 130 func (d *debugEnv) finish() error { 131 // Exit before archive if output directory was specified 132 if d.output != "" { 133 return nil 134 } 135 136 // Create archive tarball 137 archiveFile := d.tarName() 138 if err := tarCZF(archiveFile, d.dst, d.name); err != nil { 139 return fmt.Errorf("error creating archive: %s", err.Error()) 140 } 141 142 return nil 143 } 144 145 type debugStream interface { 146 Recv() (*proto.DebugFileResponse, error) 147 grpc.ClientStream 148 } 149 150 func (d *debugEnv) writeFromStream(name string, stream debugStream) error { 151 // wait for open request 152 msg, err := stream.Recv() 153 if err != nil { 154 return err 155 } 156 157 if _, ok := msg.Event.(*proto.DebugFileResponse_Open_); !ok { 158 return fmt.Errorf("expected open message") 159 } 160 161 // create the stream 162 conn := &grpc_net_conn.Conn[*proto.DebugFileResponse_Input, *proto.DebugFileResponse_Input]{ 163 Stream: stream, 164 Response: &proto.DebugFileResponse_Input{}, 165 Decode: grpc_net_conn.SimpleDecoder(func(msg *proto.DebugFileResponse_Input) *[]byte { 166 return &msg.Data 167 }), 168 } 169 170 file, err := os.OpenFile(filepath.Join(d.dst, name), os.O_RDWR|os.O_CREATE, 0644) 171 if err != nil { 172 return err 173 } 174 defer file.Close() 175 176 if _, err := io.Copy(file, conn); err != nil { 177 return err 178 } 179 180 return nil 181 } 182 183 func (d *debugEnv) writeJSON(name string, msg protoreflect.ProtoMessage) error { 184 m := protojson.MarshalOptions{} 185 data, err := m.Marshal(msg) 186 187 if err != nil { 188 return err 189 } 190 191 if err := ioutil.WriteFile(filepath.Join(d.dst, name), data, 0600); err != nil { 192 return fmt.Errorf("failed to write status: %v", err) 193 } 194 195 return nil 196 } 197 198 func trapSignal(cancel func()) { 199 sigCh := make(chan os.Signal, 1) 200 signal.Notify(sigCh, 201 syscall.SIGHUP, 202 syscall.SIGINT, 203 syscall.SIGTERM, 204 syscall.SIGQUIT) 205 206 go func() { 207 <-sigCh 208 cancel() 209 }() 210 } 211 212 func tarCZF(archive string, src, target string) error { 213 // ensure the src actually exists before trying to tar it 214 if _, err := os.Stat(src); err != nil { 215 return fmt.Errorf("unable to tar files - %v", err.Error()) 216 } 217 218 // create the archive 219 fh, err := os.Create(archive) 220 if err != nil { 221 return err 222 } 223 defer fh.Close() 224 225 zz := gzip.NewWriter(fh) 226 defer zz.Close() 227 228 tw := tar.NewWriter(zz) 229 defer tw.Close() 230 231 // tar 232 return filepath.Walk(src, func(file string, fi os.FileInfo, err error) error { 233 // return on any error 234 if err != nil { 235 return err 236 } 237 if !fi.Mode().IsRegular() { 238 return nil 239 } 240 241 header, err := tar.FileInfoHeader(fi, fi.Name()) 242 if err != nil { 243 return err 244 } 245 246 // remove leading path to the src, so files are relative to the archive 247 path := strings.ReplaceAll(file, src, "") 248 if target != "" { 249 path = filepath.Join([]string{target, path}...) 250 } 251 path = strings.TrimPrefix(path, string(filepath.Separator)) 252 253 header.Name = path 254 255 if err := tw.WriteHeader(header); err != nil { 256 return err 257 } 258 259 // copy the file contents 260 f, err := os.Open(file) 261 if err != nil { 262 return err 263 } 264 265 if _, err := io.Copy(tw, f); err != nil { 266 return err 267 } 268 269 f.Close() 270 return nil 271 }) 272 }