mirror of https://github.com/ethereum/go-ethereum
Merge pull request #2151 from fjl/debug-api
internal/debug: APIs for profiling and tracingpull/2161/head
commit
528dcc3814
@ -0,0 +1,192 @@ |
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// Package debug interfaces Go runtime debugging facilities.
|
||||||
|
// This package is mostly glue code making these facilities available
|
||||||
|
// through the CLI and RPC subsystem. If you want to use them from Go code,
|
||||||
|
// use package runtime instead.
|
||||||
|
package debug |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
"os/user" |
||||||
|
"path/filepath" |
||||||
|
"runtime" |
||||||
|
"runtime/pprof" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/logger" |
||||||
|
"github.com/ethereum/go-ethereum/logger/glog" |
||||||
|
) |
||||||
|
|
||||||
|
// Handler is the global debugging handler.
|
||||||
|
var Handler = new(HandlerT) |
||||||
|
|
||||||
|
// HandlerT implements the debugging API.
|
||||||
|
// Do not create values of this type, use the one
|
||||||
|
// in the Handler variable instead.
|
||||||
|
type HandlerT struct { |
||||||
|
mu sync.Mutex |
||||||
|
cpuW io.WriteCloser |
||||||
|
cpuFile string |
||||||
|
traceW io.WriteCloser |
||||||
|
traceFile string |
||||||
|
} |
||||||
|
|
||||||
|
// Verbosity sets the glog verbosity floor.
|
||||||
|
// The verbosity of individual packages and source files
|
||||||
|
// can be raised using Vmodule.
|
||||||
|
func (*HandlerT) Verbosity(level int) { |
||||||
|
glog.SetV(level) |
||||||
|
} |
||||||
|
|
||||||
|
// Vmodule sets the glog verbosity pattern. See package
|
||||||
|
// glog for details on pattern syntax.
|
||||||
|
func (*HandlerT) Vmodule(pattern string) error { |
||||||
|
return glog.GetVModule().Set(pattern) |
||||||
|
} |
||||||
|
|
||||||
|
// BacktraceAt sets the glog backtrace location.
|
||||||
|
// See package glog for details on pattern syntax.
|
||||||
|
func (*HandlerT) BacktraceAt(location string) error { |
||||||
|
return glog.GetTraceLocation().Set(location) |
||||||
|
} |
||||||
|
|
||||||
|
// CpuProfile turns on CPU profiling for nsec seconds and writes
|
||||||
|
// profile data to file.
|
||||||
|
func (h *HandlerT) CpuProfile(file string, nsec uint) error { |
||||||
|
if err := h.StartCPUProfile(file); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
time.Sleep(time.Duration(nsec) * time.Second) |
||||||
|
h.StopCPUProfile() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// StartCPUProfile turns on CPU profiling, writing to the given file.
|
||||||
|
func (h *HandlerT) StartCPUProfile(file string) error { |
||||||
|
h.mu.Lock() |
||||||
|
defer h.mu.Unlock() |
||||||
|
if h.cpuW != nil { |
||||||
|
return errors.New("CPU profiling already in progress") |
||||||
|
} |
||||||
|
f, err := os.Create(expandHome(file)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := pprof.StartCPUProfile(f); err != nil { |
||||||
|
f.Close() |
||||||
|
return err |
||||||
|
} |
||||||
|
h.cpuW = f |
||||||
|
h.cpuFile = file |
||||||
|
glog.V(logger.Info).Infoln("CPU profiling started, writing to", h.cpuFile) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// StopCPUProfile stops an ongoing CPU profile.
|
||||||
|
func (h *HandlerT) StopCPUProfile() error { |
||||||
|
h.mu.Lock() |
||||||
|
defer h.mu.Unlock() |
||||||
|
pprof.StopCPUProfile() |
||||||
|
if h.cpuW == nil { |
||||||
|
return errors.New("CPU profiling not in progress") |
||||||
|
} |
||||||
|
glog.V(logger.Info).Infoln("done writing CPU profile to", h.cpuFile) |
||||||
|
h.cpuW.Close() |
||||||
|
h.cpuW = nil |
||||||
|
h.cpuFile = "" |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Trace turns on tracing for nsec seconds and writes
|
||||||
|
// trace data to file.
|
||||||
|
func (h *HandlerT) Trace(file string, nsec uint) error { |
||||||
|
if err := h.StartTrace(file); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
time.Sleep(time.Duration(nsec) * time.Second) |
||||||
|
h.StopTrace() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// BlockProfile turns on CPU 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 (*HandlerT) BlockProfile(file string, nsec uint) error { |
||||||
|
runtime.SetBlockProfileRate(1) |
||||||
|
time.Sleep(time.Duration(nsec) * time.Second) |
||||||
|
defer runtime.SetBlockProfileRate(0) |
||||||
|
return writeProfile("block", file) |
||||||
|
} |
||||||
|
|
||||||
|
// SetBlockProfileRate sets the rate of goroutine block profile data collection.
|
||||||
|
// rate 0 disables block profiling.
|
||||||
|
func (*HandlerT) SetBlockProfileRate(rate int) { |
||||||
|
runtime.SetBlockProfileRate(rate) |
||||||
|
} |
||||||
|
|
||||||
|
// WriteBlockProfile writes a goroutine blocking profile to the given file.
|
||||||
|
func (*HandlerT) WriteBlockProfile(file string) error { |
||||||
|
return writeProfile("block", file) |
||||||
|
} |
||||||
|
|
||||||
|
// 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 (*HandlerT) WriteMemProfile(file string) error { |
||||||
|
return writeProfile("heap", file) |
||||||
|
} |
||||||
|
|
||||||
|
// Stacks returns a printed representation of the stacks of all goroutines.
|
||||||
|
func (*HandlerT) Stacks() string { |
||||||
|
buf := make([]byte, 1024*1024) |
||||||
|
buf = buf[:runtime.Stack(buf, true)] |
||||||
|
return string(buf) |
||||||
|
} |
||||||
|
|
||||||
|
func writeProfile(name, file string) error { |
||||||
|
p := pprof.Lookup(name) |
||||||
|
glog.V(logger.Info).Infof("writing %d %s profile records to %s", p.Count(), name, file) |
||||||
|
f, err := os.Create(expandHome(file)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer f.Close() |
||||||
|
return p.WriteTo(f, 0) |
||||||
|
} |
||||||
|
|
||||||
|
// expands home directory in file paths.
|
||||||
|
// ~someuser/tmp will not be expanded.
|
||||||
|
func expandHome(p string) string { |
||||||
|
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { |
||||||
|
home := os.Getenv("HOME") |
||||||
|
if home == "" { |
||||||
|
if usr, err := user.Current(); err == nil { |
||||||
|
home = usr.HomeDir |
||||||
|
} |
||||||
|
} |
||||||
|
if home != "" { |
||||||
|
p = home + p[1:] |
||||||
|
} |
||||||
|
} |
||||||
|
return filepath.Clean(p) |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
package debug |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"net/http" |
||||||
|
_ "net/http/pprof" |
||||||
|
"runtime" |
||||||
|
|
||||||
|
"github.com/codegangsta/cli" |
||||||
|
"github.com/ethereum/go-ethereum/logger" |
||||||
|
"github.com/ethereum/go-ethereum/logger/glog" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
verbosityFlag = cli.GenericFlag{ |
||||||
|
Name: "verbosity", |
||||||
|
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=core, 5=debug, 6=detail", |
||||||
|
Value: glog.GetVerbosity(), |
||||||
|
} |
||||||
|
vmoduleFlag = cli.GenericFlag{ |
||||||
|
Name: "vmodule", |
||||||
|
Usage: "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=6,p2p=5)", |
||||||
|
Value: glog.GetVModule(), |
||||||
|
} |
||||||
|
backtraceAtFlag = cli.GenericFlag{ |
||||||
|
Name: "backtrace", |
||||||
|
Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")", |
||||||
|
Value: glog.GetTraceLocation(), |
||||||
|
} |
||||||
|
pprofFlag = cli.BoolFlag{ |
||||||
|
Name: "pprof", |
||||||
|
Usage: "Enable the pprof HTTP server", |
||||||
|
} |
||||||
|
pprofPortFlag = cli.IntFlag{ |
||||||
|
Name: "pprofport", |
||||||
|
Usage: "pprof HTTP server listening port", |
||||||
|
Value: 6060, |
||||||
|
} |
||||||
|
memprofilerateFlag = cli.IntFlag{ |
||||||
|
Name: "memprofilerate", |
||||||
|
Usage: "Turn on memory profiling with the given rate", |
||||||
|
} |
||||||
|
blockprofilerateFlag = cli.IntFlag{ |
||||||
|
Name: "blockprofilerate", |
||||||
|
Usage: "Turn on block profiling with the given rate", |
||||||
|
} |
||||||
|
cpuprofileFlag = cli.StringFlag{ |
||||||
|
Name: "cpuprofile", |
||||||
|
Usage: "Write CPU profile to the given file", |
||||||
|
} |
||||||
|
traceFlag = cli.StringFlag{ |
||||||
|
Name: "trace", |
||||||
|
Usage: "Write execution trace to the given file", |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// Flags holds all command-line flags required for debugging.
|
||||||
|
var Flags = []cli.Flag{ |
||||||
|
verbosityFlag, vmoduleFlag, backtraceAtFlag, |
||||||
|
pprofFlag, pprofPortFlag, |
||||||
|
memprofilerateFlag, blockprofilerateFlag, cpuprofileFlag, traceFlag, |
||||||
|
} |
||||||
|
|
||||||
|
// Setup initializes profiling and logging based on the CLI flags.
|
||||||
|
// It should be called as early as possible in the program.
|
||||||
|
func Setup(ctx *cli.Context) error { |
||||||
|
// logging
|
||||||
|
glog.CopyStandardLogTo("INFO") |
||||||
|
glog.SetToStderr(true) |
||||||
|
|
||||||
|
// profiling, tracing
|
||||||
|
runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) |
||||||
|
Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name)) |
||||||
|
if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" { |
||||||
|
if err := Handler.StartTrace(traceFile); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
if cpuFile := ctx.GlobalString(cpuprofileFlag.Name); cpuFile != "" { |
||||||
|
if err := Handler.StartCPUProfile(cpuFile); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// pprof server
|
||||||
|
if ctx.GlobalBool(pprofFlag.Name) { |
||||||
|
address := fmt.Sprintf("127.0.0.1:%d", ctx.GlobalInt(pprofPortFlag.Name)) |
||||||
|
go func() { |
||||||
|
glog.V(logger.Info).Infof("starting pprof server at http://%s/debug/pprof", address) |
||||||
|
glog.Errorln(http.ListenAndServe(address, nil)) |
||||||
|
}() |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Exit stops all running profiles, flushing their output to the
|
||||||
|
// respective file.
|
||||||
|
func Exit() { |
||||||
|
Handler.StopCPUProfile() |
||||||
|
Handler.StopTrace() |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
// 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" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/logger" |
||||||
|
"github.com/ethereum/go-ethereum/logger/glog" |
||||||
|
) |
||||||
|
|
||||||
|
// StartTrace turns on tracing, writing to the given file.
|
||||||
|
func (h *HandlerT) StartTrace(file string) error { |
||||||
|
h.mu.Lock() |
||||||
|
defer h.mu.Unlock() |
||||||
|
if h.traceW != nil { |
||||||
|
return errors.New("trace already in progress") |
||||||
|
} |
||||||
|
f, err := os.Create(expandHome(file)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := trace.Start(f); err != nil { |
||||||
|
f.Close() |
||||||
|
return err |
||||||
|
} |
||||||
|
h.traceW = f |
||||||
|
h.traceFile = file |
||||||
|
glog.V(logger.Info).Infoln("trace started, writing to", h.traceFile) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// StopTrace stops an ongoing trace.
|
||||||
|
func (h *HandlerT) StopTrace() error { |
||||||
|
h.mu.Lock() |
||||||
|
defer h.mu.Unlock() |
||||||
|
trace.Stop() |
||||||
|
if h.traceW == nil { |
||||||
|
return errors.New("trace not in progress") |
||||||
|
} |
||||||
|
glog.V(logger.Info).Infoln("done writing trace to", h.traceFile) |
||||||
|
h.traceW.Close() |
||||||
|
h.traceW = nil |
||||||
|
h.traceFile = "" |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
// 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
|
||||||
|
|
||||||
|
// no-op implementation of tracing methods for Go < 1.5.
|
||||||
|
|
||||||
|
package debug |
||||||
|
|
||||||
|
import "errors" |
||||||
|
|
||||||
|
func (*HandlerT) StartTrace(string) error { |
||||||
|
return errors.New("tracing is not supported on Go < 1.5") |
||||||
|
} |
||||||
|
|
||||||
|
func (*HandlerT) StopTrace() error { |
||||||
|
return errors.New("tracing is not supported on Go < 1.5") |
||||||
|
} |
Loading…
Reference in new issue