Skip to content

Commit

Permalink
Merge pull request ethereum#208 from maticnetwork/feature-grpc-server
Browse files Browse the repository at this point in the history
Feature grpc server
  • Loading branch information
ferranbt authored Oct 26, 2021
2 parents 7bb4884 + 765a4b9 commit 026d0de
Show file tree
Hide file tree
Showing 15 changed files with 1,097 additions and 221 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ profile.cov

**/yarn-error.log
./test
./bor-debug-*
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ GO ?= latest
GORUN = env GO111MODULE=on go run
GOPATH = $(shell go env GOPATH)

protoc:
protoc --go_out=. --go-grpc_out=. ./command/server/proto/*.proto

bor:
$(GORUN) build/ci.go install ./cmd/geth
mkdir -p $(GOPATH)/bin/
Expand Down
242 changes: 242 additions & 0 deletions command/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package main

// Based on https://github.com/hashicorp/nomad/blob/main/command/operator_debug.go

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
func (d *DebugCommand) Help() string {
return `Usage: bor debug
Build an archive containing Bor pprof traces
` + d.Flags().Help()
}

func (d *DebugCommand) Flags() *flagset.Flagset {
flags := d.NewFlagSet("debug")

flags.Uint64Flag(&flagset.Uint64Flag{
Name: "seconds",
Usage: "seconds to trace",
Value: &d.seconds,
Default: 5,
})
flags.StringFlag(&flagset.StringFlag{
Name: "output",
Value: &d.output,
Usage: "Output directory",
})

return flags
}

// Synopsis implements the cli.Command interface
func (d *DebugCommand) Synopsis() string {
return "Build an archive containing Bor pprof traces"
}

// 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))
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
})
}
44 changes: 44 additions & 0 deletions command/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/command/flagset"
"github.com/ethereum/go-ethereum/command/server"
"github.com/ethereum/go-ethereum/command/server/proto"
"github.com/ethereum/go-ethereum/node"
"github.com/mitchellh/cli"
"github.com/ryanuber/columnize"
"google.golang.org/grpc"
)

func main() {
Expand Down Expand Up @@ -40,6 +42,9 @@ func commands() map[string]cli.CommandFactory {
ErrorWriter: os.Stderr,
}

meta2 := &Meta2{
UI: ui,
}
meta := &Meta{
UI: ui,
}
Expand All @@ -54,6 +59,11 @@ func commands() map[string]cli.CommandFactory {
UI: ui,
}, nil
},
"debug": func() (cli.Command, error) {
return &DebugCommand{
Meta2: meta2,
}, nil
},
"account": func() (cli.Command, error) {
return &Account{
UI: ui,
Expand All @@ -77,6 +87,40 @@ func commands() map[string]cli.CommandFactory {
}
}

type Meta2 struct {
UI cli.Ui

addr string
}

func (m *Meta2) NewFlagSet(n string) *flagset.Flagset {
f := flagset.NewFlagSet(n)

f.StringFlag(&flagset.StringFlag{
Name: "address",
Value: &m.addr,
Usage: "Address of the grpc endpoint",
Default: "127.0.0.1:3131",
})
return f
}

func (m *Meta2) Conn() (*grpc.ClientConn, error) {
conn, err := grpc.Dial(m.addr, grpc.WithInsecure())
if err != nil {
return nil, fmt.Errorf("failed to connect to server: %v", err)
}
return conn, nil
}

func (m *Meta2) BorConn() (proto.BorClient, error) {
conn, err := m.Conn()
if err != nil {
return nil, err
}
return proto.NewBorClient(conn), nil
}

// Meta is a helper utility for the commands
type Meta struct {
UI cli.Ui
Expand Down
Loading

0 comments on commit 026d0de

Please sign in to comment.