Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prometheus native histogram support to otel.receiver.prometheus #835

Merged
merged 7 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Main (unreleased)
- Update Public preview `remotecfg` to use `alloy-remote-config` instead of `agent-remote-config`. The
API has been updated to use the term `collector` over `agent`. (@erikbaranowski)

### Enhancements

- (_Public preview_) Add native histogram support to `otelcol.receiver.prometheus`. (@wildum)

### Bugfixes

- Fixed an issue with `prometheus.scrape` in which targets that move from one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ prometheus.scrape "scrape_prom_metrics" {
targets = [
{"__address__" = "localhost:9001"},
]
forward_to = [prometheus.remote_write.scrape_prom_metrics.receiver]
forward_to = [prometheus.remote_write.scrape_prom_metrics.receiver, otelcol.receiver.prometheus.scrape_prom_metrics_to_otlp.receiver]
scrape_classic_histograms = true
enable_protobuf_negotiation = true
scrape_interval = "1s"
Expand All @@ -24,3 +24,31 @@ prometheus.remote_write "scrape_prom_metrics" {
test_name = "scrape_prom_metrics",
}
}

otelcol.receiver.prometheus "scrape_prom_metrics_to_otlp" {
output {
metrics = [otelcol.processor.attributes.scrape_prom_metrics_to_otlp.input]
}
}

otelcol.processor.attributes "scrape_prom_metrics_to_otlp" {
action {
key = "test_name"
value = "scrape_prom_metrics_to_otlp"
action = "insert"
}

output {
metrics = [otelcol.exporter.otlphttp.scrape_prom_metrics_to_otlp.input]
}
}

otelcol.exporter.otlphttp "scrape_prom_metrics_to_otlp" {
client {
endpoint = "http://localhost:9009/otlp"
tls {
insecure = true
insecure_skip_verify = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !windows

package main

import (
"testing"

"github.com/grafana/alloy/internal/cmd/integration-tests/common"
)

func TestScrapePromMetricsToOtlp(t *testing.T) {
common.MimirMetricsTest(t, common.PromDefaultMetrics, common.PromDefaultHistogramMetric, "scrape_prom_metrics_to_otlp")
}
2 changes: 1 addition & 1 deletion internal/cmd/integration-tests/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func runSingleTest(testDir string, port int) {
dirName := filepath.Base(testDir)

var alloyLogBuffer bytes.Buffer
cmd := exec.Command(alloyBinaryPath, "run", "config.alloy", "--server.http.listen-addr", fmt.Sprintf("0.0.0.0:%d", port))
cmd := exec.Command(alloyBinaryPath, "run", "config.alloy", "--server.http.listen-addr", fmt.Sprintf("0.0.0.0:%d", port), "--stability.level", "experimental")
cmd.Dir = testDir
cmd.Stdout = &alloyLogBuffer
cmd.Stderr = &alloyLogBuffer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import (
)

// appendable translates Prometheus scraping diffs into OpenTelemetry format.

type appendable struct {
sink consumer.Metrics
metricAdjuster MetricsAdjuster
useStartTimeMetric bool
trimSuffixes bool
startTimeMetricRegex *regexp.Regexp
externalLabels labels.Labels
sink consumer.Metrics
metricAdjuster MetricsAdjuster
useStartTimeMetric bool
enableNativeHistograms bool
trimSuffixes bool
startTimeMetricRegex *regexp.Regexp
externalLabels labels.Labels

settings receiver.CreateSettings
obsrecv *receiverhelper.ObsReport
Expand All @@ -37,6 +37,7 @@ func NewAppendable(
useStartTimeMetric bool,
startTimeMetricRegex *regexp.Regexp,
useCreatedMetric bool,
enableNativeHistograms bool,
externalLabels labels.Labels,
trimSuffixes bool) (storage.Appendable, error) {

Expand All @@ -53,17 +54,18 @@ func NewAppendable(
}

return &appendable{
sink: sink,
settings: set,
metricAdjuster: metricAdjuster,
useStartTimeMetric: useStartTimeMetric,
startTimeMetricRegex: startTimeMetricRegex,
externalLabels: externalLabels,
obsrecv: obsrecv,
trimSuffixes: trimSuffixes,
sink: sink,
settings: set,
metricAdjuster: metricAdjuster,
useStartTimeMetric: useStartTimeMetric,
enableNativeHistograms: enableNativeHistograms,
startTimeMetricRegex: startTimeMetricRegex,
externalLabels: externalLabels,
obsrecv: obsrecv,
trimSuffixes: trimSuffixes,
}, nil
}

func (o *appendable) Appender(ctx context.Context) storage.Appender {
return newTransaction(ctx, o.metricAdjuster, o.sink, o.externalLabels, o.settings, o.obsrecv, o.trimSuffixes)
return newTransaction(ctx, o.metricAdjuster, o.sink, o.externalLabels, o.settings, o.obsrecv, o.trimSuffixes, o.enableNativeHistograms)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Package internal is a near copy of
// https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.87.0/receiver/prometheusreceiver/internal
// https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/90603afc2fe0c44c9398822e4afa3a4e045f4524/receiver/prometheusreceiver/internal
// A copy was made because the upstream package is internal. If it is ever made
// public, our copy can be removed.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ type zapToGokitLogAdapter struct {
type logData struct {
level level.Value
msg string
otherFields []interface{}
otherFields []any
}

func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error {
func (w *zapToGokitLogAdapter) Log(keyvals ...any) error {
// expecting key value pairs, the number of items need to be even
if len(keyvals)%2 == 0 {
// Extract log level and message and log them using corresponding zap function
Expand All @@ -47,7 +47,7 @@ func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error {
return nil
}

func extractLogData(keyvals []interface{}) logData {
func extractLogData(keyvals []any) logData {
ld := logData{
level: level.InfoValue(), // default
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func extractLogData(keyvals []interface{}) logData {
}

// check if a given key-value pair represents go-kit log message and return it
func matchLogMessage(key interface{}, val interface{}) (string, bool) {
func matchLogMessage(key any, val any) (string, bool) {
if strKey, ok := key.(string); !ok || strKey != msgKey {
return "", false
}
Expand All @@ -91,7 +91,7 @@ func matchLogMessage(key interface{}, val interface{}) (string, bool) {
}

// check if a given key-value pair represents go-kit log level and return it
func matchLogLevel(key interface{}, val interface{}) (level.Value, bool) {
func matchLogLevel(key any, val any) (level.Value, bool) {
strKey, ok := key.(string)
if !ok || strKey != levelKey {
return nil, false
Expand All @@ -107,7 +107,7 @@ func matchLogLevel(key interface{}, val interface{}) (level.Value, bool) {
//revive:disable:error-return

// check if a given key-value pair represents an error and return it
func matchError(key interface{}, val interface{}) (error, bool) {
func matchError(key any, val any) (error, bool) {
strKey, ok := key.(string)
if !ok || strKey != errKey {
return nil, false
Expand All @@ -123,7 +123,7 @@ func matchError(key interface{}, val interface{}) (error, bool) {
//revive:enable:error-return

// find a matching zap logging function to be used for a given level
func levelToFunc(logger *zap.SugaredLogger, lvl level.Value) func(string, ...interface{}) {
func levelToFunc(logger *zap.SugaredLogger, lvl level.Value) func(string, ...any) {
switch lvl {
case level.DebugValue():
return logger.Debugw
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import (
func TestLog(t *testing.T) {
tcs := []struct {
name string
input []interface{}
input []any
wantLevel zapcore.Level
wantMessage string
}{
{
name: "Starting provider",
input: []interface{}{
input: []any{
"level",
level.DebugValue(),
"msg",
Expand All @@ -41,7 +41,7 @@ func TestLog(t *testing.T) {
},
{
name: "Scrape failed",
input: []interface{}{
input: []any{
"level",
level.ErrorValue(),
"scrape_pool",
Expand Down Expand Up @@ -84,10 +84,10 @@ func TestLog(t *testing.T) {
func TestExtractLogData(t *testing.T) {
tcs := []struct {
name string
input []interface{}
input []any
wantLevel level.Value
wantMessage string
wantOutput []interface{}
wantOutput []any
}{
{
name: "nil fields",
Expand All @@ -98,14 +98,14 @@ func TestExtractLogData(t *testing.T) {
},
{
name: "empty fields",
input: []interface{}{},
input: []any{},
wantLevel: level.InfoValue(), // Default
wantMessage: "",
wantOutput: nil,
},
{
name: "info level",
input: []interface{}{
input: []any{
"level",
level.InfoValue(),
},
Expand All @@ -115,7 +115,7 @@ func TestExtractLogData(t *testing.T) {
},
{
name: "warn level",
input: []interface{}{
input: []any{
"level",
level.WarnValue(),
},
Expand All @@ -125,7 +125,7 @@ func TestExtractLogData(t *testing.T) {
},
{
name: "error level",
input: []interface{}{
input: []any{
"level",
level.ErrorValue(),
},
Expand All @@ -135,7 +135,7 @@ func TestExtractLogData(t *testing.T) {
},
{
name: "debug level + extra fields",
input: []interface{}{
input: []any{
"timestamp",
1596604719,
"level",
Expand All @@ -145,32 +145,32 @@ func TestExtractLogData(t *testing.T) {
},
wantLevel: level.DebugValue(),
wantMessage: "http client error",
wantOutput: []interface{}{
wantOutput: []any{
"timestamp", 1596604719,
},
},
{
name: "missing level field",
input: []interface{}{
input: []any{
"timestamp",
1596604719,
"msg",
"http client error",
},
wantLevel: level.InfoValue(), // Default
wantMessage: "http client error",
wantOutput: []interface{}{
wantOutput: []any{
"timestamp", 1596604719,
},
},
{
name: "invalid level type",
input: []interface{}{
input: []any{
"level",
"warn", // String is not recognized
},
wantLevel: level.InfoValue(), // Default
wantOutput: []interface{}{
wantOutput: []any{
"level", "warn", // Field is preserved
},
},
Expand Down
Loading
Loading