This repository has been archived by the owner on Apr 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rpc: implement internal
debug_
API namespace functions (#313)
* API Hello World * Added all the debug functions + more data to try implementing the GC functions * Getting transactions information * Added cpu profile first approach functions * new struct for cpuprofile and read filename from params * cpuprofile, gcstats and memstats * added comment * All endpoints returns error instead of string * Code cleanup * Changed errors messages to match go-eth returns * Removed activated flag and just using the file to check if it's running * Added new endpoints to the json_rpc.md file * GoTrace debug endpoints added * Block profile endpoint added * missing goeth calls * added debug logs * divide debug and internal api * Using ExpandHome on server configuration * Added rpc changes to changelog * Logging go trace status * Removed logger functions and moved logger errors to debug * Added more logs to go trace * Added more datailed changelog * Removed trace debug api interface * added comments * cleanup * Updated changelog * disable lint on cpuprofile rename Co-authored-by: Federico Kunze Küllmer <[email protected]> * return error in StopCpuProfile Co-authored-by: Federico Kunze Küllmer <[email protected]> * return error in StopGoTrace Co-authored-by: Federico Kunze Küllmer <[email protected]> * implement suggested changes Co-authored-by: ramacarlucho <[email protected]> Co-authored-by: Federico Kunze Küllmer <[email protected]>
- Loading branch information
1 parent
c67b4b1
commit 282eb13
Showing
8 changed files
with
403 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.