diff --git a/option.go b/option.go index cf7d675..314b504 100644 --- a/option.go +++ b/option.go @@ -6,6 +6,7 @@ import ( "time" "github.com/foomo/keel/service" + "github.com/grafana/pyroscope-go" "github.com/spf13/viper" "go.uber.org/zap" @@ -146,6 +147,48 @@ func WithPrometheusMeter(enabled bool) Option { } } +// WithPyroscopeService option with default value +func WithPyroscopeService(enabled bool) Option { + return func(inst *Server) { + if config.GetBool(inst.Config(), "otel.enabled", enabled)() { + tags := map[string]string{} + if v := os.Getenv("HOSTNAME"); v != "" { + tags["pod"] = v + } + if v := config.GetString(inst.Config(), "otel.service.git.ref", "")(); v != "" { + tags["service_git_ref"] = v + } + if v := config.GetString(inst.Config(), "otel.service.repository", "")(); v != "" { + tags["service_repository"] = v + } + if v := config.GetString(inst.Config(), "otel.service.root_path", "")(); v != "" { + tags["service_root_path"] = v + } + + svs := service.NewPyroscope(inst.Logger(), pyroscope.Config{ + ApplicationName: config.GetString(inst.Config(), "otel.service.name", telemetry.ServiceName)(), + Tags: tags, + Logger: telemetry.NewPyroscopeLogger(inst.l), + ProfileTypes: []pyroscope.ProfileType{ + // Default + pyroscope.ProfileCPU, + pyroscope.ProfileAllocObjects, + pyroscope.ProfileAllocSpace, + pyroscope.ProfileInuseObjects, + pyroscope.ProfileInuseSpace, + // Optional + pyroscope.ProfileGoroutines, + pyroscope.ProfileMutexCount, + pyroscope.ProfileMutexDuration, + pyroscope.ProfileBlockCount, + pyroscope.ProfileBlockDuration, + }, + }) + inst.initServices = append(inst.initServices, svs) + } + } +} + // WithHTTPPrometheusService option with default value func WithHTTPPrometheusService(enabled bool) Option { return func(inst *Server) { diff --git a/service/httppprof_pprof.go b/service/httppprof_pprof.go index 4943a7c..e29b86d 100644 --- a/service/httppprof_pprof.go +++ b/service/httppprof_pprof.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/pprof" + pyroscope_pprof "github.com/grafana/pyroscope-go/http/pprof" "go.uber.org/zap" ) @@ -20,7 +21,7 @@ func NewHTTPPProf(l *zap.Logger, name, addr, path string) *HTTP { handler := http.NewServeMux() handler.HandleFunc(path+"/", pprof.Index) handler.HandleFunc(path+"/cmdline", pprof.Cmdline) - handler.HandleFunc(path+"/profile", pprof.Profile) + handler.HandleFunc(path+"/profile", pyroscope_pprof.Profile) handler.HandleFunc(path+"/symbol", pprof.Symbol) handler.HandleFunc(path+"/trace", pprof.Trace) return NewHTTP(l, name, addr, handler) diff --git a/service/pyroscope.go b/service/pyroscope.go new file mode 100644 index 0000000..c41ac6d --- /dev/null +++ b/service/pyroscope.go @@ -0,0 +1,35 @@ +//go:build !pprof + +package service + +import ( + "context" + "sync/atomic" + + otelpyroscope "github.com/grafana/otel-profiling-go" + "github.com/grafana/pyroscope-go" + "go.opentelemetry.io/otel" + "go.uber.org/zap" +) + +type Pyroscope struct { + l *zap.Logger + name string + cfg pyroscope.Config + running atomic.Bool + profiler *pyroscope.Profiler +} + +func NewPyroscope(l *zap.Logger, cfg pyroscope.Config) *GoRoutine { + otel.SetTracerProvider(otelpyroscope.NewTracerProvider(otel.GetTracerProvider())) + return NewGoRoutine(l, "pyroscope", func(ctx context.Context, l *zap.Logger) error { + p, err := pyroscope.Start(cfg) + if err != nil { + return err + } + select { + case <-ctx.Done(): + return p.Stop() + } + }) +} diff --git a/telemetry/config.go b/telemetry/config.go index ffb0df6..7b297e5 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -2,10 +2,10 @@ package telemetry const ( DefaultServiceName = "service" - DefaultTracerName = "github.com/foomo/keel/telemetry" + DefaultName = "github.com/foomo/keel/telemetry" ) var ( - TracerName = DefaultTracerName + Name = DefaultName ServiceName = DefaultServiceName ) diff --git a/telemetry/meter.go b/telemetry/meter.go index 31eb9e8..1099d1f 100644 --- a/telemetry/meter.go +++ b/telemetry/meter.go @@ -22,7 +22,7 @@ var ( ) func Meter() metric.Meter { - return otel.Meter("") + return otel.Meter(Name) } func NewNoopMeterProvider() (metric.MeterProvider, error) { diff --git a/telemetry/profile.go b/telemetry/profile.go new file mode 100644 index 0000000..ff18d39 --- /dev/null +++ b/telemetry/profile.go @@ -0,0 +1,27 @@ +package telemetry + +import ( + "fmt" + + "go.uber.org/zap" +) + +type PyroscopeLogger struct { + l *zap.Logger +} + +func NewPyroscopeLogger(l *zap.Logger) *PyroscopeLogger { + return &PyroscopeLogger{l: l.Named("pyroscope")} +} + +func (l *PyroscopeLogger) Infof(format string, a ...interface{}) { + l.l.Info(fmt.Sprintf(format, a...)) +} + +func (l *PyroscopeLogger) Debugf(format string, a ...interface{}) { + l.l.Debug(fmt.Sprintf(format, a...)) +} + +func (l *PyroscopeLogger) Errorf(format string, a ...interface{}) { + l.l.Error(fmt.Sprintf(format, a...)) +} diff --git a/telemetry/tracer.go b/telemetry/tracer.go index 5ca41f6..6273de2 100644 --- a/telemetry/tracer.go +++ b/telemetry/tracer.go @@ -17,7 +17,7 @@ import ( ) func Tracer() trace.Tracer { - return TraceProvider().Tracer(TracerName) + return TraceProvider().Tracer(Name) } func TraceProvider() trace.TracerProvider {