Skip to content

Commit

Permalink
Support for exemplars in prometheusexporter (#13127)
Browse files Browse the repository at this point in the history
* Adding a feature to prometheusexporter to export exemplars along with histogram metrics. Also, this PR includes client_golang v1.13.0 that contains our fix for the panic issue.

* read me update

* issue # added to unreleased yaml
  • Loading branch information
arun-shopify committed Aug 19, 2022
1 parent 4e763e8 commit 976fab3
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 8 deletions.
5 changes: 3 additions & 2 deletions exporter/prometheusexporter/README.md
Expand Up @@ -18,11 +18,11 @@ The following settings can be optionally configured:

- `const_labels` (no default): key/values that are applied for every exported metric.
- `namespace` (no default): if set, exports metrics under the provided value.
- `send_timestamps` (default = `false`): if true, sends the timestamp of the underlying
metric sample in the response.
- `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.
- `enable_open_metrics`: (default = `false`): If true, metrics will be exported using the OpenMetrics format. Exemplars are only exported in the OpenMetrics format.

Example:

Expand All @@ -40,6 +40,7 @@ exporters:
"another label": spaced value
send_timestamps: true
metric_expiration: 180m
enable_open_metrics: true
resource_to_telemetry_conversion:
enabled: true
```
Expand Down
25 changes: 25 additions & 0 deletions exporter/prometheusexporter/collector.go
Expand Up @@ -217,11 +217,36 @@ func (c *collector) convertDoubleHistogram(metric pmetric.Metric, resourceAttrs
points[bucket] = cumCount
}

arrLen := ip.Exemplars().Len()
exemplars := make([]prometheus.Exemplar, arrLen)
for i := 0; i < arrLen; i++ {
e := ip.Exemplars().At(i)

labels := make(prometheus.Labels, e.FilteredAttributes().Len())
e.FilteredAttributes().Range(func(k string, v pcommon.Value) bool {
labels[k] = v.AsString()
return true
})

exemplars[i] = prometheus.Exemplar{
Value: e.DoubleVal(),
Labels: labels,
Timestamp: e.Timestamp().AsTime(),
}
}

m, err := prometheus.NewConstHistogram(desc, ip.Count(), ip.Sum(), points, attributes...)
if err != nil {
return nil, err
}

if arrLen > 0 {
m, err = prometheus.NewMetricWithExemplars(m, exemplars...)
if err != nil {
return nil, err
}
}

if c.sendTimestamps {
return prometheus.NewMetricWithTimestamp(ip.Timestamp().AsTime(), m), nil
}
Expand Down
94 changes: 94 additions & 0 deletions exporter/prometheusexporter/collector_test.go
Expand Up @@ -97,6 +97,100 @@ func TestConvertInvalidMetric(t *testing.T) {
}
}

func TestConvertDoubleHistogramExemplar(t *testing.T) {
// initialize empty histogram
metric := pmetric.NewMetric()
metric.SetDataType(pmetric.MetricDataTypeHistogram)
metric.SetName("test_metric")
metric.SetDescription("this is test metric")
metric.SetUnit("T")

// initialize empty datapoint
hd := metric.Histogram().DataPoints().AppendEmpty()

bounds := pcommon.NewImmutableFloat64Slice([]float64{5, 25, 90})
hd.SetExplicitBounds(bounds)
bc := pcommon.NewImmutableUInt64Slice([]uint64{2, 35, 70})
hd.SetBucketCounts(bc)

exemplarTs, _ := time.Parse("unix", "Mon Jan _2 15:04:05 MST 2006")
exemplars := []prometheus.Exemplar{
{
Timestamp: exemplarTs,
Value: 3,
Labels: prometheus.Labels{"test_label_0": "label_value_0"},
},
{
Timestamp: exemplarTs,
Value: 50,
Labels: prometheus.Labels{"test_label_1": "label_value_1"},
},
{
Timestamp: exemplarTs,
Value: 78,
Labels: prometheus.Labels{"test_label_2": "label_value_2"},
},
{
Timestamp: exemplarTs,
Value: 100,
Labels: prometheus.Labels{"test_label_3": "label_value_3"},
},
}

// add each exemplar value to the metric
for _, e := range exemplars {
pde := hd.Exemplars().AppendEmpty()
pde.SetDoubleVal(e.Value)
for k, v := range e.Labels {
pde.FilteredAttributes().InsertString(k, v)
}
pde.SetTimestamp(pcommon.NewTimestampFromTime(e.Timestamp))
}

pMap := pcommon.NewMap()

c := collector{
accumulator: &mockAccumulator{
metrics: []pmetric.Metric{metric},
resourceAttributes: pMap,
},
logger: zap.NewNop(),
}

pbMetric, _ := c.convertDoubleHistogram(metric, pMap)
m := io_prometheus_client.Metric{}
err := pbMetric.Write(&m)
if err != nil {
return
}

buckets := m.GetHistogram().GetBucket()

require.Equal(t, 4, len(buckets))

require.Equal(t, 3.0, buckets[0].GetExemplar().GetValue())
require.Equal(t, int32(128654848), buckets[0].GetExemplar().GetTimestamp().GetNanos())
require.Equal(t, 1, len(buckets[0].GetExemplar().GetLabel()))
require.Equal(t, "test_label_0", buckets[0].GetExemplar().GetLabel()[0].GetName())
require.Equal(t, "label_value_0", buckets[0].GetExemplar().GetLabel()[0].GetValue())

require.Equal(t, 0.0, buckets[1].GetExemplar().GetValue())
require.Equal(t, int32(0), buckets[1].GetExemplar().GetTimestamp().GetNanos())
require.Equal(t, 0, len(buckets[1].GetExemplar().GetLabel()))

require.Equal(t, 78.0, buckets[2].GetExemplar().GetValue())
require.Equal(t, int32(128654848), buckets[2].GetExemplar().GetTimestamp().GetNanos())
require.Equal(t, 1, len(buckets[2].GetExemplar().GetLabel()))
require.Equal(t, "test_label_2", buckets[2].GetExemplar().GetLabel()[0].GetName())
require.Equal(t, "label_value_2", buckets[2].GetExemplar().GetLabel()[0].GetValue())

require.Equal(t, 100.0, buckets[3].GetExemplar().GetValue())
require.Equal(t, int32(128654848), buckets[3].GetExemplar().GetTimestamp().GetNanos())
require.Equal(t, 1, len(buckets[3].GetExemplar().GetLabel()))
require.Equal(t, "test_label_3", buckets[3].GetExemplar().GetLabel()[0].GetName())
require.Equal(t, "label_value_3", buckets[3].GetExemplar().GetLabel()[0].GetValue())
}

// errorCheckCore keeps track of logged errors
type errorCheckCore struct {
errorMessages []string
Expand Down
3 changes: 3 additions & 0 deletions exporter/prometheusexporter/config.go
Expand Up @@ -43,6 +43,9 @@ type Config struct {

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

// EnableOpenMetrics enables the use of the OpenMetrics encoding option for the prometheus exporter.
EnableOpenMetrics bool `mapstructure:"enable_open_metrics"`
}

var _ config.Exporter = (*Config)(nil)
Expand Down
9 changes: 5 additions & 4 deletions exporter/prometheusexporter/factory.go
Expand Up @@ -43,10 +43,11 @@ func NewFactory() component.ExporterFactory {

func createDefaultConfig() config.Exporter {
return &Config{
ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)),
ConstLabels: map[string]string{},
SendTimestamps: false,
MetricExpiration: time.Minute * 5,
ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)),
ConstLabels: map[string]string{},
SendTimestamps: false,
MetricExpiration: time.Minute * 5,
EnableOpenMetrics: false,
}
}

Expand Down
5 changes: 3 additions & 2 deletions exporter/prometheusexporter/prometheus.go
Expand Up @@ -58,8 +58,9 @@ func newPrometheusExporter(config *Config, set component.ExporterCreateSettings)
handler: promhttp.HandlerFor(
registry,
promhttp.HandlerOpts{
ErrorHandling: promhttp.ContinueOnError,
ErrorLog: newPromLogger(set.Logger),
ErrorHandling: promhttp.ContinueOnError,
ErrorLog: newPromLogger(set.Logger),
EnableOpenMetrics: config.EnableOpenMetrics,
},
),
}, nil
Expand Down
16 changes: 16 additions & 0 deletions unreleased/prometheusexporter-exemplars.yaml
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: prometheusexporter

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Adds a feature to prometheusexporter that enables it to export exemplars along with histogram metrics.

# One or more tracking issues related to the change
issues: [5192]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

0 comments on commit 976fab3

Please sign in to comment.