diff --git a/.gitignore b/.gitignore index 15ccd00ed0f77a816351c0e2d6b11d174a6855ac..a56d2523228d6ba8f6a6f0cb17e1d052eba159ef 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ profile.cov **/yarn-error.log ./test +./bor-debug-* diff --git a/command/debug.go b/command/debug.go index c62f862a99013396d2370cf9c454cabc30d2c6aa..0168bb74a702422cd8fe32f7896e38ecd25797dc 100644 --- a/command/debug.go +++ b/command/debug.go @@ -1,13 +1,29 @@ package main import ( + "archive/tar" + "compress/gzip" + "context" + "encoding/hex" "fmt" + "io" + "io/ioutil" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" "github.com/ethereum/go-ethereum/command/flagset" + "github.com/ethereum/go-ethereum/command/server/proto" ) type DebugCommand struct { *Meta2 + + seconds uint64 + output string } // Help implements the cli.Command interface @@ -18,7 +34,20 @@ func (d *DebugCommand) Help() string { } func (d *DebugCommand) Flags() *flagset.Flagset { - return d.NewFlagSet("debug") + 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 } // Synopsis implements the cli.Command interface @@ -39,6 +68,170 @@ func (d *DebugCommand) Run(args []string) int { d.UI.Error(err.Error()) return 1 } - fmt.Println(clt) + + 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)) 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 + }) +} diff --git a/command/main.go b/command/main.go index 702fce9942bc25a3dd23fda543ff10a8429d9513..0fd38908304d1713a18c1d36b10229c1ef4eebed 100644 --- a/command/main.go +++ b/command/main.go @@ -106,7 +106,7 @@ func (m *Meta2) NewFlagSet(n string) *flagset.Flagset { func (m *Meta2) Conn() (*grpc.ClientConn, error) { if m.addr == "" { - m.addr = "http://localhost:3131" + m.addr = "127.0.0.1:3131" } conn, err := grpc.Dial(m.addr, grpc.WithInsecure()) if err != nil { diff --git a/command/server/pprof/pprof.go b/command/server/pprof/pprof.go index 7b25f4374343065d1878f2e2f9601c86ed797fea..44034f3bb8ddb4ba4437ce1389775e775d241255 100644 --- a/command/server/pprof/pprof.go +++ b/command/server/pprof/pprof.go @@ -2,9 +2,12 @@ package pprof import ( "bytes" + "context" "fmt" "runtime" "runtime/pprof" + "runtime/trace" + "time" ) // Profile generates a pprof.Profile report for the given profile name. @@ -34,3 +37,57 @@ func Profile(profile string, debug, gc int) ([]byte, map[string]string, error) { } return buf.Bytes(), headers, nil } + +// CPUProfile generates a CPU Profile for a given duration +func CPUProfile(ctx context.Context, sec int) ([]byte, map[string]string, error) { + if sec <= 0 { + sec = 1 + } + + var buf bytes.Buffer + if err := pprof.StartCPUProfile(&buf); err != nil { + return nil, nil, err + } + + sleep(ctx, time.Duration(sec)*time.Second) + + pprof.StopCPUProfile() + + return buf.Bytes(), + map[string]string{ + "X-Content-Type-Options": "nosniff", + "Content-Type": "application/octet-stream", + "Content-Disposition": `attachment; filename="profile"`, + }, nil +} + +// Trace runs a trace profile for a given duration +func Trace(ctx context.Context, sec int) ([]byte, map[string]string, error) { + if sec <= 0 { + sec = 1 + } + + var buf bytes.Buffer + if err := trace.Start(&buf); err != nil { + return nil, nil, err + } + + sleep(ctx, time.Duration(sec)*time.Second) + + trace.Stop() + + return buf.Bytes(), + map[string]string{ + "X-Content-Type-Options": "nosniff", + "Content-Type": "application/octet-stream", + "Content-Disposition": `attachment; filename="trace"`, + }, nil +} + +func sleep(ctx context.Context, d time.Duration) { + // Sleep until duration is met or ctx is cancelled + select { + case <-time.After(d): + case <-ctx.Done(): + } +} diff --git a/command/server/proto/server.pb.go b/command/server/proto/server.pb.go index c2f6c316a3eb39e96802f3e2e6c1958f6f1f1717..87ace6509ae345ce07469b9495a45372a672c22f 100644 --- a/command/server/proto/server.pb.go +++ b/command/server/proto/server.pb.go @@ -25,14 +25,67 @@ const ( // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 -type DebugInput struct { +type PprofRequest_Type int32 + +const ( + PprofRequest_LOOKUP PprofRequest_Type = 0 + PprofRequest_CPU PprofRequest_Type = 1 + PprofRequest_TRACE PprofRequest_Type = 2 +) + +// Enum value maps for PprofRequest_Type. +var ( + PprofRequest_Type_name = map[int32]string{ + 0: "LOOKUP", + 1: "CPU", + 2: "TRACE", + } + PprofRequest_Type_value = map[string]int32{ + "LOOKUP": 0, + "CPU": 1, + "TRACE": 2, + } +) + +func (x PprofRequest_Type) Enum() *PprofRequest_Type { + p := new(PprofRequest_Type) + *p = x + return p +} + +func (x PprofRequest_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PprofRequest_Type) Descriptor() protoreflect.EnumDescriptor { + return file_command_server_proto_server_proto_enumTypes[0].Descriptor() +} + +func (PprofRequest_Type) Type() protoreflect.EnumType { + return &file_command_server_proto_server_proto_enumTypes[0] +} + +func (x PprofRequest_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PprofRequest_Type.Descriptor instead. +func (PprofRequest_Type) EnumDescriptor() ([]byte, []int) { + return file_command_server_proto_server_proto_rawDescGZIP(), []int{0, 0} +} + +type PprofRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Type PprofRequest_Type `protobuf:"varint,1,opt,name=type,proto3,enum=proto.PprofRequest_Type" json:"type,omitempty"` + Profile string `protobuf:"bytes,2,opt,name=profile,proto3" json:"profile,omitempty"` + Seconds int64 `protobuf:"varint,3,opt,name=seconds,proto3" json:"seconds,omitempty"` } -func (x *DebugInput) Reset() { - *x = DebugInput{} +func (x *PprofRequest) Reset() { + *x = PprofRequest{} if protoimpl.UnsafeEnabled { mi := &file_command_server_proto_server_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -40,13 +93,13 @@ func (x *DebugInput) Reset() { } } -func (x *DebugInput) String() string { +func (x *PprofRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DebugInput) ProtoMessage() {} +func (*PprofRequest) ProtoMessage() {} -func (x *DebugInput) ProtoReflect() protoreflect.Message { +func (x *PprofRequest) ProtoReflect() protoreflect.Message { mi := &file_command_server_proto_server_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -58,23 +111,119 @@ func (x *DebugInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DebugInput.ProtoReflect.Descriptor instead. -func (*DebugInput) Descriptor() ([]byte, []int) { +// Deprecated: Use PprofRequest.ProtoReflect.Descriptor instead. +func (*PprofRequest) Descriptor() ([]byte, []int) { return file_command_server_proto_server_proto_rawDescGZIP(), []int{0} } +func (x *PprofRequest) GetType() PprofRequest_Type { + if x != nil { + return x.Type + } + return PprofRequest_LOOKUP +} + +func (x *PprofRequest) GetProfile() string { + if x != nil { + return x.Profile + } + return "" +} + +func (x *PprofRequest) GetSeconds() int64 { + if x != nil { + return x.Seconds + } + return 0 +} + +type PprofResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *PprofResponse) Reset() { + *x = PprofResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_command_server_proto_server_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PprofResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PprofResponse) ProtoMessage() {} + +func (x *PprofResponse) ProtoReflect() protoreflect.Message { + mi := &file_command_server_proto_server_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PprofResponse.ProtoReflect.Descriptor instead. +func (*PprofResponse) Descriptor() ([]byte, []int) { + return file_command_server_proto_server_proto_rawDescGZIP(), []int{1} +} + +func (x *PprofResponse) GetPayload() string { + if x != nil { + return x.Payload + } + return "" +} + +func (x *PprofResponse) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + var File_command_server_proto_server_proto protoreflect.FileDescriptor var file_command_server_proto_server_proto_rawDesc = []byte{ 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0c, 0x0a, 0x0a, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x32, 0x34, 0x0a, 0x03, 0x42, 0x6f, 0x72, 0x12, - 0x2d, 0x0a, 0x05, 0x44, 0x65, 0x62, 0x75, 0x67, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x1a, 0x11, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x17, - 0x5a, 0x15, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x01, 0x0a, 0x0c, 0x50, + 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x26, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x10, + 0x00, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, + 0x41, 0x43, 0x45, 0x10, 0x02, 0x22, 0xa2, 0x01, 0x0a, 0x0d, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x12, 0x3b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a, + 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x39, 0x0a, 0x03, 0x42, 0x6f, + 0x72, 0x12, 0x32, 0x0a, 0x05, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x17, 0x5a, 0x15, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -89,18 +238,24 @@ func file_command_server_proto_server_proto_rawDescGZIP() []byte { return file_command_server_proto_server_proto_rawDescData } -var file_command_server_proto_server_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_command_server_proto_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_command_server_proto_server_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_command_server_proto_server_proto_goTypes = []interface{}{ - (*DebugInput)(nil), // 0: proto.DebugInput + (PprofRequest_Type)(0), // 0: proto.PprofRequest.Type + (*PprofRequest)(nil), // 1: proto.PprofRequest + (*PprofResponse)(nil), // 2: proto.PprofResponse + nil, // 3: proto.PprofResponse.HeadersEntry } var file_command_server_proto_server_proto_depIdxs = []int32{ - 0, // 0: proto.Bor.Debug:input_type -> proto.DebugInput - 0, // 1: proto.Bor.Debug:output_type -> proto.DebugInput - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: proto.PprofRequest.type:type_name -> proto.PprofRequest.Type + 3, // 1: proto.PprofResponse.headers:type_name -> proto.PprofResponse.HeadersEntry + 1, // 2: proto.Bor.Pprof:input_type -> proto.PprofRequest + 2, // 3: proto.Bor.Pprof:output_type -> proto.PprofResponse + 3, // [3:4] is the sub-list for method output_type + 2, // [2:3] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_command_server_proto_server_proto_init() } @@ -110,7 +265,19 @@ func file_command_server_proto_server_proto_init() { } if !protoimpl.UnsafeEnabled { file_command_server_proto_server_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DebugInput); i { + switch v := v.(*PprofRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_command_server_proto_server_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PprofResponse); i { case 0: return &v.state case 1: @@ -127,13 +294,14 @@ func file_command_server_proto_server_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_command_server_proto_server_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, + NumEnums: 1, + NumMessages: 3, NumExtensions: 0, NumServices: 1, }, GoTypes: file_command_server_proto_server_proto_goTypes, DependencyIndexes: file_command_server_proto_server_proto_depIdxs, + EnumInfos: file_command_server_proto_server_proto_enumTypes, MessageInfos: file_command_server_proto_server_proto_msgTypes, }.Build() File_command_server_proto_server_proto = out.File diff --git a/command/server/proto/server.proto b/command/server/proto/server.proto index 64972fa1551f564eaacf376127a6b8eb02aa4de0..2348e6dd6ae3b65fc611e7ab1455e411c885bfc9 100644 --- a/command/server/proto/server.proto +++ b/command/server/proto/server.proto @@ -5,9 +5,24 @@ package proto; option go_package = "/command/server/proto"; service Bor { - rpc Debug(DebugInput) returns (DebugInput); + rpc Pprof(PprofRequest) returns (PprofResponse); } -message DebugInput { +message PprofRequest { + Type type = 1; + string profile = 2; + + int64 seconds = 3; + + enum Type { + LOOKUP = 0; + CPU = 1; + TRACE = 2; + } +} + +message PprofResponse { + string payload = 1; + map<string, string> headers = 2; } diff --git a/command/server/proto/server_grpc.pb.go b/command/server/proto/server_grpc.pb.go index e23aa7fda5932160d5cc2ad1dc09cf6c3e6ca1b7..1c101f5e85596005350b5d6aa3ff0cd0a2d04036 100644 --- a/command/server/proto/server_grpc.pb.go +++ b/command/server/proto/server_grpc.pb.go @@ -18,7 +18,7 @@ const _ = grpc.SupportPackageIsVersion7 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type BorClient interface { - Debug(ctx context.Context, in *DebugInput, opts ...grpc.CallOption) (*DebugInput, error) + Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (*PprofResponse, error) } type borClient struct { @@ -29,9 +29,9 @@ func NewBorClient(cc grpc.ClientConnInterface) BorClient { return &borClient{cc} } -func (c *borClient) Debug(ctx context.Context, in *DebugInput, opts ...grpc.CallOption) (*DebugInput, error) { - out := new(DebugInput) - err := c.cc.Invoke(ctx, "/proto.Bor/Debug", in, out, opts...) +func (c *borClient) Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (*PprofResponse, error) { + out := new(PprofResponse) + err := c.cc.Invoke(ctx, "/proto.Bor/Pprof", in, out, opts...) if err != nil { return nil, err } @@ -42,7 +42,7 @@ func (c *borClient) Debug(ctx context.Context, in *DebugInput, opts ...grpc.Call // All implementations must embed UnimplementedBorServer // for forward compatibility type BorServer interface { - Debug(context.Context, *DebugInput) (*DebugInput, error) + Pprof(context.Context, *PprofRequest) (*PprofResponse, error) mustEmbedUnimplementedBorServer() } @@ -50,8 +50,8 @@ type BorServer interface { type UnimplementedBorServer struct { } -func (UnimplementedBorServer) Debug(context.Context, *DebugInput) (*DebugInput, error) { - return nil, status.Errorf(codes.Unimplemented, "method Debug not implemented") +func (UnimplementedBorServer) Pprof(context.Context, *PprofRequest) (*PprofResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Pprof not implemented") } func (UnimplementedBorServer) mustEmbedUnimplementedBorServer() {} @@ -66,20 +66,20 @@ func RegisterBorServer(s grpc.ServiceRegistrar, srv BorServer) { s.RegisterService(&Bor_ServiceDesc, srv) } -func _Bor_Debug_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DebugInput) +func _Bor_Pprof_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PprofRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BorServer).Debug(ctx, in) + return srv.(BorServer).Pprof(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.Bor/Debug", + FullMethod: "/proto.Bor/Pprof", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BorServer).Debug(ctx, req.(*DebugInput)) + return srv.(BorServer).Pprof(ctx, req.(*PprofRequest)) } return interceptor(ctx, in, info, handler) } @@ -92,8 +92,8 @@ var Bor_ServiceDesc = grpc.ServiceDesc{ HandlerType: (*BorServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "Debug", - Handler: _Bor_Debug_Handler, + MethodName: "Pprof", + Handler: _Bor_Pprof_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/command/server/service.go b/command/server/service.go index f6a41281b0f0c9f7fc549f9604a9442eb0484d8c..86ab38d5e2db8bd20510b244afa26bd1d6a7e9ef 100644 --- a/command/server/service.go +++ b/command/server/service.go @@ -2,10 +2,32 @@ package server import ( "context" + "encoding/hex" + "github.com/ethereum/go-ethereum/command/server/pprof" "github.com/ethereum/go-ethereum/command/server/proto" ) -func (s *Server) Debug(ctx context.Context, req *proto.DebugInput) (*proto.DebugInput, error) { - return &proto.DebugInput{}, nil +func (s *Server) Pprof(ctx context.Context, req *proto.PprofRequest) (*proto.PprofResponse, error) { + var payload []byte + var headers map[string]string + var err error + + switch req.Type { + case proto.PprofRequest_CPU: + payload, headers, err = pprof.CPUProfile(ctx, int(req.Seconds)) + case proto.PprofRequest_TRACE: + payload, headers, err = pprof.Trace(ctx, int(req.Seconds)) + case proto.PprofRequest_LOOKUP: + payload, headers, err = pprof.Profile(req.Profile, 0, 0) + } + if err != nil { + return nil, err + } + + resp := &proto.PprofResponse{ + Payload: hex.EncodeToString(payload), + Headers: headers, + } + return resp, nil }