Skip to content

Commit 2e7afc5

Browse files
committed
pkg/loop: support pyroscope
1 parent 9f76f0c commit 2e7afc5

File tree

5 files changed

+157
-11
lines changed

5 files changed

+157
-11
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ require (
1818
github.com/golang-jwt/jwt/v5 v5.2.3
1919
github.com/google/go-cmp v0.7.0
2020
github.com/google/uuid v1.6.0
21+
github.com/grafana/otel-profiling-go v0.5.1
22+
github.com/grafana/pyroscope-go v1.2.8
2123
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
2224
github.com/hashicorp/go-hclog v1.6.3
2325
github.com/hashicorp/go-plugin v1.7.0
@@ -103,6 +105,7 @@ require (
103105
github.com/gofrs/uuid v4.4.0+incompatible // indirect
104106
github.com/golang/protobuf v1.5.4 // indirect
105107
github.com/google/flatbuffers v25.2.10+incompatible // indirect
108+
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
106109
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
107110
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
108111
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 // indirect

go.sum

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/loop/config.go

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ const (
4646

4747
envPromPort = "CL_PROMETHEUS_PORT"
4848

49+
envPyroscopeAuthToken = "CL_PYROSCOPE_AUTH_TOKEN"
50+
envPyroscopeServerAddress = "CL_PYROSCOPE_SERVER_ADDRESS"
51+
envPyroscopeEnvironment = "CL_PYROSCOPE_ENVIRONMENT"
52+
envPyroscopeLinkTracesToProfiles = "CL_PYROSCOPE_LINK_TRACES_TO_PROFILES"
53+
envPyroscopePPROFBlockProfileRate = "CL_PYROSCOPE_PPROF_BLOCK_PROFILE_RATE"
54+
envPyroscopePPROFMutexProfileFraction = "CL_PYROSCOPE_PPROF_MUTEX_PROFILE_FRACTION"
55+
4956
envTracingEnabled = "CL_TRACING_ENABLED"
5057
envTracingCollectorTarget = "CL_TRACING_COLLECTOR_TARGET"
5158
envTracingSamplingRatio = "CL_TRACING_SAMPLING_RATIO"
@@ -89,6 +96,12 @@ const (
8996
type EnvConfig struct {
9097
AppID string
9198

99+
ChipIngressEndpoint string
100+
ChipIngressInsecureConnection bool
101+
102+
CRESettings string
103+
CRESettingsDefault string
104+
92105
DatabaseURL *config.SecretURL
93106
DatabaseIdleInTxSessionTimeout time.Duration
94107
DatabaseLockTimeout time.Duration
@@ -115,13 +128,14 @@ type EnvConfig struct {
115128
MercuryTransmitterReaperMaxAge time.Duration
116129
MercuryVerboseLogging bool
117130

118-
PrometheusPort int //TODO more than just prom
131+
PrometheusPort int // also serves pprof routes
119132

120-
TracingEnabled bool
121-
TracingCollectorTarget string
122-
TracingSamplingRatio float64
123-
TracingTLSCertPath string
124-
TracingAttributes map[string]string
133+
PyroscopeAuthToken string
134+
PyroscopeServerAddress string
135+
PyroscopeEnvironment string
136+
PyroscopeLinkTracesToProfiles bool
137+
PyroscopePPROFBlockProfileRate int
138+
PyroscopePPROFMutexProfileFraction int
125139

126140
TelemetryEnabled bool
127141
TelemetryEndpoint string
@@ -148,11 +162,11 @@ type EnvConfig struct {
148162
TelemetryMetricCompressor string
149163
TelemetryLogCompressor string
150164

151-
ChipIngressEndpoint string
152-
ChipIngressInsecureConnection bool
153-
154-
CRESettings string
155-
CRESettingsDefault string
165+
TracingEnabled bool
166+
TracingCollectorTarget string
167+
TracingSamplingRatio float64
168+
TracingTLSCertPath string
169+
TracingAttributes map[string]string
156170
}
157171

158172
// AsCmdEnv returns a slice of environment variable key/value pairs for an exec.Cmd.
@@ -193,6 +207,13 @@ func (e *EnvConfig) AsCmdEnv() (env []string) {
193207

194208
add(envPromPort, strconv.Itoa(e.PrometheusPort))
195209

210+
add(envPyroscopeAuthToken, e.PyroscopeAuthToken)
211+
add(envPyroscopeServerAddress, e.PyroscopeServerAddress)
212+
add(envPyroscopeEnvironment, e.PyroscopeEnvironment)
213+
add(envPyroscopeLinkTracesToProfiles, strconv.FormatBool(e.PyroscopeLinkTracesToProfiles))
214+
add(envPyroscopePPROFBlockProfileRate, strconv.Itoa(e.PyroscopePPROFBlockProfileRate))
215+
add(envPyroscopePPROFMutexProfileFraction, strconv.Itoa(e.PyroscopePPROFMutexProfileFraction))
216+
196217
add(envTracingEnabled, strconv.FormatBool(e.TracingEnabled))
197218
add(envTracingCollectorTarget, e.TracingCollectorTarget)
198219
add(envTracingSamplingRatio, strconv.FormatFloat(e.TracingSamplingRatio, 'f', -1, 64))
@@ -352,6 +373,22 @@ func (e *EnvConfig) parse() error {
352373
return fmt.Errorf("failed to parse %s = %q: %w", envPromPort, promPortStr, err)
353374
}
354375

376+
e.PyroscopeAuthToken = os.Getenv(envPyroscopeAuthToken)
377+
e.PyroscopeServerAddress = os.Getenv(envPyroscopeServerAddress)
378+
e.PyroscopeEnvironment = os.Getenv(envPyroscopeEnvironment)
379+
e.PyroscopeLinkTracesToProfiles, err = getBool(envPyroscopeLinkTracesToProfiles)
380+
if err != nil {
381+
return fmt.Errorf("failed to parse %s: %w", envPyroscopeLinkTracesToProfiles, err)
382+
}
383+
e.PyroscopePPROFBlockProfileRate, err = getInt(envPyroscopePPROFBlockProfileRate)
384+
if err != nil {
385+
return fmt.Errorf("failed to parse %s: %w", envPyroscopePPROFBlockProfileRate, err)
386+
}
387+
e.PyroscopePPROFMutexProfileFraction, err = getInt(envPyroscopePPROFMutexProfileFraction)
388+
if err != nil {
389+
return fmt.Errorf("failed to parse %s: %w", envPyroscopePPROFMutexProfileFraction, err)
390+
}
391+
355392
e.TracingEnabled, err = getBool(envTracingEnabled)
356393
if err != nil {
357394
return fmt.Errorf("failed to parse %s: %w", envTracingEnabled, err)

pkg/loop/config_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ func TestEnvConfig_parse(t *testing.T) {
5555

5656
envPromPort: "8080",
5757

58+
envPyroscopeAuthToken: "token",
59+
envPyroscopeServerAddress: "http://pyroscope:4040",
60+
envPyroscopeEnvironment: "pyroscope-env",
61+
envPyroscopeLinkTracesToProfiles: "true",
62+
envPyroscopePPROFBlockProfileRate: "42",
63+
envPyroscopePPROFMutexProfileFraction: "99",
64+
5865
envTracingEnabled: "true",
5966
envTracingCollectorTarget: "some:target",
6067
envTracingSamplingRatio: "1.0",
@@ -160,6 +167,13 @@ var envCfgFull = EnvConfig{
160167

161168
PrometheusPort: 8080,
162169

170+
PyroscopeAuthToken: "token",
171+
PyroscopeServerAddress: "http://pyroscope:4040",
172+
PyroscopeEnvironment: "pyroscope-env",
173+
PyroscopeLinkTracesToProfiles: true,
174+
PyroscopePPROFBlockProfileRate: 42,
175+
PyroscopePPROFMutexProfileFraction: 99,
176+
163177
TracingEnabled: true,
164178
TracingAttributes: map[string]string{"XYZ": "value"},
165179
TracingCollectorTarget: "some:target",
@@ -213,6 +227,13 @@ func TestEnvConfig_AsCmdEnv(t *testing.T) {
213227

214228
assert.Equal(t, strconv.Itoa(8080), got[envPromPort])
215229

230+
assert.Equal(t, "token", got[envPyroscopeAuthToken])
231+
assert.Equal(t, "http://pyroscope:4040", got[envPyroscopeServerAddress])
232+
assert.Equal(t, "pyroscope-env", got[envPyroscopeEnvironment])
233+
assert.Equal(t, "true", got[envPyroscopeLinkTracesToProfiles])
234+
assert.Equal(t, "42", got[envPyroscopePPROFBlockProfileRate])
235+
assert.Equal(t, "99", got[envPyroscopePPROFMutexProfileFraction])
236+
216237
assert.Equal(t, "true", got[envTracingEnabled])
217238
assert.Equal(t, "some:target", got[envTracingCollectorTarget])
218239
assert.Equal(t, "1", got[envTracingSamplingRatio])

pkg/loop/server.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import (
55
"fmt"
66
"os"
77
"os/signal"
8+
"path/filepath"
9+
"runtime"
10+
"runtime/debug"
811
"time"
912

13+
otelpyroscope "github.com/grafana/otel-profiling-go"
14+
"github.com/grafana/pyroscope-go"
1015
"github.com/jmoiron/sqlx"
16+
"go.opentelemetry.io/otel"
1117
"go.opentelemetry.io/otel/attribute"
1218
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
1319
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
@@ -94,6 +100,7 @@ type Server struct {
94100
webServer *webServer
95101
checker *services.HealthChecker
96102
LimitsFactory limits.Factory
103+
profiler *pyroscope.Profiler
97104
}
98105

99106
func newServer(loggerName string) (*Server, error) {
@@ -221,6 +228,67 @@ func (s *Server) start(opts ...ServerOpt) error {
221228
}
222229
}
223230

231+
if addr := s.EnvConfig.PyroscopeServerAddress; addr != "" {
232+
runtime.SetBlockProfileRate(s.EnvConfig.PyroscopePPROFBlockProfileRate)
233+
runtime.SetMutexProfileFraction(s.EnvConfig.PyroscopePPROFMutexProfileFraction)
234+
235+
hostname, _ := os.Hostname()
236+
var ver, sha, goVer, module string
237+
if bi, ok := debug.ReadBuildInfo(); ok {
238+
ver = bi.Main.Version
239+
sha = bi.Main.Sum
240+
if len(sha) > 7 {
241+
sha = sha[:7]
242+
}
243+
goVer = bi.GoVersion
244+
module = bi.Main.Path
245+
}
246+
247+
appName, err := os.Executable()
248+
if err != nil {
249+
s.Logger.Warnf("Failed to get executable name: %v", err)
250+
appName = "unknown"
251+
} else {
252+
appName = filepath.Base(appName)
253+
}
254+
255+
s.profiler, err = pyroscope.Start(pyroscope.Config{
256+
ApplicationName: appName,
257+
ServerAddress: s.EnvConfig.PyroscopeServerAddress,
258+
AuthToken: s.EnvConfig.PyroscopeAuthToken,
259+
260+
Tags: map[string]string{
261+
"module": module,
262+
"SHA": sha,
263+
"Version": ver,
264+
"go": goVer,
265+
"Environment": s.EnvConfig.PyroscopeEnvironment,
266+
"hostname": hostname,
267+
},
268+
ProfileTypes: []pyroscope.ProfileType{
269+
// these profile types are enabled by default:
270+
pyroscope.ProfileCPU,
271+
pyroscope.ProfileAllocObjects,
272+
pyroscope.ProfileAllocSpace,
273+
pyroscope.ProfileInuseObjects,
274+
pyroscope.ProfileInuseSpace,
275+
276+
// these profile types are optional:
277+
pyroscope.ProfileGoroutines,
278+
pyroscope.ProfileMutexCount,
279+
pyroscope.ProfileMutexDuration,
280+
pyroscope.ProfileBlockCount,
281+
pyroscope.ProfileBlockDuration,
282+
},
283+
})
284+
if err != nil {
285+
return fmt.Errorf("failed to start pyroscope profiler: %w", err)
286+
}
287+
if tracingConfig.Enabled && s.EnvConfig.PyroscopeLinkTracesToProfiles {
288+
otel.SetTracerProvider(otelpyroscope.NewTracerProvider(otel.GetTracerProvider()))
289+
}
290+
}
291+
224292
s.webServer = WebServerOpts{}.New(s.Logger, s.EnvConfig.PrometheusPort)
225293
if err := s.webServer.Start(ctx); err != nil {
226294
return fmt.Errorf("error starting prometheus server: %w", err)
@@ -291,6 +359,9 @@ func (s *Server) Stop() {
291359
}
292360
s.Logger.ErrorIfFn(s.checker.Close, "Failed to close health checker")
293361
s.Logger.ErrorIfFn(s.webServer.Close, "Failed to close web server")
362+
if s.profiler != nil {
363+
s.Logger.ErrorIfFn(s.profiler.Stop, "Failed to stop pyroscope profiler")
364+
}
294365
if err := s.Logger.Sync(); err != nil {
295366
fmt.Println("Failed to sync logger:", err)
296367
}

0 commit comments

Comments
 (0)