Skip to content
Open
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
28 changes: 15 additions & 13 deletions packages/ember/addon/utils/instrumentEmberGlobals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ type RenderEntry = {
now: number;
};

interface RenderEntries {
[name: string]: RenderEntry;
}
export type RenderEntries = Map<string, RenderEntry>;

/** This is global, so should only be run once in tests! */
export function instrumentGlobalsForPerformance(config: {
Expand Down Expand Up @@ -127,26 +125,30 @@ function _instrumentEmberRunloop(config: { minimumRunloopQueueDuration?: number
});
}

function processComponentRenderBefore(payload: Payload, beforeEntries: RenderEntries): void {
export function _processComponentRenderBefore(payload: Payload, beforeEntries: RenderEntries): void {
const info = {
payload,
now: timestampInSeconds(),
};
beforeEntries[payload.object] = info;
beforeEntries.set(payload.object, info);
}

function processComponentRenderAfter(
export function _processComponentRenderAfter(
payload: Payload,
beforeEntries: RenderEntries,
op: string,
minComponentDuration: number,
): void {
const begin = beforeEntries[payload.object];
const begin = beforeEntries.get(payload.object);

if (!begin) {
return;
}

// Remove the entry so the render payload (which references the component
// instance) is not retained forever in this module-scope map.
beforeEntries.delete(payload.object);

const now = timestampInSeconds();
const componentRenderDuration = now - begin.now;

Expand Down Expand Up @@ -174,27 +176,27 @@ function _instrumentComponents(config: {

const minComponentDuration = minimumComponentRenderDuration ?? 2;

const beforeEntries = {} as RenderEntries;
const beforeComponentDefinitionEntries = {} as RenderEntries;
const beforeEntries: RenderEntries = new Map();
const beforeComponentDefinitionEntries: RenderEntries = new Map();

function _subscribeToRenderEvents(): void {
subscribe('render.component', {
before(_name: string, _timestamp: number, payload: Payload) {
processComponentRenderBefore(payload, beforeEntries);
_processComponentRenderBefore(payload, beforeEntries);
},

after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) {
processComponentRenderAfter(payload, beforeEntries, BROWSER_UI_RENDER_SPAN_OP, minComponentDuration);
_processComponentRenderAfter(payload, beforeEntries, BROWSER_UI_RENDER_SPAN_OP, minComponentDuration);
},
});
if (enableComponentDefinitions) {
subscribe('render.getComponentDefinition', {
before(_name: string, _timestamp: number, payload: Payload) {
processComponentRenderBefore(payload, beforeComponentDefinitionEntries);
_processComponentRenderBefore(payload, beforeComponentDefinitionEntries);
},

after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) {
processComponentRenderAfter(payload, beforeComponentDefinitionEntries, GENERAL_FUNCTION_SPAN_OP, 0);
_processComponentRenderAfter(payload, beforeComponentDefinitionEntries, GENERAL_FUNCTION_SPAN_OP, 0);
},
});
}
Expand Down
51 changes: 51 additions & 0 deletions packages/ember/tests/unit/instrument-ember-globals-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { RenderEntries } from '@sentry/ember/utils/instrumentEmberGlobals';
import {
_processComponentRenderAfter,
_processComponentRenderBefore,
} from '@sentry/ember/utils/instrumentEmberGlobals';
import { setupTest } from 'ember-qunit';
import { module, test } from 'qunit';
import type { SentryTestContext } from '../helpers/setup-sentry';
import { setupSentryTest } from '../helpers/setup-sentry';

module('Unit | Utility | instrument-ember-globals', function (hooks) {
setupTest(hooks);
setupSentryTest(hooks);

test('_processComponentRenderAfter removes the entry recorded for the render', function (this: SentryTestContext, assert) {
const beforeEntries: RenderEntries = new Map();
const payload = { containerKey: 'component:test-component', initialRender: true as const, object: '<ember123>' };

_processComponentRenderBefore(payload, beforeEntries);
assert.strictEqual(beforeEntries.size, 1, 'Entry is recorded when the render starts');

_processComponentRenderAfter(payload, beforeEntries, 'ui.ember.component.render', 1_000);
assert.strictEqual(
beforeEntries.size,
0,
'Entry is removed when the render finishes, so the payload (and the component instance it references) is not retained',
);
});

test('_processComponentRenderAfter removes the entry even when the render is long enough to create a span', function (this: SentryTestContext, assert) {
const beforeEntries: RenderEntries = new Map();
const payload = { containerKey: 'component:test-component', initialRender: true as const, object: '<ember124>' };

_processComponentRenderBefore(payload, beforeEntries);
_processComponentRenderAfter(payload, beforeEntries, 'ui.ember.component.render', 0);

assert.strictEqual(beforeEntries.size, 0, 'Entry is removed after the span is created');
});

test('_processComponentRenderAfter without a matching before-entry leaves other entries alone', function (this: SentryTestContext, assert) {
const beforeEntries: RenderEntries = new Map();
const trackedPayload = { containerKey: 'component:tracked', initialRender: true as const, object: '<ember125>' };
const unknownPayload = { containerKey: 'component:unknown', initialRender: true as const, object: '<ember126>' };

_processComponentRenderBefore(trackedPayload, beforeEntries);
_processComponentRenderAfter(unknownPayload, beforeEntries, 'ui.ember.component.render', 1_000);

assert.strictEqual(beforeEntries.size, 1, 'Unrelated in-flight entries are kept');
assert.true(beforeEntries.has(trackedPayload.object), 'The in-flight entry is still tracked');
});
});
Loading