Skip to content

Commit f528a5d

Browse files
feat: add pprof server (#72)
* feat: add pprof server * refactor: minor refactor * fix: race detection * feat: parametrize pprof server * refactor: minor refactoring
1 parent f6f5285 commit f528a5d

File tree

3 files changed

+85
-22
lines changed

3 files changed

+85
-22
lines changed

boundary.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type Config struct {
2121
Logger *slog.Logger
2222
Jailer jail.Jailer
2323
ProxyPort int
24+
PprofEnabled bool
25+
PprofPort int
2426
}
2527

2628
type Boundary struct {
@@ -35,11 +37,13 @@ type Boundary struct {
3537
func New(ctx context.Context, config Config) (*Boundary, error) {
3638
// Create proxy server
3739
proxyServer := proxy.NewProxyServer(proxy.Config{
38-
HTTPPort: config.ProxyPort,
39-
RuleEngine: config.RuleEngine,
40-
Auditor: config.Auditor,
41-
Logger: config.Logger,
42-
TLSConfig: config.TLSConfig,
40+
HTTPPort: config.ProxyPort,
41+
RuleEngine: config.RuleEngine,
42+
Auditor: config.Auditor,
43+
Logger: config.Logger,
44+
TLSConfig: config.TLSConfig,
45+
PprofEnabled: config.PprofEnabled,
46+
PprofPort: config.PprofPort,
4347
})
4448

4549
// Create cancellable context for boundary

cli/cli.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type Config struct {
2929
LogDir string
3030
Unprivileged bool
3131
ProxyPort int64
32+
PprofEnabled bool
33+
PprofPort int64
3234
}
3335

3436
// NewCommand creates and returns the root serpent command
@@ -94,6 +96,19 @@ func BaseCommand() *serpent.Command {
9496
Default: "8080",
9597
Value: serpent.Int64Of(&config.ProxyPort),
9698
},
99+
{
100+
Flag: "pprof",
101+
Env: "BOUNDARY_PPROF",
102+
Description: "Enable pprof profiling server.",
103+
Value: serpent.BoolOf(&config.PprofEnabled),
104+
},
105+
{
106+
Flag: "pprof-port",
107+
Env: "BOUNDARY_PPROF_PORT",
108+
Description: "Set port for pprof profiling server.",
109+
Default: "6060",
110+
Value: serpent.Int64Of(&config.PprofPort),
111+
},
97112
},
98113
Handler: func(inv *serpent.Invocation) error {
99114
args := inv.Args
@@ -203,12 +218,14 @@ func Run(ctx context.Context, config Config, args []string) error {
203218

204219
// Create boundary instance
205220
boundaryInstance, err := boundary.New(ctx, boundary.Config{
206-
RuleEngine: ruleEngine,
207-
Auditor: auditor,
208-
TLSConfig: tlsConfig,
209-
Logger: logger,
210-
Jailer: jailer,
211-
ProxyPort: int(config.ProxyPort),
221+
RuleEngine: ruleEngine,
222+
Auditor: auditor,
223+
TLSConfig: tlsConfig,
224+
Logger: logger,
225+
Jailer: jailer,
226+
ProxyPort: int(config.ProxyPort),
227+
PprofEnabled: config.PprofEnabled,
228+
PprofPort: int(config.PprofPort),
212229
})
213230
if err != nil {
214231
return fmt.Errorf("failed to create boundary instance: %v", err)

proxy/proxy.go

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ package proxy
33
import (
44
"bufio"
55
"bytes"
6+
"context"
67
"crypto/tls"
78
"errors"
89
"fmt"
910
"io"
1011
"log/slog"
1112
"net"
1213
"net/http"
14+
_ "net/http/pprof"
1315
"net/url"
1416
"strconv"
1517
"strings"
1618
"sync/atomic"
19+
"time"
1720

1821
"github.com/coder/boundary/audit"
1922
"github.com/coder/boundary/rules"
@@ -28,26 +31,33 @@ type Server struct {
2831
httpPort int
2932
started atomic.Bool
3033

31-
listener net.Listener
34+
listener net.Listener
35+
pprofServer *http.Server
36+
pprofEnabled bool
37+
pprofPort int
3238
}
3339

3440
// Config holds configuration for the proxy server
3541
type Config struct {
36-
HTTPPort int
37-
RuleEngine rules.Evaluator
38-
Auditor audit.Auditor
39-
Logger *slog.Logger
40-
TLSConfig *tls.Config
42+
HTTPPort int
43+
RuleEngine rules.Evaluator
44+
Auditor audit.Auditor
45+
Logger *slog.Logger
46+
TLSConfig *tls.Config
47+
PprofEnabled bool
48+
PprofPort int
4149
}
4250

4351
// NewProxyServer creates a new proxy server instance
4452
func NewProxyServer(config Config) *Server {
4553
return &Server{
46-
ruleEngine: config.RuleEngine,
47-
auditor: config.Auditor,
48-
logger: config.Logger,
49-
tlsConfig: config.TLSConfig,
50-
httpPort: config.HTTPPort,
54+
ruleEngine: config.RuleEngine,
55+
auditor: config.Auditor,
56+
logger: config.Logger,
57+
tlsConfig: config.TLSConfig,
58+
httpPort: config.HTTPPort,
59+
pprofEnabled: config.PprofEnabled,
60+
pprofPort: config.PprofPort,
5161
}
5262
}
5363

@@ -58,6 +68,29 @@ func (p *Server) Start() error {
5868
}
5969

6070
p.logger.Info("Starting HTTP proxy with TLS termination", "port", p.httpPort)
71+
72+
// Start pprof server if enabled
73+
if p.pprofEnabled {
74+
p.pprofServer = &http.Server{
75+
Addr: fmt.Sprintf(":%d", p.pprofPort),
76+
Handler: http.DefaultServeMux,
77+
}
78+
79+
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", p.pprofPort))
80+
if err != nil {
81+
p.logger.Error("failed to listen on port for pprof server", "port", p.pprofPort, "error", err)
82+
return fmt.Errorf("failed to listen on port %v for pprof server: %v", p.pprofPort, err)
83+
}
84+
85+
go func() {
86+
p.logger.Info("Serving pprof on existing listener", "port", p.pprofPort)
87+
if err := p.pprofServer.Serve(ln); err != nil && errors.Is(err, http.ErrServerClosed) {
88+
p.logger.Error("pprof server error", "error", err)
89+
}
90+
}()
91+
92+
}
93+
6194
var err error
6295
p.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", p.httpPort))
6396
if err != nil {
@@ -105,6 +138,15 @@ func (p *Server) Stop() error {
105138
return err
106139
}
107140

141+
// Close pprof server
142+
if p.pprofServer != nil {
143+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
144+
defer cancel()
145+
if err := p.pprofServer.Shutdown(ctx); err != nil {
146+
p.logger.Error("Failed to shutdown pprof server", "error", err)
147+
}
148+
}
149+
108150
return nil
109151
}
110152

0 commit comments

Comments
 (0)