Skip to content

Commit

Permalink
feat(utils): Single implementation to fetch debug ids (#14199)
Browse files Browse the repository at this point in the history
In the process of fixing
getsentry/sentry-electron#1011 I was looking
for a way to get a `Map<Filename, DebugId>`. I found that there was
already some code duplication for parsing and caching of debug ids from
the polyfilled globals.

This PR moves the common code to `@sentry/utils` which will be useful
for fixing the above.
  • Loading branch information
timfish authored Nov 7, 2024
1 parent 3750914 commit b2605be
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 162 deletions.
67 changes: 3 additions & 64 deletions packages/browser/src/profiling/utils.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
/* eslint-disable max-lines */

import { DEFAULT_ENVIRONMENT, getClient, spanToJSON } from '@sentry/core';
import type {
DebugImage,
Envelope,
Event,
EventEnvelope,
Profile,
Span,
StackFrame,
StackParser,
ThreadCpuProfile,
} from '@sentry/types';
import type { DebugImage, Envelope, Event, EventEnvelope, Profile, Span, ThreadCpuProfile } from '@sentry/types';
import {
GLOBAL_OBJ,
browserPerformanceTimeOrigin,
forEachEnvelopeItem,
getDebugImagesForResources,
logger,
timestampInSeconds,
uuid4,
Expand Down Expand Up @@ -352,17 +342,10 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[
return events;
}

const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();
/**
* Applies debug meta data to an event from a list of paths to resources (sourcemaps)
*/
export function applyDebugMetadata(resource_paths: ReadonlyArray<string>): DebugImage[] {
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;

if (!debugIdMap) {
return [];
}

const client = getClient();
const options = client && client.getOptions();
const stackParser = options && options.stackParser;
Expand All @@ -371,51 +354,7 @@ export function applyDebugMetadata(resource_paths: ReadonlyArray<string>): Debug
return [];
}

let debugIdStackFramesCache: Map<string, StackFrame[]>;
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
if (cachedDebugIdStackFrameCache) {
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
} else {
debugIdStackFramesCache = new Map<string, StackFrame[]>();
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
}

// Build a map of filename -> debug_id
const filenameDebugIdMap = Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
let parsedStack: StackFrame[];

const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
if (cachedParsedStack) {
parsedStack = cachedParsedStack;
} else {
parsedStack = stackParser(debugIdStackTrace);
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
}

for (let i = parsedStack.length - 1; i >= 0; i--) {
const stackFrame = parsedStack[i];
const file = stackFrame && stackFrame.filename;

if (stackFrame && file) {
acc[file] = debugIdMap[debugIdStackTrace] as string;
break;
}
}
return acc;
}, {});

const images: DebugImage[] = [];
for (const path of resource_paths) {
if (path && filenameDebugIdMap[path]) {
images.push({
type: 'sourcemap',
code_file: path,
debug_id: filenameDebugIdMap[path] as string,
});
}
}

return images;
return getDebugImagesForResources(stackParser, resource_paths);
}

/**
Expand Down
51 changes: 9 additions & 42 deletions packages/core/src/utils/prepareEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import type {
EventHint,
Scope as ScopeInterface,
ScopeContext,
StackFrame,
StackParser,
} from '@sentry/types';
import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, truncate, uuid4 } from '@sentry/utils';
import {
addExceptionMechanism,
dateTimestampInSeconds,
getFilenameToDebugIdMap,
normalize,
truncate,
uuid4,
} from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getGlobalScope } from '../currentScopes';
Expand Down Expand Up @@ -161,51 +167,12 @@ function applyClientOptions(event: Event, options: ClientOptions): void {
}
}

const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();

/**
* Puts debug IDs into the stack frames of an error event.
*/
export function applyDebugIds(event: Event, stackParser: StackParser): void {
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;

if (!debugIdMap) {
return;
}

let debugIdStackFramesCache: Map<string, StackFrame[]>;
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
if (cachedDebugIdStackFrameCache) {
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
} else {
debugIdStackFramesCache = new Map<string, StackFrame[]>();
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
}

// Build a map of filename -> debug_id
const filenameDebugIdMap = Object.entries(debugIdMap).reduce<Record<string, string>>(
(acc, [debugIdStackTrace, debugIdValue]) => {
let parsedStack: StackFrame[];
const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
if (cachedParsedStack) {
parsedStack = cachedParsedStack;
} else {
parsedStack = stackParser(debugIdStackTrace);
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
}

for (let i = parsedStack.length - 1; i >= 0; i--) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const stackFrame = parsedStack[i]!;
if (stackFrame.filename) {
acc[stackFrame.filename] = debugIdValue;
break;
}
}
return acc;
},
{},
);
const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser);

try {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down
65 changes: 9 additions & 56 deletions packages/profiling-node/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import type {
ProfileChunkEnvelope,
ProfileChunkItem,
SdkInfo,
StackFrame,
StackParser,
ThreadCpuProfile,
} from '@sentry/types';
import { GLOBAL_OBJ, createEnvelope, dsnToString, forEachEnvelopeItem, logger, uuid4 } from '@sentry/utils';
import {
createEnvelope,
dsnToString,
forEachEnvelopeItem,
getDebugImagesForResources,
logger,
uuid4,
} from '@sentry/utils';

import { env, versions } from 'process';
import { isMainThread, threadId } from 'worker_threads';
Expand Down Expand Up @@ -415,69 +420,17 @@ export function makeProfileChunkEnvelope(
]);
}

const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();

/**
* Cross reference profile collected resources with debug_ids and return a list of debug images.
* @param {string[]} resource_paths
* @returns {DebugImage[]}
*/
export function applyDebugMetadata(client: Client, resource_paths: ReadonlyArray<string>): DebugImage[] {
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
if (!debugIdMap) {
return [];
}

const options = client.getOptions();

if (!options || !options.stackParser) {
return [];
}

let debugIdStackFramesCache: Map<string, StackFrame[]>;
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(options.stackParser);
if (cachedDebugIdStackFrameCache) {
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
} else {
debugIdStackFramesCache = new Map<string, StackFrame[]>();
debugIdStackParserCache.set(options.stackParser, debugIdStackFramesCache);
}

// Build a map of filename -> debug_id.
const filenameDebugIdMap = Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
let parsedStack: StackFrame[];

const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
if (cachedParsedStack) {
parsedStack = cachedParsedStack;
} else {
parsedStack = options.stackParser(debugIdStackTrace);
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
}

for (let i = parsedStack.length - 1; i >= 0; i--) {
const stackFrame = parsedStack[i];
const file = stackFrame && stackFrame.filename;

if (stackFrame && file) {
acc[file] = debugIdMap[debugIdStackTrace] as string;
break;
}
}
return acc;
}, {});

const images: DebugImage[] = [];

for (const resource of resource_paths) {
if (resource && filenameDebugIdMap[resource]) {
images.push({
type: 'sourcemap',
code_file: resource,
debug_id: filenameDebugIdMap[resource] as string,
});
}
}

return images;
return getDebugImagesForResources(options.stackParser, resource_paths);
}
70 changes: 70 additions & 0 deletions packages/utils/src/debug-ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { DebugImage, StackFrame, StackParser } from '@sentry/types';
import { GLOBAL_OBJ } from './worldwide';

const debugIdStackParserCache = new WeakMap<StackParser, Map<string, StackFrame[]>>();

/**
* Returns a map of filenames to debug identifiers.
*/
export function getFilenameToDebugIdMap(stackParser: StackParser): Record<string, string> {
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
if (!debugIdMap) {
return {};
}

let debugIdStackFramesCache: Map<string, StackFrame[]>;
const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
if (cachedDebugIdStackFrameCache) {
debugIdStackFramesCache = cachedDebugIdStackFrameCache;
} else {
debugIdStackFramesCache = new Map<string, StackFrame[]>();
debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
}

// Build a map of filename -> debug_id.
return Object.keys(debugIdMap).reduce<Record<string, string>>((acc, debugIdStackTrace) => {
let parsedStack: StackFrame[];

const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
if (cachedParsedStack) {
parsedStack = cachedParsedStack;
} else {
parsedStack = stackParser(debugIdStackTrace);
debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
}

for (let i = parsedStack.length - 1; i >= 0; i--) {
const stackFrame = parsedStack[i];
const file = stackFrame && stackFrame.filename;

if (stackFrame && file) {
acc[file] = debugIdMap[debugIdStackTrace] as string;
break;
}
}
return acc;
}, {});
}

/**
* Returns a list of debug images for the given resources.
*/
export function getDebugImagesForResources(
stackParser: StackParser,
resource_paths: ReadonlyArray<string>,
): DebugImage[] {
const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser);

const images: DebugImage[] = [];
for (const path of resource_paths) {
if (path && filenameDebugIdMap[path]) {
images.push({
type: 'sourcemap',
code_file: path,
debug_id: filenameDebugIdMap[path] as string,
});
}
}

return images;
}
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ export * from './buildPolyfills';
export * from './propagationContext';
export * from './vercelWaitUntil';
export * from './version';
export * from './debug-ids';

0 comments on commit b2605be

Please sign in to comment.