Skip to content

perf(core): Remove usage of addNonEnumerableProperty from span utils #15765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions dev-packages/rollup-utils/plugins/bundlePlugins.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,7 @@ export function makeTerserPlugin() {
// These are used by instrument.ts in utils for identifying HTML elements & events
'_sentryCaptured',
'_sentryId',
// Keeps the frozen DSC on a Sentry Span
'_frozenDsc',
// These are used to keep span & scope relationships
'_sentryRootSpan',
'_sentryChildSpans',
'_sentrySpan',
'_sentryScope',
'_sentryIsolationScope',
// require-in-the-middle calls `Module._resolveFilename`. We cannot mangle this (AWS lambda layer bundle).
Expand Down
17 changes: 4 additions & 13 deletions packages/core/src/tracing/dynamicSamplingContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,18 @@ import {
baggageHeaderToDynamicSamplingContext,
dynamicSamplingContextToSentryBaggageHeader,
} from '../utils-hoist/baggage';
import { addNonEnumerableProperty, dropUndefinedKeys } from '../utils-hoist/object';
import { dropUndefinedKeys } from '../utils-hoist/object';
import { hasSpansEnabled } from '../utils/hasSpansEnabled';
import { getRootSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils';
import { getCapturedScopesOnSpan } from './utils';

/**
* If you change this value, also update the terser plugin config to
* avoid minification of the object property!
*/
const FROZEN_DSC_FIELD = '_frozenDsc';

type SpanWithMaybeDsc = Span & {
[FROZEN_DSC_FIELD]?: Partial<DynamicSamplingContext> | undefined;
};
const SPAN_TO_DSC_MAP = new WeakMap<Span, Partial<DynamicSamplingContext>>();

/**
* Freeze the given DSC on the given span.
*/
export function freezeDscOnSpan(span: Span, dsc: Partial<DynamicSamplingContext>): void {
const spanWithMaybeDsc = span as SpanWithMaybeDsc;
addNonEnumerableProperty(spanWithMaybeDsc, FROZEN_DSC_FIELD, dsc);
SPAN_TO_DSC_MAP.set(span, dsc);
}

/**
Expand Down Expand Up @@ -91,7 +82,7 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly<Partial<
}

// For core implementation, we freeze the DSC onto the span as a non-enumerable property
const frozenDsc = (rootSpan as SpanWithMaybeDsc)[FROZEN_DSC_FIELD];
const frozenDsc = SPAN_TO_DSC_MAP.get(rootSpan);
if (frozenDsc) {
return applyLocalSampleRateToDsc(frozenDsc);
}
Expand Down
16 changes: 5 additions & 11 deletions packages/core/src/utils/spanOnScope.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import type { Scope } from '../scope';
import type { Span } from '../types-hoist';
import { addNonEnumerableProperty } from '../utils-hoist/object';

const SCOPE_SPAN_FIELD = '_sentrySpan';

type ScopeWithMaybeSpan = Scope & {
[SCOPE_SPAN_FIELD]?: Span;
};
const SCOPE_TO_SPAN_MAP = new WeakMap<Scope, Span>();

/**
* Set the active span for a given scope.
* NOTE: This should NOT be used directly, but is only used internally by the trace methods.
*/
export function _setSpanForScope(scope: Scope, span: Span | undefined): void {
if (span) {
addNonEnumerableProperty(scope as ScopeWithMaybeSpan, SCOPE_SPAN_FIELD, span);
SCOPE_TO_SPAN_MAP.set(scope, span);
} else {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete (scope as ScopeWithMaybeSpan)[SCOPE_SPAN_FIELD];
SCOPE_TO_SPAN_MAP.delete(scope);
}
}

/**
* Get the active span for a given scope.
* NOTE: This should NOT be used directly, but is only used internally by the trace methods.
*/
export function _getSpanForScope(scope: ScopeWithMaybeSpan): Span | undefined {
return scope[SCOPE_SPAN_FIELD];
export function _getSpanForScope(scope: Scope): Span | undefined {
return SCOPE_TO_SPAN_MAP.get(scope);
}
47 changes: 23 additions & 24 deletions packages/core/src/utils/spanUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
} from '../types-hoist';
import type { SpanLink, SpanLinkJSON } from '../types-hoist/link';
import { consoleSandbox } from '../utils-hoist/logger';
import { addNonEnumerableProperty, dropUndefinedKeys } from '../utils-hoist/object';
import { dropUndefinedKeys } from '../utils-hoist/object';
import { generateSpanId } from '../utils-hoist/propagationContext';
import { timestampInSeconds } from '../utils-hoist/time';
import { generateSentryTraceHeader } from '../utils-hoist/tracing';
Expand Down Expand Up @@ -223,55 +223,54 @@ export function getStatusMessage(status: SpanStatus | undefined): string | undef
return status.message || 'unknown_error';
}

const CHILD_SPANS_FIELD = '_sentryChildSpans';
const ROOT_SPAN_FIELD = '_sentryRootSpan';

type SpanWithPotentialChildren = Span & {
[CHILD_SPANS_FIELD]?: Set<Span>;
[ROOT_SPAN_FIELD]?: Span;
};
const SPAN_TO_ROOT_SPAN_MAP = new WeakMap<Span, Span>();
const SPAN_TO_CHILD_SPANS_MAP = new WeakMap<Span, Set<Span>>();

/**
* Adds an opaque child span reference to a span.
*/
export function addChildSpanToSpan(span: SpanWithPotentialChildren, childSpan: Span): void {
export function addChildSpanToSpan(span: Span, childSpan: Span): void {
// We store the root span reference on the child span
// We need this for `getRootSpan()` to work
const rootSpan = span[ROOT_SPAN_FIELD] || span;
addNonEnumerableProperty(childSpan as SpanWithPotentialChildren, ROOT_SPAN_FIELD, rootSpan);
const rootSpan = SPAN_TO_ROOT_SPAN_MAP.get(span) || span;
SPAN_TO_ROOT_SPAN_MAP.set(childSpan, rootSpan);

// We store a list of child spans on the parent span
// We need this for `getSpanDescendants()` to work
if (span[CHILD_SPANS_FIELD]) {
span[CHILD_SPANS_FIELD].add(childSpan);
const childSpans = SPAN_TO_CHILD_SPANS_MAP.get(span);
if (childSpans) {
childSpans.add(childSpan);
} else {
addNonEnumerableProperty(span, CHILD_SPANS_FIELD, new Set([childSpan]));
SPAN_TO_CHILD_SPANS_MAP.set(span, new Set([childSpan]));
}
}

/** This is only used internally by Idle Spans. */
export function removeChildSpanFromSpan(span: SpanWithPotentialChildren, childSpan: Span): void {
if (span[CHILD_SPANS_FIELD]) {
span[CHILD_SPANS_FIELD].delete(childSpan);
export function removeChildSpanFromSpan(span: Span, childSpan: Span): void {
const childSpans = SPAN_TO_CHILD_SPANS_MAP.get(span);
if (childSpans) {
childSpans.delete(childSpan);
}
}

/**
* Returns an array of the given span and all of its descendants.
*/
export function getSpanDescendants(span: SpanWithPotentialChildren): Span[] {
export function getSpanDescendants(span: Span): Span[] {
const resultSet = new Set<Span>();

function addSpanChildren(span: SpanWithPotentialChildren): void {
function addSpanChildren(span: Span): void {
// This exit condition is required to not infinitely loop in case of a circular dependency.
if (resultSet.has(span)) {
return;
// We want to ignore unsampled spans (e.g. non recording spans)
} else if (spanIsSampled(span)) {
resultSet.add(span);
const childSpans = span[CHILD_SPANS_FIELD] ? Array.from(span[CHILD_SPANS_FIELD]) : [];
for (const childSpan of childSpans) {
addSpanChildren(childSpan);
const childSpans = SPAN_TO_CHILD_SPANS_MAP.get(span);
if (childSpans) {
for (const childSpan of childSpans) {
addSpanChildren(childSpan);
}
}
}
}
Expand All @@ -284,8 +283,8 @@ export function getSpanDescendants(span: SpanWithPotentialChildren): Span[] {
/**
* Returns the root span of a given span.
*/
export function getRootSpan(span: SpanWithPotentialChildren): Span {
return span[ROOT_SPAN_FIELD] || span;
export function getRootSpan(span: Span): Span {
return SPAN_TO_ROOT_SPAN_MAP.get(span) || span;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/core/test/lib/tracing/trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ describe('startSpan', () => {
expect.assertions(1);
startSpan({ name: 'outer' }, (outerSpan: any) => {
startSpan({ name: 'inner' }, innerSpan => {
const childSpans = Array.from(outerSpan._sentryChildSpans);
const childSpans = getSpanDescendants(outerSpan);
expect(childSpans).toContain(innerSpan);
});
});
Expand Down Expand Up @@ -1178,7 +1178,7 @@ describe('startSpanManual', () => {
expect.assertions(1);
startSpan({ name: 'outer' }, (outerSpan: any) => {
startSpanManual({ name: 'inner' }, innerSpan => {
const childSpans = Array.from(outerSpan._sentryChildSpans);
const childSpans = getSpanDescendants(outerSpan);
expect(childSpans).toContain(innerSpan);
});
});
Expand Down Expand Up @@ -1591,7 +1591,7 @@ describe('startInactiveSpan', () => {
expect.assertions(1);
startSpan({ name: 'outer' }, (outerSpan: any) => {
const innerSpan = startInactiveSpan({ name: 'inner' });
const childSpans = Array.from(outerSpan._sentryChildSpans);
const childSpans = getSpanDescendants(outerSpan);
expect(childSpans).toContain(innerSpan);
});
});
Expand Down
9 changes: 4 additions & 5 deletions packages/opentelemetry/src/utils/contextData.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import type { Context } from '@opentelemetry/api';
import type { Scope } from '@sentry/core';
import { addNonEnumerableProperty } from '@sentry/core';
import { SENTRY_SCOPES_CONTEXT_KEY } from '../constants';
import type { CurrentScopes } from '../types';

const SCOPE_CONTEXT_FIELD = '_scopeContext';

/**
* Try to get the current scopes from the given OTEL context.
* This requires a Context Manager that was wrapped with getWrappedContextManager.
Expand All @@ -22,17 +19,19 @@ export function setScopesOnContext(context: Context, scopes: CurrentScopes): Con
return context.setValue(SENTRY_SCOPES_CONTEXT_KEY, scopes);
}

const SCOPE_TO_CONTEXT_MAP = new WeakMap<Scope, Context>();

/**
* Set the context on the scope so we can later look it up.
* We need this to get the context from the scope in the `trace` functions.
*/
export function setContextOnScope(scope: Scope, context: Context): void {
addNonEnumerableProperty(scope, SCOPE_CONTEXT_FIELD, context);
SCOPE_TO_CONTEXT_MAP.set(scope, context);
}

/**
* Get the context related to a scope.
*/
export function getContextFromScope(scope: Scope): Context | undefined {
return (scope as { [SCOPE_CONTEXT_FIELD]?: Context })[SCOPE_CONTEXT_FIELD];
return SCOPE_TO_CONTEXT_MAP.get(scope);
}
Loading