Skip to content

Commit

Permalink
Fix prometheusexporter droping the OTEL resource labels (#2899)
Browse files Browse the repository at this point in the history
* fix: add resource_attributes_as_tags support prometheusexporter

* fix: prometheus exporter test cases

* fix: yaml indentation

* chore: add test case to ensure resource labels are added to prometheus output

* fix: missing test case for exporterhelper.NewMetricsExporter error

* refactor: use existing resource_to_telemetry_conversion convention from exporterhelper
  • Loading branch information
krak3n authored Apr 21, 2021
1 parent e4a0713 commit 034902c
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 3 deletions.
4 changes: 4 additions & 0 deletions exporter/prometheusexporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ The following settings can be optionally configured:
- `send_timestamps` (default = `false`): if true, sends the timestamp of the underlying
metric sample in the response.
- `metric_expiration` (default = `5m`): defines how long metrics are exposed without updates
- `resource_to_telemetry_conversion`
- `enabled` (default = false): If `enabled` is `true`, all the resource attributes will be converted to metric labels by default.

Example:

Expand All @@ -30,4 +32,6 @@ exporters:
"another label": spaced value
send_timestamps: true
metric_expiration: 180m
resource_to_telemetry_conversion:
enabled: true
```
4 changes: 4 additions & 0 deletions exporter/prometheusexporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/prometheus/client_golang/prometheus"

"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)

// Config defines configuration for Prometheus exporter.
Expand All @@ -40,6 +41,9 @@ type Config struct {

// MetricExpiration defines how long metrics are kept without updates
MetricExpiration time.Duration `mapstructure:"metric_expiration"`

// ResourceToTelemetrySettings defines configuration for converting resource attributes to metric labels.
exporterhelper.ResourceToTelemetrySettings `mapstructure:"resource_to_telemetry_conversion"`
}

var _ config.Exporter = (*Config)(nil)
Expand Down
27 changes: 26 additions & 1 deletion exporter/prometheusexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,30 @@ func createMetricsExporter(
) (component.MetricsExporter, error) {
pcfg := cfg.(*Config)

return newPrometheusExporter(pcfg, params.Logger)
prometheus, err := newPrometheusExporter(pcfg, params.Logger)
if err != nil {
return nil, err
}

exporter, err := exporterhelper.NewMetricsExporter(
cfg,
params.Logger,
prometheus.ConsumeMetrics,
exporterhelper.WithStart(prometheus.Start),
exporterhelper.WithShutdown(prometheus.Shutdown),
exporterhelper.WithResourceToTelemetryConversion(pcfg.ResourceToTelemetrySettings),
)
if err != nil {
return nil, err
}

return &wrapMetricsExpoter{
MetricsExporter: exporter,
exporter: prometheus,
}, nil
}

type wrapMetricsExpoter struct {
component.MetricsExporter
exporter *prometheusExporter
}
16 changes: 16 additions & 0 deletions exporter/prometheusexporter/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,19 @@ func TestCreateMetricsExporter(t *testing.T) {
require.Equal(t, errBlankPrometheusAddress, err)
require.Nil(t, exp)
}

func TestCreateMetricsExporterExportHelperError(t *testing.T) {
cfg, ok := createDefaultConfig().(*Config)
require.True(t, ok)

cfg.Endpoint = "http://localhost:8889"

// Should give us an exporterhelper.errNilLogger
exp, err := createMetricsExporter(
context.Background(),
component.ExporterCreateParams{Logger: nil},
cfg)

assert.Nil(t, exp)
assert.Error(t, err)
}
65 changes: 63 additions & 2 deletions exporter/prometheusexporter/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/internal/testdata"
"go.opentelemetry.io/collector/translator/internaldata"
)

Expand Down Expand Up @@ -162,7 +164,7 @@ func TestPrometheusExporter_endToEnd(t *testing.T) {
}

// Expired metrics should be removed during first scrape
exp.(*prometheusExporter).collector.accumulator.(*lastValueAccumulator).metricExpiration = 1 * time.Millisecond
exp.(*wrapMetricsExpoter).exporter.collector.accumulator.(*lastValueAccumulator).metricExpiration = 1 * time.Millisecond
time.Sleep(10 * time.Millisecond)

res, err := http.Get("http://localhost:7777/metrics")
Expand Down Expand Up @@ -240,7 +242,7 @@ func TestPrometheusExporter_endToEndWithTimestamps(t *testing.T) {
}

// Expired metrics should be removed during first scrape
exp.(*prometheusExporter).collector.accumulator.(*lastValueAccumulator).metricExpiration = 1 * time.Millisecond
exp.(*wrapMetricsExpoter).exporter.collector.accumulator.(*lastValueAccumulator).metricExpiration = 1 * time.Millisecond
time.Sleep(10 * time.Millisecond)

res, err := http.Get("http://localhost:7777/metrics")
Expand All @@ -254,6 +256,65 @@ func TestPrometheusExporter_endToEndWithTimestamps(t *testing.T) {
require.Emptyf(t, string(blob), "Metrics did not expire")
}

func TestPrometheusExporter_endToEndWithResource(t *testing.T) {
cfg := &Config{
ExporterSettings: config.NewExporterSettings(typeStr),
Namespace: "test",
ConstLabels: map[string]string{
"foo2": "bar2",
"code2": "one2",
},
Endpoint: ":7777",
SendTimestamps: true,
MetricExpiration: 120 * time.Minute,
ResourceToTelemetrySettings: exporterhelper.ResourceToTelemetrySettings{
Enabled: true,
},
}

factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
exp, err := factory.CreateMetricsExporter(context.Background(), creationParams, cfg)
assert.NoError(t, err)

t.Cleanup(func() {
require.NoError(t, exp.Shutdown(context.Background()))
// trigger a get so that the server cleans up our keepalive socket
http.Get("http://localhost:7777/metrics")
})

assert.NotNil(t, exp)
require.NoError(t, exp.Start(context.Background(), componenttest.NewNopHost()))

md := testdata.GenerateMetricsOneMetric()
assert.NotNil(t, md)

assert.NoError(t, exp.ConsumeMetrics(context.Background(), md))

rsp, err := http.Get("http://localhost:7777/metrics")
require.NoError(t, err, "Failed to perform a scrape")

if g, w := rsp.StatusCode, 200; g != w {
t.Errorf("Mismatched HTTP response status code: Got: %d Want: %d", g, w)
}

blob, _ := ioutil.ReadAll(rsp.Body)
_ = rsp.Body.Close()

want := []string{
`# HELP test_counter_int`,
`# TYPE test_counter_int counter`,
`test_counter_int{code2="one2",foo2="bar2",label_1="label-value-1",resource_attr="resource-attr-val-1"} 123 1581452773000`,
`test_counter_int{code2="one2",foo2="bar2",label_2="label-value-2",resource_attr="resource-attr-val-1"} 456 1581452773000`,
}

for _, w := range want {
if !strings.Contains(string(blob), w) {
t.Errorf("Missing %v from response:\n%v", w, string(blob))
}
}
}

func metricBuilder(delta int64, prefix string) []*metricspb.Metric {
return []*metricspb.Metric{
{
Expand Down

0 comments on commit 034902c

Please sign in to comment.