forked from mirror/go-ethereum
Merge pull request #2151 from fjl/debug-api
internal/debug: APIs for profiling and tracingrelease/1.4
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