Skip to content

Commit 8f61805

Browse files
odubajDTevan-bradleyedmocosta
authored
[processor/transform] Introduce convert_summary_quantile_val_to_gauge() function (#37245)
Co-authored-by: Evan Bradley <[email protected]> Co-authored-by: Edmo Vamerlatti Costa <[email protected]>
1 parent 03f656f commit 8f61805

8 files changed

+296
-0
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: processor/transform
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Introduce convert_summary_quantile_val_to_gauge() function"
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: [33850]
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: []

processor/transformprocessor/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,13 @@ These common functions can be used for any Signal.
257257
In addition to the common OTTL functions, the processor defines its own functions to help with transformations specific to this processor:
258258

259259
**Metrics only functions**
260+
260261
- [convert_sum_to_gauge](#convert_sum_to_gauge)
261262
- [convert_gauge_to_sum](#convert_gauge_to_sum)
262263
- [extract_count_metric](#extract_count_metric)
263264
- [extract_sum_metric](#extract_sum_metric)
264265
- [convert_summary_count_val_to_sum](#convert_summary_count_val_to_sum)
266+
- [convert_summary_quantile_val_to_gauge](#convert_summary_quantile_val_to_gauge)
265267
- [convert_summary_sum_val_to_sum](#convert_summary_sum_val_to_sum)
266268
- [copy_metric](#copy_metric)
267269
- [scale_metric](#scale_metric)
@@ -371,6 +373,25 @@ Examples:
371373

372374
- `convert_summary_count_val_to_sum("cumulative", false, ".count")`
373375

376+
### convert_summary_quantile_val_to_gauge
377+
378+
`convert_summary_quantile_val_to_gauge(Optional[attributeKey], Optional[suffix])`
379+
380+
The `convert_summary_quantile_val_to_gauge` function creates a new Gauge metric and injects each of the Summary's quantiles into a single Gauge datapoint.
381+
382+
`attributeKey` is an optional string that specifies the attribute key holding the quantile value for each corresponding output data point. The default key is `quantile`.
383+
`suffix` is an optional string representing the suffix of the metric name. The default value is `.quantiles`.
384+
385+
The name for the new metric will be `<summary metric name>.quantiles`. The fields that are copied are: `timestamp`, `starttimestamp`, `attributes`, `unit` and `description`. The new metric that is created will be passed to all functions in the metrics statements list. Function conditions will apply.
386+
387+
Examples:
388+
389+
- `convert_summary_quantile_val_to_gauge("custom_quantile", "custom_suffix")`
390+
391+
- `convert_summary_quantile_val_to_gauge("custom_quantile")`
392+
393+
- `convert_summary_quantile_val_to_gauge()`
394+
374395
### convert_summary_sum_val_to_sum
375396

376397
`convert_summary_sum_val_to_sum(aggregation_temporality, is_monotonic, Optional[suffix])`
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/metrics"
5+
6+
import (
7+
"context"
8+
"errors"
9+
10+
"go.opentelemetry.io/collector/pdata/pmetric"
11+
12+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
14+
)
15+
16+
type convertSummaryQuantileValToGaugeArguments struct {
17+
AttributeKey ottl.Optional[string]
18+
Suffix ottl.Optional[string]
19+
}
20+
21+
func newConvertSummaryQuantileValToGaugeFactory() ottl.Factory[ottlmetric.TransformContext] {
22+
return ottl.NewFactory("convert_summary_quantile_val_to_gauge", &convertSummaryQuantileValToGaugeArguments{}, createConvertSummaryQuantileValToGaugeFunction)
23+
}
24+
25+
func createConvertSummaryQuantileValToGaugeFunction(_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottlmetric.TransformContext], error) {
26+
args, ok := oArgs.(*convertSummaryQuantileValToGaugeArguments)
27+
28+
if !ok {
29+
return nil, errors.New("convertSummaryQuantileValToGaugeFactory args must be of type *convertSummaryQuantileValToGaugeArguments")
30+
}
31+
32+
return convertSummaryQuantileValToGauge(args.AttributeKey, args.Suffix)
33+
}
34+
35+
func convertSummaryQuantileValToGauge(attrKey, suffix ottl.Optional[string]) (ottl.ExprFunc[ottlmetric.TransformContext], error) {
36+
metricNameSuffix := suffix.GetOr(".quantiles")
37+
attributeKey := attrKey.GetOr("quantile")
38+
39+
return func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
40+
metric := tCtx.GetMetric()
41+
if metric.Type() != pmetric.MetricTypeSummary {
42+
return nil, nil
43+
}
44+
45+
gaugeMetric := tCtx.GetMetrics().AppendEmpty()
46+
gaugeMetric.SetDescription(metric.Description())
47+
gaugeMetric.SetName(metric.Name() + metricNameSuffix)
48+
gaugeMetric.SetUnit(metric.Unit())
49+
gauge := gaugeMetric.SetEmptyGauge()
50+
51+
dps := metric.Summary().DataPoints()
52+
for i := 0; i < dps.Len(); i++ {
53+
dp := dps.At(i)
54+
for j := 0; j < dp.QuantileValues().Len(); j++ {
55+
q := dp.QuantileValues().At(j)
56+
gaugeDp := gauge.DataPoints().AppendEmpty()
57+
dp.Attributes().CopyTo(gaugeDp.Attributes())
58+
gaugeDp.Attributes().PutDouble(attributeKey, q.Quantile())
59+
gaugeDp.SetDoubleValue(q.Value())
60+
gaugeDp.SetStartTimestamp(dp.StartTimestamp())
61+
gaugeDp.SetTimestamp(dp.Timestamp())
62+
}
63+
}
64+
return nil, nil
65+
}, nil
66+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metrics
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"go.opentelemetry.io/collector/pdata/pcommon"
11+
"go.opentelemetry.io/collector/pdata/pmetric"
12+
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
15+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest"
16+
)
17+
18+
func Test_ConvertSummaryQuantileValToGauge(t *testing.T) {
19+
tests := []summaryTestCase{
20+
{
21+
name: "convert_summary_quantile_val_to_gauge",
22+
input: getTestSummaryMetric(),
23+
want: func(metrics pmetric.MetricSlice) {
24+
summaryMetric := getTestSummaryMetric()
25+
summaryMetric.CopyTo(metrics.AppendEmpty())
26+
27+
gaugeMetric := metrics.AppendEmpty()
28+
gaugeMetric.SetDescription(summaryMetric.Description())
29+
gaugeMetric.SetName(summaryMetric.Name() + ".quantiles")
30+
gaugeMetric.SetUnit(summaryMetric.Unit())
31+
gauge := gaugeMetric.SetEmptyGauge()
32+
33+
attrs := getTestAttributes()
34+
35+
gaugeDp := gauge.DataPoints().AppendEmpty()
36+
attrs.CopyTo(gaugeDp.Attributes())
37+
gaugeDp.Attributes().PutDouble("quantile", 0.99)
38+
gaugeDp.SetDoubleValue(1)
39+
40+
gaugeDp1 := gauge.DataPoints().AppendEmpty()
41+
attrs.CopyTo(gaugeDp1.Attributes())
42+
gaugeDp1.Attributes().PutDouble("quantile", 0.95)
43+
gaugeDp1.SetDoubleValue(2)
44+
45+
gaugeDp2 := gauge.DataPoints().AppendEmpty()
46+
attrs.CopyTo(gaugeDp2.Attributes())
47+
gaugeDp2.Attributes().PutDouble("quantile", 0.50)
48+
gaugeDp2.SetDoubleValue(3)
49+
},
50+
},
51+
{
52+
name: "convert_summary_quantile_val_to_gauge custom attribute key",
53+
input: getTestSummaryMetric(),
54+
key: ottl.NewTestingOptional[string]("custom_quantile"),
55+
want: func(metrics pmetric.MetricSlice) {
56+
summaryMetric := getTestSummaryMetric()
57+
summaryMetric.CopyTo(metrics.AppendEmpty())
58+
59+
gaugeMetric := metrics.AppendEmpty()
60+
gaugeMetric.SetDescription(summaryMetric.Description())
61+
gaugeMetric.SetName(summaryMetric.Name() + ".quantiles")
62+
gaugeMetric.SetUnit(summaryMetric.Unit())
63+
gauge := gaugeMetric.SetEmptyGauge()
64+
65+
attrs := getTestAttributes()
66+
67+
gaugeDp := gauge.DataPoints().AppendEmpty()
68+
attrs.CopyTo(gaugeDp.Attributes())
69+
gaugeDp.Attributes().PutDouble("custom_quantile", 0.99)
70+
gaugeDp.SetDoubleValue(1)
71+
72+
gaugeDp1 := gauge.DataPoints().AppendEmpty()
73+
attrs.CopyTo(gaugeDp1.Attributes())
74+
gaugeDp1.Attributes().PutDouble("custom_quantile", 0.95)
75+
gaugeDp1.SetDoubleValue(2)
76+
77+
gaugeDp2 := gauge.DataPoints().AppendEmpty()
78+
attrs.CopyTo(gaugeDp2.Attributes())
79+
gaugeDp2.Attributes().PutDouble("custom_quantile", 0.50)
80+
gaugeDp2.SetDoubleValue(3)
81+
},
82+
},
83+
{
84+
name: "convert_summary_quantile_val_to_gauge custom attribute key and suffix",
85+
input: getTestSummaryMetric(),
86+
key: ottl.NewTestingOptional[string]("custom_quantile"),
87+
suffix: ottl.NewTestingOptional[string](".custom_suffix"),
88+
want: func(metrics pmetric.MetricSlice) {
89+
summaryMetric := getTestSummaryMetric()
90+
summaryMetric.CopyTo(metrics.AppendEmpty())
91+
92+
gaugeMetric := metrics.AppendEmpty()
93+
gaugeMetric.SetDescription(summaryMetric.Description())
94+
gaugeMetric.SetName(summaryMetric.Name() + ".custom_suffix")
95+
gaugeMetric.SetUnit(summaryMetric.Unit())
96+
gauge := gaugeMetric.SetEmptyGauge()
97+
98+
attrs := getTestAttributes()
99+
100+
gaugeDp := gauge.DataPoints().AppendEmpty()
101+
attrs.CopyTo(gaugeDp.Attributes())
102+
gaugeDp.Attributes().PutDouble("custom_quantile", 0.99)
103+
gaugeDp.SetDoubleValue(1)
104+
105+
gaugeDp1 := gauge.DataPoints().AppendEmpty()
106+
attrs.CopyTo(gaugeDp1.Attributes())
107+
gaugeDp1.Attributes().PutDouble("custom_quantile", 0.95)
108+
gaugeDp1.SetDoubleValue(2)
109+
110+
gaugeDp2 := gauge.DataPoints().AppendEmpty()
111+
attrs.CopyTo(gaugeDp2.Attributes())
112+
gaugeDp2.Attributes().PutDouble("custom_quantile", 0.50)
113+
gaugeDp2.SetDoubleValue(3)
114+
},
115+
},
116+
{
117+
name: "convert_summary_quantile_val_to_gauge (no op)",
118+
input: getTestGaugeMetric(),
119+
want: func(metrics pmetric.MetricSlice) {
120+
gaugeMetric := getTestGaugeMetric()
121+
gaugeMetric.CopyTo(metrics.AppendEmpty())
122+
},
123+
},
124+
}
125+
for _, tt := range tests {
126+
t.Run(tt.name, func(t *testing.T) {
127+
actualMetric := pmetric.NewMetricSlice()
128+
tt.input.CopyTo(actualMetric.AppendEmpty())
129+
130+
evaluate, err := convertSummaryQuantileValToGauge(tt.key, tt.suffix)
131+
assert.NoError(t, err)
132+
133+
_, err = evaluate(nil, ottlmetric.NewTransformContext(tt.input, actualMetric, pcommon.NewInstrumentationScope(), pcommon.NewResource(), pmetric.NewScopeMetrics(), pmetric.NewResourceMetrics()))
134+
assert.NoError(t, err)
135+
136+
expected := pmetric.NewMetricSlice()
137+
tt.want(expected)
138+
139+
expectedMetrics := pmetric.NewMetrics()
140+
sl := expectedMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
141+
expected.CopyTo(sl)
142+
143+
actualMetrics := pmetric.NewMetrics()
144+
sl2 := actualMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty().Metrics()
145+
actualMetric.CopyTo(sl2)
146+
147+
assert.NoError(t, pmetrictest.CompareMetrics(expectedMetrics, actualMetrics))
148+
})
149+
}
150+
}

processor/transformprocessor/internal/metrics/func_convert_summary_sum_val_to_sum_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type summaryTestCase struct {
1919
input pmetric.Metric
2020
temporality string
2121
monotonicity bool
22+
key ottl.Optional[string]
2223
suffix ottl.Optional[string]
2324
want func(pmetric.MetricSlice)
2425
}

processor/transformprocessor/internal/metrics/functions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func MetricFunctions() map[string]ottl.Factory[ottlmetric.TransformContext] {
4747
newAggregateOnAttributesFactory(),
4848
newconvertExponentialHistToExplicitHistFactory(),
4949
newAggregateOnAttributeValueFactory(),
50+
newConvertSummaryQuantileValToGaugeFactory(),
5051
)
5152

5253
for k, v := range metricFunctions {

processor/transformprocessor/internal/metrics/functions_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func Test_MetricFunctions(t *testing.T) {
5959
expected["copy_metric"] = newCopyMetricFactory()
6060
expected["scale_metric"] = newScaleMetricFactory()
6161
expected["convert_exponential_histogram_to_histogram"] = newconvertExponentialHistToExplicitHistFactory()
62+
expected["convert_summary_quantile_val_to_gauge"] = newConvertSummaryQuantileValToGaugeFactory()
6263

6364
actual := MetricFunctions()
6465
require.Len(t, actual, len(expected))

processor/transformprocessor/internal/metrics/processor_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,35 @@ func Test_ProcessMetrics_MetricContext(t *testing.T) {
251251
// so we should only have one Sum datapoint
252252
},
253253
},
254+
{
255+
statements: []string{`convert_summary_quantile_val_to_gauge("custom_quantile") where metric.name == "operationD"`},
256+
want: func(td pmetric.Metrics) {
257+
summaryMetric := pmetric.NewMetric()
258+
fillMetricFour(summaryMetric)
259+
summaryDp := summaryMetric.Summary().DataPoints().At(0)
260+
261+
gaugeMetric := td.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().AppendEmpty()
262+
gaugeMetric.SetDescription(summaryMetric.Description())
263+
gaugeMetric.SetName(summaryMetric.Name() + ".quantiles")
264+
gaugeMetric.SetUnit(summaryMetric.Unit())
265+
266+
gauge := gaugeMetric.SetEmptyGauge()
267+
gaugeDp := gauge.DataPoints().AppendEmpty()
268+
gaugeDp1 := gauge.DataPoints().AppendEmpty()
269+
270+
summaryDp.Attributes().CopyTo(gaugeDp.Attributes())
271+
gaugeDp.Attributes().PutDouble("custom_quantile", 0.99)
272+
gaugeDp.SetDoubleValue(123)
273+
gaugeDp.SetStartTimestamp(StartTimestamp)
274+
gaugeDp.SetTimestamp(TestTimeStamp)
275+
276+
summaryDp.Attributes().CopyTo(gaugeDp1.Attributes())
277+
gaugeDp1.Attributes().PutDouble("custom_quantile", 0.95)
278+
gaugeDp1.SetDoubleValue(321)
279+
gaugeDp1.SetStartTimestamp(StartTimestamp)
280+
gaugeDp1.SetTimestamp(TestTimeStamp)
281+
},
282+
},
254283
{
255284
statements: []string{`extract_count_metric(true) where name == "operationB"`},
256285
want: func(td pmetric.Metrics) {

0 commit comments

Comments
 (0)