import {EntrySpans, EventOrGroupType, EventTransaction} from 'sentry/types/event'; import {SpanChart} from 'sentry/utils/profiling/spanChart'; import {SpanTree} from 'sentry/utils/profiling/spanTree'; import {Rect} from './speedscope'; function s(partial: Partial): EntrySpans['data'][0] { return { timestamp: 0, start_timestamp: 0, exclusive_time: 0, description: '', op: '', span_id: '', parent_span_id: '', trace_id: '', hash: '', data: {}, ...partial, }; } function txn(partial: Partial): EventTransaction { return { id: '', projectID: '', user: {}, contexts: {}, entries: [], errors: [], dateCreated: '', startTimestamp: Date.now(), endTimestamp: Date.now() + 1000, title: '', type: EventOrGroupType.TRANSACTION, culprit: '', dist: null, eventID: '', fingerprints: [], dateReceived: new Date().toISOString(), message: '', metadata: {}, size: 0, tags: [], occurrence: null, location: '', crashFile: null, ...partial, }; } describe('spanChart', () => { it('iterates over all spans with depth', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 0, endTimestamp: start + 1, }), [ s({ span_id: '1', parent_span_id: 'root', timestamp: start + 1, start_timestamp: start, }), s({ span_id: '2', parent_span_id: '1', timestamp: start + 0.5, start_timestamp: start, }), s({ span_id: '3', parent_span_id: '2', timestamp: start + 0.2, start_timestamp: start, }), s({ span_id: '4', parent_span_id: '1', timestamp: start + 1, start_timestamp: start + 0.5, }), ] ); expect(tree.root.children[0].children[1].span.span_id).toBe('4'); const chart = new SpanChart(tree); chart.forEachSpanOfTree(chart.spanTrees[0], 0, span => { if (span.node.span.span_id === '1') { expect(span.depth).toBe(1); } else if (span.node.span.span_id === '2') { expect(span.depth).toBe(2); } else if (span.node.span.span_id === '3') { expect(span.depth).toBe(3); } else if (span.node.span.span_id === '4') { expect(span.depth).toBe(2); } }); }); it('keeps track of shortest duration span', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 0, endTimestamp: start + 10, }), [ s({ span_id: '1', parent_span_id: 'root', timestamp: start + 10, start_timestamp: start, }), s({ span_id: '2', parent_span_id: '1', timestamp: start + 5, start_timestamp: start, }), s({ span_id: '3', parent_span_id: '2', timestamp: start + 1, start_timestamp: start, }), ] ); const chart = new SpanChart(tree); expect(chart.minSpanDuration).toBe(1 * 1e3); }); it('tracks chart depth', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 0, endTimestamp: start + 1, }), [ s({ span_id: '1', parent_span_id: 'root', timestamp: start + 1, start_timestamp: start, }), s({ span_id: '2', parent_span_id: '1', timestamp: start + 0.5, start_timestamp: start, }), s({ span_id: '3', parent_span_id: '2', timestamp: start + 0.2, start_timestamp: start, }), s({ span_id: '4', parent_span_id: '3', timestamp: start + 0.1, start_timestamp: start, }), ] ); const chart = new SpanChart(tree); expect(chart.depth).toBe(4); }); it('initializes configSpace', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 0, endTimestamp: start + 10, }), [ s({ span_id: '1', parent_span_id: 'root', timestamp: start + 10, start_timestamp: start, }), s({ span_id: '2', parent_span_id: '1', timestamp: start + 5, start_timestamp: start, }), s({ span_id: '3', parent_span_id: '2', timestamp: start + 1, start_timestamp: start, }), ] ); const chart = new SpanChart(tree); expect(chart.configSpace.equals(new Rect(0, 0, 10 * 1e3, 3))).toBe(true); }); it('remaps spans to start of benchmark', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 5, endTimestamp: start + 10, }), [ s({ span_id: '1', parent_span_id: 'root', timestamp: start + 10, start_timestamp: start + 6, }), s({ span_id: '2', parent_span_id: '1', timestamp: start + 9, start_timestamp: start + 8, }), ] ); const chart = new SpanChart(tree); expect(chart.spans[1].start).toBe(1 * 1e3); expect(chart.spans[1].end).toBe(5 * 1e3); expect(chart.spans[2].start).toBe(3 * 1e3); expect(chart.spans[2].end).toBe(4 * 1e3); }); it('converts durations to final unit', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 1, endTimestamp: start + 11, }), [ s({ span_id: '1', parent_span_id: 'root', timestamp: start + 4, start_timestamp: start + 2, }), ] ); const chart = new SpanChart(tree, {unit: 'nanoseconds'}); expect(chart.spans[1].start).toBe(1 * 1e9); expect(chart.spans[1].end).toBe(3 * 1e9); expect(chart.spans[1].duration).toBe(2 * 1e9); expect(chart.configSpace.height).toBe(1); expect(chart.configSpace.width).toBe(10 * 1e9); }); it('does not infinitely loop if a span is truly orphaned', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 0, endTimestamp: start + 10, }), [ // If a span belongs to nothing, we dont render it, // for now we only render spans that ultimately belong // to the transaction when the parent_span_id is followed s({ span_id: '1', parent_span_id: 'root', timestamp: start + 10, start_timestamp: start, }), s({ span_id: '2', parent_span_id: undefined, timestamp: start + 10, start_timestamp: start, }), ] ); const chart = new SpanChart(tree); expect(chart.spanTrees.length).toBe(1); }); it('creates a new tree from orphaned spans', () => { const start = Date.now(); const tree = new SpanTree( txn({ contexts: {trace: {span_id: 'root'}}, startTimestamp: start + 0, endTimestamp: start + 10, }), [ // These two spans overlap and as first is inserted, // the 2nd span can no longer be inserted and the parent_span_id // edge is no longer satisfied s({ span_id: '1', parent_span_id: 'root', timestamp: start + 10, start_timestamp: start, }), s({ span_id: '2', parent_span_id: 'root', timestamp: start + 10, start_timestamp: start, }), ] ); const chart = new SpanChart(tree); expect(chart.spanTrees.length).toBe(2); // Even though they belong to the same root, // the spans are offset visually by the tree height expect(chart.spans[1].depth).toBe(1); expect(chart.spans[2].depth).toBe(2); }); });