Skip to content

Commit 4263e8d

Browse files
dmathieurockdaboot
andauthored
Introduce FromLocationIndices and PutLocation (#13150)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description This introduces the `FromLocationIndices` and `PutLocation` methods, as we have for attributes. https://github.com/open-telemetry/opentelemetry-collector/blob/main/pdata/pprofile/attributes.go The intent is to be able to use this to perform merging/batching of profiles. I will be adding similar methods for the other tables once the concept of this first PR is validated. ## Benchmark Results ``` goos: darwin goarch: arm64 pkg: go.opentelemetry.io/collector/pdata/pprofile cpu: Apple M1 Max BenchmarkFromLocationIndices-10 7058838 171.7 ns/op 308 B/op 7 allocs/op BenchmarkPutLocation/with_a_new_location-10 47819084 24.13 ns/op 16 B/op 1 allocs/op BenchmarkPutLocation/with_an_existing_location-10 47871942 23.71 ns/op 16 B/op 1 allocs/op BenchmarkPutLocation/with_a_duplicate_location-10 49755453 23.80 ns/op 16 B/op 1 allocs/op BenchmarkPutLocation/with_a_hundred_locations_to_loop_through-10 47431298 24.13 ns/op 16 B/op 1 allocs/op ``` --------- Co-authored-by: Tim Rühsen <[email protected]>
1 parent 2ac89c3 commit 4263e8d

File tree

9 files changed

+541
-3
lines changed

9 files changed

+541
-3
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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. otlpreceiver)
7+
component: pdata/pprofile
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add new helper methods `FromLocationIndices` and `PutLocation` to read and modify the content of locations.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [13150]
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+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: []

.chloggen/pprofile-equal.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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. otlpreceiver)
7+
component: pdata/pprofile
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Introduce `Equal` methods on the `Line` and `Location` types
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [13150]
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+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: [api]

pdata/pprofile/json_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
package pprofile
55

66
import (
7-
"fmt"
8-
"os"
97
"testing"
108

119
jsoniter "github.com/json-iterator/go"
@@ -133,7 +131,6 @@ func TestJSONUnmarshal(t *testing.T) {
133131
func TestJSONMarshal(t *testing.T) {
134132
encoder := &JSONMarshaler{}
135133
jsonBuf, err := encoder.MarshalProfiles(profilesOTLP)
136-
fmt.Fprintf(os.Stdout, "=================\n%#v\n----------------", string(jsonBuf))
137134
require.NoError(t, err)
138135
assert.JSONEq(t, profilesJSON, string(jsonBuf))
139136
}

pdata/pprofile/line.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
5+
6+
// Equal checks equality with another LineSlice
7+
func (l LineSlice) Equal(val LineSlice) bool {
8+
if l.Len() != val.Len() {
9+
return false
10+
}
11+
12+
for i := range l.Len() {
13+
if !l.At(i).Equal(val.At(i)) {
14+
return false
15+
}
16+
}
17+
18+
return true
19+
}
20+
21+
// Equal checks equality with another Line
22+
func (l Line) Equal(val Line) bool {
23+
return l.Column() == val.Column() &&
24+
l.FunctionIndex() == val.FunctionIndex() &&
25+
l.Line() == val.Line()
26+
}

pdata/pprofile/line_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofile
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestLineSliceEqual(t *testing.T) {
13+
for _, tt := range []struct {
14+
name string
15+
orig LineSlice
16+
dest LineSlice
17+
want bool
18+
}{
19+
{
20+
name: "with empty slices",
21+
orig: NewLineSlice(),
22+
dest: NewLineSlice(),
23+
want: true,
24+
},
25+
{
26+
name: "with non-empty equal slices",
27+
orig: func() LineSlice {
28+
ls := NewLineSlice()
29+
ls.AppendEmpty().SetLine(1)
30+
return ls
31+
}(),
32+
dest: func() LineSlice {
33+
ls := NewLineSlice()
34+
ls.AppendEmpty().SetLine(1)
35+
return ls
36+
}(),
37+
want: true,
38+
},
39+
{
40+
name: "with different lengths",
41+
orig: func() LineSlice {
42+
ls := NewLineSlice()
43+
ls.AppendEmpty()
44+
return ls
45+
}(),
46+
dest: NewLineSlice(),
47+
want: false,
48+
},
49+
{
50+
name: "with non-equal slices",
51+
orig: func() LineSlice {
52+
ls := NewLineSlice()
53+
ls.AppendEmpty().SetLine(2)
54+
return ls
55+
}(),
56+
dest: func() LineSlice {
57+
ls := NewLineSlice()
58+
ls.AppendEmpty().SetLine(1)
59+
return ls
60+
}(),
61+
want: false,
62+
},
63+
} {
64+
t.Run(tt.name, func(t *testing.T) {
65+
if tt.want {
66+
assert.True(t, tt.orig.Equal(tt.dest))
67+
} else {
68+
assert.False(t, tt.orig.Equal(tt.dest))
69+
}
70+
})
71+
}
72+
}
73+
74+
func TestLineEqual(t *testing.T) {
75+
for _, tt := range []struct {
76+
name string
77+
orig Line
78+
dest Line
79+
want bool
80+
}{
81+
{
82+
name: "with empty lines",
83+
orig: NewLine(),
84+
dest: NewLine(),
85+
want: true,
86+
},
87+
{
88+
name: "with non-empty lines",
89+
orig: buildLine(1, 2, 3),
90+
dest: buildLine(1, 2, 3),
91+
want: true,
92+
},
93+
{
94+
name: "with non-equal column",
95+
orig: buildLine(1, 2, 3),
96+
dest: buildLine(2, 2, 3),
97+
want: false,
98+
},
99+
{
100+
name: "with non-equal function index",
101+
orig: buildLine(1, 2, 3),
102+
dest: buildLine(1, 3, 3),
103+
want: false,
104+
},
105+
{
106+
name: "with non-equal line",
107+
orig: buildLine(1, 2, 3),
108+
dest: buildLine(1, 2, 4),
109+
want: false,
110+
},
111+
} {
112+
t.Run(tt.name, func(t *testing.T) {
113+
if tt.want {
114+
assert.True(t, tt.orig.Equal(tt.dest))
115+
} else {
116+
assert.False(t, tt.orig.Equal(tt.dest))
117+
}
118+
})
119+
}
120+
}
121+
122+
func buildLine(col int64, funcIdx int32, line int64) Line {
123+
l := NewLine()
124+
l.SetColumn(col)
125+
l.SetFunctionIndex(funcIdx)
126+
l.SetLine(line)
127+
128+
return l
129+
}

pdata/pprofile/location.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
5+
6+
// Equal checks equality with another Location
7+
func (l Location) Equal(val Location) bool {
8+
return l.MappingIndex() == val.MappingIndex() &&
9+
l.Address() == val.Address() &&
10+
l.AttributeIndices().Equal(val.AttributeIndices()) &&
11+
l.IsFolded() == val.IsFolded() &&
12+
l.Line().Equal(val.Line())
13+
}

pdata/pprofile/location_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofile
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestLocationEqual(t *testing.T) {
13+
for _, tt := range []struct {
14+
name string
15+
orig Location
16+
dest Location
17+
want bool
18+
}{
19+
{
20+
name: "empty locations",
21+
orig: NewLocation(),
22+
dest: NewLocation(),
23+
want: true,
24+
},
25+
{
26+
name: "non-empty locations",
27+
orig: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
28+
dest: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
29+
want: true,
30+
},
31+
{
32+
name: "with non-equal mapping index",
33+
orig: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
34+
dest: buildLocation(2, 2, []int32{3}, true, buildLine(1, 2, 3)),
35+
want: false,
36+
},
37+
{
38+
name: "with non-equal address",
39+
orig: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
40+
dest: buildLocation(1, 3, []int32{3}, true, buildLine(1, 2, 3)),
41+
want: false,
42+
},
43+
{
44+
name: "with non-equal attribute indices",
45+
orig: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
46+
dest: buildLocation(1, 2, []int32{5}, true, buildLine(1, 2, 3)),
47+
want: false,
48+
},
49+
{
50+
name: "with non-equal is folded",
51+
orig: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
52+
dest: buildLocation(1, 2, []int32{3}, false, buildLine(1, 2, 3)),
53+
want: false,
54+
},
55+
{
56+
name: "with non-equal lines",
57+
orig: buildLocation(1, 2, []int32{3}, true, buildLine(4, 5, 6)),
58+
dest: buildLocation(1, 2, []int32{3}, true, buildLine(1, 2, 3)),
59+
want: false,
60+
},
61+
} {
62+
t.Run(tt.name, func(t *testing.T) {
63+
if tt.want {
64+
assert.True(t, tt.orig.Equal(tt.dest))
65+
} else {
66+
assert.False(t, tt.orig.Equal(tt.dest))
67+
}
68+
})
69+
}
70+
}
71+
72+
func buildLocation(mapIdx int32, addr uint64, attrIdxs []int32, isFolded bool, line Line) Location {
73+
l := NewLocation()
74+
l.SetMappingIndex(mapIdx)
75+
l.SetAddress(addr)
76+
l.AttributeIndices().FromRaw(attrIdxs)
77+
l.SetIsFolded(isFolded)
78+
line.MoveTo(l.Line().AppendEmpty())
79+
return l
80+
}

pdata/pprofile/locations.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"math"
10+
)
11+
12+
// FromLocationIndices builds a slice containing all the locations of a Profile.
13+
// Updates made to the returned map will not be applied back to the Profile.
14+
func FromLocationIndices(table LocationSlice, record Profile) LocationSlice {
15+
m := NewLocationSlice()
16+
m.EnsureCapacity(record.LocationIndices().Len())
17+
18+
for _, idx := range record.LocationIndices().All() {
19+
l := table.At(int(idx))
20+
l.CopyTo(m.AppendEmpty())
21+
}
22+
23+
return m
24+
}
25+
26+
var (
27+
errTooManyLocationTableEntries = errors.New("too many entries in LocationTable")
28+
errTooManyLocationIndicesEntries = errors.New("too many entries in LocationIndices")
29+
)
30+
31+
// PutLocation updates a LocationTable and a Profile's LocationIndices to
32+
// add or update a location.
33+
func PutLocation(table LocationSlice, record Profile, loc Location) error {
34+
for i, locIdx := range record.LocationIndices().All() {
35+
idx := int(locIdx)
36+
if idx < 0 || idx >= table.Len() {
37+
return fmt.Errorf("index value %d out of range in LocationIndices[%d]", idx, i)
38+
}
39+
locAt := table.At(idx)
40+
if locAt.Equal(loc) {
41+
// Location already exists, nothing to do.
42+
return nil
43+
}
44+
}
45+
46+
if record.LocationIndices().Len() >= math.MaxInt32 {
47+
return errTooManyLocationIndicesEntries
48+
}
49+
50+
for j, a := range table.All() {
51+
if a.Equal(loc) {
52+
if j > math.MaxInt32 {
53+
return errTooManyLocationTableEntries
54+
}
55+
// Add the index of the existing location to the indices.
56+
record.LocationIndices().Append(int32(j)) //nolint:gosec // G115 overflow checked
57+
return nil
58+
}
59+
}
60+
61+
if table.Len() >= math.MaxInt32 {
62+
return errTooManyLocationTableEntries
63+
}
64+
65+
loc.CopyTo(table.AppendEmpty())
66+
record.LocationIndices().Append(int32(table.Len() - 1)) //nolint:gosec // G115 overflow checked
67+
return nil
68+
}

0 commit comments

Comments
 (0)