import { ChromeTraceProfile, collapseSamples, parseTypescriptChromeTraceArrayFormat, splitEventsByProcessAndTraceId, } from 'sentry/utils/profiling/profile/chromeTraceProfile'; describe('splitEventsByProcessAndTraceId', () => { it('splits by thread id', () => { const trace: ChromeTrace.ArrayFormat = [ { ph: 'B', tid: 0, pid: 0, cat: '', name: '', ts: 0, args: [], }, { ph: 'B', tid: 1, pid: 0, cat: '', name: '', ts: 0, args: [], }, ]; expect(splitEventsByProcessAndTraceId(trace).get(0)?.get(0)).toEqual([trace[0]]); expect(splitEventsByProcessAndTraceId(trace).get(0)?.get(1)).toEqual([trace[1]]); }); }); describe('parseTypescriptChromeTraceArrayFormat', () => { it('returns typescript profile', () => { expect( parseTypescriptChromeTraceArrayFormat( [ { ph: 'M', ts: 0, cat: '', pid: 0, tid: 0, name: 'process_name', args: {name: 'Process Name'}, }, { ph: 'B', ts: 0, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'}, }, ], '' ).profiles[0] ).toBeInstanceOf(ChromeTraceProfile); }); it('marks process name', () => { expect( parseTypescriptChromeTraceArrayFormat( [ { ph: 'M', ts: 0, cat: '', pid: 0, tid: 0, name: 'process_name', args: {name: 'Process Name'}, }, { ph: 'B', ts: 0, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'}, }, ], '' ).profiles[0].name ).toBe('Process Name (0): tid (0)'); }); it('marks thread name', () => { expect( parseTypescriptChromeTraceArrayFormat( [ { ph: 'M', ts: 0, cat: '', pid: 0, tid: 0, name: 'thread_name', args: {name: 'Thread Name'}, }, { ph: 'B', ts: 0, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'}, }, ], '' ).profiles[0].name ).toBe('pid (0): Thread Name (0)'); }); it('imports a simple trace', () => { const trace = parseTypescriptChromeTraceArrayFormat( [ { ph: 'B', ts: 1000, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'}, }, { ph: 'E', ts: 2000, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {configFilePath: '/Users/jonasbadalic/Work/sentry/tsconfig.json'}, }, ], '' ); expect(trace.profiles[0].startedAt).toBe(1000); expect(trace.profiles[0].endedAt).toBe(2000); expect(trace.profiles[0].duration).toBe(1000); expect(trace.profiles[0].appendOrderTree.children[0].totalWeight).toBe(1000); }); it('closes unclosed events', () => { const trace = parseTypescriptChromeTraceArrayFormat( [ { ph: 'B', ts: 0, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {frame: '0'}, }, { ph: 'B', ts: 1000, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {frame: '1'}, }, { ph: 'E', ts: 2000, cat: 'program', pid: 0, tid: 0, name: 'createProgram', args: {frame: '1'}, }, ], '' ); expect(trace.profiles[0].duration).toBe(2000); expect(trace.profiles[0].appendOrderTree.children[0].selfWeight).toBe(1000); expect(trace.profiles[0].appendOrderTree.children[0].totalWeight).toBe(2000); expect(trace.profiles[0].appendOrderTree.children[0].children[0].selfWeight).toBe( 1000 ); }); it('handles out of order E events', () => { const trace = parseTypescriptChromeTraceArrayFormat( [ { ph: 'B', ts: 0, cat: '', pid: 0, tid: 0, name: '', args: {frame: '0'}, }, { ph: 'B', ts: 1, cat: '', pid: 0, tid: 0, name: '', args: {frame: '1'}, }, { ph: 'E', ts: 2, cat: '', pid: 0, tid: 0, name: '', args: {frame: '0'}, }, { ph: 'E', ts: 2, cat: '', pid: 0, tid: 0, name: '', args: {frame: '1'}, }, ], '' ); expect(trace.profiles[0].duration).toBe(2); expect(trace.profiles[0].appendOrderTree.children[0].selfWeight).toBe(1); expect(trace.profiles[0].appendOrderTree.children[0].totalWeight).toBe(2); expect(trace.profiles[0].appendOrderTree.children[0].frame.name).toBe( 'Unknown {"frame":"0"}' ); expect(trace.profiles[0].appendOrderTree.children[0].children[0].frame.name).toBe( 'Unknown {"frame":"1"}' ); expect(trace.profiles[0].appendOrderTree.children[0].children[0].selfWeight).toBe(1); expect(trace.profiles[0].appendOrderTree.children[0].children[0].totalWeight).toBe(1); }); it('handles out of order B events', () => { const trace = parseTypescriptChromeTraceArrayFormat( [ { ph: 'B', ts: 0, cat: '', pid: 0, tid: 0, name: '', args: {frame: '0'}, }, { ph: 'B', ts: 1, cat: '', pid: 0, tid: 0, name: '', args: {frame: '1'}, }, { ph: 'E', ts: 2, cat: '', pid: 0, tid: 0, name: '', args: {frame: '0'}, }, { ph: 'E', ts: 2, cat: '', pid: 0, tid: 0, name: '', args: {frame: '1'}, }, ], '' ); expect(trace.profiles[0].duration).toBe(2); expect(trace.profiles[0].appendOrderTree.children[0].selfWeight).toBe(1); expect(trace.profiles[0].appendOrderTree.children[0].totalWeight).toBe(2); expect(trace.profiles[0].appendOrderTree.children[0].frame.name).toBe( 'Unknown {"frame":"0"}' ); expect(trace.profiles[0].appendOrderTree.children[0].children[0].frame.name).toBe( 'Unknown {"frame":"1"}' ); expect(trace.profiles[0].appendOrderTree.children[0].children[0].selfWeight).toBe(1); expect(trace.profiles[0].appendOrderTree.children[0].children[0].totalWeight).toBe(1); }); it('handles X trace with tdur', () => { const trace = parseTypescriptChromeTraceArrayFormat( [ { ph: 'X', ts: 0, cat: '', pid: 0, tid: 0, tdur: 100, name: '', args: {frame: '0'}, }, ], '' ); expect(trace.profiles[0].duration).toBe(100); }); it('handles X trace with dur', () => { const trace = parseTypescriptChromeTraceArrayFormat( [ { ph: 'X', ts: 0, cat: '', pid: 0, tid: 0, dur: 100, name: '', args: {frame: '0'}, }, ], '' ); expect(trace.profiles[0].duration).toBe(100); }); }); describe('collapseSamples', () => { it.each([ { samples: [1, 1], timeDeltas: [0, 1], expectedSamples: [1, 1], expectedTimeDeltas: [0, 1], }, { samples: [1, 1, 1], timeDeltas: [0, 1, 1], expectedSamples: [1, 1], expectedTimeDeltas: [0, 2], }, { samples: [1, 2, 1], timeDeltas: [0, 1, 2], expectedSamples: [1, 2, 1], expectedTimeDeltas: [0, 1, 3], }, { samples: [1, 2, 3, 4], timeDeltas: [0, 1, 1, 1], expectedSamples: [1, 2, 3, 4], expectedTimeDeltas: [0, 1, 2, 3], }, ])('collapses sample', test => { const result = collapseSamples({ startTime: 0, endTime: 100, samples: test.samples, timeDeltas: test.timeDeltas, nodes: [], }); expect(result.samples).toEqual(test.expectedSamples); expect(result.sampleTimes).toEqual(test.expectedTimeDeltas); }); it('guards from negative samples', () => { const result = collapseSamples({ startTime: 0, endTime: 100, samples: [1, 2, 3], timeDeltas: [1, -1, 1], nodes: [], }); expect(result.samples).toEqual([1, 2, 3]); expect(result.sampleTimes).toEqual([1, 1, 2]); }); it('guards from negative samples when they are being collapsed', () => { const result = collapseSamples({ startTime: 0, endTime: 100, samples: [1, 1, 1], timeDeltas: [1, -1, 2], nodes: [], }); expect(result.samples).toEqual([1, 1]); expect(result.sampleTimes).toEqual([1, 3]); }); });