123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import * as Sentry from '@sentry/react';
- import {Transaction} from '@sentry/types';
- import {
- isChromeTraceArrayFormat,
- isChromeTraceFormat,
- isChromeTraceObjectFormat,
- isEventedProfile,
- isJSProfile,
- isSampledProfile,
- isSchema,
- } from '../guards/profile';
- import {parseChromeTraceArrayFormat} from './chromeTraceProfile';
- import {EventedProfile} from './eventedProfile';
- import {JSSelfProfile} from './jsSelfProfile';
- import {Profile} from './profile';
- import {SampledProfile} from './sampledProfile';
- import {createFrameIndex, wrapWithSpan} from './utils';
- export interface ImportOptions {
- transaction: Transaction | undefined;
- }
- export interface ProfileGroup {
- activeProfileIndex: number;
- name: string;
- profiles: Profile[];
- traceID: string;
- }
- export function importProfile(
- input: Profiling.Schema | JSSelfProfiling.Trace | ChromeTrace.ProfileType,
- traceID: string
- ): ProfileGroup {
- const transaction = Sentry.startTransaction({
- op: 'import',
- name: 'profiles.import',
- });
- try {
- if (isJSProfile(input)) {
- // In some cases, the SDK may return transaction as undefined and we dont want to throw there.
- if (transaction) {
- transaction.setTag('profile.type', 'js-self-profile');
- }
- return importJSSelfProfile(input, traceID, {transaction});
- }
- if (isChromeTraceFormat(input)) {
- // In some cases, the SDK may return transaction as undefined and we dont want to throw there.
- if (transaction) {
- transaction.setTag('profile.type', 'chrometrace');
- }
- return importChromeTrace(input, traceID, {transaction});
- }
- if (isSchema(input)) {
- // In some cases, the SDK may return transaction as undefined and we dont want to throw there.
- if (transaction) {
- transaction.setTag('profile.type', 'schema');
- }
- return importSchema(input, traceID, {transaction});
- }
- throw new Error('Unsupported trace format');
- } catch (error) {
- if (transaction) {
- transaction.setStatus('internal_error');
- }
- throw error;
- } finally {
- if (transaction) {
- transaction.finish();
- }
- }
- }
- function importJSSelfProfile(
- input: JSSelfProfiling.Trace,
- traceID: string,
- options: ImportOptions
- ): ProfileGroup {
- const frameIndex = createFrameIndex(input.frames);
- return {
- traceID,
- name: traceID,
- activeProfileIndex: 0,
- profiles: [importSingleProfile(input, frameIndex, options)],
- };
- }
- function importChromeTrace(
- input: ChromeTrace.ProfileType,
- traceID: string,
- options: ImportOptions
- ): ProfileGroup {
- if (isChromeTraceObjectFormat(input)) {
- throw new Error('Chrometrace object format is not yet supported');
- }
- if (isChromeTraceArrayFormat(input)) {
- return parseChromeTraceArrayFormat(input, traceID, options);
- }
- throw new Error('Failed to parse trace input format');
- }
- function importSchema(
- input: Profiling.Schema,
- traceID: string,
- options: ImportOptions
- ): ProfileGroup {
- const frameIndex = createFrameIndex(input.shared.frames);
- return {
- traceID,
- name: input.transactionName,
- activeProfileIndex: input.activeProfileIndex ?? 0,
- profiles: input.profiles.map(profile =>
- importSingleProfile(profile, frameIndex, options)
- ),
- };
- }
- function importSingleProfile(
- profile: Profiling.ProfileTypes,
- frameIndex: ReturnType<typeof createFrameIndex>,
- {transaction}: ImportOptions
- ): Profile {
- if (isEventedProfile(profile)) {
- // In some cases, the SDK may return transaction as undefined and we dont want to throw there.
- if (!transaction) {
- return EventedProfile.FromProfile(profile, frameIndex);
- }
- return wrapWithSpan(
- transaction,
- () => EventedProfile.FromProfile(profile, frameIndex),
- {
- op: 'profile.import',
- description: 'evented',
- }
- );
- }
- if (isSampledProfile(profile)) {
- // In some cases, the SDK may return transaction as undefined and we dont want to throw there.
- if (!transaction) {
- return SampledProfile.FromProfile(profile, frameIndex);
- }
- return wrapWithSpan(
- transaction,
- () => SampledProfile.FromProfile(profile, frameIndex),
- {
- op: 'profile.import',
- description: 'sampled',
- }
- );
- }
- if (isJSProfile(profile)) {
- // In some cases, the SDK may return transaction as undefined and we dont want to throw there.
- if (!transaction) {
- return JSSelfProfile.FromProfile(profile, createFrameIndex(profile.frames));
- }
- return wrapWithSpan(
- transaction,
- () => JSSelfProfile.FromProfile(profile, createFrameIndex(profile.frames)),
- {
- op: 'profile.import',
- description: 'js-self-profile',
- }
- );
- }
- throw new Error('Unrecognized trace format');
- }
- const tryParseInputString: JSONParser = input => {
- try {
- return [JSON.parse(input), null];
- } catch (e) {
- return [null, e];
- }
- };
- type JSONParser = (input: string) => [any, null] | [null, Error];
- const TRACE_JSON_PARSERS: ((string) => ReturnType<JSONParser>)[] = [
- (input: string) => tryParseInputString(input),
- (input: string) => tryParseInputString(input + ']'),
- ];
- function readFileAsString(file: File): Promise<string> {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.addEventListener('load', (e: ProgressEvent<FileReader>) => {
- if (typeof e.target?.result === 'string') {
- resolve(e.target.result);
- return;
- }
- reject('Failed to read string contents of input file');
- });
- reader.addEventListener('error', () => {
- reject('Failed to read string contents of input file');
- });
- reader.readAsText(file);
- });
- }
- export async function importDroppedProfile(
- file: File,
- parsers: JSONParser[] = TRACE_JSON_PARSERS
- ): Promise<ProfileGroup> {
- const fileContents = await readFileAsString(file);
- for (const parser of parsers) {
- const [json] = parser(fileContents);
- if (json) {
- if (typeof json !== 'object' || json === null) {
- throw new TypeError('Input JSON is not an object');
- }
- return importProfile(json, file.name);
- }
- }
- throw new Error('Failed to parse input JSON');
- }
|