123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- import {Event} from 'sentry/types/event';
- import {TraceFull} from 'sentry/utils/performance/quickTrace/types';
- import {
- flattenRelevantPaths,
- parseQuickTrace,
- } from 'sentry/utils/performance/quickTrace/utils';
- type Position = {
- generation: number;
- offset: number;
- };
- function hexCharFor(x: number): string {
- x = x % 16;
- if (x < 10) {
- return String(x);
- }
- return String.fromCharCode('a'.charCodeAt(0) + x - 10);
- }
- function generateId(prefix: string, {generation, offset}: Position) {
- const s = `${hexCharFor(generation)}${hexCharFor(offset)}`;
- return `${prefix}:${Array(7).join(s)}`;
- }
- function generateEventId({generation, offset}: Position) {
- return generateId('e', {generation, offset});
- }
- function generateSpanId({generation, offset}: Position) {
- return generateId('s', {generation, offset});
- }
- function generateTransactionName({generation, offset}: Position) {
- return `transaction-${generation}-${offset}`;
- }
- function generateProjectSlug({generation, offset}: Position) {
- const c = hexCharFor(generation);
- return `project-${c.toUpperCase()}-${offset}`;
- }
- function computePosition(index: number) {
- index += 1;
- const generation = Math.floor(Math.log2(index));
- const offset = index - 2 ** generation;
- return {generation, offset};
- }
- function generateTransactionLite({
- generation,
- offset,
- }: {
- generation: number;
- offset: number;
- }) {
- const position = {generation, offset};
- const parentPosition = {
- generation: generation - 1,
- offset: Math.floor(offset / 2),
- };
- return {
- event_id: generateEventId(position),
- generation,
- span_id: generateSpanId(position),
- transaction: generateTransactionName(position),
- 'transaction.duration': 0,
- project_id: generation, // just use generation as project id
- project_slug: generateProjectSlug(position),
- parent_event_id: generation <= 0 ? null : generateEventId(parentPosition),
- parent_span_id: generation <= 0 ? null : generateSpanId(parentPosition),
- errors: [],
- };
- }
- function generateTransaction(opts: {depth: number; index: number}): TraceFull {
- const {index, depth = -1} = opts;
- const {generation, offset} = computePosition(index);
- return {
- ...generateTransactionLite({generation, offset}),
- errors: [],
- children: Array(depth <= 0 || generation >= depth - 1 ? 0 : 2)
- .fill(null)
- .map((_, i) =>
- generateTransaction({
- index: 2 * index + i + 1,
- depth,
- })
- ),
- /**
- * These timestamps aren't used in tests here, just adding them to pass
- * the type checking.
- */
- 'transaction.duration': 0,
- };
- }
- function generateTrace(depth = 1): TraceFull {
- if (depth < 1) {
- throw new Error('Minimum depth is 1!');
- }
- return generateTransaction({
- depth,
- index: 0,
- });
- }
- function generateEventSelector(position: Position, eventType: string): Event {
- return {id: generateEventId(position), type: eventType} as Event;
- }
- describe('Quick Trace Utils', function () {
- describe('flattenRelevantPaths', function () {
- it('flattens trace without the expected event', function () {
- const trace = generateTrace(1);
- const current = {id: 'you cant find me'} as Event;
- expect(() => flattenRelevantPaths(current, trace)).toThrow(
- 'No relevant path exists!'
- );
- });
- it('flattens a single transaction trace', function () {
- const trace = generateTrace(1);
- const current = generateEventSelector({generation: 0, offset: 0}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- expect(relevantPath).toMatchObject([
- generateTransactionLite({generation: 0, offset: 0}),
- ]);
- });
- it('flattens trace from the leaf', function () {
- const trace = generateTrace(3);
- const current = generateEventSelector({generation: 2, offset: 3}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- expect(relevantPath).toMatchObject([
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 1, offset: 1}),
- generateTransactionLite({generation: 2, offset: 3}),
- ]);
- });
- it('flattens trace from the middle', function () {
- const trace = generateTrace(3);
- const current = generateEventSelector({generation: 1, offset: 1}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- expect(relevantPath).toMatchObject([
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 1, offset: 1}),
- generateTransactionLite({generation: 2, offset: 2}),
- generateTransactionLite({generation: 2, offset: 3}),
- ]);
- });
- it('flattens trace from the root', function () {
- const trace = generateTrace(3);
- const current = generateEventSelector({generation: 0, offset: 0}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- expect(relevantPath).toMatchObject([
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 1, offset: 0}),
- generateTransactionLite({generation: 1, offset: 1}),
- generateTransactionLite({generation: 2, offset: 0}),
- generateTransactionLite({generation: 2, offset: 1}),
- generateTransactionLite({generation: 2, offset: 2}),
- generateTransactionLite({generation: 2, offset: 3}),
- ]);
- });
- });
- describe('parseQuickTrace', function () {
- const organization = TestStubs.Organization();
- it('parses empty trace', function () {
- const current = generateEventSelector({generation: 0, offset: 0}, 'transaction');
- expect(() =>
- parseQuickTrace({type: 'empty', trace: []}, current, organization)
- ).toThrow('Current event not in trace navigator');
- });
- describe('partial trace', function () {
- it('parses correctly without the expected event', function () {
- const relevantPath = [generateTransactionLite({generation: 0, offset: 0})];
- const current = generateEventSelector({generation: 1, offset: 0}, 'transaction');
- expect(() =>
- parseQuickTrace({type: 'partial', trace: relevantPath}, current, organization)
- ).toThrow('Current event not in trace navigator');
- });
- it('parses only the current event', function () {
- const relevantPath = [generateTransactionLite({generation: 0, offset: 0})];
- const current = generateEventSelector({generation: 0, offset: 0}, 'transaction');
- const parsedQuickTrace = parseQuickTrace(
- {type: 'partial', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toEqual({
- root: null,
- ancestors: null,
- parent: null,
- current: generateTransactionLite({generation: 0, offset: 0}),
- children: [],
- descendants: null,
- });
- });
- it('parses current with only parent', function () {
- const relevantPath = [
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 1, offset: 0}),
- ];
- const current = generateEventSelector({generation: 1, offset: 0}, 'transaction');
- const parsedQuickTrace = parseQuickTrace(
- {type: 'partial', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toEqual({
- root: null,
- ancestors: null,
- parent: generateTransactionLite({generation: 0, offset: 0}),
- current: generateTransactionLite({generation: 1, offset: 0}),
- children: [],
- descendants: null,
- });
- });
- it('parses current with only root', function () {
- const relevantPath = [
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 2, offset: 0}),
- ];
- const current = generateEventSelector({generation: 2, offset: 0}, 'transaction');
- const parsedQuickTrace = parseQuickTrace(
- {type: 'partial', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toEqual({
- root: generateTransactionLite({generation: 0, offset: 0}),
- ancestors: null,
- parent: null,
- current: generateTransactionLite({generation: 2, offset: 0}),
- children: [],
- descendants: null,
- });
- });
- it('parses current with only children', function () {
- const relevantPath = [
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 1, offset: 0}),
- generateTransactionLite({generation: 1, offset: 1}),
- ];
- const current = generateEventSelector({generation: 0, offset: 0}, 'transaction');
- const parsedQuickTrace = parseQuickTrace(
- {type: 'partial', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toEqual({
- root: null,
- ancestors: null,
- parent: null,
- current: generateTransactionLite({generation: 0, offset: 0}),
- children: [
- generateTransactionLite({generation: 1, offset: 0}),
- generateTransactionLite({generation: 1, offset: 1}),
- ],
- descendants: null,
- });
- });
- it('parses current with parent and children', function () {
- const relevantPath = [
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 1, offset: 1}),
- generateTransactionLite({generation: 2, offset: 2}),
- ];
- const current = generateEventSelector({generation: 1, offset: 1}, 'transaction');
- const parsedQuickTrace = parseQuickTrace(
- {type: 'partial', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toEqual({
- root: null,
- ancestors: null,
- parent: generateTransactionLite({generation: 0, offset: 0}),
- current: generateTransactionLite({generation: 1, offset: 1}),
- children: [generateTransactionLite({generation: 2, offset: 2})],
- descendants: null,
- });
- });
- it('parses current with root and children', function () {
- const relevantPath = [
- generateTransactionLite({generation: 0, offset: 0}),
- generateTransactionLite({generation: 2, offset: 2}),
- generateTransactionLite({generation: 3, offset: 4}),
- generateTransactionLite({generation: 3, offset: 5}),
- ];
- const current = generateEventSelector({generation: 2, offset: 2}, 'transaction');
- const parsedQuickTrace = parseQuickTrace(
- {type: 'partial', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toEqual({
- root: generateTransactionLite({generation: 0, offset: 0}),
- ancestors: null,
- parent: null,
- current: generateTransactionLite({generation: 2, offset: 2}),
- children: [
- generateTransactionLite({generation: 3, offset: 4}),
- generateTransactionLite({generation: 3, offset: 5}),
- ],
- descendants: null,
- });
- });
- });
- describe('full trace', function () {
- it('parses the full trace', function () {
- const trace = generateTrace(6);
- const current = generateEventSelector({generation: 3, offset: 0}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- const parsedQuickTrace = parseQuickTrace(
- {type: 'full', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toMatchObject({
- root: generateTransactionLite({generation: 0, offset: 0}),
- ancestors: [generateTransactionLite({generation: 1, offset: 0})],
- parent: generateTransactionLite({generation: 2, offset: 0}),
- current: generateTransactionLite({generation: 3, offset: 0}),
- children: [
- generateTransactionLite({generation: 4, offset: 0}),
- generateTransactionLite({generation: 4, offset: 1}),
- ],
- descendants: [
- generateTransactionLite({generation: 5, offset: 0}),
- generateTransactionLite({generation: 5, offset: 1}),
- generateTransactionLite({generation: 5, offset: 2}),
- generateTransactionLite({generation: 5, offset: 3}),
- ],
- });
- });
- it('parses full trace without ancestors', function () {
- const trace = generateTrace(5);
- const current = generateEventSelector({generation: 2, offset: 0}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- const parsedQuickTrace = parseQuickTrace(
- {type: 'full', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toMatchObject({
- root: generateTransactionLite({generation: 0, offset: 0}),
- ancestors: [],
- parent: generateTransactionLite({generation: 1, offset: 0}),
- current: generateTransactionLite({generation: 2, offset: 0}),
- children: [
- generateTransactionLite({generation: 3, offset: 0}),
- generateTransactionLite({generation: 3, offset: 1}),
- ],
- descendants: [
- generateTransactionLite({generation: 4, offset: 0}),
- generateTransactionLite({generation: 4, offset: 1}),
- generateTransactionLite({generation: 4, offset: 2}),
- generateTransactionLite({generation: 4, offset: 3}),
- ],
- });
- });
- it('parses full trace without descendants', function () {
- const trace = generateTrace(5);
- const current = generateEventSelector({generation: 3, offset: 0}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- const parsedQuickTrace = parseQuickTrace(
- {type: 'full', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toMatchObject({
- root: generateTransactionLite({generation: 0, offset: 0}),
- ancestors: [generateTransactionLite({generation: 1, offset: 0})],
- parent: generateTransactionLite({generation: 2, offset: 0}),
- current: generateTransactionLite({generation: 3, offset: 0}),
- children: [
- generateTransactionLite({generation: 4, offset: 0}),
- generateTransactionLite({generation: 4, offset: 1}),
- ],
- descendants: [],
- });
- });
- it('parses full trace without children descendants', function () {
- const trace = generateTrace(4);
- const current = generateEventSelector({generation: 3, offset: 0}, 'transaction');
- const relevantPath = flattenRelevantPaths(current, trace);
- const parsedQuickTrace = parseQuickTrace(
- {type: 'full', trace: relevantPath},
- current,
- organization
- );
- expect(parsedQuickTrace).toMatchObject({
- root: generateTransactionLite({generation: 0, offset: 0}),
- ancestors: [generateTransactionLite({generation: 1, offset: 0})],
- parent: generateTransactionLite({generation: 2, offset: 0}),
- current: generateTransactionLite({generation: 3, offset: 0}),
- children: [],
- descendants: [],
- });
- });
- });
- });
- });
|