From a7ce2308eaa1dcda7b5409a3e16b0dea4af2abe1 Mon Sep 17 00:00:00 2001 From: Cynthia Ma Date: Tue, 10 Dec 2024 15:25:39 -0800 Subject: [PATCH] fix: update types in tests, update timing of tests --- src/v3/recordingComputeUtils.test.ts | 18 +- src/v3/test/fixtures.ts | 35 +- src/v3/test/makeTimeline.ts | 65 ++- src/v3/test/processEntries.ts | 29 -- src/v3/test/processSpans.ts | 13 + src/v3/test/types.test.ts | 202 +++++++++ src/v3/traceManager.test.ts | 617 ++++++++++++++++----------- 7 files changed, 675 insertions(+), 304 deletions(-) delete mode 100644 src/v3/test/processEntries.ts create mode 100644 src/v3/test/processSpans.ts create mode 100644 src/v3/test/types.test.ts diff --git a/src/v3/recordingComputeUtils.test.ts b/src/v3/recordingComputeUtils.test.ts index da16b901..73eface2 100644 --- a/src/v3/recordingComputeUtils.test.ts +++ b/src/v3/recordingComputeUtils.test.ts @@ -8,7 +8,7 @@ import type { SpanAndAnnotation, SpanAnnotation } from './spanAnnotationTypes' import type { Span } from './spanTypes' import type { CompleteTraceDefinition, Timestamp } from './types' -type ScopeBase = Record +type AnyScope = Record describe('recordingComputeUtils', () => { const EPOCH_START = 1_000 @@ -18,7 +18,7 @@ describe('recordingComputeUtils', () => { }) const createAnnotation = ( - span: Span, + span: Span, traceStartTime: Timestamp, partial: Partial = {}, ): SpanAnnotation => ({ @@ -31,7 +31,7 @@ describe('recordingComputeUtils', () => { ...partial, }) - const createMockSpan = >( + const createMockSpan = >( startTimeNow: number, partial: Partial, ): TSpan => @@ -57,11 +57,11 @@ describe('recordingComputeUtils', () => { ...partial, }) as TSpan - const createMockSpanAndAnnotation = >( + const createMockSpanAndAnnotation = >( startTimeNow: number, spanPartial: Partial = {}, annotationPartial: Partial = {}, - ): SpanAndAnnotation => { + ): SpanAndAnnotation => { const span = createMockSpan(startTimeNow, spanPartial) return { span, @@ -72,7 +72,7 @@ describe('recordingComputeUtils', () => { const onEnd = jest.fn() describe('error status propagation', () => { - const baseDefinition: CompleteTraceDefinition = { + const baseDefinition: CompleteTraceDefinition = { name: 'test-trace', scopes: [], requiredToEnd: [() => true], @@ -194,7 +194,7 @@ describe('recordingComputeUtils', () => { }) describe('getComputedSpans', () => { - const baseDefinition: CompleteTraceDefinition = { + const baseDefinition: CompleteTraceDefinition = { name: 'test-trace', scopes: [], requiredToEnd: [() => true], @@ -231,7 +231,7 @@ describe('recordingComputeUtils', () => { }) it('should handle operation-start and operation-end special matchers', () => { - const definition: CompleteTraceDefinition = { + const definition: CompleteTraceDefinition = { ...baseDefinition, computedSpanDefinitions: [ { @@ -261,7 +261,7 @@ describe('recordingComputeUtils', () => { }) describe('getComputedValues', () => { - const baseDefinition: CompleteTraceDefinition = { + const baseDefinition: CompleteTraceDefinition = { name: 'test-trace', scopes: [], requiredToEnd: [() => true], diff --git a/src/v3/test/fixtures.ts b/src/v3/test/fixtures.ts index 7ddb5da2..eb547f59 100644 --- a/src/v3/test/fixtures.ts +++ b/src/v3/test/fixtures.ts @@ -1,4 +1,4 @@ -import type { ScopeBase, TraceDefinition } from '../types' +import type { TraceDefinition } from '../types' export interface TicketIdScope { ticketId: string @@ -7,10 +7,15 @@ export interface UserIdScope { userId: string } +export type FixtureAllPossibleScopes = TicketIdScope | UserIdScope + const TICKET_DISPOSE_EVENT_NAME = `ticket.dispose` const TICKET_NAVIGATED_AWAY_EVENT_NAME = `ticket.navigated-away` -export const ticketActivationDefinition: TraceDefinition = { +export const ticketActivationDefinition: TraceDefinition< + TicketIdScope, + FixtureAllPossibleScopes +> = { name: 'ticket.activation', type: 'operation', requiredScopeKeys: ['ticketId'], @@ -20,30 +25,36 @@ export const ticketActivationDefinition: TraceDefinition = { { type: 'component-render', name: 'OmniLog', - scopes: ['ticketId'], + matchScopes: ['ticketId'], isIdle: true, }, ], interruptOn: [ - { type: 'mark', name: TICKET_DISPOSE_EVENT_NAME, scopes: ['ticketId'] }, + { + type: 'mark', + name: TICKET_DISPOSE_EVENT_NAME, + matchScopes: ['ticketId'], + }, { type: 'mark', name: TICKET_NAVIGATED_AWAY_EVENT_NAME, - scopes: ['ticketId'], + matchScopes: ['ticketId'], }, ], debounceOn: [ // debounce on anything that has matching ticketId scope: - { scopes: ['ticketId'] }, + { matchScopes: ['ticketId'] }, // TODO: { type: 'measure', name: (name, scope) => `ticket/${scope.ticketId}/open` }, // metric from ember: ticket_workspace.module.js - ({ span }, scope) => - span.type === 'measure' && span.name === `ticket/${scope.ticketId}/open`, + ({ span }) => + span.type === 'measure' && + span.name === `ticket/${span.scope?.ticketId}/open`, // debounce on element timing sentinels: - ({ span }, scope) => + ({ span }) => + span.type === 'element' && + span.name === `ticket_workspace/${span.scope?.ticketId}`, + ({ span }) => span.type === 'element' && - span.name === `ticket_workspace/${scope.ticketId}`, - ({ span }, scope) => - span.type === 'element' && span.name === `omnilog/${scope.ticketId}`, + span.name === `omnilog/${span.scope?.ticketId}`, ], } diff --git a/src/v3/test/makeTimeline.ts b/src/v3/test/makeTimeline.ts index 751bf495..363e0ccd 100644 --- a/src/v3/test/makeTimeline.ts +++ b/src/v3/test/makeTimeline.ts @@ -1,4 +1,14 @@ -export interface ComponentRenderStub { +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { ensureTimestamp } from '../ensureTimestamp' +import type { + ComponentRenderSpan, + PerformanceEntrySpan, + Span, + SpanType, +} from '../spanTypes' + +export interface ComponentRenderStub + extends Partial, 'startTime' | 'duration'>> { // TODO: double check what this is entryType: 'component-render' duration: number @@ -13,7 +23,8 @@ export interface LongTaskStub { name?: string } -export interface MarkStub { +export interface MarkStub + extends Partial, 'startTime' | 'duration'>> { entryType: 'mark' name: string startTime?: number @@ -29,17 +40,25 @@ export interface IdleStub { duration: number } -export type Stub = ComponentRenderStub | LongTaskStub | MarkStub | FmpStub | IdleStub +export type Stub = + | ComponentRenderStub + | LongTaskStub + | MarkStub + | FmpStub + | IdleStub export const Render = ( name: string, duration: number, - options: { start?: number, isIdle?: boolean } = {}, + options: { startTime?: number } & Partial< + Omit, 'startTime' | 'duration'> + > = {}, ): ComponentRenderStub => ({ entryType: 'component-render', - duration, - startTime: options.start, name, + duration, + startTime: options.startTime, + ...options, }) export const LongTask = ( @@ -108,11 +127,11 @@ export function makeEntries(events: Stub[]): { return { entries, fmpTime } } -export function getEventsFromTimeline( +export function getSpansFromTimeline( _: TemplateStringsArray, ...exprs: (Stub | number)[] -): { entries: PerformanceEntry[]; fmpTime: number | null } { - const entries: PerformanceEntry[] = [] +): { spans: Span[]; fmpTime: number | null } { + const spans: Span[] = [] let fmpTime: number | null = null const stubs = exprs.filter((expr) => typeof expr !== 'number') @@ -143,15 +162,35 @@ export function getEventsFromTimeline( if (stub.entryType === 'fmp') { fmpTime = currentTime } - entries.push({ + spans.push({ + type: stub.entryType as SpanType, duration: 0, name: `${stub.entryType}`, ...stub, - startTime: currentTime, - } as PerformanceEntry) + startTime: { + now: 'startTime' in stub ? stub.startTime ?? currentTime : currentTime, + epoch: + 'startTime' in stub ? stub.startTime ?? currentTime : currentTime, + }, + isIdle: 'name' in stub ? stub.name?.includes('idle') : undefined, + renderedOutput: + 'name' in stub + ? stub.name?.includes('idle') + ? 'content' + : 'loading' + : undefined, + performanceEntry: { + duration: 0, + name: `${stub.entryType}`, + ...stub, + startTime: + 'startTime' in stub ? stub.startTime ?? currentTime : currentTime, + toJSON: () => {}, + }, + } as Span) } - return { entries, fmpTime } + return { spans, fmpTime } } // example usage diff --git a/src/v3/test/processEntries.ts b/src/v3/test/processEntries.ts deleted file mode 100644 index b207526c..00000000 --- a/src/v3/test/processEntries.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ensureTimestamp } from '../ensureTimestamp' -import { Span, SpanType } from '../spanTypes' -import { TraceManager } from '../traceManager' -import { ScopeBase } from '../types' - -function processEntries( - entries: PerformanceEntry[], - traceManager: TraceManager, -) { - entries.forEach((entry, i) => { - if (i > 0) { - const prevEntry = entries[i - 1]! - jest.advanceTimersByTime(entry.startTime - prevEntry.startTime) - } - - traceManager.processSpan({ - name: entry.name, - duration: entry.duration, - isIdle: entry.name.includes('idle'), - renderedOutput: entry.name.includes('idle') ? 'content' : 'loading', - startTime: ensureTimestamp({ now: entry.startTime }), - type: entry.entryType as SpanType, - performanceEntry: entry, - scope: {}, - } as Span) - }) -} - -export default processEntries diff --git a/src/v3/test/processSpans.ts b/src/v3/test/processSpans.ts new file mode 100644 index 00000000..5bf1859e --- /dev/null +++ b/src/v3/test/processSpans.ts @@ -0,0 +1,13 @@ +import { Span } from '../spanTypes' +import { TraceManager } from '../traceManager' + +function processSpans( + spans: Span[], + traceManager: TraceManager, +) { + spans.forEach((span, i) => { + traceManager.processSpan(span) + }) +} + +export default processSpans diff --git a/src/v3/test/types.test.ts b/src/v3/test/types.test.ts new file mode 100644 index 00000000..9d9c7d7b --- /dev/null +++ b/src/v3/test/types.test.ts @@ -0,0 +1,202 @@ +/* eslint-disable jest/expect-expect */ +import { generateUseBeacon } from '../hooks' +import { TraceManager } from '../traceManager' + +interface ExampleTicketScope { + ticketId: string +} + +interface ExampleUserScope { + userId: string +} + +interface ExampleCustomFieldScope { + customFieldId: string +} + +type ExampleAllPossibleScopes = + | ExampleTicketScope + | ExampleUserScope + | ExampleCustomFieldScope + +// eslint-disable-next-line jest/no-disabled-tests +describe.skip('type tests', () => { + const traceManager = new TraceManager({ + generateId: () => 'id', + reportFn: () => {}, + }) + const useBeacon = generateUseBeacon(traceManager) + + it('works', () => { + // invalid because in the matcher functions, we cannot compare objects (due to object equality comparison) + interface InvalidScope { + something: { blah: string } + } + + // invalid: + // @ts-expect-error invalid scope + const invalidTraceManager = new TraceManager({ + generateId: () => 'id', + reportFn: () => {}, + }) + + // valid beacon + useBeacon({ + name: 'OmniLog', + renderedOutput: 'content', + scope: { ticketId: '123' }, + }) + + // valid beacon + useBeacon({ + name: 'UserPage', + renderedOutput: 'content', + scope: { userId: '123' }, + }) + + // invalid + useBeacon({ + name: 'UserPage', + renderedOutput: 'content', + // @ts-expect-error invalid scope + scope: { invalid: '123' }, + }) + + // valid definition + const ticketActivationTracer = traceManager.createTracer({ + name: 'ticket.activation', + scopes: ['ticketId'], + requiredToEnd: [{ matchScopes: ['ticketId'] }], + }) + + // valid definition + const userPageTracer = traceManager.createTracer({ + name: 'user.activation', + scopes: ['userId'], + requiredToEnd: [{ matchScopes: ['userId'] }], + }) + + // valid definition + const customFieldDropdownTracer = traceManager.createTracer({ + name: 'ticket.custom_field', + scopes: ['ticketId', 'customFieldId'], + requiredToEnd: [{ matchScopes: ['ticketId'] }], + }) + + // invalid definition. scopes match but not included in AllPossibleScopes + const invalidTracer = traceManager.createTracer({ + name: 'ticket.activation', + // @ts-expect-error invalid scope + scopes: ['invalid'], + requiredToEnd: [ + { + // @ts-expect-error invalid scope + matchScopes: ['invalid'], + }, + ], + }) + + // invalid definition. userId given in requiredToEnd isn't one of the scopes the tracer says it can have + const shouldErrorTrace = traceManager.createTracer({ + name: 'ticket.should_error', + scopes: ['ticketId', 'customFieldId'], + requiredToEnd: [ + { + // @ts-expect-error invalid scope + matchScopes: ['userId'], + }, + ], + }) + + // valid definition + const ticketActivationWithFnTracer = traceManager.createTracer({ + name: 'ticket.activation', + scopes: ['ticketId'], + requiredToEnd: [ + { matchScopes: ['ticketId'] }, + ({ span }) => span.scope?.ticketId === '123', + ], + }) + + // valid start + ticketActivationTracer.start({ + scope: { ticketId: '123' }, + }) + + // invalid start (errors) + ticketActivationTracer.start({ + // @ts-expect-error invalid scope + scope: { whatever: '123' }, + }) + + // invalid start (errors) + ticketActivationTracer.start({ + // @ts-expect-error invalid scope + scope: { userId: '123' }, + }) + + const mockSpanWithoutScope = { + name: 'some-span', + duration: 0, + type: 'mark', + attributes: {}, + startTime: { now: 0, epoch: 0 }, + } as const + + // valid - excess scope + traceManager.processSpan({ + ...mockSpanWithoutScope, + scope: { ticketId: '123', customFieldId: '123', userId: '123' }, + }) + + // valid + traceManager.processSpan({ + ...mockSpanWithoutScope, + scope: { ticketId: '123' }, + }) + + // valid - multiple scopes simultaneously + traceManager.processSpan({ + ...mockSpanWithoutScope, + scope: { + ticketId: '123', + customFieldId: '123', + }, + }) + + // invalid + traceManager.processSpan({ + ...mockSpanWithoutScope, + scope: { + // @ts-expect-error bad scope + bad: '123', + }, + }) + + // invalid + traceManager.processSpan({ + ...mockSpanWithoutScope, + scope: { + // @ts-expect-error bad scope + ticketId: 123, + }, + }) + }) + + it('does not allow to include bad scopes', () => { + const tracer = traceManager.createTracer({ + name: 'ticket.scope-operation', + type: 'operation', + scopes: ['ticketId'], + timeoutDuration: 5_000, + requiredToEnd: [{ name: 'end', matchScopes: true }], + }) + // start trace + const traceId = tracer.start({ + scope: { + ticketId: '4', + userId: '3', + }, + }) + }) +}) diff --git a/src/v3/traceManager.test.ts b/src/v3/traceManager.test.ts index abde51ba..209c3477 100644 --- a/src/v3/traceManager.test.ts +++ b/src/v3/traceManager.test.ts @@ -7,21 +7,22 @@ import * as matchSpan from './matchSpan' import type { Span, StartTraceConfig } from './spanTypes' import { type TicketIdScope, - UserIdScope, ticketActivationDefinition, + UserIdScope, } from './test/fixtures' import { Check, - getEventsFromTimeline, + getSpansFromTimeline, LongTask, Render, } from './test/makeTimeline' -import processEntries from './test/processEntries' +import processSpans from './test/processSpans' import { shouldCompleteAndHaveInteractiveTime } from './test/shouldCompleteAndHaveInteractiveTime' import { shouldNotEndWithInteractiveTimeout } from './test/shouldNotEndWithInteractiveTimeout' -import { shouldNotEndWithInterruption } from './test/shouldNotEndWithInterruption' +// import { shouldNotEndWithInterruption } from './test/shouldNotEndWithInterruption' import { TraceManager } from './traceManager' -import type { ReportFn, ScopeBase, TraceDefinition } from './types' +import type { ReportFn, TraceDefinition } from './types' +import { Ticket } from '../stories/mockComponentsv3/mockTickets' function processCheckSpan( traceManager: TraceManager, @@ -50,16 +51,22 @@ const cpuIdleProcessorOptions = { heavyClusterThreshold: GOOGLES_HEAVY_CLUSTER_THRESHOLD, } +interface TicketScope { + ticketId: string +} + describe('TraceManager', () => { let reportFn: jest.Mock let generateId: jest.Mock - jest.useFakeTimers() + jest.useFakeTimers({ + now: 0, + }) beforeEach(() => { reportFn = jest.fn< - ReturnType>, - Parameters> + ReturnType>, + Parameters> >() generateId = jest.fn().mockReturnValue('trace-id') }) @@ -70,16 +77,18 @@ describe('TraceManager', () => { }) it('tracks trace with minimal requirements', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ reportFn, generateId }) + const traceDefinition: TraceDefinition = { name: 'ticket.basic-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // start trace @@ -87,15 +96,16 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('middle', 0)}-----${Render('end', 0)}---<===+2s===>----${Check} Time: ${0} ${50} ${100} ${2_100} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = reportFn.mock.calls[0][0] + const report: Parameters>[0] = + reportFn.mock.calls[0][0] expect( report.entries.map( (spanAndAnnotation) => spanAndAnnotation.span.performanceEntry, @@ -112,16 +122,18 @@ describe('TraceManager', () => { }) it('correctly calculates a computed span', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ reportFn, generateId }) + const traceDefinition: TraceDefinition = { name: 'ticket.computed-span-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } const computedSpanName = 'render-1-to-3' @@ -137,15 +149,16 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}---${Render('render-1', 50)}----${Render('render-2', 50)}----${Render('render-3', 50)}--------${Render('end', 0)} Time: ${0} ${50} ${100} ${150} ${200} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = reportFn.mock.calls[0][0] + const report: Parameters>[0] = + reportFn.mock.calls[0][0] expect(report.name).toBe('ticket.computed-span-operation') expect(report.duration).toBe(200) expect(report.status).toBe('ok') @@ -164,16 +177,18 @@ describe('TraceManager', () => { }) it('correctly calculates a computed value', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ reportFn, generateId }) + const traceDefinition: TraceDefinition = { name: 'ticket.computed-value-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // Define a computed value @@ -188,14 +203,15 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}--${Render('feature', 50)}--${Render('feature', 50)}-${Render('end', 0)} Time: ${0} ${50} ${100} ${150} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = reportFn.mock.calls[0][0] + const report: Parameters>[0] = + reportFn.mock.calls[0][0] expect(report.name).toBe('ticket.computed-value-operation') expect(report.duration).toBe(150) expect(report.status).toBe('ok') @@ -215,7 +231,7 @@ describe('TraceManager', () => { `) }) - it('tracks trace with scope ticketId: 4', () => { + it('when matchScopes is true for two scopes: tracks trace with scope ticketId: 4 and scope userId: 3', () => { type AllScopesT = TicketIdScope & UserIdScope type AllScopeKeys = keyof AllScopesT const traceManager = new TraceManager({ @@ -226,25 +242,29 @@ describe('TraceManager', () => { name: 'ticket.scope-operation', type: 'operation', scopes: ['ticketId'], + timeoutDuration: 5_000, requiredToEnd: [{ name: 'end', matchScopes: true }], }) + + const scope = { + ticketId: '4', + userId: '3', + } // start trace const traceId = tracer.start({ - scope: { - ticketId: '4', - }, + scope, }) expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` - Events: ${Render('start', 0)}-----${Render('middle', 0)}-----${Render('end', 0)}---<===+2s===>----${Check} - Time: ${0} ${50} ${100} ${2_100} + const { spans } = getSpansFromTimeline` + Events: ${Render('start', 0)}-----${Render('middle', 0)}-----${Render('end', 0, { scope })}---<===+6s===>----${Check} + Time: ${0} ${50} ${100} ${6_000} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -256,26 +276,31 @@ describe('TraceManager', () => { time (ms) | 0 50 100 `) expect(report.name).toBe('ticket.scope-operation') + expect(report.interruptionReason).toBeUndefined() expect(report.duration).toBe(100) expect(report.status).toBe('ok') - expect(report.interruptionReason).toBeUndefined() - expect(report.scope).toEqual({ ticketId: '4' }) + expect(report.scope).toEqual(scope) }) describe('debounce', () => { it('tracks trace when debouncedOn is defined but no debounce events', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], debounceOn: [{ name: 'debounce' }], } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // start trace @@ -283,14 +308,14 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('middle', 0)}-----${Render('end', 0)}---<===+2s===>----${Check} Time: ${0} ${50} ${100} ${2_100} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -308,19 +333,22 @@ describe('TraceManager', () => { }) it('tracks trace correctly when debounced entries are seen', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.debounce-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [matchSpan.withName('end')], debounceOn: [matchSpan.withName((n: string) => n.endsWith('debounce'))], debounceDuration: 300, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { + const startConfig: StartTraceConfig = { scope: { - ticketId: 1, + ticketId: '1', }, } @@ -328,14 +356,14 @@ describe('TraceManager', () => { tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${Render('shorter-debounce', 0)}-----${Render('short-debounce', 0)}-----${Render('long-debounce', 0)}-----${Check})} Time: ${0} ${50} ${51} ${251} ${451} u ${1_051} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -357,33 +385,38 @@ describe('TraceManager', () => { describe('interrupts', () => { it('interrupts a basic trace when interruptOn criteria is met', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.interrupt-on-basic-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [matchSpan.withName('end')], interruptOn: [matchSpan.withName('interrupt')], } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // start trace tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('interrupt', 0)}-----${Render('end', 0)} Time: ${0} ${100} ${200} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -401,17 +434,22 @@ describe('TraceManager', () => { }) it('interrupts itself when another trace is started', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.interrupt-itself-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], debounceOn: [{ name: 'debounce' }], } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // start trace @@ -419,18 +457,18 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)} Time: ${0} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) // Start another operation to interrupt the first one const newTraceId = tracer.start(startConfig) expect(newTraceId).toBe('trace-id') expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -452,20 +490,23 @@ describe('TraceManager', () => { describe('timeout', () => { it('timeouts when the basic trace when the default timeout duration is reached', () => { - const traceManager = new TraceManager({ + const traceManager = new TraceManager({ reportFn, generateId, }) - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.timeout-operation', type: 'operation', - requiredScopeKeys: [], + scopes: ['ticketId'], requiredToEnd: [{ name: 'timed-out-render' }], timeoutDuration: 500, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + startTime: { now: 0, epoch: 0 }, + scope: { + ticketId: '1', + }, } // start trace @@ -473,15 +514,17 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}------${Render('timed-out-render', 0)} - Time: ${0} ${DEFAULT_TIMEOUT_DURATION + 1} + Time: ${0} ${500 + 1} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) + expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] + expect( report.entries.map( (spanAndAnnotation) => spanAndAnnotation.span.performanceEntry, @@ -491,28 +534,35 @@ describe('TraceManager', () => { timeline | | time (ms) | 0 `) - expect(report.name).toBe('ticket.timeout-operation') + expect(report.interruptionReason).toBe('timeout') expect(report.duration).toBeNull() + + expect(report.name).toBe('ticket.timeout-operation') expect(report.status).toBe('interrupted') + expect(report.interruptionReason).toBe('timeout') + expect(report.duration).toBeNull() }) it('timeouts when the basic trace when a custom timeout duration is reached', () => { - const traceManager = new TraceManager({ + const traceManager = new TraceManager({ reportFn, generateId, }) const CUSTOM_TIMEOUT_DURATION = 500 - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.timeout-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'timed-out-render' }], timeoutDuration: CUSTOM_TIMEOUT_DURATION, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + startTime: { now: 0, epoch: 0 }, + scope: { + ticketId: '1', + }, } // start trace @@ -520,15 +570,19 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}------${Render('timed-out-render', 0)} Time: ${0} ${CUSTOM_TIMEOUT_DURATION + 1} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) + expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] + + expect(report.status).toBe('interrupted') + expect(report.interruptionReason).toBe('timeout') expect( report.entries.map( (spanAndAnnotation) => spanAndAnnotation.span.performanceEntry, @@ -540,27 +594,28 @@ describe('TraceManager', () => { `) expect(report.name).toBe('ticket.timeout-operation') expect(report.duration).toBeNull() - expect(report.status).toBe('interrupted') - expect(report.interruptionReason).toBe('timeout') }) it('transitions from debouncing to timeout', () => { - const traceManager = new TraceManager({ + const traceManager = new TraceManager({ reportFn, generateId, }) const CUSTOM_TIMEOUT_DURATION = 500 - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.timeout-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], debounceOn: [{ name: 'debounce' }], timeoutDuration: CUSTOM_TIMEOUT_DURATION, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + startTime: { now: 0, epoch: 0 }, + scope: { + ticketId: '1', + }, } // start trace @@ -568,14 +623,15 @@ describe('TraceManager', () => { expect(traceId).toBe('trace-id') // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}--${Render('end', 0)}--${Render('debounce', 0)}--${Check}} Time: ${0} ${50} ${51} ${CUSTOM_TIMEOUT_DURATION + 1} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) + expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -598,29 +654,34 @@ describe('TraceManager', () => { describe('while capturing interactivity (when captureInteractive is defined)', () => { it('correctly captures trace while waiting the long tasks to settle', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(50)}------<===5s===>---------${Check} Time: ${0} ${2_000} ${2_001} ${2_001 + DEFAULT_CAPTURE_INTERACTIVE_TIME} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -639,12 +700,15 @@ describe('TraceManager', () => { }) it('completes the trace with waiting-for-interactive-timeout', () => { - const traceManager = new TraceManager({ reportFn, generateId }) + const traceManager = new TraceManager({ + reportFn, + generateId, + }) const interactiveTimeout = 5_000 - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: { ...cpuIdleProcessorOptions, @@ -652,21 +716,23 @@ describe('TraceManager', () => { }, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(interactiveTimeout)} Time: ${0} ${2_000} ${2_050} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -685,33 +751,38 @@ describe('TraceManager', () => { }) it('completes the trace when interrupted during waiting for capture interactive to finish', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.interrupt-during-long-task-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [matchSpan.withName('end')], interruptOn: [matchSpan.withName('interrupt')], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // start trace tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}---------${Render('end', 0)}---------${Render('interrupt', 0)}--${LongTask(100)}--${Check} Time: ${0} ${200} ${201} ${300} ${5_000} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -730,33 +801,38 @@ describe('TraceManager', () => { }) it('timeouts the trace while deboucing AND the last span is past the timeout duration', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.debounce-then-interrupted-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], debounceOn: [{ name: 'debounce' }], captureInteractive: cpuIdleProcessorOptions, debounceDuration: 300, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } // start trace tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${Render('debounce', 0)}------${Check} Time: ${0} ${50} ${100} ${DEFAULT_TIMEOUT_DURATION + 100} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -768,19 +844,22 @@ describe('TraceManager', () => { time (ms) | 0 50 100 `) expect(report.name).toBe('ticket.debounce-then-interrupted-operation') - expect(report.duration).toBeNull() - expect(report.startTillInteractive).toBeNull() expect(report.status).toBe('interrupted') + expect(report.duration).toBeNull() expect(report.interruptionReason).toBe('timeout') + expect(report.startTillInteractive).toBeNull() }) it('completes the trace when debouncing is done AND is waiting for capture interactive to finish', () => { - const traceManager = new TraceManager({ reportFn, generateId }) + const traceManager = new TraceManager({ + reportFn, + generateId, + }) const CUSTOM_CAPTURE_INTERACTIVE_TIMEOUT = 300 - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.debounce-then-interrupted-operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], debounceOn: [{ name: 'debounce' }], captureInteractive: { @@ -790,7 +869,7 @@ describe('TraceManager', () => { debounceDuration: 100, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { + const startConfig: StartTraceConfig = { scope: {}, } @@ -798,14 +877,14 @@ describe('TraceManager', () => { tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}---------------------${Render('debounce', 100)}------------------${Check} Time: ${0} ${DEFAULT_TIMEOUT_DURATION - 500} ${DEFAULT_TIMEOUT_DURATION - 499} ${DEFAULT_TIMEOUT_DURATION + 1} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -824,16 +903,19 @@ describe('TraceManager', () => { }) it('uses getQuietWindowDuration from capture interactive config', () => { - const traceManager = new TraceManager({ reportFn, generateId }) + const traceManager = new TraceManager({ + reportFn, + generateId, + }) const CUSTOM_QUIET_WINDOW_DURATION = 2_000 const TRACE_DURATION = 1_000 const getQuietWindowDuration = jest .fn() .mockReturnValue(CUSTOM_QUIET_WINDOW_DURATION) - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: { ...cpuIdleProcessorOptions, @@ -842,21 +924,23 @@ describe('TraceManager', () => { }, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(5)}------------${LongTask(5)}-----${Check} Time: ${0} ${TRACE_DURATION} ${1_001} ${1_050} ${1_200} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( @@ -878,14 +962,17 @@ describe('TraceManager', () => { }) it('uses createQuietWindowDurationCalculator for getQuietWindowDuration in capture interactive config', () => { - const traceManager = new TraceManager({ reportFn, generateId }) + const traceManager = new TraceManager({ + reportFn, + generateId, + }) // const CUSTOM_QUIET_WINDOW_DURATION = 2_000 // 2 seconds const TRACE_DURATION = 1_000 - const traceDefinition: TraceDefinition = { + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: { ...cpuIdleProcessorOptions, @@ -894,21 +981,23 @@ describe('TraceManager', () => { }, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----------${LongTask(5)}----------------${LongTask(50)}---${Check} Time: ${0} ${TRACE_DURATION} ${1_001} ${1_945} ${2_001} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( @@ -928,31 +1017,36 @@ describe('TraceManager', () => { }) it('No long tasks after FMP, FirstCPUIdle immediately after FMP + quiet window', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}----------${LongTask(400)}-----${Check} Time: ${0} ${200} ${4_600} ${5_000} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -972,31 +1066,34 @@ describe('TraceManager', () => { }) it('One light cluster after FMP, FirstCPUIdle at FMP', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { + const startConfig: StartTraceConfig = { scope: {}, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}----------${LongTask(50)}------${LongTask(50)}-----${LongTask(50)}---------${Check} Time: ${0} ${200} ${300} ${400} ${450} ${3_050} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1016,31 +1113,36 @@ describe('TraceManager', () => { }) it('One heavy cluster after FMP, FirstCPUIdle after the cluster', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}--${LongTask(50)}}-${LongTask(200)}-----${LongTask(50)}-----------${Check} Time: ${0} ${200} ${300} ${400} ${650} ${3_200} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1062,31 +1164,36 @@ describe('TraceManager', () => { }) it('Multiple heavy clusters, FirstCPUIdle updated to end of last cluster', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(200)}-----${LongTask(200)}-----${LongTask(200)}-----${Check} Time: ${0} ${200} ${300} ${900} ${1_500} ${3_800} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1098,8 +1205,8 @@ describe('TraceManager', () => { time (ms) | 0 200 300 900 1500 `) - const lastLongTask = entries.at(-2)! - const expectedResult = lastLongTask.startTime + lastLongTask.duration + const lastLongTask = spans.at(-2)! + const expectedResult = lastLongTask.startTime.now + lastLongTask.duration expect(report.name).toBe('ticket.operation') expect(report.duration).toBe(200) @@ -1109,31 +1216,36 @@ describe('TraceManager', () => { }) it('Checking before the quiet window has passed - no long tasks processed, FirstCPUIdle not found', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${Check} Time: ${0} ${200} ${2_400} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1153,32 +1265,37 @@ describe('TraceManager', () => { }) it('completes the trace with one heavy cluster followed by two light clusters, value is after 1st heavy cluster', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(200)}-----${LongTask(100)}-----${LongTask(200)}-----${LongTask(200)}-----${Check} Time: ${0} ${200} ${300} ${600} ${1_700} ${2_900} ${4_650} } ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1190,9 +1307,10 @@ describe('TraceManager', () => { time (ms) | 0 200 300 600 `) - const lastHeavyClusterLongTask = entries.at(3)! + const lastHeavyClusterLongTask = spans.at(3)! const expectedResult = - lastHeavyClusterLongTask.startTime + lastHeavyClusterLongTask.duration + lastHeavyClusterLongTask.startTime.now + + lastHeavyClusterLongTask.duration expect(report.name).toBe('ticket.operation') expect(report.duration).toBe(200) @@ -1202,31 +1320,36 @@ describe('TraceManager', () => { }) it('completes the trace with continuous heavy clusters', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${LongTask(300)}-----${Check} Time: ${0} ${200} ${550} ${900} ${1_250} ${1_600} ${1_950} ${2_300} ${2_650} ${3_000} ${3_350} ${3_700} ${4_050} ${4_400} ${4_750} ${5_100} ${7_450} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1248,31 +1371,36 @@ describe('TraceManager', () => { }) it('completes the trace with a light cluster followed by a heavy cluster a second later, FirstCPUIdle updated', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}-----${Render('end', 0)}-----${LongTask(50)}----------${LongTask(50)}----------${LongTask(50)}--------${LongTask(50)}---------${LongTask(200)}-------${LongTask(200)}--------${Check} Time: ${0} ${200} ${300} ${350} ${1_450} ${1_550} ${1_650} ${1_900} ${4_150} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1285,8 +1413,8 @@ describe('TraceManager', () => { time (ms) | 0 200 300 350 1450 1550 1650 1900 `) - const lastLongTask = entries.at(-2)! - const expectedResult = lastLongTask.startTime + lastLongTask.duration + const lastLongTask = spans.at(-2)! + const expectedResult = lastLongTask.startTime.now + lastLongTask.duration expect(report.name).toBe('ticket.operation') expect(report.duration).toBe(200) @@ -1296,31 +1424,36 @@ describe('TraceManager', () => { }) it('completes the trace with a long task overlapping FMP, FirstCPUIdle after the long task', () => { - const traceManager = new TraceManager({ reportFn, generateId }) - const traceDefinition: TraceDefinition = { + const traceManager = new TraceManager({ + reportFn, + generateId, + }) + const traceDefinition: TraceDefinition = { name: 'ticket.operation', type: 'operation', - requiredScopeKeys: [], + scopes: [], requiredToEnd: [{ name: 'end' }], captureInteractive: cpuIdleProcessorOptions, } const tracer = traceManager.createTracer(traceDefinition) - const startConfig: StartTraceConfig = { - scope: {}, + const startConfig: StartTraceConfig = { + scope: { + ticketId: '1', + }, } tracer.start(startConfig) // prettier-ignore - const { entries } = getEventsFromTimeline` + const { spans } = getSpansFromTimeline` Events: ${Render('start', 0)}----------${LongTask(110, { start: 150 })}----${Render('end', 0)}-----${Check} Time: ${0} ${150} ${200} ${2_500} ` - processEntries(entries, traceManager) + processSpans(spans, traceManager) expect(reportFn).toHaveBeenCalled() - const report: Parameters>[0] = + const report: Parameters>[0] = reportFn.mock.calls[0][0] expect( report.entries.map( @@ -1332,8 +1465,8 @@ describe('TraceManager', () => { time (ms) | 0 200 `) - const lastLongTask = entries.at(-2)! - const expectedResult = lastLongTask.startTime + lastLongTask.duration + const lastLongTask = spans.at(-2)! + const expectedResult = lastLongTask.startTime.now + lastLongTask.duration expect(report.name).toBe('ticket.operation') expect(report.duration).toBe(200) @@ -1374,7 +1507,8 @@ describe('TraceManager', () => { entries, spanAttributes, ...report - }: Parameters>[0] = reportFn.mock.calls[0][0] + }: Parameters>[0] = + reportFn.mock.calls[0][0] expect(report).toMatchInlineSnapshot(` { @@ -1463,7 +1597,8 @@ describe('TraceManager', () => { entries, spanAttributes, ...report - }: Parameters>[0] = reportFn.mock.calls[0][0] + }: Parameters>[0] = + reportFn.mock.calls[0][0] expect(report).toMatchInlineSnapshot(` {