import {JSSelfProfile} from 'sentry/utils/profiling/profile/jsSelfProfile'; import {createFrameIndex} from 'sentry/utils/profiling/profile/utils'; import {firstCallee, makeTestingBoilerplate, nthCallee} from './profile.spec'; describe('jsSelfProfile', () => { it('imports the base properties', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js', 'vendor.js'], frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}], samples: [ { timestamp: 0, stackId: 0, }, { timestamp: 1000, stackId: 0, }, ], stacks: [ { frameId: 0, }, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); expect(profile.duration).toBe(1000); expect(profile.startedAt).toBe(0); expect(profile.endedAt).toBe(1000); expect(profile.callTree.children[0].frame.name).toBe('ReactDOM.render'); expect(profile.callTree.children[0].frame.resource).toBe('app.js'); }); it('tracks discarded samples', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js', 'vendor.js'], frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}], samples: [ { timestamp: 0, stackId: 0, }, ], stacks: [ { frameId: 0, }, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', [{name: 'f0'}]), {type: 'flamechart'} ); expect(profile.stats.discardedSamplesCount).toBe(1); }); it('tracks negative samples', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js', 'vendor.js'], frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}], samples: [ { timestamp: 0, stackId: 0, }, { timestamp: -1, stackId: 0, }, ], stacks: [ { frameId: 0, }, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', [{name: 'f0'}]), {type: 'flamechart'} ); expect(profile.stats.negativeSamplesCount).toBe(1); }); it('tracks raw weights', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js', 'vendor.js'], frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}], samples: [ { timestamp: 5, stackId: 0, }, { timestamp: 10, stackId: 0, }, { timestamp: 15, stackId: 0, }, ], stacks: [ { frameId: 0, }, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', [{name: 'f0'}]), {type: 'flamechart'} ); // For JsSelfProfile, first sample is appended with 0 weight because it // contains the stack sample of when startProfile was called expect(profile.rawWeights.length).toBe(2); }); it('handles the first stack sample differently', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [ {name: 'main', line: 1, column: 1, resourceId: 0}, {name: 'new Profiler', line: 1, column: 1, resourceId: 0}, {name: 'afterProfiler.init', line: 1, column: 1, resourceId: 0}, ], samples: [ { stackId: 1, timestamp: 500, }, { stackId: 2, timestamp: 1500, }, ], stacks: [ {frameId: 0, parentId: undefined}, {frameId: 1, parentId: 0}, {frameId: 2, parentId: 0}, ], }; const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate(); const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); profile.forEach(open, close); expect(timings).toEqual([ ['main', 'open'], ['new Profiler', 'open'], ['new Profiler', 'close'], ['afterProfiler.init', 'open'], ['afterProfiler.init', 'close'], ['main', 'close'], ]); expect(openSpy).toHaveBeenCalledTimes(3); expect(closeSpy).toHaveBeenCalledTimes(3); const root = firstCallee(profile.callTree); if (!root) { throw new Error('root is null'); } expect(root.totalWeight).toEqual(1000); expect(root.selfWeight).toEqual(0); expect(nthCallee(root, 0).selfWeight).toEqual(0); expect(nthCallee(root, 0).totalWeight).toEqual(0); expect(nthCallee(root, 1).selfWeight).toEqual(1000); expect(nthCallee(root, 1).totalWeight).toEqual(1000); }); it('rebuilds the stack', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [ {name: 'f0', line: 1, column: 1, resourceId: 0}, {name: 'f1', line: 1, column: 1, resourceId: 0}, ], samples: [ { stackId: 0, timestamp: 0, }, { timestamp: 1000, stackId: 0, }, ], stacks: [{frameId: 1, parentId: 1}, {frameId: 0}], }; const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate(); const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); profile.forEach(open, close); expect(timings).toEqual([ ['f0', 'open'], ['f1', 'open'], ['f1', 'close'], ['f0', 'close'], ]); expect(openSpy).toHaveBeenCalledTimes(2); expect(closeSpy).toHaveBeenCalledTimes(2); const root = firstCallee(profile.callTree); expect(root.totalWeight).toEqual(1000); expect(firstCallee(root).totalWeight).toEqual(1000); expect(root.selfWeight).toEqual(0); expect(firstCallee(root).selfWeight).toEqual(1000); }); it('marks direct recursion', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [{name: 'f0', line: 1, column: 1, resourceId: 0}], samples: [ { stackId: 0, timestamp: 0, }, { stackId: 0, timestamp: 0, }, ], stacks: [{frameId: 0, parentId: 1}, {frameId: 0}], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); expect(!!firstCallee(firstCallee(profile.callTree)).recursive).toBe(true); }); it('marks indirect recursion', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [ {name: 'f0', line: 1, column: 1, resourceId: 0}, {name: 'f1', line: 1, column: 1, resourceId: 0}, ], samples: [ { stackId: 0, timestamp: 0, }, { stackId: 2, timestamp: 100, }, ], stacks: [ {frameId: 0, parentId: undefined}, {frameId: 1, parentId: 0}, {frameId: 0, parentId: 1}, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); expect(!!firstCallee(firstCallee(firstCallee(profile.callTree))).recursive).toBe( true ); }); it('tracks minFrameDuration', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [ {name: 'f0', line: 1, column: 1, resourceId: 0}, {name: 'f1', line: 1, column: 1, resourceId: 0}, {name: 'f2', line: 1, column: 1, resourceId: 0}, ], samples: [ { stackId: 0, timestamp: 0, }, { stackId: 2, timestamp: 10, }, { stackId: 3, timestamp: 100, }, ], stacks: [ {frameId: 0, parentId: undefined}, {frameId: 1, parentId: 0}, {frameId: 0, parentId: 1}, {frameId: 2}, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); expect(profile.minFrameDuration).toBe(10); }); it('appends gc to previous stack', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [ {name: 'f1', line: 1, column: 1, resourceId: 0}, {name: 'f0', line: 1, column: 1, resourceId: 0}, ], samples: [ { stackId: 0, timestamp: 0, }, { timestamp: 10, marker: 'gc', stackId: 0, }, ], stacks: [ {frameId: 0, parentId: 1}, {frameId: 1, parentId: undefined}, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate(); profile.forEach(open, close); expect(openSpy).toHaveBeenCalledTimes(3); expect(closeSpy).toHaveBeenCalledTimes(3); expect(timings).toEqual([ ['f0', 'open'], ['f1', 'open'], ['Garbage Collection', 'open'], ['Garbage Collection', 'close'], ['f1', 'close'], ['f0', 'close'], ]); }); it('flamegraph tracks node occurences', () => { const trace: JSSelfProfiling.Trace = { resources: ['app.js'], frames: [ {name: 'f1', line: 1, column: 1, resourceId: 0}, {name: 'f0', line: 1, column: 1, resourceId: 0}, ], samples: [ { stackId: 0, timestamp: 0, }, { timestamp: 10, stackId: 1, }, { timestamp: 20, stackId: 0, }, ], stacks: [ {frameId: 0, parentId: undefined}, {frameId: 1, parentId: 0}, ], }; const profile = JSSelfProfile.FromProfile( trace, createFrameIndex('javascript', trace.frames, trace), {type: 'flamechart'} ); expect(profile.callTree.children[0].count).toBe(3); expect(profile.callTree.children[0].children[0].count).toBe(1); }); });