Skip to content

Commit 8e4435f

Browse files
committed
Added pprof for profiling
1 parent 421369e commit 8e4435f

File tree

8 files changed

+635
-0
lines changed

8 files changed

+635
-0
lines changed

cmd/bridge/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/axieinfinity/bridge-v2/cmd/utils"
1515
"github.com/axieinfinity/bridge-v2/configs"
1616
"github.com/axieinfinity/bridge-v2/internal"
17+
"github.com/axieinfinity/bridge-v2/internal/debug"
1718
"github.com/axieinfinity/bridge-v2/internal/migration"
1819
"github.com/axieinfinity/bridge-v2/internal/stores"
1920
"github.com/axieinfinity/bridge-v2/internal/types"
@@ -70,6 +71,14 @@ func init() {
7071
app.HideVersion = true // we have a command to print the version
7172
app.Copyright = "Copyright 2022 The Sky Mavis Authors"
7273
app.Flags = append(app.Flags, ConfigFlag, LogLvlFlag)
74+
app.Flags = append(app.Flags, debug.Flags...)
75+
app.Before = func(ctx *cli.Context) error {
76+
return debug.Setup(ctx)
77+
}
78+
app.After = func(ctx *cli.Context) error {
79+
debug.Exit()
80+
return nil
81+
}
7382
app.Commands = []cli.Command{
7483
cleanerCommand,
7584
}

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ go 1.17
44

55
require (
66
github.com/ethereum/go-ethereum v1.10.17
7+
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
78
github.com/go-co-op/gocron v1.13.0
89
github.com/go-gormigrate/gormigrate/v2 v2.0.1
10+
github.com/hashicorp/go-bexpr v0.1.10
11+
github.com/mattn/go-colorable v0.1.8
12+
github.com/mattn/go-isatty v0.0.12
913
github.com/stretchr/testify v1.7.1
1014
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
1115
golang.org/x/text v0.3.7
@@ -41,6 +45,8 @@ require (
4145
github.com/kelseyhightower/envconfig v1.4.0 // indirect
4246
github.com/mattn/go-sqlite3 v1.14.12 // indirect
4347
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
48+
github.com/mitchellh/mapstructure v1.4.1 // indirect
49+
github.com/mitchellh/pointerstructure v1.2.0 // indirect
4450
github.com/pmezard/go-difflib v1.0.0 // indirect
4551
github.com/prometheus/client_golang v1.12.2 // indirect
4652
github.com/prometheus/client_model v0.2.0 // indirect

internal/debug/api.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
// Copyright 2016 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
// Package debug interfaces Go runtime debugging facilities.
18+
// This package is mostly glue code making these facilities available
19+
// through the CLI and RPC subsystem. If you want to use them from Go code,
20+
// use package runtime instead.
21+
package debug
22+
23+
import (
24+
"bytes"
25+
"errors"
26+
"io"
27+
"os"
28+
"os/user"
29+
"path/filepath"
30+
"regexp"
31+
"runtime"
32+
"runtime/debug"
33+
"runtime/pprof"
34+
"strings"
35+
"sync"
36+
"time"
37+
38+
"github.com/ethereum/go-ethereum/log"
39+
"github.com/hashicorp/go-bexpr"
40+
)
41+
42+
// Handler is the global debugging handler.
43+
var Handler = new(HandlerT)
44+
45+
// HandlerT implements the debugging API.
46+
// Do not create values of this type, use the one
47+
// in the Handler variable instead.
48+
type HandlerT struct {
49+
mu sync.Mutex
50+
cpuW io.WriteCloser
51+
cpuFile string
52+
traceW io.WriteCloser
53+
traceFile string
54+
}
55+
56+
// Verbosity sets the log verbosity ceiling. The verbosity of individual packages
57+
// and source files can be raised using Vmodule.
58+
func (*HandlerT) Verbosity(level int) {
59+
glogger.Verbosity(log.Lvl(level))
60+
}
61+
62+
// Vmodule sets the log verbosity pattern. See package log for details on the
63+
// pattern syntax.
64+
func (*HandlerT) Vmodule(pattern string) error {
65+
return glogger.Vmodule(pattern)
66+
}
67+
68+
// BacktraceAt sets the log backtrace location. See package log for details on
69+
// the pattern syntax.
70+
func (*HandlerT) BacktraceAt(location string) error {
71+
return glogger.BacktraceAt(location)
72+
}
73+
74+
// MemStats returns detailed runtime memory statistics.
75+
func (*HandlerT) MemStats() *runtime.MemStats {
76+
s := new(runtime.MemStats)
77+
runtime.ReadMemStats(s)
78+
return s
79+
}
80+
81+
// GcStats returns GC statistics.
82+
func (*HandlerT) GcStats() *debug.GCStats {
83+
s := new(debug.GCStats)
84+
debug.ReadGCStats(s)
85+
return s
86+
}
87+
88+
// CpuProfile turns on CPU profiling for nsec seconds and writes
89+
// profile data to file.
90+
func (h *HandlerT) CpuProfile(file string, nsec uint) error {
91+
if err := h.StartCPUProfile(file); err != nil {
92+
return err
93+
}
94+
time.Sleep(time.Duration(nsec) * time.Second)
95+
h.StopCPUProfile()
96+
return nil
97+
}
98+
99+
// StartCPUProfile turns on CPU profiling, writing to the given file.
100+
func (h *HandlerT) StartCPUProfile(file string) error {
101+
h.mu.Lock()
102+
defer h.mu.Unlock()
103+
if h.cpuW != nil {
104+
return errors.New("CPU profiling already in progress")
105+
}
106+
f, err := os.Create(expandHome(file))
107+
if err != nil {
108+
return err
109+
}
110+
if err := pprof.StartCPUProfile(f); err != nil {
111+
f.Close()
112+
return err
113+
}
114+
h.cpuW = f
115+
h.cpuFile = file
116+
log.Info("CPU profiling started", "dump", h.cpuFile)
117+
return nil
118+
}
119+
120+
// StopCPUProfile stops an ongoing CPU profile.
121+
func (h *HandlerT) StopCPUProfile() error {
122+
h.mu.Lock()
123+
defer h.mu.Unlock()
124+
pprof.StopCPUProfile()
125+
if h.cpuW == nil {
126+
return errors.New("CPU profiling not in progress")
127+
}
128+
log.Info("Done writing CPU profile", "dump", h.cpuFile)
129+
h.cpuW.Close()
130+
h.cpuW = nil
131+
h.cpuFile = ""
132+
return nil
133+
}
134+
135+
// GoTrace turns on tracing for nsec seconds and writes
136+
// trace data to file.
137+
func (h *HandlerT) GoTrace(file string, nsec uint) error {
138+
if err := h.StartGoTrace(file); err != nil {
139+
return err
140+
}
141+
time.Sleep(time.Duration(nsec) * time.Second)
142+
h.StopGoTrace()
143+
return nil
144+
}
145+
146+
// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
147+
// file. It uses a profile rate of 1 for most accurate information. If a different rate is
148+
// desired, set the rate and write the profile manually.
149+
func (*HandlerT) BlockProfile(file string, nsec uint) error {
150+
runtime.SetBlockProfileRate(1)
151+
time.Sleep(time.Duration(nsec) * time.Second)
152+
defer runtime.SetBlockProfileRate(0)
153+
return writeProfile("block", file)
154+
}
155+
156+
// SetBlockProfileRate sets the rate of goroutine block profile data collection.
157+
// rate 0 disables block profiling.
158+
func (*HandlerT) SetBlockProfileRate(rate int) {
159+
runtime.SetBlockProfileRate(rate)
160+
}
161+
162+
// WriteBlockProfile writes a goroutine blocking profile to the given file.
163+
func (*HandlerT) WriteBlockProfile(file string) error {
164+
return writeProfile("block", file)
165+
}
166+
167+
// MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file.
168+
// It uses a profile rate of 1 for most accurate information. If a different rate is
169+
// desired, set the rate and write the profile manually.
170+
func (*HandlerT) MutexProfile(file string, nsec uint) error {
171+
runtime.SetMutexProfileFraction(1)
172+
time.Sleep(time.Duration(nsec) * time.Second)
173+
defer runtime.SetMutexProfileFraction(0)
174+
return writeProfile("mutex", file)
175+
}
176+
177+
// SetMutexProfileFraction sets the rate of mutex profiling.
178+
func (*HandlerT) SetMutexProfileFraction(rate int) {
179+
runtime.SetMutexProfileFraction(rate)
180+
}
181+
182+
// WriteMutexProfile writes a goroutine blocking profile to the given file.
183+
func (*HandlerT) WriteMutexProfile(file string) error {
184+
return writeProfile("mutex", file)
185+
}
186+
187+
// WriteMemProfile writes an allocation profile to the given file.
188+
// Note that the profiling rate cannot be set through the API,
189+
// it must be set on the command line.
190+
func (*HandlerT) WriteMemProfile(file string) error {
191+
return writeProfile("heap", file)
192+
}
193+
194+
// Stacks returns a printed representation of the stacks of all goroutines. It
195+
// also permits the following optional filters to be used:
196+
// - filter: boolean expression of packages to filter for
197+
func (*HandlerT) Stacks(filter *string) string {
198+
buf := new(bytes.Buffer)
199+
pprof.Lookup("goroutine").WriteTo(buf, 2)
200+
201+
// If any filtering was requested, execute them now
202+
if filter != nil && len(*filter) > 0 {
203+
expanded := *filter
204+
205+
// The input filter is a logical expression of package names. Transform
206+
// it into a proper boolean expression that can be fed into a parser and
207+
// interpreter:
208+
//
209+
// E.g. (eth || snap) && !p2p -> (eth in Value || snap in Value) && p2p not in Value
210+
expanded = regexp.MustCompile(`[:/\.A-Za-z0-9_-]+`).ReplaceAllString(expanded, "`$0` in Value")
211+
expanded = regexp.MustCompile("!(`[:/\\.A-Za-z0-9_-]+`)").ReplaceAllString(expanded, "$1 not")
212+
expanded = strings.Replace(expanded, "||", "or", -1)
213+
expanded = strings.Replace(expanded, "&&", "and", -1)
214+
log.Info("Expanded filter expression", "filter", *filter, "expanded", expanded)
215+
216+
expr, err := bexpr.CreateEvaluator(expanded)
217+
if err != nil {
218+
log.Error("Failed to parse filter expression", "expanded", expanded, "err", err)
219+
return ""
220+
}
221+
// Split the goroutine dump into segments and filter each
222+
dump := buf.String()
223+
buf.Reset()
224+
225+
for _, trace := range strings.Split(dump, "\n\n") {
226+
if ok, _ := expr.Evaluate(map[string]string{"Value": trace}); ok {
227+
buf.WriteString(trace)
228+
buf.WriteString("\n\n")
229+
}
230+
}
231+
}
232+
return buf.String()
233+
}
234+
235+
// FreeOSMemory forces a garbage collection.
236+
func (*HandlerT) FreeOSMemory() {
237+
debug.FreeOSMemory()
238+
}
239+
240+
// SetGCPercent sets the garbage collection target percentage. It returns the previous
241+
// setting. A negative value disables GC.
242+
func (*HandlerT) SetGCPercent(v int) int {
243+
return debug.SetGCPercent(v)
244+
}
245+
246+
func writeProfile(name, file string) error {
247+
p := pprof.Lookup(name)
248+
log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file)
249+
f, err := os.Create(expandHome(file))
250+
if err != nil {
251+
return err
252+
}
253+
defer f.Close()
254+
return p.WriteTo(f, 0)
255+
}
256+
257+
// expands home directory in file paths.
258+
// ~someuser/tmp will not be expanded.
259+
func expandHome(p string) string {
260+
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
261+
home := os.Getenv("HOME")
262+
if home == "" {
263+
if usr, err := user.Current(); err == nil {
264+
home = usr.HomeDir
265+
}
266+
}
267+
if home != "" {
268+
p = home + p[1:]
269+
}
270+
}
271+
return filepath.Clean(p)
272+
}

0 commit comments

Comments
 (0)