feat(pprof): add pyroscope profiler support

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
2026-01-07 12:30:18 +01:00
parent 8e50da9205
commit 126f080612
4 changed files with 173 additions and 0 deletions

2
go.mod
View File

@@ -15,6 +15,7 @@ require (
github.com/go-logr/logr v1.4.2
github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.6.0
github.com/grafana/pyroscope-go v1.2.7
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
@@ -77,6 +78,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/ws v1.1.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect

6
go.sum
View File

@@ -272,6 +272,10 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac=
github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
@@ -492,6 +496,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

127
pprof/options.go Normal file
View File

@@ -0,0 +1,127 @@
package pprof
import (
"os"
"github.com/grafana/pyroscope-go"
)
const (
PyroscopeAddressEnv = "PYROSCOPE_ADDRESS"
PyroscopeUserEnv = "PYROSCOPE_USER"
PyroscopePasswordEnv = "PYROSCOPE_PASSWORD"
)
type Option func(*options)
func WithAddress(address string) Option {
return func(o *options) {
if address != "" {
o.address = address
}
}
}
func WithUser(user string) Option {
return func(o *options) {
if user != "" {
o.user = user
}
}
}
func WithPassword(password string) Option {
return func(o *options) {
if password != "" {
o.password = password
}
}
}
func WithAddressEnv(env string) Option {
return func(o *options) {
if env != "" {
o.addressEnv = env
}
}
}
func WithUserEnv(env string) Option {
return func(o *options) {
if env != "" {
o.userEnv = env
}
}
}
func WithPasswordEnv(env string) Option {
return func(o *options) {
if env != "" {
o.passwordEnv = env
}
}
}
func WithMutexProfileFraction(fraction int) Option {
return func(o *options) {
o.mutexProfileFraction = fraction
}
}
func WithBlockProfileRate(rate int) Option {
return func(o *options) {
o.blockProfileRate = rate
}
}
func WithProfiles(profiles ...pyroscope.ProfileType) Option {
return func(o *options) {
if len(profiles) != 0 {
o.profiles = profiles
}
}
}
type options struct {
address string
user string
password string
addressEnv string
userEnv string
passwordEnv string
mutexProfileFraction int
blockProfileRate int
profiles []pyroscope.ProfileType
}
var defaultOptions = options{
addressEnv: PyroscopeAddressEnv,
userEnv: PyroscopeUserEnv,
passwordEnv: PyroscopePasswordEnv,
mutexProfileFraction: 5,
blockProfileRate: 5,
profiles: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileInuseSpace,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileGoroutines,
pyroscope.ProfileMutexCount,
pyroscope.ProfileMutexDuration,
pyroscope.ProfileBlockCount,
pyroscope.ProfileBlockDuration,
},
}
func valueOrEnv(value, env string) string {
if value != "" {
return value
}
return os.Getenv(env)
}

38
pprof/pprof.go Normal file
View File

@@ -0,0 +1,38 @@
package pprof
import (
"context"
"runtime"
"github.com/grafana/pyroscope-go"
"go.linka.cloud/grpc-toolkit/logger"
)
func Init(ctx context.Context, app string, opts ...Option) {
if app == "" {
panic("application name is required to start pyroscope profiler")
}
o := defaultOptions
for _, v := range opts {
v(&o)
}
if valueOrEnv(o.address, o.addressEnv) == "" {
return
}
runtime.SetMutexProfileFraction(o.mutexProfileFraction)
runtime.SetBlockProfileRate(o.blockProfileRate)
log := logger.C(ctx).WithFields("service", "pyroscope")
log.Info("starting pyroscope profiler")
_, err := pyroscope.Start(pyroscope.Config{
ApplicationName: app,
ServerAddress: valueOrEnv(o.address, o.addressEnv),
BasicAuthUser: valueOrEnv(o.user, o.userEnv),
BasicAuthPassword: valueOrEnv(o.password, o.passwordEnv),
Logger: log,
ProfileTypes: o.profiles,
})
if err != nil {
log.WithError(err).Error("failed to start pyroscope")
}
}