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

[receiver/prometheus] Support OpenMetrics Info and Stateset metrics #9378

Merged
merged 3 commits into from
Jun 7, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- `transformprocessor`: Add new `limit` function to allow limiting the number of items in a map, such as the number of attributes in `attributes` or `resource.attributes` (#9552)
- `processor/attributes`: Support attributes set by server authenticator (#9420)
- `datadogexporter`: Experimental support for Exponential Histograms with delta aggregation temporality (#8350)
- `prometheusreceiver`: Support OpenMetrics Info and Stateset metrics (#9378)

### 🧰 Bug fixes 🧰

Expand Down
9 changes: 6 additions & 3 deletions receiver/prometheusreceiver/internal/otlp_metricfamily.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import (
)

type metricFamily struct {
mtype pmetric.MetricDataType
mtype pmetric.MetricDataType
// isMonotonic only applies to sums
isMonotonic bool
groups map[string]*metricGroup
name string
mc MetadataCache
Expand Down Expand Up @@ -59,13 +61,14 @@ var pdataStaleFlags = pmetric.NewMetricDataPointFlags(pmetric.MetricDataPointFla

func newMetricFamily(metricName string, mc MetadataCache, logger *zap.Logger) *metricFamily {
metadata, familyName := metadataForMetric(metricName, mc)
mtype := convToMetricType(metadata.Type)
mtype, isMonotonic := convToMetricType(metadata.Type)
if mtype == pmetric.MetricDataTypeNone {
logger.Debug(fmt.Sprintf("Unknown-typed metric : %s %+v", metricName, metadata))
}

return &metricFamily{
mtype: mtype,
isMonotonic: isMonotonic,
groups: make(map[string]*metricGroup),
name: familyName,
mc: mc,
Expand Down Expand Up @@ -354,7 +357,7 @@ func (mf *metricFamily) ToMetric(metrics *pmetric.MetricSlice) (int, int) {
case pmetric.MetricDataTypeSum:
sum := metric.Sum()
sum.SetAggregationTemporality(pmetric.MetricAggregationTemporalityCumulative)
sum.SetIsMonotonic(true)
sum.SetIsMonotonic(mf.isMonotonic)
sdpL := sum.DataPoints()
for _, mg := range mf.getGroups() {
if !mg.toNumberDataPoint(mf.labelKeysOrdered, &sdpL) {
Expand Down
17 changes: 10 additions & 7 deletions receiver/prometheusreceiver/internal/otlp_metricsbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,28 @@ func getBoundary(metricType pmetric.MetricDataType, labels labels.Labels) (float
return strconv.ParseFloat(v, 64)
}

func convToMetricType(metricType textparse.MetricType) pmetric.MetricDataType {
// convToMetricType returns the data type and if it is monotonic
func convToMetricType(metricType textparse.MetricType) (pmetric.MetricDataType, bool) {
switch metricType {
case textparse.MetricTypeCounter:
// always use float64, as it's the internal data type used in prometheus
return pmetric.MetricDataTypeSum
return pmetric.MetricDataTypeSum, true
// textparse.MetricTypeUnknown is converted to gauge by default to prevent Prometheus untyped metrics from being dropped
case textparse.MetricTypeGauge, textparse.MetricTypeUnknown:
return pmetric.MetricDataTypeGauge
return pmetric.MetricDataTypeGauge, false
case textparse.MetricTypeHistogram:
return pmetric.MetricDataTypeHistogram
return pmetric.MetricDataTypeHistogram, true
// dropping support for gaugehistogram for now until we have an official spec of its implementation
// a draft can be found in: https://docs.google.com/document/d/1KwV0mAXwwbvvifBvDKH_LU1YjyXE_wxCkHNoCGq1GX0/edit#heading=h.1cvzqd4ksd23
// case textparse.MetricTypeGaugeHistogram:
// return <pdata gauge histogram type>
case textparse.MetricTypeSummary:
return pmetric.MetricDataTypeSummary
return pmetric.MetricDataTypeSummary, true
case textparse.MetricTypeInfo, textparse.MetricTypeStateset:
return pmetric.MetricDataTypeSum, false
default:
// including: textparse.MetricTypeGaugeHistogram, textparse.MetricTypeInfo, textparse.MetricTypeStateset
return pmetric.MetricDataTypeNone
// including: textparse.MetricTypeGaugeHistogram
return pmetric.MetricDataTypeNone, false
}
}

Expand Down
65 changes: 40 additions & 25 deletions receiver/prometheusreceiver/internal/otlp_metricsbuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,52 +181,67 @@ func TestGetBoundary(t *testing.T) {

func TestConvToMetricType(t *testing.T) {
tests := []struct {
name string
mtype textparse.MetricType
want pmetric.MetricDataType
name string
mtype textparse.MetricType
want pmetric.MetricDataType
wantMonotonic bool
}{
{
name: "textparse.counter",
mtype: textparse.MetricTypeCounter,
want: pmetric.MetricDataTypeSum,
name: "textparse.counter",
mtype: textparse.MetricTypeCounter,
want: pmetric.MetricDataTypeSum,
wantMonotonic: true,
},
{
name: "textparse.gauge",
mtype: textparse.MetricTypeGauge,
want: pmetric.MetricDataTypeGauge,
name: "textparse.gauge",
mtype: textparse.MetricTypeGauge,
want: pmetric.MetricDataTypeGauge,
wantMonotonic: false,
},
{
name: "textparse.unknown",
mtype: textparse.MetricTypeUnknown,
want: pmetric.MetricDataTypeGauge,
name: "textparse.unknown",
mtype: textparse.MetricTypeUnknown,
want: pmetric.MetricDataTypeGauge,
wantMonotonic: false,
},
{
name: "textparse.histogram",
mtype: textparse.MetricTypeHistogram,
want: pmetric.MetricDataTypeHistogram,
name: "textparse.histogram",
mtype: textparse.MetricTypeHistogram,
want: pmetric.MetricDataTypeHistogram,
wantMonotonic: true,
},
{
name: "textparse.summary",
mtype: textparse.MetricTypeSummary,
want: pmetric.MetricDataTypeSummary,
name: "textparse.summary",
mtype: textparse.MetricTypeSummary,
want: pmetric.MetricDataTypeSummary,
wantMonotonic: true,
},
{
name: "textparse.metric_type_info",
mtype: textparse.MetricTypeInfo,
want: pmetric.MetricDataTypeNone,
name: "textparse.metric_type_info",
mtype: textparse.MetricTypeInfo,
want: pmetric.MetricDataTypeSum,
wantMonotonic: false,
},
{
name: "textparse.metric_state_set",
mtype: textparse.MetricTypeStateset,
want: pmetric.MetricDataTypeNone,
name: "textparse.metric_state_set",
mtype: textparse.MetricTypeStateset,
want: pmetric.MetricDataTypeSum,
wantMonotonic: false,
},
{
name: "textparse.metric_gauge_hostogram",
mtype: textparse.MetricTypeGaugeHistogram,
want: pmetric.MetricDataTypeNone,
wantMonotonic: false,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got := convToMetricType(tt.mtype)
got, monotonic := convToMetricType(tt.mtype)
require.Equal(t, got.String(), tt.want.String())
require.Equal(t, monotonic, tt.wantMonotonic)
})
}
}
Expand Down
3 changes: 2 additions & 1 deletion receiver/prometheusreceiver/internal/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
metricsSuffixBucket = "_bucket"
metricsSuffixSum = "_sum"
metricSuffixTotal = "_total"
metricSuffixInfo = "_info"
startTimeMetricName = "process_start_time_seconds"
scrapeUpMetricName = "up"

Expand All @@ -35,7 +36,7 @@ const (
)

var (
trimmableSuffixes = []string{metricsSuffixBucket, metricsSuffixCount, metricsSuffixSum, metricSuffixTotal}
trimmableSuffixes = []string{metricsSuffixBucket, metricsSuffixCount, metricsSuffixSum, metricSuffixTotal, metricSuffixInfo}
errNoDataToBuild = errors.New("there's no data to build")
errNoBoundaryLabel = errors.New("given metricType has no BucketLabel or QuantileLabel")
errEmptyBoundaryLabel = errors.New("BucketLabel or QuantileLabel is empty")
Expand Down
7 changes: 7 additions & 0 deletions receiver/prometheusreceiver/metrics_receiver_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,13 @@ func compareMetricType(typ pmetric.MetricDataType) metricTypeComparator {
}
}

func compareMetricIsMonotonic(isMonotonic bool) metricTypeComparator {
return func(t *testing.T, metric *pmetric.Metric) {
assert.Equal(t, pmetric.MetricDataTypeSum.String(), metric.DataType().String(), "IsMonotonic only exists for sums")
assert.Equal(t, isMonotonic, metric.Sum().IsMonotonic(), "IsMonotonic does not match")
}
}

func compareAttributes(attributes map[string]string) numberPointComparator {
return func(t *testing.T, numberDataPoint *pmetric.NumberDataPoint) {
req := assert.Equal(t, len(attributes), numberDataPoint.Attributes().Len(), "Attributes length do not match")
Expand Down
113 changes: 113 additions & 0 deletions receiver/prometheusreceiver/metrics_receiver_open_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pmetric"
)
Expand Down Expand Up @@ -151,3 +152,115 @@ func readTestCase(testName string) (string, error) {
}
return string(content), nil
}

// info and stateset metrics are converted to non-monotonic sums
var infoAndStatesetMetrics = `# TYPE foo info
foo_info{entity="controller",name="prettyname",version="8.2.7"} 1.0
foo_info{entity="replica",name="prettiername",version="8.1.9"} 1.0
# TYPE bar stateset
bar{entity="controller",foo="a"} 1.0
bar{entity="controller",foo="bb"} 0.0
bar{entity="controller",foo="ccc"} 0.0
bar{entity="replica",foo="a"} 1.0
bar{entity="replica",foo="bb"} 0.0
bar{entity="replica",foo="ccc"} 1.0
# EOF
`

// TestInfoStatesetMetrics validates the translation of info and stateset
// metrics
func TestInfoStatesetMetrics(t *testing.T) {
targets := []*testData{
{
name: "target1",
pages: []mockPrometheusResponse{
{code: 200, data: infoAndStatesetMetrics, useOpenMetrics: true},
},
validateFunc: verifyInfoStatesetMetrics,
validateScrapes: true,
},
}

testComponent(t, targets, false, "")

}

func verifyInfoStatesetMetrics(t *testing.T, td *testData, resourceMetrics []*pmetric.ResourceMetrics) {
verifyNumValidScrapeResults(t, td, resourceMetrics)
m1 := resourceMetrics[0]

// m1 has 2 metrics + 5 internal scraper metrics
assert.Equal(t, 7, metricsCount(m1))

wantAttributes := td.attributes

metrics1 := m1.ScopeMetrics().At(0).Metrics()
ts1 := getTS(metrics1)
e1 := []testExpectation{
assertMetricPresent("foo",
compareMetricIsMonotonic(false),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(1.0),
compareAttributes(map[string]string{"entity": "controller", "name": "prettyname", "version": "8.2.7"}),
},
},
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(1.0),
compareAttributes(map[string]string{"entity": "replica", "name": "prettiername", "version": "8.1.9"}),
},
},
}),
assertMetricPresent("bar",
compareMetricIsMonotonic(false),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(1.0),
compareAttributes(map[string]string{"entity": "controller", "foo": "a"}),
},
},
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(0.0),
compareAttributes(map[string]string{"entity": "controller", "foo": "bb"}),
},
},
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(0.0),
compareAttributes(map[string]string{"entity": "controller", "foo": "ccc"}),
},
},
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(1.0),
compareAttributes(map[string]string{"entity": "replica", "foo": "a"}),
},
},
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(0.0),
compareAttributes(map[string]string{"entity": "replica", "foo": "bb"}),
},
},
{
numberPointComparator: []numberPointComparator{
compareTimestamp(ts1),
compareDoubleValue(1.0),
compareAttributes(map[string]string{"entity": "replica", "foo": "ccc"}),
},
},
}),
}
doCompare(t, "scrape-infostatesetmetrics-1", wantAttributes, m1, e1)
}