Skip to content

Commit 3cbd967

Browse files
Performance improvements for recordingSpan SetAttributes and addOverCapAttrs (#5864)
Good day, While working on #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 <[email protected]>
1 parent 9e791a6 commit 3cbd967

File tree

2 files changed

+45
-15
lines changed

2 files changed

+45
-15
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1818
- `Logger.Enabled` in `go.opentelemetry.io/otel/log` now accepts a newly introduced `EnabledParameters` type instead of `Record`. (#5791)
1919
- `FilterProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log/internal/x` now accepts `EnabledParameters` instead of `Record`. (#5791)
2020
- The `Record` type in `go.opentelemetry.io/otel/log` is no longer comparable. (#5847)
21+
- Performance improvements for the trace SDK `SetAttributes` method in `Span`. (#5864)
2122

2223
### Deprecated
2324

sdk/trace/span.go

+44-15
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ func (s *recordingSpan) IsRecording() bool {
174174
s.mu.Lock()
175175
defer s.mu.Unlock()
176176

177+
return s.isRecording()
178+
}
179+
180+
// isRecording returns if this span is being recorded. If this span has ended
181+
// this will return false.
182+
// This is done without acquiring a lock.
183+
func (s *recordingSpan) isRecording() bool {
184+
if s == nil {
185+
return false
186+
}
177187
return s.endTime.IsZero()
178188
}
179189

@@ -182,11 +192,15 @@ func (s *recordingSpan) IsRecording() bool {
182192
// included in the set status when the code is for an error. If this span is
183193
// not being recorded than this method does nothing.
184194
func (s *recordingSpan) SetStatus(code codes.Code, description string) {
185-
if !s.IsRecording() {
195+
if s == nil {
186196
return
187197
}
198+
188199
s.mu.Lock()
189200
defer s.mu.Unlock()
201+
if !s.isRecording() {
202+
return
203+
}
190204
if s.status.Code > code {
191205
return
192206
}
@@ -210,12 +224,15 @@ func (s *recordingSpan) SetStatus(code codes.Code, description string) {
210224
// attributes the span is configured to have, the last added attributes will
211225
// be dropped.
212226
func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
213-
if !s.IsRecording() {
227+
if s == nil || len(attributes) == 0 {
214228
return
215229
}
216230

217231
s.mu.Lock()
218232
defer s.mu.Unlock()
233+
if !s.isRecording() {
234+
return
235+
}
219236

220237
limit := s.tracer.provider.spanLimits.AttributeCountLimit
221238
if limit == 0 {
@@ -233,7 +250,7 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
233250

234251
// Otherwise, add without deduplication. When attributes are read they
235252
// will be deduplicated, optimizing the operation.
236-
s.attributes = slices.Grow(s.attributes, len(s.attributes)+len(attributes))
253+
s.attributes = slices.Grow(s.attributes, len(attributes))
237254
for _, a := range attributes {
238255
if !a.Valid() {
239256
// Drop all invalid attributes.
@@ -280,13 +297,17 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
280297

281298
// Do not set a capacity when creating this map. Benchmark testing has
282299
// showed this to only add unused memory allocations in general use.
283-
exists := make(map[attribute.Key]int)
284-
s.dedupeAttrsFromRecord(&exists)
300+
exists := make(map[attribute.Key]int, len(s.attributes))
301+
s.dedupeAttrsFromRecord(exists)
285302

286303
// Now that s.attributes is deduplicated, adding unique attributes up to
287304
// the capacity of s will not over allocate s.attributes.
288-
sum := len(attrs) + len(s.attributes)
289-
s.attributes = slices.Grow(s.attributes, min(sum, limit))
305+
306+
// max size = limit
307+
maxCap := min(len(attrs)+len(s.attributes), limit)
308+
if cap(s.attributes) < maxCap {
309+
s.attributes = slices.Grow(s.attributes, maxCap-cap(s.attributes))
310+
}
290311
for _, a := range attrs {
291312
if !a.Valid() {
292313
// Drop all invalid attributes.
@@ -296,6 +317,7 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
296317

297318
if idx, ok := exists[a.Key]; ok {
298319
// Perform all updates before dropping, even when at capacity.
320+
a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a)
299321
s.attributes[idx] = a
300322
continue
301323
}
@@ -518,12 +540,15 @@ func (s *recordingSpan) addEvent(name string, o ...trace.EventOption) {
518540
// SetName sets the name of this span. If this span is not being recorded than
519541
// this method does nothing.
520542
func (s *recordingSpan) SetName(name string) {
521-
if !s.IsRecording() {
543+
if s == nil {
522544
return
523545
}
524546

525547
s.mu.Lock()
526548
defer s.mu.Unlock()
549+
if !s.isRecording() {
550+
return
551+
}
527552
s.name = name
528553
}
529554

@@ -579,23 +604,23 @@ func (s *recordingSpan) Attributes() []attribute.KeyValue {
579604
func (s *recordingSpan) dedupeAttrs() {
580605
// Do not set a capacity when creating this map. Benchmark testing has
581606
// showed this to only add unused memory allocations in general use.
582-
exists := make(map[attribute.Key]int)
583-
s.dedupeAttrsFromRecord(&exists)
607+
exists := make(map[attribute.Key]int, len(s.attributes))
608+
s.dedupeAttrsFromRecord(exists)
584609
}
585610

586611
// dedupeAttrsFromRecord deduplicates the attributes of s to fit capacity
587612
// using record as the record of unique attribute keys to their index.
588613
//
589614
// This method assumes s.mu.Lock is held by the caller.
590-
func (s *recordingSpan) dedupeAttrsFromRecord(record *map[attribute.Key]int) {
615+
func (s *recordingSpan) dedupeAttrsFromRecord(record map[attribute.Key]int) {
591616
// Use the fact that slices share the same backing array.
592617
unique := s.attributes[:0]
593618
for _, a := range s.attributes {
594-
if idx, ok := (*record)[a.Key]; ok {
619+
if idx, ok := record[a.Key]; ok {
595620
unique[idx] = a
596621
} else {
597622
unique = append(unique, a)
598-
(*record)[a.Key] = len(unique) - 1
623+
record[a.Key] = len(unique) - 1
599624
}
600625
}
601626
// s.attributes have element types of attribute.KeyValue. These types are
@@ -755,12 +780,16 @@ func (s *recordingSpan) snapshot() ReadOnlySpan {
755780
}
756781

757782
func (s *recordingSpan) addChild() {
758-
if !s.IsRecording() {
783+
if s == nil {
759784
return
760785
}
786+
761787
s.mu.Lock()
788+
defer s.mu.Unlock()
789+
if !s.isRecording() {
790+
return
791+
}
762792
s.childSpanCount++
763-
s.mu.Unlock()
764793
}
765794

766795
func (*recordingSpan) private() {}

0 commit comments

Comments
 (0)