Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

rpc: implement internal debug_ API namespace functions #313

Merged
merged 35 commits into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fd98764
API Hello World
hanchon Jul 14, 2021
09c3097
Merge branch 'main' of github.com:hanchon/ethermint into main
hanchon Jul 14, 2021
9739bab
Added all the debug functions + more data to try implementing the GC …
hanchon Jul 14, 2021
829b24f
Getting transactions information
hanchon Jul 14, 2021
25fb968
Added cpu profile first approach functions
hanchon Jul 15, 2021
3f3ad5a
new struct for cpuprofile and read filename from params
ramacarlucho Jul 15, 2021
5e57a11
cpuprofile, gcstats and memstats
ramacarlucho Jul 15, 2021
96088d6
added comment
ramacarlucho Jul 15, 2021
94e1d51
All endpoints returns error instead of string
hanchon Jul 16, 2021
e1c32e6
Code cleanup
hanchon Jul 16, 2021
87ebd2c
Changed errors messages to match go-eth returns
hanchon Jul 16, 2021
bc37e5d
Removed activated flag and just using the file to check if it's running
hanchon Jul 16, 2021
6ea07ce
Added new endpoints to the json_rpc.md file
hanchon Jul 16, 2021
347197f
GoTrace debug endpoints added
hanchon Jul 16, 2021
f208766
Block profile endpoint added
hanchon Jul 16, 2021
82c0f32
missing goeth calls
ramacarlucho Jul 16, 2021
f03e310
added debug logs
ramacarlucho Jul 16, 2021
89858a1
divide debug and internal api
ramacarlucho Jul 16, 2021
47ee6b4
Using ExpandHome on server configuration
hanchon Jul 16, 2021
c512912
Added rpc changes to changelog
hanchon Jul 16, 2021
18e46f1
Logging go trace status
hanchon Jul 16, 2021
df1e0ed
Removed logger functions and moved logger errors to debug
hanchon Jul 16, 2021
2318af0
Added more logs to go trace
hanchon Jul 16, 2021
481b6d9
Added more datailed changelog
hanchon Jul 16, 2021
461633b
Removed trace debug api interface
hanchon Jul 16, 2021
c3f63ce
added comments
ramacarlucho Jul 16, 2021
324e877
cleanup
ramacarlucho Jul 16, 2021
22e353f
Merge branch 'tharsis:main' into debug_api
hanchon Jul 16, 2021
54bce0a
Updated changelog
hanchon Jul 16, 2021
53ef595
disable lint on cpuprofile rename
ramacarlucho Jul 19, 2021
c8b5b0f
return error in StopCpuProfile
ramacarlucho Jul 19, 2021
eaf95ae
return error in StopGoTrace
ramacarlucho Jul 19, 2021
3ac327a
implement suggested changes
ramacarlucho Jul 19, 2021
51b9f30
Resolve Changelog conflicts
hanchon Jul 19, 2021
aa47333
Merge branch 'main' into debug_api
fedekunze Jul 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (rpc) [tharsis#112](https://github.com/tharsis/ethermint/pull/153) Fix `eth_coinbase` to return the ethereum address of the validator
* (rpc) [tharsis#176](https://github.com/tharsis/ethermint/issues/176) Support fetching pending nonce
* (rpc) [tharsis#272](https://github.com/tharsis/ethermint/pull/272) do binary search to estimate gas accurately
* (rpc) [#313](https://github.com/tharsis/ethermint/pull/313) Implement internal debug namespace (Not including logger functions nor traces).

### Bug Fixes

Expand Down
31 changes: 18 additions & 13 deletions docs/basics/json_rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,25 @@ Check the JSON-RPC methods and namespaces supported on Ethermint. {synopsis}
| `clique_discard` | Clique | | |
| `clique_status` | Clique | | |
| `debug_backtraceAt` | Debug | | |
| `debug_blockProfile` | Debug | | |
| `debug_cpuProfile` | Debug | | |
| `debug_blockProfile` | Debug | | |
| `debug_cpuProfile` | Debug | | |
| `debug_dumpBlock` | Debug | | |
| `debug_gcStats` | Debug | | |
| `debug_gcStats` | Debug | | |
| `debug_getBlockRlp` | Debug | | |
| `debug_goTrace` | Debug | | |
| `debug_memStats` | Debug | | |
| `debug_goTrace` | Debug | ✔ | |
| `debug_freeOSMemory` | Debug | ✔ | |
| `debug_memStats` | Debug | ✔ | |
| `debug_mutexProfile` | Debug | ✔ | |
| `debug_seedHash` | Debug | | |
| `debug_setHead` | Debug | | |
| `debug_setBlockProfileRate` | Debug | | |
| `debug_stacks` | Debug | | |
| `debug_startCPUProfile` | Debug | | |
| `debug_startGoTrace` | Debug | | |
| `debug_stopCPUProfile` | Debug | | |
| `debug_stopGoTrace` | Debug | | |
| `debug_setBlockProfileRate` | Debug | ✔ | |
| `debug_setGCPercent` | Debug | ✔ | |
| `debug_setMutexProfileFraction` | Debug | ✔ | |
| `debug_stacks` | Debug | ✔ | |
| `debug_startCPUProfile` | Debug | ✔ | |
| `debug_startGoTrace` | Debug | ✔ | |
| `debug_stopCPUProfile` | Debug | ✔ | |
| `debug_stopGoTrace` | Debug | ✔ | |
| `debug_traceBlock` | Debug | | |
| `debug_traceBlockByNumber` | Debug | | |
| `debug_traceBlockByHash` | Debug | | |
Expand All @@ -127,8 +131,9 @@ Check the JSON-RPC methods and namespaces supported on Ethermint. {synopsis}
| `debug_traceTransaction` | Debug | | |
| `debug_verbosity` | Debug | | |
| `debug_vmodule` | Debug | | |
| `debug_writeBlockProfile` | Debug | | |
| `debug_writeMemProfile` | Debug | | |
| `debug_writeBlockProfile` | Debug | ✔ | |
| `debug_writeMemProfile` | Debug | ✔ | |
| `debug_writeMutexProfile` | Debug | ✔ | |
| `les_serverInfo` | Les | | |
| `les_clientInfo` | Les | | |
| `les_priorityClientInfo` | Les | | |
Expand Down
8 changes: 8 additions & 0 deletions ethereum/rpc/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/ethereum/go-ethereum/rpc"
"github.com/tharsis/ethermint/ethereum/rpc/backend"
"github.com/tharsis/ethermint/ethereum/rpc/namespaces/debug"
"github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth"
"github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth/filters"
"github.com/tharsis/ethermint/ethereum/rpc/namespaces/net"
Expand All @@ -26,6 +27,7 @@ const (
PersonalNamespace = "personal"
NetNamespace = "net"
TxPoolNamespace = "txpool"
DebugNamespace = "debug"

apiVersion = "1.0"
)
Expand Down Expand Up @@ -73,5 +75,11 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl
Service: txpool.NewPublicAPI(ctx.Logger),
Public: true,
},
{
Namespace: DebugNamespace,
Version: apiVersion,
Service: debug.NewInternalAPI(ctx),
Public: true,
},
}
}
216 changes: 216 additions & 0 deletions ethereum/rpc/namespaces/debug/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package debug

import (
"bytes"
"errors"
"io"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
"sync"
"time"

"github.com/cosmos/cosmos-sdk/server"
"github.com/tendermint/tendermint/libs/log"
)

// HandlerT keeps track of the cpu profiler and trace execution
type HandlerT struct {
cpuFilename string
cpuFile io.WriteCloser
mu sync.Mutex
traceFilename string
traceFile io.WriteCloser
}

// InternalAPI is the debug_ prefixed set of APIs in the Debug JSON-RPC spec.
type InternalAPI struct {
ctx *server.Context
logger log.Logger
handler *HandlerT
}

// NewInternalAPI creates an instance of the Debug API.
func NewInternalAPI(
ctx *server.Context,
) *InternalAPI {
return &InternalAPI{
ctx: ctx,
logger: ctx.Logger.With("module", "debug"),
handler: new(HandlerT),
}
}

// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
// file. It uses a profile rate of 1 for most accurate information. If a different rate is
// desired, set the rate and write the profile manually.
func (a *InternalAPI) BlockProfile(file string, nsec uint) error {
a.logger.Debug("debug_blockProfile", "file", file, "nsec", nsec)
runtime.SetBlockProfileRate(1)
defer runtime.SetBlockProfileRate(0)

time.Sleep(time.Duration(nsec) * time.Second)
return writeProfile("block", file, a.logger)
}

// CpuProfile turns on CPU profiling for nsec seconds and writes
// profile data to file.
func (a *InternalAPI) CpuProfile(file string, nsec uint) error { // nolint: golint
a.logger.Debug("debug_cpuProfile", "file", file, "nsec", nsec)
if err := a.StartCPUProfile(file); err != nil {
return err
}
time.Sleep(time.Duration(nsec) * time.Second)
return a.StopCPUProfile()
}

// GcStats returns GC statistics.
func (a *InternalAPI) GcStats() *debug.GCStats {
a.logger.Debug("debug_gcStats")
s := new(debug.GCStats)
debug.ReadGCStats(s)
return s
}

// GoTrace turns on tracing for nsec seconds and writes
// trace data to file.
func (a *InternalAPI) GoTrace(file string, nsec uint) error {
a.logger.Debug("debug_goTrace", "file", file, "nsec", nsec)
if err := a.StartGoTrace(file); err != nil {
return err
}
time.Sleep(time.Duration(nsec) * time.Second)
return a.StopGoTrace()
}

// MemStats returns detailed runtime memory statistics.
func (a *InternalAPI) MemStats() *runtime.MemStats {
a.logger.Debug("debug_memStats")
s := new(runtime.MemStats)
runtime.ReadMemStats(s)
return s
}

// SetBlockProfileRate sets the rate of goroutine block profile data collection.
// rate 0 disables block profiling.
func (a *InternalAPI) SetBlockProfileRate(rate int) {
a.logger.Debug("debug_setBlockProfileRate", "rate", rate)
runtime.SetBlockProfileRate(rate)
}

// Stacks returns a printed representation of the stacks of all goroutines.
func (a *InternalAPI) Stacks() string {
a.logger.Debug("debug_stacks")
buf := new(bytes.Buffer)
err := pprof.Lookup("goroutine").WriteTo(buf, 2)
if err != nil {
a.logger.Error("Failed to create stacks", "error", err.Error())
}
return buf.String()
}

// StartCPUProfile turns on CPU profiling, writing to the given file.
func (a *InternalAPI) StartCPUProfile(file string) error {
a.logger.Debug("debug_startCPUProfile", "file", file)
a.handler.mu.Lock()
defer a.handler.mu.Unlock()

switch {
case isCPUProfileConfigurationActivated(a.ctx):
a.logger.Debug("CPU profiling already in progress using the configuration file")
return errors.New("CPU profiling already in progress using the configuration file")
case a.handler.cpuFile != nil:
a.logger.Debug("CPU profiling already in progress")
return errors.New("CPU profiling already in progress")
default:
f, err := os.Create(ExpandHome(file))
if err != nil {
a.logger.Debug("failed to create CPU profile file", "error", err.Error())
return err
}
if err := pprof.StartCPUProfile(f); err != nil {
a.logger.Debug("cpu profiling already in use", "error", err.Error())
f.Close()
return err
}

a.logger.Info("CPU profiling started", "profile", file)
a.handler.cpuFile = f
a.handler.cpuFilename = file
return nil
}
}

// StopCPUProfile stops an ongoing CPU profile.
func (a *InternalAPI) StopCPUProfile() error {
a.logger.Debug("debug_stopCPUProfile")
a.handler.mu.Lock()
defer a.handler.mu.Unlock()

switch {
case isCPUProfileConfigurationActivated(a.ctx):
a.logger.Debug("CPU profiling already in progress using the configuration file")
return errors.New("CPU profiling already in progress using the configuration file")
case a.handler.cpuFile != nil:
a.logger.Info("Done writing CPU profile", "profile", a.handler.cpuFilename)
pprof.StopCPUProfile()
a.handler.cpuFile.Close()
a.handler.cpuFile = nil
a.handler.cpuFilename = ""
return nil
default:
a.logger.Debug("CPU profiling not in progress")
return errors.New("CPU profiling not in progress")
}
}

// WriteBlockProfile writes a goroutine blocking profile to the given file.
func (a *InternalAPI) WriteBlockProfile(file string) error {
a.logger.Debug("debug_writeBlockProfile", "file", file)
return writeProfile("block", file, a.logger)
}

// WriteMemProfile writes an allocation profile to the given file.
// Note that the profiling rate cannot be set through the API,
// it must be set on the command line.
func (a *InternalAPI) WriteMemProfile(file string) error {
a.logger.Debug("debug_writeMemProfile", "file", file)
return writeProfile("heap", file, a.logger)
}

// MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file.
// It uses a profile rate of 1 for most accurate information. If a different rate is
// desired, set the rate and write the profile manually.
func (a *InternalAPI) MutexProfile(file string, nsec uint) error {
a.logger.Debug("debug_mutexProfile", "file", file, "nsec", nsec)
runtime.SetMutexProfileFraction(1)
time.Sleep(time.Duration(nsec) * time.Second)
defer runtime.SetMutexProfileFraction(0)
return writeProfile("mutex", file, a.logger)
}

// SetMutexProfileFraction sets the rate of mutex profiling.
func (a *InternalAPI) SetMutexProfileFraction(rate int) {
a.logger.Debug("debug_setMutexProfileFraction", "rate", rate)
runtime.SetMutexProfileFraction(rate)
}

// WriteMutexProfile writes a goroutine blocking profile to the given file.
func (a *InternalAPI) WriteMutexProfile(file string) error {
a.logger.Debug("debug_writeMutexProfile", "file", file)
return writeProfile("mutex", file, a.logger)
}

// FreeOSMemory forces a garbage collection.
func (a *InternalAPI) FreeOSMemory() {
a.logger.Debug("debug_freeOSMemory")
debug.FreeOSMemory()
}

// SetGCPercent sets the garbage collection target percentage. It returns the previous
// setting. A negative value disables GC.
func (a *InternalAPI) SetGCPercent(v int) int {
a.logger.Debug("debug_setGCPercent", "percent", v)
return debug.SetGCPercent(v)
}
69 changes: 69 additions & 0 deletions ethereum/rpc/namespaces/debug/trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

//+build go1.5

package debug

import (
"errors"
"os"
"runtime/trace"
)

// StartGoTrace turns on tracing, writing to the given file.
func (a *InternalAPI) StartGoTrace(file string) error {
a.logger.Debug("debug_stopGoTrace", "file", file)
a.handler.mu.Lock()
defer a.handler.mu.Unlock()

if a.handler.traceFile != nil {
a.logger.Debug("trace already in progress")
return errors.New("trace already in progress")
}
f, err := os.Create(ExpandHome(file))
if err != nil {
a.logger.Debug("failed to create go trace file", "error", err.Error())
return err
}
if err := trace.Start(f); err != nil {
a.logger.Debug("Go tracing already started", "error", err.Error())
f.Close()
return err
}
a.handler.traceFile = f
a.handler.traceFilename = file
a.logger.Info("Go tracing started", "dump", a.handler.traceFilename)
return nil
}

// StopGoTrace stops an ongoing trace.
func (a *InternalAPI) StopGoTrace() error {
a.logger.Debug("debug_stopGoTrace")
a.handler.mu.Lock()
defer a.handler.mu.Unlock()

trace.Stop()
if a.handler.traceFile == nil {
a.logger.Debug("trace not in progress")
return errors.New("trace not in progress")
}
a.logger.Info("Done writing Go trace", "dump", a.handler.traceFilename)
a.handler.traceFile.Close()
a.handler.traceFile = nil
a.handler.traceFilename = ""
return nil
}
Loading