Skip to content

Commit ccf3cae

Browse files
authored
[pkg/pdatatest] support Profiles signal comparison (open-telemetry#36273)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes open-telemetry#36232 --------- Signed-off-by: odubajDT <[email protected]>
1 parent adef54e commit ccf3cae

File tree

9 files changed

+3142
-25
lines changed

9 files changed

+3142
-25
lines changed

.chloggen/profiles-pdata.yaml

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: pkg/pdatatest
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Add support for Profiles signal comparison."
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: [36232]
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: []

pkg/pdatatest/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.115.0
88
github.com/stretchr/testify v1.10.0
99
go.opentelemetry.io/collector/pdata v1.21.1-0.20241206185113-3f3e208e71b8
10+
go.opentelemetry.io/collector/pdata/pprofile v0.115.1-0.20241206185113-3f3e208e71b8
1011
go.uber.org/goleak v1.3.0
1112
go.uber.org/multierr v1.11.0
1213
)

pkg/pdatatest/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/pdatatest/internal/util.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"reflect"
99
"regexp"
10+
"sort"
1011

1112
"go.opentelemetry.io/collector/pdata/pcommon"
1213
"go.uber.org/multierr"
@@ -88,3 +89,22 @@ func CompareDroppedAttributesCount(expected, actual uint32) error {
8889
}
8990
return nil
9091
}
92+
93+
func OrderMapByKey(input map[string]any) map[string]any {
94+
// Create a slice to hold the keys
95+
keys := make([]string, 0, len(input))
96+
for k := range input {
97+
keys = append(keys, k)
98+
}
99+
100+
// Sort the keys
101+
sort.Strings(keys)
102+
103+
// Create a new map to hold the sorted key-value pairs
104+
orderedMap := make(map[string]any, len(input))
105+
for _, k := range keys {
106+
orderedMap[k] = input[k]
107+
}
108+
109+
return orderedMap
110+
}

pkg/pdatatest/pmetrictest/options.go

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"fmt"
99
"math"
1010
"regexp"
11-
"sort"
1211
"time"
1312

1413
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -275,27 +274,27 @@ func orderDatapointAttributes(metrics pmetric.Metrics) {
275274
switch msl.At(g).Type() {
276275
case pmetric.MetricTypeGauge:
277276
for k := 0; k < msl.At(g).Gauge().DataPoints().Len(); k++ {
278-
rawOrdered := orderMapByKey(msl.At(g).Gauge().DataPoints().At(k).Attributes().AsRaw())
277+
rawOrdered := internal.OrderMapByKey(msl.At(g).Gauge().DataPoints().At(k).Attributes().AsRaw())
279278
_ = msl.At(g).Gauge().DataPoints().At(k).Attributes().FromRaw(rawOrdered)
280279
}
281280
case pmetric.MetricTypeSum:
282281
for k := 0; k < msl.At(g).Sum().DataPoints().Len(); k++ {
283-
rawOrdered := orderMapByKey(msl.At(g).Sum().DataPoints().At(k).Attributes().AsRaw())
282+
rawOrdered := internal.OrderMapByKey(msl.At(g).Sum().DataPoints().At(k).Attributes().AsRaw())
284283
_ = msl.At(g).Sum().DataPoints().At(k).Attributes().FromRaw(rawOrdered)
285284
}
286285
case pmetric.MetricTypeHistogram:
287286
for k := 0; k < msl.At(g).Histogram().DataPoints().Len(); k++ {
288-
rawOrdered := orderMapByKey(msl.At(g).Histogram().DataPoints().At(k).Attributes().AsRaw())
287+
rawOrdered := internal.OrderMapByKey(msl.At(g).Histogram().DataPoints().At(k).Attributes().AsRaw())
289288
_ = msl.At(g).Histogram().DataPoints().At(k).Attributes().FromRaw(rawOrdered)
290289
}
291290
case pmetric.MetricTypeExponentialHistogram:
292291
for k := 0; k < msl.At(g).ExponentialHistogram().DataPoints().Len(); k++ {
293-
rawOrdered := orderMapByKey(msl.At(g).ExponentialHistogram().DataPoints().At(k).Attributes().AsRaw())
292+
rawOrdered := internal.OrderMapByKey(msl.At(g).ExponentialHistogram().DataPoints().At(k).Attributes().AsRaw())
294293
_ = msl.At(g).ExponentialHistogram().DataPoints().At(k).Attributes().FromRaw(rawOrdered)
295294
}
296295
case pmetric.MetricTypeSummary:
297296
for k := 0; k < msl.At(g).Summary().DataPoints().Len(); k++ {
298-
rawOrdered := orderMapByKey(msl.At(g).Summary().DataPoints().At(k).Attributes().AsRaw())
297+
rawOrdered := internal.OrderMapByKey(msl.At(g).Summary().DataPoints().At(k).Attributes().AsRaw())
299298
_ = msl.At(g).Summary().DataPoints().At(k).Attributes().FromRaw(rawOrdered)
300299
}
301300
case pmetric.MetricTypeEmpty:
@@ -305,25 +304,6 @@ func orderDatapointAttributes(metrics pmetric.Metrics) {
305304
}
306305
}
307306

308-
func orderMapByKey(input map[string]any) map[string]any {
309-
// Create a slice to hold the keys
310-
keys := make([]string, 0, len(input))
311-
for k := range input {
312-
keys = append(keys, k)
313-
}
314-
315-
// Sort the keys
316-
sort.Strings(keys)
317-
318-
// Create a new map to hold the sorted key-value pairs
319-
orderedMap := make(map[string]any, len(input))
320-
for _, k := range keys {
321-
orderedMap[k] = input[k]
322-
}
323-
324-
return orderedMap
325-
}
326-
327307
func maskMetricAttributeValue(metrics pmetric.Metrics, attributeName string, metricNames []string) {
328308
rms := metrics.ResourceMetrics()
329309
for i := 0; i < rms.Len(); i++ {
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofiletest // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pprofiletest"
5+
6+
import (
7+
"bytes"
8+
"time"
9+
10+
"go.opentelemetry.io/collector/pdata/pcommon"
11+
"go.opentelemetry.io/collector/pdata/pprofile"
12+
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/internal"
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil"
15+
)
16+
17+
// CompareProfilesOption can be used to mutate expected and/or actual profiles before comparing.
18+
type CompareProfilesOption interface {
19+
applyOnProfiles(expected, actual pprofile.Profiles)
20+
}
21+
22+
type compareProfilesOptionFunc func(expected, actual pprofile.Profiles)
23+
24+
func (f compareProfilesOptionFunc) applyOnProfiles(expected, actual pprofile.Profiles) {
25+
f(expected, actual)
26+
}
27+
28+
// IgnoreResourceAttributeValue is a CompareProfilesOption that removes a resource attribute
29+
// from all resources.
30+
func IgnoreResourceAttributeValue(attributeName string) CompareProfilesOption {
31+
return ignoreResourceAttributeValue{
32+
attributeName: attributeName,
33+
}
34+
}
35+
36+
type ignoreResourceAttributeValue struct {
37+
attributeName string
38+
}
39+
40+
func (opt ignoreResourceAttributeValue) applyOnProfiles(expected, actual pprofile.Profiles) {
41+
opt.maskProfilesResourceAttributeValue(expected)
42+
opt.maskProfilesResourceAttributeValue(actual)
43+
}
44+
45+
func (opt ignoreResourceAttributeValue) maskProfilesResourceAttributeValue(profiles pprofile.Profiles) {
46+
rls := profiles.ResourceProfiles()
47+
for i := 0; i < rls.Len(); i++ {
48+
internal.MaskResourceAttributeValue(rls.At(i).Resource(), opt.attributeName)
49+
}
50+
}
51+
52+
// IgnoreResourceAttributeValue is a CompareProfilesOption that removes a resource attribute
53+
// from all resources.
54+
func IgnoreScopeAttributeValue(attributeName string) CompareProfilesOption {
55+
return ignoreScopeAttributeValue{
56+
attributeName: attributeName,
57+
}
58+
}
59+
60+
type ignoreScopeAttributeValue struct {
61+
attributeName string
62+
}
63+
64+
func (opt ignoreScopeAttributeValue) applyOnProfiles(expected, actual pprofile.Profiles) {
65+
opt.maskProfilesScopeAttributeValue(expected)
66+
opt.maskProfilesScopeAttributeValue(actual)
67+
}
68+
69+
func (opt ignoreScopeAttributeValue) maskProfilesScopeAttributeValue(profiles pprofile.Profiles) {
70+
rls := profiles.ResourceProfiles()
71+
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
72+
sls := rls.At(i).ScopeProfiles()
73+
for j := 0; j < sls.Len(); j++ {
74+
lr := sls.At(j)
75+
val, exists := lr.Scope().Attributes().Get(opt.attributeName)
76+
if exists {
77+
val.SetEmptyBytes()
78+
}
79+
}
80+
}
81+
}
82+
83+
// IgnoreProfileAttributeValue is a CompareProfilesOption that sets the value of an attribute
84+
// to empty bytes for every profile
85+
func IgnoreProfileAttributeValue(attributeName string) CompareProfilesOption {
86+
return ignoreProfileAttributeValue{
87+
attributeName: attributeName,
88+
}
89+
}
90+
91+
type ignoreProfileAttributeValue struct {
92+
attributeName string
93+
}
94+
95+
func (opt ignoreProfileAttributeValue) applyOnProfiles(expected, actual pprofile.Profiles) {
96+
opt.maskProfileAttributeValue(expected)
97+
opt.maskProfileAttributeValue(actual)
98+
}
99+
100+
func (opt ignoreProfileAttributeValue) maskProfileAttributeValue(profiles pprofile.Profiles) {
101+
rls := profiles.ResourceProfiles()
102+
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
103+
sls := rls.At(i).ScopeProfiles()
104+
for j := 0; j < sls.Len(); j++ {
105+
lrs := sls.At(j).Profiles()
106+
for k := 0; k < lrs.Len(); k++ {
107+
lr := lrs.At(k)
108+
val, exists := lr.Attributes().Get(opt.attributeName)
109+
if exists {
110+
val.SetEmptyBytes()
111+
}
112+
}
113+
}
114+
}
115+
}
116+
117+
// IgnoreProfileTimestampValues is a CompareProfilesOption that sets the value of start timestamp
118+
// and duration to empty bytes for every profile
119+
func IgnoreProfileTimestampValues() CompareProfilesOption {
120+
return ignoreProfileTimestampValues{}
121+
}
122+
123+
type ignoreProfileTimestampValues struct{}
124+
125+
func (opt ignoreProfileTimestampValues) applyOnProfiles(expected, actual pprofile.Profiles) {
126+
opt.maskProfileTimestampValues(expected)
127+
opt.maskProfileTimestampValues(actual)
128+
}
129+
130+
func (opt ignoreProfileTimestampValues) maskProfileTimestampValues(profiles pprofile.Profiles) {
131+
rls := profiles.ResourceProfiles()
132+
for i := 0; i < profiles.ResourceProfiles().Len(); i++ {
133+
sls := rls.At(i).ScopeProfiles()
134+
for j := 0; j < sls.Len(); j++ {
135+
lrs := sls.At(j).Profiles()
136+
for k := 0; k < lrs.Len(); k++ {
137+
lr := lrs.At(k)
138+
lr.SetStartTime(pcommon.NewTimestampFromTime(time.Time{}))
139+
lr.SetDuration(pcommon.NewTimestampFromTime(time.Time{}))
140+
}
141+
}
142+
}
143+
}
144+
145+
// IgnoreResourceProfilesOrder is a CompareProfilesOption that ignores the order of resource traces/metrics/profiles.
146+
func IgnoreResourceProfilesOrder() CompareProfilesOption {
147+
return compareProfilesOptionFunc(func(expected, actual pprofile.Profiles) {
148+
sortResourceProfilesSlice(expected.ResourceProfiles())
149+
sortResourceProfilesSlice(actual.ResourceProfiles())
150+
})
151+
}
152+
153+
func sortResourceProfilesSlice(rls pprofile.ResourceProfilesSlice) {
154+
rls.Sort(func(a, b pprofile.ResourceProfiles) bool {
155+
if a.SchemaUrl() != b.SchemaUrl() {
156+
return a.SchemaUrl() < b.SchemaUrl()
157+
}
158+
aAttrs := pdatautil.MapHash(a.Resource().Attributes())
159+
bAttrs := pdatautil.MapHash(b.Resource().Attributes())
160+
return bytes.Compare(aAttrs[:], bAttrs[:]) < 0
161+
})
162+
}
163+
164+
// IgnoreScopeProfilesOrder is a CompareProfilesOption that ignores the order of instrumentation scope traces/metrics/profiles.
165+
func IgnoreScopeProfilesOrder() CompareProfilesOption {
166+
return compareProfilesOptionFunc(func(expected, actual pprofile.Profiles) {
167+
sortScopeProfilesSlices(expected)
168+
sortScopeProfilesSlices(actual)
169+
})
170+
}
171+
172+
func sortScopeProfilesSlices(ls pprofile.Profiles) {
173+
for i := 0; i < ls.ResourceProfiles().Len(); i++ {
174+
ls.ResourceProfiles().At(i).ScopeProfiles().Sort(func(a, b pprofile.ScopeProfiles) bool {
175+
if a.SchemaUrl() != b.SchemaUrl() {
176+
return a.SchemaUrl() < b.SchemaUrl()
177+
}
178+
if a.Scope().Name() != b.Scope().Name() {
179+
return a.Scope().Name() < b.Scope().Name()
180+
}
181+
return a.Scope().Version() < b.Scope().Version()
182+
})
183+
}
184+
}
185+
186+
// IgnoreProfilesOrder is a CompareProfilesOption that ignores the order of profile records.
187+
func IgnoreProfilesOrder() CompareProfilesOption {
188+
return compareProfilesOptionFunc(func(expected, actual pprofile.Profiles) {
189+
sortProfileSlices(expected)
190+
sortProfileSlices(actual)
191+
})
192+
}
193+
194+
func sortProfileSlices(ls pprofile.Profiles) {
195+
for i := 0; i < ls.ResourceProfiles().Len(); i++ {
196+
for j := 0; j < ls.ResourceProfiles().At(i).ScopeProfiles().Len(); j++ {
197+
ls.ResourceProfiles().At(i).ScopeProfiles().At(j).Profiles().Sort(func(a, b pprofile.Profile) bool {
198+
if a.StartTime() != b.StartTime() {
199+
return a.StartTime() < b.StartTime()
200+
}
201+
if a.Duration() != b.Duration() {
202+
return a.Duration() < b.Duration()
203+
}
204+
as := a.ProfileID()
205+
bs := b.ProfileID()
206+
if !bytes.Equal(as[:], bs[:]) {
207+
return bytes.Compare(as[:], bs[:]) < 0
208+
}
209+
aAttrs := pdatautil.MapHash(a.Attributes())
210+
bAttrs := pdatautil.MapHash(b.Attributes())
211+
return bytes.Compare(aAttrs[:], bAttrs[:]) < 0
212+
})
213+
}
214+
}
215+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofiletest
5+
6+
import (
7+
"testing"
8+
9+
"go.uber.org/goleak"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
goleak.VerifyTestMain(m)
14+
}

0 commit comments

Comments
 (0)