mirror of
https://github.com/hanzoai/telemetry.git
synced 2026-10-30 20:01:49 +00:00
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description When multiple exporters are used in a profiles pipeline, the same data object is shared and marked as read-only, causing a panic when mutation is attempted. Fix this by only creating a copy when the input is marked as read-only. This preserves the in-place mutation optimization for mutable data while preventing panics in shared data scenarios. <!-- Issue number if applicable --> #### Link to tracking issue Fixes #15080 <!--Describe what testing was performed and which tests were added.--> #### Testing <!--Describe the documentation added.--> #### Documentation <!--Please delete paragraphs that you did not use before submitting.--> --------- Signed-off-by: Florian Lehner <[email protected]> Co-authored-by: Damien Mathieu <[email protected]>
110 lines
3.8 KiB
Go
110 lines
3.8 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package pprofile
|
|
|
|
import (
|
|
stdjson "encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newProfilesWithAttributes creates a Profiles with resource and scope
|
|
// attributes for testing reference conversion.
|
|
func newProfilesWithAttributes() Profiles {
|
|
profiles := NewProfiles()
|
|
profiles.Dictionary().StringTable().Append("") // index 0
|
|
|
|
rp := profiles.ResourceProfiles().AppendEmpty()
|
|
rp.Resource().Attributes().PutStr("service.name", "test-service")
|
|
rp.Resource().Attributes().PutStr("host.name", "test-host")
|
|
|
|
sp := rp.ScopeProfiles().AppendEmpty()
|
|
sp.Scope().Attributes().PutStr("scope.attr", "scope-value")
|
|
|
|
return profiles
|
|
}
|
|
|
|
func TestJSONMarshalConvertsToReferences(t *testing.T) {
|
|
marshaler := JSONMarshaler{}
|
|
jsonBytes, err := marshaler.MarshalProfiles(newProfilesWithAttributes())
|
|
require.NoError(t, err)
|
|
|
|
// Parse the JSON output to verify references were used
|
|
var parsed map[string]any
|
|
require.NoError(t, stdjson.Unmarshal(jsonBytes, &parsed))
|
|
|
|
// The dictionary's stringTable should contain the attribute keys and values
|
|
dictionary, ok := parsed["dictionary"].(map[string]any)
|
|
require.True(t, ok, "JSON output should contain a dictionary object")
|
|
stringTable, ok := dictionary["stringTable"].([]any)
|
|
require.True(t, ok, "dictionary should contain a stringTable array")
|
|
|
|
tableStrs := make([]string, len(stringTable))
|
|
for i, v := range stringTable {
|
|
tableStrs[i], _ = v.(string)
|
|
}
|
|
assert.Contains(t, tableStrs, "service.name")
|
|
assert.Contains(t, tableStrs, "test-service")
|
|
assert.Contains(t, tableStrs, "host.name")
|
|
assert.Contains(t, tableStrs, "test-host")
|
|
assert.Contains(t, tableStrs, "scope.attr")
|
|
assert.Contains(t, tableStrs, "scope-value")
|
|
}
|
|
|
|
func TestJSONUnmarshalResolvesReferences(t *testing.T) {
|
|
profiles := newProfilesWithAttributes()
|
|
|
|
// Manually convert to references before marshaling, so the JSON output
|
|
// contains key_ref/string_value_ref regardless of whether the JSON
|
|
// marshaler itself calls convertProfilesToReferences.
|
|
convertProfilesToReferences(profiles)
|
|
|
|
marshaler := JSONMarshaler{}
|
|
jsonBytes, err := marshaler.MarshalProfiles(profiles)
|
|
require.NoError(t, err)
|
|
|
|
// Unmarshal and verify references were resolved
|
|
unmarshaler := JSONUnmarshaler{}
|
|
restored, err := unmarshaler.UnmarshalProfiles(jsonBytes)
|
|
require.NoError(t, err)
|
|
|
|
rp := restored.ResourceProfiles().At(0)
|
|
serviceNameVal, ok := rp.Resource().Attributes().Get("service.name")
|
|
assert.True(t, ok, "service.name attribute should be accessible after JSON unmarshal")
|
|
assert.Equal(t, "test-service", serviceNameVal.Str())
|
|
|
|
hostNameVal, ok := rp.Resource().Attributes().Get("host.name")
|
|
assert.True(t, ok, "host.name attribute should be accessible after JSON unmarshal")
|
|
assert.Equal(t, "test-host", hostNameVal.Str())
|
|
|
|
sp := rp.ScopeProfiles().At(0)
|
|
scopeAttrVal, ok := sp.Scope().Attributes().Get("scope.attr")
|
|
assert.True(t, ok, "scope.attr should be accessible after JSON unmarshal")
|
|
assert.Equal(t, "scope-value", scopeAttrVal.Str())
|
|
}
|
|
|
|
func TestJSONMarshalReadOnlyProfiles(t *testing.T) {
|
|
profiles := newProfilesWithAttributes()
|
|
marshaler := JSONMarshaler{}
|
|
|
|
// Mark as read-only to simulate sharing across multiple exporters
|
|
profiles.MarkReadOnly()
|
|
|
|
// MarshalProfiles should not panic or fail when given read-only data
|
|
jsonBytes, err := marshaler.MarshalProfiles(profiles)
|
|
require.NoError(t, err)
|
|
|
|
// Verify the JSON contains the expected data
|
|
var parsed map[string]any
|
|
require.NoError(t, stdjson.Unmarshal(jsonBytes, &parsed))
|
|
|
|
// Verify the original profiles is still usable and hasn't been mutated
|
|
rp := profiles.ResourceProfiles().At(0)
|
|
serviceNameVal, ok := rp.Resource().Attributes().Get("service.name")
|
|
assert.True(t, ok, "service.name should still be accessible")
|
|
assert.Equal(t, "test-service", serviceNameVal.Str())
|
|
}
|