Skip to content

Commit a0e6491

Browse files
Juraj MichálekatoulmeAneurysm9
authored
[exporter/prometheusremotewrite] prometheusremotewrite exporter add option to send metadata (#27565)
**Description:** <Describe what has changed.> <!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> This PR adds an option to send metric Metadata to prometheus compatible backend (disabled by default). This contains information such as metric descrtiption, type, unit, and name. **Link to tracking Issue:** <Issue number if applicable> #13849 **Testing:** <Describe what testing was performed and which tests were added.> Tested in our testing environment with locally built image. **Documentation:** <Describe the documentation added.> --------- Co-authored-by: Antoine Toulme <[email protected]> Co-authored-by: Anthony Mirabella <[email protected]>
1 parent d0ca48f commit a0e6491

File tree

11 files changed

+368
-8
lines changed

11 files changed

+368
-8
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: exporter/prometheusremotewrite
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: prometheusremotewrite exporter add option to send metadata
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [ 13849 ]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

exporter/prometheusremotewriteexporter/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ The following settings can be optionally configured:
5252
- *Note the following headers cannot be changed: `Content-Encoding`, `Content-Type`, `X-Prometheus-Remote-Write-Version`, and `User-Agent`.*
5353
- `namespace`: prefix attached to each exported metric name.
5454
- `add_metric_suffixes`: If set to false, type and unit suffixes will not be added to metrics. Default: true.
55+
- `send_metadata`: If set to true, prometheus metadata will be generated and sent. Default: false.
5556
- `remote_write_queue`: fine tuning for queueing and sending of the outgoing remote writes.
5657
- `enabled`: enable the sending queue (default: `true`)
5758
- `queue_size`: number of OTLP metrics that can be queued. Ignored if `enabled` is `false` (default: `10000`)

exporter/prometheusremotewriteexporter/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ type Config struct {
4848

4949
// AddMetricSuffixes controls whether unit and type suffixes are added to metrics on export
5050
AddMetricSuffixes bool `mapstructure:"add_metric_suffixes"`
51+
52+
// SendMetadata controls whether prometheus metadata will be generated and sent
53+
SendMetadata bool `mapstructure:"send_metadata"`
5154
}
5255

5356
type CreatedMetric struct {

exporter/prometheusremotewriteexporter/exporter.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func newPRWExporter(cfg *Config, set exporter.CreateSettings) (*prwExporter, err
7777
DisableTargetInfo: !cfg.TargetInfo.Enabled,
7878
ExportCreatedMetric: cfg.CreatedMetric.Enabled,
7979
AddMetricSuffixes: cfg.AddMetricSuffixes,
80+
SendMetadata: cfg.SendMetadata,
8081
},
8182
}
8283
if cfg.WAL == nil {
@@ -130,12 +131,18 @@ func (prwe *prwExporter) PushMetrics(ctx context.Context, md pmetric.Metrics) er
130131
case <-prwe.closeChan:
131132
return errors.New("shutdown has been called")
132133
default:
134+
133135
tsMap, err := prometheusremotewrite.FromMetrics(md, prwe.exporterSettings)
134136
if err != nil {
135137
err = consumererror.NewPermanent(err)
136138
}
139+
140+
var m []*prompb.MetricMetadata
141+
if prwe.exporterSettings.SendMetadata {
142+
m = prometheusremotewrite.OtelMetricsToMetadata(md, prwe.exporterSettings.AddMetricSuffixes)
143+
}
137144
// Call export even if a conversion error, since there may be points that were successfully converted.
138-
return multierr.Combine(err, prwe.handleExport(ctx, tsMap))
145+
return multierr.Combine(err, prwe.handleExport(ctx, tsMap, m))
139146
}
140147
}
141148

@@ -151,14 +158,14 @@ func validateAndSanitizeExternalLabels(cfg *Config) (map[string]string, error) {
151158
return sanitizedLabels, nil
152159
}
153160

154-
func (prwe *prwExporter) handleExport(ctx context.Context, tsMap map[string]*prompb.TimeSeries) error {
161+
func (prwe *prwExporter) handleExport(ctx context.Context, tsMap map[string]*prompb.TimeSeries, m []*prompb.MetricMetadata) error {
155162
// There are no metrics to export, so return.
156163
if len(tsMap) == 0 {
157164
return nil
158165
}
159166

160167
// Calls the helper function to convert and batch the TsMap to the desired format
161-
requests, err := batchTimeSeries(tsMap, prwe.maxBatchSizeBytes)
168+
requests, err := batchTimeSeries(tsMap, prwe.maxBatchSizeBytes, m)
162169
if err != nil {
163170
return err
164171
}

exporter/prometheusremotewriteexporter/exporter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ func runExportPipeline(ts *prompb.TimeSeries, endpoint *url.URL) error {
368368
return err
369369
}
370370

371-
return prwe.handleExport(context.Background(), testmap)
371+
return prwe.handleExport(context.Background(), testmap, nil)
372372
}
373373

374374
// Test_PushMetrics checks the number of TimeSeries received by server and the number of metrics dropped is the same as
@@ -919,7 +919,7 @@ func TestWALOnExporterRoundTrip(t *testing.T) {
919919
"timeseries1": ts1,
920920
"timeseries2": ts2,
921921
}
922-
errs := prwe.handleExport(ctx, tsMap)
922+
errs := prwe.handleExport(ctx, tsMap, nil)
923923
assert.NoError(t, errs)
924924
// Shutdown after we've written to the WAL. This ensures that our
925925
// exported data in-flight will flushed flushed to the WAL before exiting.

exporter/prometheusremotewriteexporter/factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func createDefaultConfig() component.Config {
8181
Multiplier: backoff.DefaultMultiplier,
8282
},
8383
AddMetricSuffixes: true,
84+
SendMetadata: false,
8485
HTTPClientSettings: confighttp.HTTPClientSettings{
8586
Endpoint: "http://some.url:9411/api/prom/push",
8687
// We almost read 0 bytes, so no need to tune ReadBufferSize.

exporter/prometheusremotewriteexporter/helper.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import (
1111
)
1212

1313
// batchTimeSeries splits series into multiple batch write requests.
14-
func batchTimeSeries(tsMap map[string]*prompb.TimeSeries, maxBatchByteSize int) ([]*prompb.WriteRequest, error) {
14+
func batchTimeSeries(tsMap map[string]*prompb.TimeSeries, maxBatchByteSize int, m []*prompb.MetricMetadata) ([]*prompb.WriteRequest, error) {
1515
if len(tsMap) == 0 {
1616
return nil, errors.New("invalid tsMap: cannot be empty map")
1717
}
1818

19-
requests := make([]*prompb.WriteRequest, 0, len(tsMap))
19+
requests := make([]*prompb.WriteRequest, 0, len(tsMap)+len(m))
2020
tsArray := make([]prompb.TimeSeries, 0, len(tsMap))
2121
sizeOfCurrentBatch := 0
2222

@@ -42,6 +42,30 @@ func batchTimeSeries(tsMap map[string]*prompb.TimeSeries, maxBatchByteSize int)
4242
requests = append(requests, wrapped)
4343
}
4444

45+
mArray := make([]prompb.MetricMetadata, 0, len(m))
46+
sizeOfCurrentBatch = 0
47+
i = 0
48+
for _, v := range m {
49+
sizeOfM := v.Size()
50+
51+
if sizeOfCurrentBatch+sizeOfM >= maxBatchByteSize {
52+
wrapped := convertMetadataToRequest(mArray)
53+
requests = append(requests, wrapped)
54+
55+
mArray = make([]prompb.MetricMetadata, 0, len(m)-i)
56+
sizeOfCurrentBatch = 0
57+
}
58+
59+
mArray = append(mArray, *v)
60+
sizeOfCurrentBatch += sizeOfM
61+
i++
62+
}
63+
64+
if len(mArray) != 0 {
65+
wrapped := convertMetadataToRequest(mArray)
66+
requests = append(requests, wrapped)
67+
}
68+
4569
return requests, nil
4670
}
4771

@@ -57,6 +81,12 @@ func convertTimeseriesToRequest(tsArray []prompb.TimeSeries) *prompb.WriteReques
5781
}
5882
}
5983

84+
func convertMetadataToRequest(m []prompb.MetricMetadata) *prompb.WriteRequest {
85+
return &prompb.WriteRequest{
86+
Metadata: m,
87+
}
88+
}
89+
6090
func orderBySampleTimestamp(tsArray []prompb.TimeSeries) []prompb.TimeSeries {
6191
for i := range tsArray {
6292
sL := tsArray[i].Samples

exporter/prometheusremotewriteexporter/helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func Test_batchTimeSeries(t *testing.T) {
5757
// run tests
5858
for _, tt := range tests {
5959
t.Run(tt.name, func(t *testing.T) {
60-
requests, err := batchTimeSeries(tt.tsMap, tt.maxBatchByteSize)
60+
requests, err := batchTimeSeries(tt.tsMap, tt.maxBatchByteSize, nil)
6161
if tt.returnErr {
6262
assert.Error(t, err)
6363
return

pkg/translator/prometheusremotewrite/metrics_to_prw.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Settings struct {
2121
DisableTargetInfo bool
2222
ExportCreatedMetric bool
2323
AddMetricSuffixes bool
24+
SendMetadata bool
2425
}
2526

2627
// FromMetrics converts pmetric.Metrics to prometheus remote write format.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package prometheusremotewrite // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheusremotewrite"
5+
6+
import (
7+
"github.com/prometheus/prometheus/prompb"
8+
"go.opentelemetry.io/collector/pdata/pmetric"
9+
10+
prometheustranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"
11+
)
12+
13+
func otelMetricTypeToPromMetricType(otelMetric pmetric.Metric) prompb.MetricMetadata_MetricType {
14+
switch otelMetric.Type() {
15+
case pmetric.MetricTypeGauge:
16+
return prompb.MetricMetadata_GAUGE
17+
case pmetric.MetricTypeSum:
18+
metricType := prompb.MetricMetadata_GAUGE
19+
if otelMetric.Sum().IsMonotonic() {
20+
metricType = prompb.MetricMetadata_COUNTER
21+
}
22+
return metricType
23+
case pmetric.MetricTypeHistogram:
24+
return prompb.MetricMetadata_HISTOGRAM
25+
case pmetric.MetricTypeSummary:
26+
return prompb.MetricMetadata_SUMMARY
27+
case pmetric.MetricTypeExponentialHistogram:
28+
return prompb.MetricMetadata_HISTOGRAM
29+
}
30+
return prompb.MetricMetadata_UNKNOWN
31+
}
32+
33+
func OtelMetricsToMetadata(md pmetric.Metrics, addMetricSuffixes bool) []*prompb.MetricMetadata {
34+
resourceMetricsSlice := md.ResourceMetrics()
35+
36+
metadataLength := 0
37+
for i := 0; i < resourceMetricsSlice.Len(); i++ {
38+
scopeMetricsSlice := resourceMetricsSlice.At(i).ScopeMetrics()
39+
for j := 0; j < scopeMetricsSlice.Len(); j++ {
40+
metadataLength += scopeMetricsSlice.At(j).Metrics().Len()
41+
}
42+
}
43+
44+
var metadata = make([]*prompb.MetricMetadata, 0, metadataLength)
45+
for i := 0; i < resourceMetricsSlice.Len(); i++ {
46+
resourceMetrics := resourceMetricsSlice.At(i)
47+
scopeMetricsSlice := resourceMetrics.ScopeMetrics()
48+
49+
for j := 0; j < scopeMetricsSlice.Len(); j++ {
50+
scopeMetrics := scopeMetricsSlice.At(j)
51+
for k := 0; k < scopeMetrics.Metrics().Len(); k++ {
52+
metric := scopeMetrics.Metrics().At(k)
53+
entry := prompb.MetricMetadata{
54+
Type: otelMetricTypeToPromMetricType(metric),
55+
MetricFamilyName: prometheustranslator.BuildCompliantName(metric, "", addMetricSuffixes),
56+
Help: metric.Description(),
57+
}
58+
metadata = append(metadata, &entry)
59+
}
60+
}
61+
}
62+
63+
return metadata
64+
}

0 commit comments

Comments
 (0)