Files
telemetry/pdata/pprofile/json.go
d686cf9058 [pdata/pprofile] copy data if read-only (#15082)
<!--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]>
2026-04-09 10:44:50 +00:00

60 lines
1.7 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package pprofile // import "go.opentelemetry.io/collector/pdata/pprofile"
import (
"slices"
"go.opentelemetry.io/collector/pdata/internal/json"
"go.opentelemetry.io/collector/pdata/internal/otlp"
)
// JSONMarshaler marshals pprofile.Profiles to JSON bytes using the OTLP/JSON format.
type JSONMarshaler struct{}
// MarshalProfiles marshals Profiles to OTLP/JSON format bytes.
// If the input data is read-only, it will be copied to a mutable
// instance before mutation.
func (*JSONMarshaler) MarshalProfiles(pd Profiles) ([]byte, error) {
// Only copy if data is shared/read-only to avoid unnecessary allocation
pdToUse := pd
if pd.IsReadOnly() {
pdCopy := NewProfiles()
pd.CopyTo(pdCopy)
pdToUse = pdCopy
}
// Convert strings to references for efficient transmission
convertProfilesToReferences(pdToUse)
dest := json.BorrowStream(nil)
defer json.ReturnStream(dest)
pdToUse.getOrig().MarshalJSON(dest)
if dest.Error() != nil {
return nil, dest.Error()
}
return slices.Clone(dest.Buffer()), nil
}
// JSONUnmarshaler unmarshals OTLP/JSON formatted-bytes to pprofile.Profiles.
type JSONUnmarshaler struct{}
// UnmarshalProfiles from OTLP/JSON format into pprofile.Profiles.
func (*JSONUnmarshaler) UnmarshalProfiles(buf []byte) (Profiles, error) {
iter := json.BorrowIterator(buf)
defer json.ReturnIterator(iter)
pd := NewProfiles()
pd.getOrig().UnmarshalJSON(iter)
if iter.Error() != nil {
return Profiles{}, iter.Error()
}
otlp.MigrateProfiles(pd.getOrig().ResourceProfiles)
// Resolve all string_value_ref and key_ref to their actual strings
// so the pdata API works transparently
resolveProfilesReferences(pd)
return pd, nil
}