diff --git a/CHANGELOG.md b/CHANGELOG.md index c946fbbf104..cbc3b5290b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2 * perf(sdk-trace-base): use Uint8Array for browser RandomIdGenerator [#6209](https://github.com/open-telemetry/opentelemetry-js/pull/6209) @overbalance * test(sdk-trace-base): remove obsolete TypeScript and platform workarounds [#6327](https://github.com/open-telemetry/opentelemetry-js/pull/6327) @overbalance * fix(example-web): update Docker config and deps for collector [#6342](https://github.com/open-telemetry/opentelemetry-js/pull/6342) @overbalance +* perf(sdk-trace-base): optimize setAttribute performance [#6283](https://github.com/open-telemetry/opentelemetry-js/pull/6283) @AbhiPrasad ## 2.5.0 diff --git a/packages/opentelemetry-sdk-trace-base/src/Span.ts b/packages/opentelemetry-sdk-trace-base/src/Span.ts index 508557f6a34..c9b1cd9cabe 100644 --- a/packages/opentelemetry-sdk-trace-base/src/Span.ts +++ b/packages/opentelemetry-sdk-trace-base/src/Span.ts @@ -93,6 +93,7 @@ export class SpanImpl implements Span { private _droppedAttributesCount = 0; private _droppedEventsCount: number = 0; private _droppedLinksCount: number = 0; + private _attributesCount: number = 0; name: string; status: SpanStatus = { @@ -157,16 +158,24 @@ export class SpanImpl implements Span { } const { attributeCountLimit } = this._spanLimits; + const isNewKey = !Object.prototype.hasOwnProperty.call( + this.attributes, + key + ); if ( attributeCountLimit !== undefined && - Object.keys(this.attributes).length >= attributeCountLimit && - !Object.prototype.hasOwnProperty.call(this.attributes, key) + this._attributesCount >= attributeCountLimit && + isNewKey ) { this._droppedAttributesCount++; return this; } + this.attributes[key] = this._truncateToSize(value); + if (isNewKey) { + this._attributesCount++; + } return this; } diff --git a/packages/opentelemetry-sdk-trace-base/test/performance/benchmark/setAttributes.js b/packages/opentelemetry-sdk-trace-base/test/performance/benchmark/setAttributes.js new file mode 100644 index 00000000000..ef0d96c4bc1 --- /dev/null +++ b/packages/opentelemetry-sdk-trace-base/test/performance/benchmark/setAttributes.js @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const Benchmark = require('benchmark'); +const { BasicTracerProvider } = require('../../../build/src'); + +const tracerProvider = new BasicTracerProvider(); +const tracer = tracerProvider.getTracer('test'); + +const suite = new Benchmark.Suite('setAttribute performance'); + +suite.on('cycle', event => { + console.log(String(event.target)); +}); + +// Generate attribute keys for different test sizes +function generateAttributeKeys(count) { + const keys = []; + for (let i = 0; i < count; i++) { + keys.push(`attribute_key_${i.toString().padStart(4, '0')}`); + } + return keys; +} + +const keys10 = generateAttributeKeys(10); +const keys50 = generateAttributeKeys(50); +const keys100 = generateAttributeKeys(100); +const keys128 = generateAttributeKeys(128); + +// Test with 10 attributes +suite.add('setAttribute (10 attributes)', function () { + const span = tracer.startSpan('span'); + for (let i = 0; i < keys10.length; i++) { + span.setAttribute(keys10[i], 'test_value'); + } + span.end(); +}); + +// Test with 50 attributes +suite.add('setAttribute (50 attributes)', function () { + const span = tracer.startSpan('span'); + for (let i = 0; i < keys50.length; i++) { + span.setAttribute(keys50[i], 'test_value'); + } + span.end(); +}); + +// Test with 100 attributes +suite.add('setAttribute (100 attributes)', function () { + const span = tracer.startSpan('span'); + for (let i = 0; i < keys100.length; i++) { + span.setAttribute(keys100[i], 'test_value'); + } + span.end(); +}); + +// Test with 128 attributes (default limit) +suite.add('setAttribute (128 attributes - at limit)', function () { + const span = tracer.startSpan('span'); + for (let i = 0; i < keys128.length; i++) { + span.setAttribute(keys128[i], 'test_value'); + } + span.end(); +}); + +// Test setAttribute with overwriting existing keys (no Object.keys growth) +suite.add('setAttribute (100 overwrites of same key)', function () { + const span = tracer.startSpan('span'); + for (let i = 0; i < 100; i++) { + span.setAttribute('same_key', 'test_value_' + i); + } + span.end(); +}); + +// Test setAttributes in bulk vs individual setAttribute +suite.add('setAttributes bulk (100 attributes)', function () { + const span = tracer.startSpan('span'); + const attrs = {}; + for (let i = 0; i < 100; i++) { + attrs[keys100[i]] = 'test_value'; + } + span.setAttributes(attrs); + span.end(); +}); + +suite.run({ async: false });