Skip to content

Commit c8125b8

Browse files
authored
Merge pull request #217 from heroku/vmc-pprof
Refactor and Start PProf debug server
2 parents 1e0ca7c + 41c2c18 commit c8125b8

File tree

4 files changed

+116
-53
lines changed

4 files changed

+116
-53
lines changed

cmdutil/debug/config.go

+20
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,24 @@ package debug
33
// Config describes the configurable parameters for debugging.
44
type Config struct {
55
Port int `env:"DEBUG_PORT,default=9999"`
6+
PProf
7+
}
8+
9+
type PProf struct {
10+
Port int `env:"DEBUG_PPROF_PORT,default=9998"`
11+
// This controls how much of fraction of mutexes we need to consider for profiling.
12+
MutexProfileFraction int `env:"DBEUG_PPROF_MUTEX_PROFILE_FRACTION,default=2"`
13+
// This controls how much blocking time in nano seconds we need to consider.
14+
// Default 10 indicates, we consider all 10 ns blocking events .
15+
BlockProfileRate int `env:"DBEUG_PPROF_BLOCK_PROFILE_RATE,default=10"`
16+
// Heap Profiling is enabled by default.
17+
// This controls the frequency of heap allocation sampling.
18+
// It defines the number of bytes allocated between samples.
19+
// Default is actual default value of MemProfileRate
20+
MemProfileRate int `env:"DBEUG_PPROF_MEM_PROFILE_RATE,default=524288"`
21+
Enabled bool `env:"DEBUG_PPROF_ENABLE,default=false"`
22+
// Mutex Profiling is not enabled by default
23+
EnableMutexProfiling bool `env:"DEBUG_PPROF_MUTEX_PROFILE_ENABLE,default=false"`
24+
// Block Profiling is not enabled default
25+
EnableBlockProfiling bool `env:"DEBUG_PPROF_BLOCK_PROFILE_ENABLE,default=false"`
626
}

cmdutil/debug/debug.go

+70-27
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ package debug
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021
"net/http"
2122
"net/http/pprof"
2223
"runtime"
24+
"sync"
2325
"time"
2426

2527
"github.com/google/gops/agent"
@@ -31,25 +33,75 @@ import (
3133
// Connect to the debug server with gops:
3234
//
3335
// gops stack localhost:PORT
34-
func New(l logrus.FieldLogger, port int) *Server {
35-
return &Server{
36+
//
37+
// Connect to the pprof server with pprof.port when pprof enabled
38+
func New(l logrus.FieldLogger, config Config) *Server {
39+
server := &Server{
3640
logger: l,
37-
addr: fmt.Sprintf("127.0.0.1:%d", port),
41+
addr: fmt.Sprintf("127.0.0.1:%d", config.Port),
3842
done: make(chan struct{}),
3943
}
44+
if config.Enabled {
45+
server.pprof = NewPProfServer(l, &config.PProf)
46+
}
47+
return server
4048
}
4149

4250
// Server wraps a gops server for easy use with oklog/group.
4351
type Server struct {
4452
logger logrus.FieldLogger
4553
addr string
4654
done chan struct{}
55+
pprof *PProfServer
4756
}
4857

4958
// Run starts the debug server.
5059
//
51-
// It implements oklog group's runFn.
60+
// It implements oklog group's runFn and pprof.
5261
func (s *Server) Run() error {
62+
63+
var wg sync.WaitGroup
64+
var gopsErr error
65+
var pprofErr error
66+
67+
wg.Add(1)
68+
go func() {
69+
defer wg.Done()
70+
gopsErr = s.RunGOPS()
71+
if gopsErr != nil {
72+
s.logger.WithError(gopsErr).Error("gops server failed")
73+
}
74+
}()
75+
76+
if s.pprof != nil {
77+
wg.Add(1)
78+
go func() {
79+
defer wg.Done()
80+
pprofErr = s.pprof.Run()
81+
if pprofErr != nil {
82+
s.pprof.logger.WithError(pprofErr).Error("pprof server failed")
83+
}
84+
}()
85+
}
86+
87+
wg.Wait()
88+
89+
var err error
90+
if gopsErr != nil {
91+
err = fmt.Errorf("gops error: %w", gopsErr)
92+
}
93+
if pprofErr != nil {
94+
errPProf := fmt.Errorf("pprof error: %w", pprofErr)
95+
err = errors.Join(err, errPProf)
96+
}
97+
98+
return err
99+
}
100+
101+
// Run starts the debug server.
102+
//
103+
// It implements oklog group's runFn.
104+
func (s *Server) RunGOPS() error {
53105
s.logger.WithFields(logrus.Fields{
54106
"at": "binding",
55107
"service": "debug",
@@ -70,11 +122,15 @@ func (s *Server) Run() error {
70122

71123
// Stop shuts down the debug server.
72124
//
73-
// It implements oklog group's interruptFn.
125+
// It implements oklog group's interruptFn and pprof stop.
74126
func (s *Server) Stop(_ error) {
75127
agent.Close()
76128

77129
close(s.done)
130+
131+
if s.pprof != nil {
132+
s.pprof.Stop(nil)
133+
}
78134
}
79135

80136
// PProfServer wraps a pprof server.
@@ -85,45 +141,34 @@ type PProfServer struct {
85141
pprofServer *http.Server
86142
}
87143

88-
// ProfileConfig holds the configuration for the pprof server.
89-
type PProfServerConfig struct {
90-
Addr string
91-
MutexProfileFraction int
92-
}
144+
// NewPProfServer sets up a pprof server with configurable profiling types and returns a PProfServer instance.
145+
func NewPProfServer(l logrus.FieldLogger, pprofConfig *PProf) *PProfServer {
93146

94-
// defaultMutexProfileFraction is the default value for MutexProfileFraction
95-
const defaultMutexProfileFraction = 2
147+
runtime.MemProfileRate = pprofConfig.MemProfileRate
96148

97-
// NewPProfServer sets up a pprof server with configurable profiling types and returns a PProfServer instance.
98-
func NewPProfServer(config PProfServerConfig, l logrus.FieldLogger) *PProfServer {
99-
if config.Addr == "" {
100-
config.Addr = "127.0.0.1:9998" // Default port
149+
if pprofConfig.EnableMutexProfiling {
150+
runtime.SetMutexProfileFraction(pprofConfig.MutexProfileFraction)
101151
}
102152

103-
// Use a local variable for the mutex profile fraction
104-
mpf := defaultMutexProfileFraction
105-
if config.MutexProfileFraction != 0 {
106-
mpf = config.MutexProfileFraction
153+
if pprofConfig.EnableBlockProfiling {
154+
runtime.SetBlockProfileRate(pprofConfig.BlockProfileRate)
107155
}
108-
runtime.SetMutexProfileFraction(mpf)
109156

110157
httpServer := &http.Server{
111-
Addr: config.Addr,
158+
Addr: fmt.Sprintf("127.0.0.1:%d", pprofConfig.Port),
112159
Handler: http.HandlerFunc(pprof.Index),
113160
ReadHeaderTimeout: 5 * time.Second,
114161
}
115162

116163
return &PProfServer{
117164
logger: l,
118-
addr: config.Addr,
165+
addr: httpServer.Addr,
119166
done: make(chan struct{}),
120167
pprofServer: httpServer,
121168
}
122169
}
123170

124171
// Run starts the pprof server.
125-
//
126-
// It implements oklog group's runFn.
127172
func (s *PProfServer) Run() error {
128173
if s.pprofServer == nil {
129174
return fmt.Errorf("pprofServer is nil")
@@ -144,8 +189,6 @@ func (s *PProfServer) Run() error {
144189
}
145190

146191
// Stop shuts down the pprof server.
147-
//
148-
// It implements oklog group's interruptFn.
149192
func (s *PProfServer) Stop(_ error) {
150193
if s.pprofServer != nil {
151194
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

cmdutil/debug/debug_test.go

+25-25
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,46 @@ func TestNewPProfServer(t *testing.T) {
1313
logger := logrus.New()
1414

1515
tests := []struct {
16-
name string
17-
config PProfServerConfig
18-
expectedAddr string
19-
expectedMutexFraction int
16+
name string
17+
expectedAddr string
18+
pprofConfig *PProf
19+
expectedMemProfileRate int
2020
}{
2121
{
22-
name: "DefaultAddr",
23-
config: PProfServerConfig{},
24-
expectedAddr: "127.0.0.1:9998",
25-
expectedMutexFraction: defaultMutexProfileFraction,
22+
name: "test port as 9998 and mpf as 2",
23+
expectedAddr: "127.0.0.1:9998",
24+
pprofConfig: &PProf{
25+
Port: 9998,
26+
Enabled: true,
27+
MemProfileRate: 524288,
28+
},
29+
expectedMemProfileRate: 524288,
2630
},
2731
{
28-
name: "CustomAddr",
29-
config: PProfServerConfig{Addr: "127.0.0.1:9090"},
30-
expectedAddr: "127.0.0.1:9090",
31-
expectedMutexFraction: defaultMutexProfileFraction,
32-
},
33-
{
34-
name: "CustomMutexProfileFraction",
35-
config: PProfServerConfig{MutexProfileFraction: 5},
36-
expectedAddr: "127.0.0.1:9998",
37-
expectedMutexFraction: 5,
32+
name: "test port as 9997 and mpf as 4",
33+
expectedAddr: "127.0.0.1:9997",
34+
pprofConfig: &PProf{
35+
Port: 9997,
36+
Enabled: true,
37+
MemProfileRate: 524287,
38+
},
39+
expectedMemProfileRate: 524287,
3840
},
3941
}
4042

4143
for _, tt := range tests {
4244
t.Run(tt.name, func(t *testing.T) {
43-
server := NewPProfServer(tt.config, logger)
45+
server := NewPProfServer(logger, tt.pprofConfig)
4446

4547
// Check server address
4648
if server.addr != tt.expectedAddr {
4749
t.Errorf("NewPProfServer() addr = %v, want %v", server.addr, tt.expectedAddr)
4850
}
4951

52+
if runtime.MemProfileRate != tt.expectedMemProfileRate {
53+
t.Errorf("MemProfileRate expected %v, got %v", tt.expectedMemProfileRate, runtime.MemProfileRate)
54+
}
55+
5056
// Start the server
5157
go func() {
5258
if err := server.Run(); err != nil {
@@ -57,12 +63,6 @@ func TestNewPProfServer(t *testing.T) {
5763
// Give the server a moment to start
5864
time.Sleep(100 * time.Millisecond)
5965

60-
// Check mutex profile fraction
61-
if got := runtime.SetMutexProfileFraction(0); got != tt.expectedMutexFraction {
62-
t.Errorf("runtime.SetMutexProfileFraction() = %v, want %v", got, tt.expectedMutexFraction)
63-
}
64-
runtime.SetMutexProfileFraction(tt.expectedMutexFraction) // Reset to the expected value
65-
6666
// Perform HTTP GET request to the root path
6767
url := "http://" + server.addr + "/debug/pprof/"
6868
client := &http.Client{}

cmdutil/service/standard.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func New(appConfig interface{}, ofs ...OptionFunc) *Standard {
7373
s.Add(cmdutil.NewContextServer(l2met.Run))
7474
}
7575

76-
s.Add(debug.New(logger, sc.Debug.Port))
76+
s.Add(debug.New(logger, sc.Debug))
7777
s.Add(signals.NewServer(logger, syscall.SIGINT, syscall.SIGTERM))
7878

7979
// only setup an exporter if indicated && the AgentAddress is set

0 commit comments

Comments
 (0)