Skip to content

Commit d054af5

Browse files
committed
feat(core): Further optimize debug ID parsing
1 parent 100c662 commit d054af5

File tree

3 files changed

+67
-46
lines changed

3 files changed

+67
-46
lines changed

packages/core/src/utils/prepareEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export function applyDebugIds(event: Event, stackParser: StackParser): void {
180180
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
181181
exception.stacktrace!.frames!.forEach(frame => {
182182
if (frame.filename) {
183-
frame.debug_id = filenameDebugIdMap[frame.filename];
183+
frame.debug_id = filenameDebugIdMap.get(frame.filename);
184184
}
185185
});
186186
});

packages/node/src/integrations/anr/index.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as diagnosticsChannel from 'node:diagnostics_channel';
21
import { Worker } from 'node:worker_threads';
32
import { defineIntegration, getCurrentScope, getGlobalScope, getIsolationScope, mergeScopeData } from '@sentry/core';
43
import type { Contexts, Event, EventHint, Integration, IntegrationFn, ScopeData } from '@sentry/types';
@@ -101,13 +100,6 @@ type AnrReturn = (options?: Partial<AnrIntegrationOptions>) => Integration & Anr
101100

102101
export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn;
103102

104-
function onModuleLoad(callback: () => void): void {
105-
// eslint-disable-next-line deprecation/deprecation
106-
diagnosticsChannel.channel('module.require.end').subscribe(() => callback());
107-
// eslint-disable-next-line deprecation/deprecation
108-
diagnosticsChannel.channel('module.import.asyncEnd').subscribe(() => callback());
109-
}
110-
111103
/**
112104
* Starts the ANR worker thread
113105
*
@@ -161,12 +153,6 @@ async function _startWorker(
161153
}
162154
}
163155

164-
let debugImages: Record<string, string> = getFilenameToDebugIdMap(initOptions.stackParser);
165-
166-
onModuleLoad(() => {
167-
debugImages = getFilenameToDebugIdMap(initOptions.stackParser);
168-
});
169-
170156
const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
171157
workerData: options,
172158
// We don't want any Node args to be passed to the worker
@@ -185,7 +171,7 @@ async function _startWorker(
185171
// serialized without making it a SerializedSession
186172
const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
187173
// message the worker to tell it the main event loop is still running
188-
worker.postMessage({ session, debugImages });
174+
worker.postMessage({ session, debugImages: getFilenameToDebugIdMap(initOptions.stackParser) });
189175
} catch (_) {
190176
//
191177
}

packages/utils/src/debug-ids.ts

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,83 @@
1-
import type { DebugImage, StackFrame, StackParser } from '@sentry/types';
1+
import type { DebugImage, StackParser } from '@sentry/types';
22
import { GLOBAL_OBJ } from './worldwide';
33

4-
const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();
4+
type StackString = string;
5+
6+
interface CachedResult {
7+
filename: string;
8+
debugId: string;
9+
}
10+
11+
let debugIdStackParserCache: WeakMap<StackParser, Map<StackString, CachedResult>> | undefined;
12+
13+
function getCacheForStackParser(stackParser: StackParser): Map<StackString, CachedResult> {
14+
if (!debugIdStackParserCache) {
15+
debugIdStackParserCache = new WeakMap();
16+
}
17+
18+
let result = debugIdStackParserCache.get(stackParser);
19+
20+
if (!result) {
21+
result = new Map();
22+
debugIdStackParserCache.set(stackParser, result);
23+
}
24+
25+
return result;
26+
}
27+
28+
let lastDebugIdKeyCount = 0;
29+
let cachedFilenameToDebugId: Map<string, string> | undefined;
530

631
/**
732
* Returns a map of filenames to debug identifiers.
833
*/
9-
export function getFilenameToDebugIdMap(stackParser: StackParser): Record<string, string> {
34+
export function getFilenameToDebugIdMap(stackParser: StackParser): Map<string, string> {
1035
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
1136
if (!debugIdMap) {
12-
return {};
37+
return new Map();
1338
}
1439

15-
let debugIdStackFramesCache: Map<string, StackFrame[]>;
16-
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
17-
if (cachedDebugIdStackFrameCache) {
18-
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
19-
} else {
20-
debugIdStackFramesCache = new Map<string, StackFrame[]>();
21-
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
40+
const debugIdKeys = Object.keys(debugIdMap);
41+
42+
// If the count of registered globals hasn't changed since the last call, we
43+
// can just return the cached result.
44+
if (debugIdKeys.length === lastDebugIdKeyCount && cachedFilenameToDebugId) {
45+
return cachedFilenameToDebugId;
2246
}
2347

48+
const debugIdStackFramesCache = getCacheForStackParser(stackParser);
49+
2450
// Build a map of filename -> debug_id.
25-
return Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
26-
let parsedStack: StackFrame[];
27-
28-
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
29-
if (cachedParsedStack) {
30-
parsedStack = cachedParsedStack;
31-
} else {
32-
parsedStack = stackParser(debugIdStackTrace);
33-
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
34-
}
51+
const output = debugIdKeys.reduce<Map<string, string>>((acc, debugIdStackTrace) => {
52+
let result = debugIdStackFramesCache.get(debugIdStackTrace);
53+
54+
if (!result) {
55+
const parsedStack = stackParser(debugIdStackTrace);
3556

36-
for (let i = parsedStack.length - 1; i >= 0; i--) {
37-
const stackFrame = parsedStack[i];
38-
const file = stackFrame && stackFrame.filename;
57+
for (let i = parsedStack.length - 1; i >= 0; i--) {
58+
const stackFrame = parsedStack[i];
59+
const filename = stackFrame && stackFrame.filename;
60+
const debugId = debugIdMap[debugIdStackTrace];
3961

40-
if (stackFrame && file) {
41-
acc[file] = debugIdMap[debugIdStackTrace] as string;
42-
break;
62+
if (filename && debugId) {
63+
result = { filename, debugId };
64+
debugIdStackFramesCache.set(debugIdStackTrace, result);
65+
break;
66+
}
4367
}
4468
}
69+
70+
if (result) {
71+
acc.set(result.filename, result.debugId);
72+
}
73+
4574
return acc;
46-
}, {});
75+
}, new Map());
76+
77+
lastDebugIdKeyCount = Object.keys(debugIdMap).length;
78+
cachedFilenameToDebugId = output;
79+
80+
return output;
4781
}
4882

4983
/**
@@ -57,11 +91,12 @@ export function getDebugImagesForResources(
5791

5892
const images: DebugImage[] = [];
5993
for (const path of resource_paths) {
60-
if (path && filenameDebugIdMap[path]) {
94+
const debug_id = filenameDebugIdMap.get(path);
95+
if (path && debug_id) {
6196
images.push({
6297
type: 'sourcemap',
6398
code_file: path,
64-
debug_id: filenameDebugIdMap[path] as string,
99+
debug_id,
65100
});
66101
}
67102
}

0 commit comments

Comments
 (0)