diff --git a/cmd/otel-collector/components.go b/cmd/otel-collector/components.go index ea2e149..9f31761 100644 --- a/cmd/otel-collector/components.go +++ b/cmd/otel-collector/components.go @@ -1,6 +1,7 @@ package main import ( + "github.com/metrico/otel-collector/extension/pyroscope" "github.com/open-telemetry/opentelemetry-collector-contrib/connector/countconnector" "github.com/open-telemetry/opentelemetry-collector-contrib/connector/datadogconnector" "github.com/open-telemetry/opentelemetry-collector-contrib/connector/exceptionsconnector" @@ -204,6 +205,7 @@ func components() (otelcol.Factories, error) { jsonlogencodingextension.NewFactory(), textencodingextension.NewFactory(), zipkinencodingextension.NewFactory(), + pyroscope.NewFactory(), } for _, ext := range factories.Extensions { extensions = append(extensions, ext) diff --git a/extension/pyroscope/README.md b/extension/pyroscope/README.md new file mode 100644 index 0000000..42070c5 --- /dev/null +++ b/extension/pyroscope/README.md @@ -0,0 +1,42 @@ +# Pyroscope client + +| Status | | +|---------------------------|--------| +| Stability | [beta] | + + +The Extension provides the functionality to export the opentelementry collector profiles into a pyroscope compatible +server. + + +The following settings can be configured: + +- application_name (default = "opentelemetry-collector"): The name of the application as it will appear in Pyroscope. +- tags (default = empty map): A map of key-value pairs for additional tags to be associated with the profiles. +- server_address (default = "http://localhost:8062"): The URL of the Pyroscope server to which the profiles will be sent. +- basic_auth (default = empty): Basic authentication configuration for connecting to the Pyroscope server. + - username: The username for basic authentication. + - password: The password for basic authentication. +- profile_types (default = ["cpu", "alloc_objects", "alloc_space", "inuse_objects", "inuse_space"]): + An array of profile types to collect and send to Pyroscope. +- tenant_id (default = ""): The tenant ID to use when sending profiles to Pyroscope, for multi-tenancy support. + +Example: +```yaml +extensions: + pyroscope: + application_name: "my-collector" + tags: + environment: "production" + region: "us-west-2" + server_address: "http://localhost:8062" + basic_auth: + username: "user" + password: "secret" + profile_types: + - "cpu" + - "alloc_objects" + - "inuse_space" +service: + extensions: [pyroscope] +``` diff --git a/extension/pyroscope/config.go b/extension/pyroscope/config.go new file mode 100644 index 0000000..e660ef2 --- /dev/null +++ b/extension/pyroscope/config.go @@ -0,0 +1,27 @@ +package pyroscope + +import "go.opentelemetry.io/collector/component" + +type Config struct { + ApplicationName string `json:"application_name"` + Tags map[string]string `json:"tags"` + ServerAddress string `json:"server_address"` + BasicAuth BasicAuth `json:"basic_auth"` + ProfileTypes []string `json:"profile_types"` + TenantID string `json:"tenant_id"` +} + +type BasicAuth struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func defaultConfig() component.Config { + return &Config{ + ApplicationName: "opentelemetry-collector", + ServerAddress: "http://localhost:8062", + Tags: map[string]string{}, + BasicAuth: BasicAuth{}, + ProfileTypes: []string{"cpu", "alloc_objects", "alloc_space", "inuse_objects", "inuse_space"}, + } +} diff --git a/extension/pyroscope/extension.go b/extension/pyroscope/extension.go new file mode 100644 index 0000000..5cd1d59 --- /dev/null +++ b/extension/pyroscope/extension.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package pyroscope + +import ( + "context" + + "github.com/google/uuid" + "github.com/grafana/pyroscope-go" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/extension" +) + +var pyroscopeType = component.MustNewType("pyroscope") + +// NewNopCreateSettings returns a new nop settings for extension.Factory Create* functions. +func NewPyroscopeCreateSettings() extension.CreateSettings { + return extension.CreateSettings{ + ID: component.NewIDWithName(pyroscopeType, uuid.NewString()), + TelemetrySettings: componenttest.NewNopTelemetrySettings(), + BuildInfo: component.NewDefaultBuildInfo(), + } +} + +// NewNopFactory returns an extension.Factory that constructs nop extensions. +func NewFactory() extension.Factory { + return extension.NewFactory( + pyroscopeType, + defaultConfig, + func(ctx context.Context, settings extension.CreateSettings, config component.Config) (extension.Extension, error) { + return &PyroscopeExtension{ + config: config.(*Config), + }, nil + }, + component.StabilityLevelStable) +} + +type PyroscopeExtension struct { + config *Config + settings extension.CreateSettings + ctx context.Context + profiler *pyroscope.Profiler +} + +func (p PyroscopeExtension) Start(ctx context.Context, host component.Host) error { + cfg := pyroscope.Config{ + ApplicationName: p.config.ApplicationName, + + // replace this with the address of pyroscope server + ServerAddress: p.config.ServerAddress, + + // you can disable logging by setting this to nil + Logger: pyroscope.StandardLogger, + + // Optional HTTP Basic authentication (Grafana Cloud) + BasicAuthUser: p.config.BasicAuth.Username, + BasicAuthPassword: p.config.BasicAuth.Password, + Tags: p.config.Tags, + TenantID: p.config.TenantID, + ProfileTypes: nil, + } + for _, profileType := range p.config.ProfileTypes { + cfg.ProfileTypes = append(cfg.ProfileTypes, pyroscope.ProfileType(profileType)) + } + var err error + p.profiler, err = pyroscope.Start(cfg) + return err +} + +func (p PyroscopeExtension) Shutdown(ctx context.Context) error { + return p.profiler.Stop() +} diff --git a/go.mod b/go.mod index 3ae6d5f..c42a763 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/go-faster/city v1.0.1 github.com/go-logfmt/logfmt v0.6.0 github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07 + github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/grafana/jfr-parser v0.8.0 + github.com/grafana/pyroscope-go v1.1.2 github.com/grafana/pyroscope/api v0.4.0 github.com/open-telemetry/opentelemetry-collector-contrib/connector/countconnector v0.98.0 github.com/open-telemetry/opentelemetry-collector-contrib/connector/datadogconnector v0.98.0 @@ -369,13 +371,13 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/gophercloud/gophercloud v1.8.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/gosnmp/gosnmp v1.37.0 // indirect github.com/grafana/loki/pkg/push v0.0.0-20231127162423-bd505f8e2d37 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/grobie/gomemcache v0.0.0-20230213081705-239240bbc445 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect diff --git a/go.sum b/go.sum index b700b74..805a6e1 100644 --- a/go.sum +++ b/go.sum @@ -718,7 +718,10 @@ github.com/grafana/jfr-parser v0.8.0 h1:/uo2wZNXrxw7tKLFwP2omJ3EQGMkD9wzhPsRogVo github.com/grafana/jfr-parser v0.8.0/go.mod h1:M5u1ux34Qo47ZBWksbMYVk40s7dvU3WMVYpxweEu4R0= github.com/grafana/loki/pkg/push v0.0.0-20231127162423-bd505f8e2d37 h1:w59bmBeLOk4enGtyX4kTBNY3FCw/nwDTYUqcjC4vKhg= github.com/grafana/loki/pkg/push v0.0.0-20231127162423-bd505f8e2d37/go.mod h1:f3JSoxBTPXX5ec4FxxeC19nTBSxoTz+cBgS3cYLMcr0= -github.com/grafana/pyroscope v1.5.0/go.mod h1:rg53VGcqOf3FawAcAUpkcNNF7+gV1VZFbZY9Gfxry+c= +github.com/grafana/pyroscope-go v1.1.2 h1:7vCfdORYQMCxIzI3NlYAs3FcBP760+gWuYWOyiVyYx8= +github.com/grafana/pyroscope-go v1.1.2/go.mod h1:HSSmHo2KRn6FasBA4vK7BMiQqyQq8KSuBKvrhkXxYPU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/pyroscope/api v0.4.0 h1:J86DxoNeLOvtJhB1Cn65JMZkXe682D+RqeoIUiYc/eo= github.com/grafana/pyroscope/api v0.4.0/go.mod h1:MFnZNeUM4RDsDOnbgKW3GWoLSBpLzMMT9nkvhHHo81o= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww=