diff --git a/receiver/pyroscopereceiver/README.md b/receiver/pyroscopereceiver/README.md index e0aa639..94fb165 100644 --- a/receiver/pyroscopereceiver/README.md +++ b/receiver/pyroscopereceiver/README.md @@ -11,6 +11,18 @@ Implements the Pyroscope ingest protocol and conveys the accepted profiles as Op - `protocols`: sets the application layer protocols that the receiver will serve. See [Supported Protocols](#supported-protocols). Default is http/s on 0.0.0.0:8062 with max request body size of: 5e6 + 1e6. - `timeout`: sets the server reponse timeout. Default is 10 seconds. +- `metrics`: configures the metrics collection for the Pyroscope receiver. + - `enable`: enables or disables metrics collection. Default is true. + - `exclude_labels`: a list of label names to exclude from the metrics. Available labels are: + - `service`: name of the service provided the pprof request + - `type`: type of pprof request (jfr or pprof) + - `encoding`: not used, empty + - `error_code`: http error code response for http request count + - `status_code`: http response status code for http request count + - `exclude_metrics`: a list of metric names to exclude from collection. Available metrics are: + - `http_request_total`: Pyroscope receiver http request count. + - `request_body_uncompressed_size_bytes`: Pyroscope receiver uncompressed request body size in bytes. + - `parsed_body_uncompressed_size_bytes`: Pyroscope receiver uncompressed parsed body size in bytes. ## Example diff --git a/receiver/pyroscopereceiver/config.go b/receiver/pyroscopereceiver/config.go index 988a755..7cad55f 100644 --- a/receiver/pyroscopereceiver/config.go +++ b/receiver/pyroscopereceiver/config.go @@ -14,12 +14,20 @@ type Protocols struct { HTTP *confighttp.ServerConfig `mapstructure:"http"` } +type MetricsConfig struct { + Enable bool `mapstructure:"enable" default:"true"` + ExcludeLabels []string `mapstructure:"exclude_labels"` + ExcludeMetrics []string `mapstructure:"exclude_metrics"` +} + // Represents the receiver config within the collector's config.yaml type Config struct { Protocols Protocols `mapstructure:"protocols"` // Cofigures timeout for synchronous request handling by the receiver server Timeout time.Duration `mapstructure:"timeout"` + + Metrics MetricsConfig `mapstructure:"metrics"` } var _ component.Config = (*Config)(nil) diff --git a/receiver/pyroscopereceiver/metrics.go b/receiver/pyroscopereceiver/metrics.go index c7dbc6f..2737499 100644 --- a/receiver/pyroscopereceiver/metrics.go +++ b/receiver/pyroscopereceiver/metrics.go @@ -2,6 +2,7 @@ package pyroscopereceiver import ( "fmt" + "slices" "go.opentelemetry.io/otel/metric" ) @@ -14,22 +15,28 @@ var ( otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes metric.Int64Histogram ) -func initMetrics(meter metric.Meter) error { +func initMetrics(meter metric.Meter, conf *Config) error { var err error - if otelcolReceiverPyroscopeHttpRequestTotal, err = meter.Int64Counter( + if !conf.Metrics.Enable || slices.Contains(conf.Metrics.ExcludeMetrics, "http_request_total") { + otelcolReceiverPyroscopeHttpRequestTotal = nil + } else if otelcolReceiverPyroscopeHttpRequestTotal, err = meter.Int64Counter( fmt.Sprint(prefix, "http_request_total"), metric.WithDescription("Pyroscope receiver http request count"), ); err != nil { return err } - if otelcolReceiverPyroscopeRequestBodyUncompressedSizeBytes, err = meter.Int64Histogram( + if !conf.Metrics.Enable || slices.Contains(conf.Metrics.ExcludeMetrics, "request_body_uncompressed_size_bytes") { + otelcolReceiverPyroscopeRequestBodyUncompressedSizeBytes = nil + } else if otelcolReceiverPyroscopeRequestBodyUncompressedSizeBytes, err = meter.Int64Histogram( fmt.Sprint(prefix, "request_body_uncompressed_size_bytes"), metric.WithDescription("Pyroscope receiver uncompressed request body size in bytes"), metric.WithExplicitBucketBoundaries(0, 1024, 4096, 16384, 32768, 65536, 131072, 262144, 524288, 1048576), ); err != nil { return err } - if otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes, err = meter.Int64Histogram( + if !conf.Metrics.Enable || slices.Contains(conf.Metrics.ExcludeMetrics, "parsed_body_uncompressed_size_bytes") { + otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes = nil + } else if otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes, err = meter.Int64Histogram( fmt.Sprint(prefix, "parsed_body_uncompressed_size_bytes"), metric.WithDescription("Pyroscope receiver uncompressed parsed body size in bytes"), metric.WithExplicitBucketBoundaries(0, 1024, 4096, 16384, 32768, 65536, 131072, 262144, 524288, 1048576), diff --git a/receiver/pyroscopereceiver/receiver.go b/receiver/pyroscopereceiver/receiver.go index 6e88992..6688939 100644 --- a/receiver/pyroscopereceiver/receiver.go +++ b/receiver/pyroscopereceiver/receiver.go @@ -12,6 +12,7 @@ import ( "net" "net/http" "net/url" + "slices" "strconv" "strings" "sync" @@ -117,7 +118,7 @@ func newPyroscopeReceiver(cfg *Config, consumer consumer.Logs, set *receiver.Set r.mux.HandleFunc(ingestPath, func(resp http.ResponseWriter, req *http.Request) { r.httpHandlerIngest(resp, req) }) - if err := initMetrics(r.meter); err != nil { + if err := initMetrics(r.meter, cfg); err != nil { r.logger.Error(fmt.Sprintf("failed to init metrics: %s", err.Error())) return r, err } @@ -176,15 +177,23 @@ func (r *pyroscopeReceiver) handle(ctx context.Context, resp http.ResponseWriter r.handleError(ctx, resp, "text/plain", http.StatusInternalServerError, err.Error(), pm.name, errorCodeError) return } - - otelcolReceiverPyroscopeHttpRequestTotal.Add(ctx, 1, metric.WithAttributeSet(*newOtelcolAttrSetHttp(pm.name, errorCodeSuccess, http.StatusNoContent))) + if otelcolReceiverPyroscopeHttpRequestTotal != nil { + otelcolReceiverPyroscopeHttpRequestTotal.Add( + ctx, 1, + metric.WithAttributeSet(*r.newOtelcolAttrSetHttp(pm.name, errorCodeSuccess, http.StatusNoContent)), + ) + } writeResponseNoContent(resp) }() return c } func (recv *pyroscopeReceiver) handleError(ctx context.Context, resp http.ResponseWriter, contentType string, statusCode int, msg string, service string, errorCode string) { - otelcolReceiverPyroscopeHttpRequestTotal.Add(ctx, 1, metric.WithAttributeSet(*newOtelcolAttrSetHttp(service, errorCode, statusCode))) + if otelcolReceiverPyroscopeHttpRequestTotal != nil { + otelcolReceiverPyroscopeHttpRequestTotal.Add(ctx, 1, + metric.WithAttributeSet(*recv.newOtelcolAttrSetHttp(service, errorCode, statusCode))) + } + recv.logger.Error(msg) writeResponse(resp, "text/plain", statusCode, []byte(msg)) } @@ -240,12 +249,19 @@ func readParams(qs *url.Values) (params, error) { return p, nil } -func newOtelcolAttrSetHttp(service string, errorCode string, statusCode int) *attribute.Set { - s := attribute.NewSet( - attribute.KeyValue{Key: keyService, Value: attribute.StringValue(service)}, - attribute.KeyValue{Key: "error_code", Value: attribute.StringValue(errorCode)}, - attribute.KeyValue{Key: "status_code", Value: attribute.IntValue(statusCode)}, - ) +func (r *pyroscopeReceiver) newOtelcolAttrSetHttp(service string, errorCode string, statusCode int) *attribute.Set { + keyValues := []attribute.KeyValue{ + {Key: keyService, Value: attribute.StringValue(service)}, + {Key: "error_code", Value: attribute.StringValue(errorCode)}, + {Key: "status_code", Value: attribute.IntValue(statusCode)}, + } + for i := len(keyValues) - 1; i >= 0; i-- { + if slices.Contains(r.cfg.Metrics.ExcludeLabels, string(keyValues[i].Key)) { + keyValues[i] = keyValues[len(keyValues)-1] + keyValues = keyValues[:len(keyValues)-1] + } + } + s := attribute.NewSet(keyValues...) return &s } @@ -334,7 +350,9 @@ func (r *pyroscopeReceiver) readProfiles(ctx context.Context, req *http.Request, r.logger.Debug("received profiles", zap.String("url_query", req.URL.RawQuery)) qs := req.URL.Query() + format := formatPprof if tmp, ok = qs["format"]; ok && (tmp[0] == "jfr") { + format = formatJfr p = jfrparser.NewJfrPprofParser() } else if tmp, ok = qs["spyName"]; ok && (tmp[0] == "nodespy") { p = nodeparser.NewNodePprofParser() @@ -358,7 +376,11 @@ func (r *pyroscopeReceiver) readProfiles(ctx context.Context, req *http.Request, return logs, fmt.Errorf("failed to decompress body: %w", err) } // TODO: try measure compressed size - otelcolReceiverPyroscopeRequestBodyUncompressedSizeBytes.Record(ctx, int64(buf.Len()), metric.WithAttributeSet(*newOtelcolAttrSetPayloadSizeBytes(formatJfr, ""))) + if otelcolReceiverPyroscopeRequestBodyUncompressedSizeBytes != nil { + otelcolReceiverPyroscopeRequestBodyUncompressedSizeBytes.Record(ctx, int64(buf.Len()), + metric.WithAttributeSet(*r.newOtelcolAttrSetPayloadSizeBytes(pm.name, format, ""))) + } + resetHeaders(req) md := profile_types.Metadata{SampleRateHertz: 0} @@ -421,7 +443,11 @@ func (r *pyroscopeReceiver) readProfiles(ctx context.Context, req *http.Request, ) } // sz may be 0 and it will be recorded - otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes.Record(ctx, int64(sz), metric.WithAttributeSet(*newOtelcolAttrSetPayloadSizeBytes(formatPprof, ""))) + if otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes != nil { + otelcolReceiverPyroscopeParsedBodyUncompressedSizeBytes.Record(ctx, int64(sz), + metric.WithAttributeSet(*r.newOtelcolAttrSetPayloadSizeBytes(pm.name, formatPprof, ""))) + } + return logs, nil } @@ -429,8 +455,20 @@ func ns(sec uint64) uint64 { return sec * 1e9 } -func newOtelcolAttrSetPayloadSizeBytes(typ string, encoding string) *attribute.Set { - s := attribute.NewSet(attribute.KeyValue{Key: "type", Value: attribute.StringValue(typ)}, attribute.KeyValue{Key: "encoding", Value: attribute.StringValue(encoding)}) +func (r *pyroscopeReceiver) newOtelcolAttrSetPayloadSizeBytes(service string, typ string, + encoding string) *attribute.Set { + keyValues := []attribute.KeyValue{ + {Key: keyService, Value: attribute.StringValue(service)}, + {Key: "type", Value: attribute.StringValue(typ)}, + {Key: "encoding", Value: attribute.StringValue(encoding)}, + } + for i := len(keyValues) - 1; i >= 0; i-- { + if slices.Contains(r.cfg.Metrics.ExcludeLabels, string(keyValues[i].Key)) { + keyValues[i] = keyValues[len(keyValues)-1] + keyValues = keyValues[:len(keyValues)-1] + } + } + s := attribute.NewSet(keyValues...) return &s }