1
0
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:
Warnar Boekkooi 2024-10-07 09:30:29 +02:00 committed by GitHub
parent 9e791a62ba
commit 3cbd967152
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 15 deletions

View File

@ -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

View File

@ -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() {}