Files
2024-02-15 17:01:50 +00:00

340 lines
8.4 KiB
TypeScript

import type {EntrySpans, EventTransaction} from 'sentry/types/event';
import {EventOrGroupType} 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]>): 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>): 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(2);
});
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);
});
});