Skip to content

Commit e8d313c

Browse files
authored
Merge branch 'main' into dependabot/github_actions/actions/create-github-app-token-2.2.1
2 parents 3120060 + 647ec7a commit e8d313c

File tree

10 files changed

+420
-6
lines changed

10 files changed

+420
-6
lines changed

.github/workflows/changelog.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010

1111
steps:
12-
- uses: actions/checkout@v6
12+
- uses: actions/checkout@v6.0.1
1313

1414
- name: Run Changelog CI
1515
uses: saadmk11/changelog-ci@v1.2.0

.github/workflows/gemini-cli.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ jobs:
126126
- name: 'Checkout PR branch'
127127
if: |-
128128
${{ steps.get_context.outputs.is_pr == 'true' }}
129-
uses: 'actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd' # v5
129+
uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # v5
130130
with:
131131
token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
132132
repository: '${{ github.repository }}'
@@ -136,7 +136,7 @@ jobs:
136136
- name: 'Checkout main branch'
137137
if: |-
138138
${{ steps.get_context.outputs.is_pr == 'false' }}
139-
uses: 'actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd' # v5
139+
uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # v5
140140
with:
141141
token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
142142
repository: '${{ github.repository }}'

.github/workflows/gemini-pr-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
runs-on: 'ubuntu-latest'
7777
steps:
7878
- name: 'Checkout PR code'
79-
uses: 'actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd' # v5
79+
uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # v5
8080

8181
- name: 'Generate GitHub App Token'
8282
id: 'generate_token'

.github/workflows/golangci-lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
name: lint
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v6
15+
- uses: actions/checkout@v6.0.1
1616
- name: golangci-lint
1717
uses: golangci/golangci-lint-action@v9
1818
with:

.github/workflows/run_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88

99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@v6
11+
- uses: actions/checkout@v6.0.1
1212
- uses: actions/setup-go@v6
1313
with:
1414
go-version: 'stable'

config/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ type ConfigurationDefault struct {
8888
TraceRequests bool `envDefault:"false" env:"TRACE_REQUESTS" yaml:"trace_requests"`
8989
TraceRequestsLogBody bool `envDefault:"false" env:"TRACE_REQUESTS_LOG_BODY" yaml:"trace_requests_log_body"`
9090

91+
ProfilerEnable bool `envDefault:"false" env:"PROFILER_ENABLE" yaml:"profiler_enable"`
92+
ProfilerPortAddr string `envDefault:":6060" env:"PROFILER_PORT" yaml:"profiler_port"`
93+
9194
OpenTelemetryDisable bool `envDefault:"false" env:"OPENTELEMETRY_DISABLE" yaml:"opentelemetry_disable"`
9295
OpenTelemetryTraceRatio float64 `envDefault:"1" env:"OPENTELEMETRY_TRACE_ID_RATIO" yaml:"opentelemetry_trace_id_ratio"`
9396

@@ -221,6 +224,24 @@ func (c *ConfigurationDefault) TraceReqLogBody() bool {
221224
return c.TraceRequestsLogBody
222225
}
223226

227+
type ConfigurationProfiler interface {
228+
ProfilerEnabled() bool
229+
ProfilerPort() string
230+
}
231+
232+
var _ ConfigurationProfiler = new(ConfigurationDefault)
233+
234+
func (c *ConfigurationDefault) ProfilerEnabled() bool {
235+
return c.ProfilerEnable
236+
}
237+
238+
func (c *ConfigurationDefault) ProfilerPort() string {
239+
if c.ProfilerPortAddr != "" {
240+
return c.ProfilerPortAddr
241+
}
242+
return ":6060"
243+
}
244+
224245
type ConfigurationPorts interface {
225246
Port() string
226247
HTTPPort() string

profiler/README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Profiler Package
2+
3+
The profiler package provides a clean interface for managing Go's pprof server within the frame framework.
4+
5+
## Features
6+
7+
- Automatic pprof server lifecycle management
8+
- Configuration-based enable/disable
9+
- Configurable port binding
10+
- Graceful shutdown support
11+
- Thread-safe operations
12+
13+
## Usage
14+
15+
### Basic Usage
16+
17+
```go
18+
import (
19+
"context"
20+
"github.com/pitabwire/frame/profiler"
21+
"github.com/pitabwire/frame/config"
22+
)
23+
24+
func main() {
25+
cfg := &config.ConfigurationDefault{
26+
ProfilerEnable: true,
27+
ProfilerPortAddr: ":6060",
28+
}
29+
30+
server := profiler.NewServer()
31+
ctx := context.Background()
32+
33+
// Start the profiler if enabled
34+
err := server.StartIfEnabled(ctx, cfg)
35+
if err != nil {
36+
panic(err)
37+
}
38+
39+
// ... your application logic ...
40+
41+
// Gracefully stop the profiler
42+
err = server.Stop(ctx)
43+
if err != nil {
44+
panic(err)
45+
}
46+
}
47+
```
48+
49+
### Integration with Frame Service
50+
51+
The profiler package is automatically integrated into the frame service when using the default configuration:
52+
53+
```go
54+
import "github.com/pitabwire/frame"
55+
56+
func main() {
57+
// Set PROFILER_ENABLE=true environment variable
58+
ctx, svc := frame.NewService()
59+
60+
// Profiler will automatically start on :6060
61+
err := svc.Run(ctx, "")
62+
if err != nil {
63+
panic(err)
64+
}
65+
}
66+
```
67+
68+
## Configuration
69+
70+
The profiler responds to the following configuration options:
71+
72+
### Environment Variables
73+
74+
- `PROFILER_ENABLE`: Set to "true" to enable the profiler (default: false)
75+
- `PROFILER_PORT`: Port to bind the profiler server (default: ":6060")
76+
77+
### YAML Configuration
78+
79+
```yaml
80+
profiler_enable: true
81+
profiler_port: ":6061"
82+
```
83+
84+
## Available Endpoints
85+
86+
When the profiler is enabled, the following endpoints are available:
87+
88+
- `/debug/pprof/` - Index of all available profiles
89+
- `/debug/pprof/profile` - CPU profile (30 seconds by default)
90+
- `/debug/pprof/heap` - Heap profile
91+
- `/debug/pprof/goroutine` - Goroutine profile
92+
- `/debug/pprof/block` - Blocking profile
93+
- `/debug/pprof/mutex` - Mutex contention profile
94+
- `/debug/pprof/trace` - Execution trace
95+
96+
## Command Line Tools
97+
98+
### CPU Profiling
99+
```bash
100+
# Collect 30-second CPU profile
101+
go tool pprof http://localhost:6060/debug/pprof/profile
102+
103+
# Collect 10-second CPU profile
104+
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10
105+
```
106+
107+
### Heap Profiling
108+
```bash
109+
go tool pprof http://localhost:6060/debug/pprof/heap
110+
```
111+
112+
### Web Interface
113+
```bash
114+
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile
115+
```
116+
117+
## API Reference
118+
119+
### Server
120+
121+
The main type for managing the pprof server.
122+
123+
#### Methods
124+
125+
- `NewServer() *Server` - Creates a new profiler server instance
126+
- `StartIfEnabled(ctx context.Context, cfg config.ConfigurationProfiler) error` - Starts the profiler if enabled in configuration
127+
- `Stop(ctx context.Context) error` - Gracefully stops the profiler server
128+
- `IsRunning() bool` - Returns true if the profiler server is currently running
129+
130+
## Security Considerations
131+
132+
- The profiler should only be enabled in development/staging environments
133+
- Consider firewall rules to restrict access to profiler endpoints
134+
- The profiler exposes sensitive information about your application's internals
135+
- Always disable the profiler in production environments
136+
137+
## Thread Safety
138+
139+
All methods on the Server type are thread-safe and can be called concurrently from multiple goroutines.

profiler/server.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package profiler
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net/http"
7+
_ "net/http/pprof" //nolint:gosec // This is a controlled profiling endpoint
8+
"time"
9+
10+
"github.com/pitabwire/util"
11+
12+
"github.com/pitabwire/frame/config"
13+
)
14+
15+
const (
16+
// DefaultShutdownTimeout is the timeout for graceful shutdown of the pprof server.
17+
DefaultShutdownTimeout = 5 * time.Second
18+
// DefaultReadHeaderTimeout is the timeout for reading request headers to prevent Slowloris attacks.
19+
DefaultReadHeaderTimeout = 5 * time.Second
20+
)
21+
22+
// Server manages the pprof server lifecycle.
23+
type Server struct {
24+
server *http.Server
25+
}
26+
27+
// NewServer creates a new profiler server instance.
28+
func NewServer() *Server {
29+
return &Server{}
30+
}
31+
32+
// StartIfEnabled checks if profiler is enabled and starts the pprof server.
33+
func (s *Server) StartIfEnabled(ctx context.Context, cfg config.ConfigurationProfiler) error {
34+
if !cfg.ProfilerEnabled() {
35+
// Profiler is explicitly disabled
36+
return nil
37+
}
38+
39+
log := util.Log(ctx)
40+
profilerPort := cfg.ProfilerPort()
41+
log.WithField("port", profilerPort).Info("Starting pprof server")
42+
43+
// Create pprof server
44+
s.server = &http.Server{
45+
Addr: profilerPort,
46+
Handler: nil, // Use default handler which includes pprof endpoints
47+
ReadHeaderTimeout: DefaultReadHeaderTimeout,
48+
}
49+
50+
// Start pprof server in a goroutine
51+
go func() {
52+
if err := s.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
53+
log.WithError(err).Error("pprof server failed")
54+
}
55+
}()
56+
57+
return nil
58+
}
59+
60+
// Stop gracefully shuts down the pprof server.
61+
func (s *Server) Stop(ctx context.Context) error {
62+
if s.server == nil {
63+
return nil
64+
}
65+
66+
log := util.Log(ctx)
67+
log.Info("stopping pprof server")
68+
69+
shutdownCtx, cancel := context.WithTimeout(ctx, DefaultShutdownTimeout)
70+
defer cancel()
71+
72+
if err := s.server.Shutdown(shutdownCtx); err != nil {
73+
log.WithError(err).Error("failed to shutdown pprof server")
74+
return err
75+
}
76+
77+
s.server = nil
78+
return nil
79+
}
80+
81+
// IsRunning returns true if the profiler server is currently running.
82+
func (s *Server) IsRunning() bool {
83+
return s.server != nil
84+
}

0 commit comments

Comments
 (0)