mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-14 02:33:21 +02:00
Performance improvements for recordingSpan
SetAttributes
and addOverCapAttrs
(#5864)
Good day, While working on https://github.com/open-telemetry/opentelemetry-go/pull/5858 I found some other possible improvements. This PR: - Adds an early return to `SetAttributes` when no attributes are provided. - Only increases `s.attributes` to guarantee that there is enough space for elements to be added. - Fixes and issue where `truncateAttr` was not used when a attribute was being updated in `addOverCapAttrs`. Thanks for reviewing and please let me know if any changes are needed. --------- Co-authored-by: Damien Mathieu <42@dmathieu.com>
This commit is contained in:
parent
9e791a62ba
commit
3cbd967152
@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
- `Logger.Enabled` in `go.opentelemetry.io/otel/log` now accepts a newly introduced `EnabledParameters` type instead of `Record`. (#5791)
|
- `Logger.Enabled` in `go.opentelemetry.io/otel/log` now accepts a newly introduced `EnabledParameters` type instead of `Record`. (#5791)
|
||||||
- `FilterProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log/internal/x` now accepts `EnabledParameters` instead of `Record`. (#5791)
|
- `FilterProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log/internal/x` now accepts `EnabledParameters` instead of `Record`. (#5791)
|
||||||
- The `Record` type in `go.opentelemetry.io/otel/log` is no longer comparable. (#5847)
|
- The `Record` type in `go.opentelemetry.io/otel/log` is no longer comparable. (#5847)
|
||||||
|
- Performance improvements for the trace SDK `SetAttributes` method in `Span`. (#5864)
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
@ -174,6 +174,16 @@ func (s *recordingSpan) IsRecording() bool {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
return s.isRecording()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRecording returns if this span is being recorded. If this span has ended
|
||||||
|
// this will return false.
|
||||||
|
// This is done without acquiring a lock.
|
||||||
|
func (s *recordingSpan) isRecording() bool {
|
||||||
|
if s == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return s.endTime.IsZero()
|
return s.endTime.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,11 +192,15 @@ func (s *recordingSpan) IsRecording() bool {
|
|||||||
// included in the set status when the code is for an error. If this span is
|
// included in the set status when the code is for an error. If this span is
|
||||||
// not being recorded than this method does nothing.
|
// not being recorded than this method does nothing.
|
||||||
func (s *recordingSpan) SetStatus(code codes.Code, description string) {
|
func (s *recordingSpan) SetStatus(code codes.Code, description string) {
|
||||||
if !s.IsRecording() {
|
if s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
if !s.isRecording() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if s.status.Code > code {
|
if s.status.Code > code {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -210,12 +224,15 @@ func (s *recordingSpan) SetStatus(code codes.Code, description string) {
|
|||||||
// attributes the span is configured to have, the last added attributes will
|
// attributes the span is configured to have, the last added attributes will
|
||||||
// be dropped.
|
// be dropped.
|
||||||
func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
|
func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
|
||||||
if !s.IsRecording() {
|
if s == nil || len(attributes) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
if !s.isRecording() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
limit := s.tracer.provider.spanLimits.AttributeCountLimit
|
limit := s.tracer.provider.spanLimits.AttributeCountLimit
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
@ -233,7 +250,7 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
|
|||||||
|
|
||||||
// Otherwise, add without deduplication. When attributes are read they
|
// Otherwise, add without deduplication. When attributes are read they
|
||||||
// will be deduplicated, optimizing the operation.
|
// will be deduplicated, optimizing the operation.
|
||||||
s.attributes = slices.Grow(s.attributes, len(s.attributes)+len(attributes))
|
s.attributes = slices.Grow(s.attributes, len(attributes))
|
||||||
for _, a := range attributes {
|
for _, a := range attributes {
|
||||||
if !a.Valid() {
|
if !a.Valid() {
|
||||||
// Drop all invalid attributes.
|
// Drop all invalid attributes.
|
||||||
@ -280,13 +297,17 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
|
|||||||
|
|
||||||
// Do not set a capacity when creating this map. Benchmark testing has
|
// Do not set a capacity when creating this map. Benchmark testing has
|
||||||
// showed this to only add unused memory allocations in general use.
|
// showed this to only add unused memory allocations in general use.
|
||||||
exists := make(map[attribute.Key]int)
|
exists := make(map[attribute.Key]int, len(s.attributes))
|
||||||
s.dedupeAttrsFromRecord(&exists)
|
s.dedupeAttrsFromRecord(exists)
|
||||||
|
|
||||||
// Now that s.attributes is deduplicated, adding unique attributes up to
|
// Now that s.attributes is deduplicated, adding unique attributes up to
|
||||||
// the capacity of s will not over allocate s.attributes.
|
// the capacity of s will not over allocate s.attributes.
|
||||||
sum := len(attrs) + len(s.attributes)
|
|
||||||
s.attributes = slices.Grow(s.attributes, min(sum, limit))
|
// max size = limit
|
||||||
|
maxCap := min(len(attrs)+len(s.attributes), limit)
|
||||||
|
if cap(s.attributes) < maxCap {
|
||||||
|
s.attributes = slices.Grow(s.attributes, maxCap-cap(s.attributes))
|
||||||
|
}
|
||||||
for _, a := range attrs {
|
for _, a := range attrs {
|
||||||
if !a.Valid() {
|
if !a.Valid() {
|
||||||
// Drop all invalid attributes.
|
// Drop all invalid attributes.
|
||||||
@ -296,6 +317,7 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
|
|||||||
|
|
||||||
if idx, ok := exists[a.Key]; ok {
|
if idx, ok := exists[a.Key]; ok {
|
||||||
// Perform all updates before dropping, even when at capacity.
|
// Perform all updates before dropping, even when at capacity.
|
||||||
|
a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a)
|
||||||
s.attributes[idx] = a
|
s.attributes[idx] = a
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -518,12 +540,15 @@ func (s *recordingSpan) addEvent(name string, o ...trace.EventOption) {
|
|||||||
// SetName sets the name of this span. If this span is not being recorded than
|
// SetName sets the name of this span. If this span is not being recorded than
|
||||||
// this method does nothing.
|
// this method does nothing.
|
||||||
func (s *recordingSpan) SetName(name string) {
|
func (s *recordingSpan) SetName(name string) {
|
||||||
if !s.IsRecording() {
|
if s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
if !s.isRecording() {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.name = name
|
s.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,23 +604,23 @@ func (s *recordingSpan) Attributes() []attribute.KeyValue {
|
|||||||
func (s *recordingSpan) dedupeAttrs() {
|
func (s *recordingSpan) dedupeAttrs() {
|
||||||
// Do not set a capacity when creating this map. Benchmark testing has
|
// Do not set a capacity when creating this map. Benchmark testing has
|
||||||
// showed this to only add unused memory allocations in general use.
|
// showed this to only add unused memory allocations in general use.
|
||||||
exists := make(map[attribute.Key]int)
|
exists := make(map[attribute.Key]int, len(s.attributes))
|
||||||
s.dedupeAttrsFromRecord(&exists)
|
s.dedupeAttrsFromRecord(exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dedupeAttrsFromRecord deduplicates the attributes of s to fit capacity
|
// dedupeAttrsFromRecord deduplicates the attributes of s to fit capacity
|
||||||
// using record as the record of unique attribute keys to their index.
|
// using record as the record of unique attribute keys to their index.
|
||||||
//
|
//
|
||||||
// This method assumes s.mu.Lock is held by the caller.
|
// This method assumes s.mu.Lock is held by the caller.
|
||||||
func (s *recordingSpan) dedupeAttrsFromRecord(record *map[attribute.Key]int) {
|
func (s *recordingSpan) dedupeAttrsFromRecord(record map[attribute.Key]int) {
|
||||||
// Use the fact that slices share the same backing array.
|
// Use the fact that slices share the same backing array.
|
||||||
unique := s.attributes[:0]
|
unique := s.attributes[:0]
|
||||||
for _, a := range s.attributes {
|
for _, a := range s.attributes {
|
||||||
if idx, ok := (*record)[a.Key]; ok {
|
if idx, ok := record[a.Key]; ok {
|
||||||
unique[idx] = a
|
unique[idx] = a
|
||||||
} else {
|
} else {
|
||||||
unique = append(unique, a)
|
unique = append(unique, a)
|
||||||
(*record)[a.Key] = len(unique) - 1
|
record[a.Key] = len(unique) - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// s.attributes have element types of attribute.KeyValue. These types are
|
// s.attributes have element types of attribute.KeyValue. These types are
|
||||||
@ -755,12 +780,16 @@ func (s *recordingSpan) snapshot() ReadOnlySpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingSpan) addChild() {
|
func (s *recordingSpan) addChild() {
|
||||||
if !s.IsRecording() {
|
if s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if !s.isRecording() {
|
||||||
|
return
|
||||||
|
}
|
||||||
s.childSpanCount++
|
s.childSpanCount++
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*recordingSpan) private() {}
|
func (*recordingSpan) private() {}
|
||||||
|
Loading…
Reference in New Issue
Block a user