Skip to content

Commit

Permalink
Translate unit from words to UCUM in the prometheus receiver (#25887)
Browse files Browse the repository at this point in the history
Fixes
#23208

This is the reverse of the logic in the prometheus exporter, which
changes UCUM to words.
  • Loading branch information
dashpole authored Aug 22, 2023
1 parent 04dd0cf commit 23f286c
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 2 deletions.
27 changes: 27 additions & 0 deletions .chloggen/prometheus_translate_ucum.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# 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: receiver/prometheus

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: translate units from prometheus to UCUM

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [23208]

# (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:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user, api]
90 changes: 90 additions & 0 deletions pkg/translator/prometheus/unit_to_ucum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package prometheus // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"

import "strings"

var wordToUCUM = map[string]string{

// Time
"days": "d",
"hours": "h",
"minutes": "min",
"seconds": "s",
"milliseconds": "ms",
"microseconds": "us",
"nanoseconds": "ns",

// Bytes
"bytes": "By",
"kibibytes": "KiBy",
"mebibytes": "MiBy",
"gibibytes": "GiBy",
"tibibytes": "TiBy",
"kilobytes": "KBy",
"megabytes": "MBy",
"gigabytes": "GBy",
"terabytes": "TBy",

// SI
"meters": "m",
"volts": "V",
"amperes": "A",
"joules": "J",
"watts": "W",
"grams": "g",

// Misc
"celsius": "Cel",
"hertz": "Hz",
"ratio": "1",
"percent": "%",
}

// The map that translates the "per" unit
// Example: per_second (singular) => /s
var perWordToUCUM = map[string]string{
"second": "s",
"minute": "m",
"hour": "h",
"day": "d",
"week": "w",
"month": "mo",
"year": "y",
}

// UnitWordToUCUM converts english unit words to UCUM units:
// https://ucum.org/ucum#section-Alphabetic-Index-By-Symbol
// It also handles rates, such as meters_per_second, by translating the first
// word to UCUM, and the "per" word to UCUM. It joins them with a "/" between.
func UnitWordToUCUM(unit string) string {
unitTokens := strings.SplitN(unit, "_per_", 2)
if len(unitTokens) == 0 {
return ""
}
ucumUnit := wordToUCUMOrDefault(unitTokens[0])
if len(unitTokens) > 1 && unitTokens[1] != "" {
ucumUnit += "/" + perWordToUCUMOrDefault(unitTokens[1])
}
return ucumUnit
}

// wordToUCUMOrDefault retrieves the Prometheus "basic" unit corresponding to
// the specified "basic" unit. Returns the specified unit if not found in
// wordToUCUM.
func wordToUCUMOrDefault(unit string) string {
if promUnit, ok := wordToUCUM[unit]; ok {
return promUnit
}
return unit
}

// perWordToUCUMOrDefault retrieve the Prometheus "per" unit corresponding to
// the specified "per" unit. Returns the specified unit if not found in perWordToUCUM.
func perWordToUCUMOrDefault(perUnit string) string {
if promPerUnit, ok := perWordToUCUM[perUnit]; ok {
return promPerUnit
}
return perUnit
}
61 changes: 61 additions & 0 deletions pkg/translator/prometheus/unit_to_ucum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package prometheus // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestUnitWordToUCUM(t *testing.T) {
for _, tc := range []struct {
input string
expected string
}{
{
input: "",
expected: "",
},
{
input: "days",
expected: "d",
},
{
input: "seconds",
expected: "s",
},
{
input: "kibibytes",
expected: "KiBy",
},
{
input: "volts",
expected: "V",
},
{
input: "bananas_per_day",
expected: "bananas/d",
},
{
input: "meters_per_hour",
expected: "m/h",
},
{
input: "ratio",
expected: "1",
},
{
input: "percent",
expected: "%",
},
} {
t.Run(fmt.Sprintf("input: \"%v\"", tc.input), func(t *testing.T) {
got := UnitWordToUCUM(tc.input)
assert.Equal(t, tc.expected, got)
})
}

}
2 changes: 1 addition & 1 deletion receiver/prometheusreceiver/internal/metricfamily.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ func (mf *metricFamily) appendMetric(metrics pmetric.MetricSlice, trimSuffixes b
}
metric.SetName(name)
metric.SetDescription(mf.metadata.Help)
metric.SetUnit(mf.metadata.Unit)
metric.SetUnit(prometheus.UnitWordToUCUM(mf.metadata.Unit))

pointCount := 0

Expand Down
9 changes: 8 additions & 1 deletion receiver/prometheusreceiver/metrics_receiver_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func doCompareNormalized(t *testing.T, name string, want pcommon.Map, got pmetri
})
}

func assertMetricPresent(name string, metricTypeExpectations metricTypeComparator, dataPointExpectations []dataPointExpectation) testExpectation {
func assertMetricPresent(name string, metricTypeExpectations metricTypeComparator, metricUnitExpectations metricTypeComparator, dataPointExpectations []dataPointExpectation) testExpectation {
return func(t *testing.T, rm pmetric.ResourceMetrics) {
allMetrics := getMetrics(rm)
var present bool
Expand All @@ -389,6 +389,7 @@ func assertMetricPresent(name string, metricTypeExpectations metricTypeComparato

present = true
metricTypeExpectations(t, m)
metricUnitExpectations(t, m)
for i, de := range dataPointExpectations {
switch m.Type() {
case pmetric.MetricTypeGauge:
Expand Down Expand Up @@ -434,6 +435,12 @@ func compareMetricType(typ pmetric.MetricType) metricTypeComparator {
}
}

func compareMetricUnit(unit string) metricTypeComparator {
return func(t *testing.T, metric pmetric.Metric) {
assert.Equal(t, unit, metric.Unit(), "Metric unit does not match")
}
}

func compareMetricIsMonotonic(isMonotonic bool) metricTypeComparator {
return func(t *testing.T, metric pmetric.Metric) {
assert.Equal(t, pmetric.MetricTypeSum.String(), metric.Type().String(), "IsMonotonic only exists for sums")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
e1 := []testExpectation{
assertMetricPresent("go_threads",
compareMetricType(pmetric.MetricTypeGauge),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -218,6 +219,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("http_requests_total",
compareMetricType(pmetric.MetricTypeSum),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -238,6 +240,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("http_request_duration_seconds",
compareMetricType(pmetric.MetricTypeHistogram),
compareMetricUnit(""),
[]dataPointExpectation{
{
histogramPointComparator: []histogramPointComparator{
Expand All @@ -249,6 +252,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("rpc_duration_seconds",
compareMetricType(pmetric.MetricTypeSummary),
compareMetricUnit(""),
[]dataPointExpectation{
{
summaryPointComparator: []summaryPointComparator{
Expand All @@ -268,6 +272,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
e2 := []testExpectation{
assertMetricPresent("go_threads",
compareMetricType(pmetric.MetricTypeGauge),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -278,6 +283,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("http_requests_total",
compareMetricType(pmetric.MetricTypeSum),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -298,6 +304,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("http_request_duration_seconds",
compareMetricType(pmetric.MetricTypeHistogram),
compareMetricUnit(""),
[]dataPointExpectation{
{
histogramPointComparator: []histogramPointComparator{
Expand All @@ -309,6 +316,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("rpc_duration_seconds",
compareMetricType(pmetric.MetricTypeSummary),
compareMetricUnit(""),
[]dataPointExpectation{
{
summaryPointComparator: []summaryPointComparator{
Expand All @@ -328,6 +336,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
e3 := []testExpectation{
assertMetricPresent("go_threads",
compareMetricType(pmetric.MetricTypeGauge),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -338,6 +347,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("http_requests_total",
compareMetricType(pmetric.MetricTypeSum),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -358,6 +368,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("http_request_duration_seconds",
compareMetricType(pmetric.MetricTypeHistogram),
compareMetricUnit(""),
[]dataPointExpectation{
{
histogramPointComparator: []histogramPointComparator{
Expand All @@ -369,6 +380,7 @@ func verifyHonorTimeStampsTrue(t *testing.T, td *testData, resourceMetrics []pme
}),
assertMetricPresent("rpc_duration_seconds",
compareMetricType(pmetric.MetricTypeSummary),
compareMetricUnit(""),
[]dataPointExpectation{
{
summaryPointComparator: []summaryPointComparator{
Expand Down Expand Up @@ -396,6 +408,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
e1 := []testExpectation{
assertMetricPresent("go_threads",
compareMetricType(pmetric.MetricTypeGauge),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -406,6 +419,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
}),
assertMetricPresent("http_requests_total",
compareMetricType(pmetric.MetricTypeSum),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -426,6 +440,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
}),
assertMetricPresent("http_request_duration_seconds",
compareMetricType(pmetric.MetricTypeHistogram),
compareMetricUnit(""),
[]dataPointExpectation{
{
histogramPointComparator: []histogramPointComparator{
Expand All @@ -437,6 +452,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
}),
assertMetricPresent("rpc_duration_seconds",
compareMetricType(pmetric.MetricTypeSummary),
compareMetricUnit(""),
[]dataPointExpectation{
{
summaryPointComparator: []summaryPointComparator{
Expand All @@ -458,6 +474,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
e2 := []testExpectation{
assertMetricPresent("go_threads",
compareMetricType(pmetric.MetricTypeGauge),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -468,6 +485,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
}),
assertMetricPresent("http_requests_total",
compareMetricType(pmetric.MetricTypeSum),
compareMetricUnit(""),
[]dataPointExpectation{
{
numberPointComparator: []numberPointComparator{
Expand All @@ -488,6 +506,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
}),
assertMetricPresent("http_request_duration_seconds",
compareMetricType(pmetric.MetricTypeHistogram),
compareMetricUnit(""),
[]dataPointExpectation{
{
histogramPointComparator: []histogramPointComparator{
Expand All @@ -499,6 +518,7 @@ func verifyHonorTimeStampsFalse(t *testing.T, td *testData, resourceMetrics []pm
}),
assertMetricPresent("rpc_duration_seconds",
compareMetricType(pmetric.MetricTypeSummary),
compareMetricUnit(""),
[]dataPointExpectation{
{
summaryPointComparator: []summaryPointComparator{
Expand Down
Loading

0 comments on commit 23f286c

Please sign in to comment.