Skip to content

Commit e560c1b

Browse files
TylerHelmuthevan-bradley
authored andcommitted
[processor/transform] Add copy_metric function (open-telemetry#30846)
**Description:** Adds a new function to the metric functions that allows copying the current TransformContext's method. **Link to tracking Issue:** <Issue number if applicable> Closes open-telemetry#16221 **Testing:** <Describe what testing was performed and which tests were added.> unit tests **Documentation:** Updated README --------- Co-authored-by: Evan Bradley <[email protected]>
1 parent 2b33c4f commit e560c1b

File tree

8 files changed

+269
-0
lines changed

8 files changed

+269
-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: Add `copy_metric` function to allow duplicating a metric
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: [30846]
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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ In addition to OTTL functions, the processor defines its own functions to help w
191191
- [convert_gauge_to_sum](#convert_gauge_to_sum)
192192
- [convert_summary_count_val_to_sum](#convert_summary_count_val_to_sum)
193193
- [convert_summary_sum_val_to_sum](#convert_summary_sum_val_to_sum)
194+
- [copy_metric](#copy_metric)
194195

195196
### convert_sum_to_gauge
196197

@@ -307,6 +308,25 @@ Examples:
307308

308309
- `convert_summary_sum_val_to_sum("cumulative", false)`
309310

311+
### copy_metric
312+
313+
`copy_metric(Optional[name], Optional[description], Optional[unit])`
314+
315+
The `copy_metric` function copies the current metric, adding it to the end of the metric slice.
316+
317+
`name` is an optional string. `description` is an optional string. `unit` is an optional string.
318+
319+
The new metric will be exactly the same as the current metric. You can use the optional parameters to set the new metric's name, description, and unit.
320+
321+
**NOTE:** The new metric is appended to the end of the metric slice and therefore will be included in all the metric statements. It is a best practice to ALWAYS include a Where clause when copying a metric that WILL NOT match the new metric.
322+
323+
Examples:
324+
325+
- `copy_metric(name="http.request.status_code", unit="s") where name == "http.status_code`
326+
327+
328+
- `copy_metric(desc="new desc") where description == "old desc"`
329+
310330
## Examples
311331

312332
### Perform transformation if field does not exist

processor/transformprocessor/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.93.0
77
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.93.0
88
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.93.0
9+
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.93.0
910
github.com/stretchr/testify v1.8.4
1011
go.opentelemetry.io/collector/component v0.93.1-0.20240130182548-89388addcc7f
1112
go.opentelemetry.io/collector/confmap v0.93.1-0.20240130182548-89388addcc7f
@@ -42,6 +43,7 @@ require (
4243
github.com/mitchellh/reflectwalk v1.0.2 // indirect
4344
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4445
github.com/modern-go/reflect2 v1.0.2 // indirect
46+
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.93.0 // indirect
4547
github.com/pmezard/go-difflib v1.0.0 // indirect
4648
github.com/prometheus/client_golang v1.18.0 // indirect
4749
github.com/prometheus/client_model v0.5.0 // indirect
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
"fmt"
9+
10+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
11+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
12+
)
13+
14+
type copyMetricArguments struct {
15+
Name ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]
16+
Description ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]
17+
Unit ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]
18+
}
19+
20+
func newCopyMetricFactory() ottl.Factory[ottlmetric.TransformContext] {
21+
return ottl.NewFactory("copy_metric", &copyMetricArguments{}, createCopyMetricFunction)
22+
}
23+
24+
func createCopyMetricFunction(_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottlmetric.TransformContext], error) {
25+
args, ok := oArgs.(*copyMetricArguments)
26+
27+
if !ok {
28+
return nil, fmt.Errorf("createCopyMetricFunction args must be of type *copyMetricArguments")
29+
}
30+
31+
return copyMetric(args.Name, args.Description, args.Unit)
32+
}
33+
34+
func copyMetric(name ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]], desc ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]], unit ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]) (ottl.ExprFunc[ottlmetric.TransformContext], error) {
35+
return func(ctx context.Context, tCtx ottlmetric.TransformContext) (any, error) {
36+
cur := tCtx.GetMetric()
37+
metrics := tCtx.GetMetrics()
38+
newMetric := metrics.AppendEmpty()
39+
cur.CopyTo(newMetric)
40+
41+
if !name.IsEmpty() {
42+
n, err := name.Get().Get(ctx, tCtx)
43+
if err != nil {
44+
return nil, err
45+
}
46+
newMetric.SetName(n)
47+
}
48+
49+
if !desc.IsEmpty() {
50+
d, err := desc.Get().Get(ctx, tCtx)
51+
if err != nil {
52+
return nil, err
53+
}
54+
newMetric.SetDescription(d)
55+
}
56+
57+
if !unit.IsEmpty() {
58+
u, err := unit.Get().Get(ctx, tCtx)
59+
if err != nil {
60+
return nil, err
61+
}
62+
newMetric.SetUnit(u)
63+
}
64+
65+
return nil, nil
66+
}, nil
67+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metrics
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"go.opentelemetry.io/collector/pdata/pcommon"
12+
"go.opentelemetry.io/collector/pdata/pmetric"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
15+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
16+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest"
17+
)
18+
19+
func Test_copyMetric(t *testing.T) {
20+
tests := []struct {
21+
testName string
22+
name ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]
23+
desc ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]
24+
unit ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]
25+
want func(s pmetric.MetricSlice)
26+
}{
27+
{
28+
testName: "basic copy",
29+
name: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
30+
desc: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
31+
unit: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
32+
want: func(ms pmetric.MetricSlice) {
33+
metric := ms.At(0)
34+
newMetric := ms.AppendEmpty()
35+
metric.CopyTo(newMetric)
36+
},
37+
},
38+
{
39+
testName: "set name",
40+
name: ottl.NewTestingOptional[ottl.StringGetter[ottlmetric.TransformContext]](ottl.StandardStringGetter[ottlmetric.TransformContext]{
41+
Getter: func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
42+
return "new name", nil
43+
},
44+
}),
45+
desc: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
46+
unit: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
47+
want: func(ms pmetric.MetricSlice) {
48+
metric := ms.At(0)
49+
newMetric := ms.AppendEmpty()
50+
metric.CopyTo(newMetric)
51+
newMetric.SetName("new name")
52+
},
53+
},
54+
{
55+
testName: "set description",
56+
name: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
57+
desc: ottl.NewTestingOptional[ottl.StringGetter[ottlmetric.TransformContext]](ottl.StandardStringGetter[ottlmetric.TransformContext]{
58+
Getter: func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
59+
return "new desc", nil
60+
},
61+
}),
62+
unit: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
63+
want: func(ms pmetric.MetricSlice) {
64+
metric := ms.At(0)
65+
newMetric := ms.AppendEmpty()
66+
metric.CopyTo(newMetric)
67+
newMetric.SetDescription("new desc")
68+
},
69+
},
70+
{
71+
testName: "set unit",
72+
name: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
73+
desc: ottl.Optional[ottl.StringGetter[ottlmetric.TransformContext]]{},
74+
unit: ottl.NewTestingOptional[ottl.StringGetter[ottlmetric.TransformContext]](ottl.StandardStringGetter[ottlmetric.TransformContext]{
75+
Getter: func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
76+
return "new unit", nil
77+
},
78+
}),
79+
want: func(ms pmetric.MetricSlice) {
80+
metric := ms.At(0)
81+
newMetric := ms.AppendEmpty()
82+
metric.CopyTo(newMetric)
83+
newMetric.SetUnit("new unit")
84+
},
85+
},
86+
{
87+
testName: "set all",
88+
name: ottl.NewTestingOptional[ottl.StringGetter[ottlmetric.TransformContext]](ottl.StandardStringGetter[ottlmetric.TransformContext]{
89+
Getter: func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
90+
return "new name", nil
91+
},
92+
}),
93+
desc: ottl.NewTestingOptional[ottl.StringGetter[ottlmetric.TransformContext]](ottl.StandardStringGetter[ottlmetric.TransformContext]{
94+
Getter: func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
95+
return "new desc", nil
96+
},
97+
}),
98+
unit: ottl.NewTestingOptional[ottl.StringGetter[ottlmetric.TransformContext]](ottl.StandardStringGetter[ottlmetric.TransformContext]{
99+
Getter: func(_ context.Context, tCtx ottlmetric.TransformContext) (any, error) {
100+
return "new unit", nil
101+
},
102+
}),
103+
want: func(ms pmetric.MetricSlice) {
104+
metric := ms.At(0)
105+
newMetric := ms.AppendEmpty()
106+
metric.CopyTo(newMetric)
107+
newMetric.SetName("new name")
108+
newMetric.SetDescription("new desc")
109+
newMetric.SetUnit("new unit")
110+
},
111+
},
112+
}
113+
for _, tt := range tests {
114+
t.Run(tt.testName, func(t *testing.T) {
115+
ms := pmetric.NewMetricSlice()
116+
input := ms.AppendEmpty()
117+
input.SetName("test")
118+
input.SetDescription("test")
119+
input.SetUnit("test")
120+
input.SetEmptySum()
121+
d := input.Sum().DataPoints().AppendEmpty()
122+
d.SetIntValue(1)
123+
124+
expected := pmetric.NewMetricSlice()
125+
ms.CopyTo(expected)
126+
tt.want(expected)
127+
128+
exprFunc, err := copyMetric(tt.name, tt.desc, tt.unit)
129+
assert.NoError(t, err)
130+
_, err = exprFunc(nil, ottlmetric.NewTransformContext(input, ms, pcommon.NewInstrumentationScope(), pcommon.NewResource()))
131+
assert.NoError(t, err)
132+
133+
x := pmetric.NewScopeMetrics()
134+
y := pmetric.NewScopeMetrics()
135+
136+
expected.CopyTo(x.Metrics())
137+
ms.CopyTo(y.Metrics())
138+
139+
assert.NoError(t, pmetrictest.CompareScopeMetrics(x, y))
140+
})
141+
}
142+
}

processor/transformprocessor/internal/metrics/functions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func MetricFunctions() map[string]ottl.Factory[ottlmetric.TransformContext] {
4848
metricFunctions := ottl.CreateFactoryMap(
4949
newExtractSumMetricFactory(),
5050
newExtractCountMetricFactory(),
51+
newCopyMetricFactory(),
5152
)
5253

5354
if useConvertBetweenSumAndGaugeMetricContext.IsEnabled() {

processor/transformprocessor/internal/metrics/functions_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func Test_MetricFunctions(t *testing.T) {
3636
expected["convert_gauge_to_sum"] = newConvertGaugeToSumFactory()
3737
expected["extract_sum_metric"] = newExtractSumMetricFactory()
3838
expected["extract_count_metric"] = newExtractCountMetricFactory()
39+
expected["copy_metric"] = newCopyMetricFactory()
3940

4041
defer testutil.SetFeatureGateForTest(t, useConvertBetweenSumAndGaugeMetricContext, true)()
4142
actual := MetricFunctions()

processor/transformprocessor/internal/metrics/processor_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ func Test_ProcessMetrics_MetricContext(t *testing.T) {
179179
countDp1.SetStartTimestamp(StartTimestamp)
180180
},
181181
},
182+
{
183+
statements: []string{`copy_metric(name="http.request.status_code", unit="s") where name == "operationA"`},
184+
want: func(td pmetric.Metrics) {
185+
newMetric := td.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().AppendEmpty()
186+
td.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).CopyTo(newMetric)
187+
newMetric.SetName("http.request.status_code")
188+
newMetric.SetUnit("s")
189+
},
190+
},
182191
}
183192

184193
for _, tt := range tests {

0 commit comments

Comments
 (0)