UNPKG

@zendesk/react-measure-timing-hooks

Version:

react hooks for measuring time to interactive and time to render of components

269 lines (268 loc) 15.6 kB
import { type CPUIdleLongTaskProcessor, type PerformanceEntryLike } from './firstCPUIdle'; import { type SpanMatcherFn } from './matchSpan'; import type { SpanAndAnnotation, SpanAnnotationRecord } from './spanAnnotationTypes'; import type { ActiveTraceConfig, DraftTraceInput, Span } from './spanTypes'; import type { TraceRecording } from './traceRecordingTypes'; import type { CompleteTraceDefinition, DraftTraceContext, RelationSchemasBase, TraceContext, TraceDefinitionModifications, TraceInterruptionReason, TraceManagerUtilities, TraceModifications } from './types'; import type { DistributiveOmit, StateHandlerPayloads } from './typeUtils'; export interface FinalState<RelationSchemaT> { transitionFromState: NonTerminalTraceStates; interruptionReason?: TraceInterruptionReason; cpuIdleSpanAndAnnotation?: SpanAndAnnotation<RelationSchemaT>; completeSpanAndAnnotation?: SpanAndAnnotation<RelationSchemaT>; lastRequiredSpanAndAnnotation?: SpanAndAnnotation<RelationSchemaT>; } declare const INITIAL_STATE = "draft"; type InitialTraceState = typeof INITIAL_STATE; export type NonTerminalTraceStates = InitialTraceState | 'active' | 'debouncing' | 'waiting-for-interactive'; export declare const TERMINAL_STATES: readonly ["interrupted", "complete"]; type TerminalTraceStates = (typeof TERMINAL_STATES)[number]; export type TraceStates = NonTerminalTraceStates | TerminalTraceStates; interface OnEnterActive { transitionToState: 'active'; transitionFromState: NonTerminalTraceStates; } interface OnEnterInterrupted { transitionToState: 'interrupted'; transitionFromState: NonTerminalTraceStates; interruptionReason: TraceInterruptionReason; } interface OnEnterComplete<RelationSchemasT> extends FinalState<RelationSchemasT> { transitionToState: 'complete'; } interface OnEnterWaitingForInteractive { transitionToState: 'waiting-for-interactive'; transitionFromState: NonTerminalTraceStates; } interface OnEnterDebouncing { transitionToState: 'debouncing'; transitionFromState: NonTerminalTraceStates; } type OnEnterStatePayload<RelationSchemasT> = OnEnterActive | OnEnterInterrupted | OnEnterComplete<RelationSchemasT> | OnEnterDebouncing | OnEnterWaitingForInteractive; export type Transition<RelationSchemasT> = DistributiveOmit<OnEnterStatePayload<RelationSchemasT>, 'transitionFromState'>; export type States<SelectedRelationNameT extends keyof RelationSchemasT, RelationSchemasT, VariantsT extends string> = TraceStateMachine<SelectedRelationNameT, RelationSchemasT, VariantsT>['states']; interface TraceStateMachineSideEffectHandlers<RelationSchemasT> { readonly addSpanToRecording: (spanAndAnnotation: SpanAndAnnotation<RelationSchemasT>) => void; readonly prepareAndEmitRecording: (options: PrepareAndEmitRecordingOptions<RelationSchemasT>) => void; } type EntryType<RelationSchemasT> = PerformanceEntryLike & { entry: SpanAndAnnotation<RelationSchemasT>; }; interface StateMachineContext<SelectedRelationNameT extends keyof RelationSchemasT, RelationSchemasT, VariantsT extends string> extends DraftTraceContext<SelectedRelationNameT, RelationSchemasT, VariantsT> { sideEffectFns: TraceStateMachineSideEffectHandlers<RelationSchemasT>; } type DeadlineType = 'global' | 'debounce' | 'interactive' | 'next-quiet-window'; export declare class TraceStateMachine<SelectedRelationNameT extends keyof RelationSchemasT, RelationSchemasT, const VariantsT extends string> { #private; constructor(context: StateMachineContext<SelectedRelationNameT, RelationSchemasT, VariantsT>); readonly successfullyMatchedRequiredSpanMatchers: Set<SpanMatcherFn<SelectedRelationNameT, RelationSchemasT, VariantsT>>; get sideEffectFns(): TraceStateMachineSideEffectHandlers<RelationSchemasT>; currentState: TraceStates; /** the span that ended at the furthest point in time */ lastRelevant: SpanAndAnnotation<RelationSchemasT> | undefined; lastRequiredSpan: SpanAndAnnotation<RelationSchemasT> | undefined; /** it is set once the LRS value is established */ completeSpan: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleLongTaskProcessor: CPUIdleLongTaskProcessor<EntryType<RelationSchemasT>> | undefined; nextDeadlineRef: ReturnType<typeof setTimeout> | undefined; setDeadline(deadlineType: Exclude<DeadlineType, 'global'>, deadlineEpoch: number): void; setGlobalDeadline(deadline: number): void; clearDeadline(): void; /** * while debouncing, we need to buffer any spans that come in so they can be re-processed * once we transition to the 'waiting-for-interactive' state * otherwise we might miss out on spans that are relevant to calculating the interactive * * if we have long tasks before FMP, we want to use them as a potential grouping post FMP. */ debouncingSpanBuffer: SpanAndAnnotation<RelationSchemasT>[]; readonly states: { draft: { onEnterState: () => void; onMakeActive: () => { transitionToState: "active"; }; onProcessSpan: (spanAndAnnotation: SpanAndAnnotation<RelationSchemasT>) => { transitionToState: "interrupted"; interruptionReason: "timeout"; lastRequiredSpanAndAnnotation?: undefined; completeSpanAndAnnotation?: undefined; } | { transitionToState: "complete"; interruptionReason: "matched-on-interrupt" | "matched-on-required-span-with-error"; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; } | undefined; onInterrupt: (reason: TraceInterruptionReason) => { transitionToState: "interrupted"; interruptionReason: TraceInterruptionReason; }; onDeadline: (deadlineType: DeadlineType) => { transitionToState: "interrupted"; interruptionReason: "timeout"; } | undefined; }; active: { onEnterState: (_transition: OnEnterActive) => Transition<RelationSchemasT> | undefined; onProcessSpan: (spanAndAnnotation: SpanAndAnnotation<RelationSchemasT>) => { transitionToState: "interrupted"; interruptionReason: "timeout"; } | { transitionToState: "interrupted"; interruptionReason: "matched-on-interrupt" | "matched-on-required-span-with-error"; } | { transitionToState: "debouncing"; interruptionReason?: undefined; } | undefined; onInterrupt: (reason: TraceInterruptionReason) => { transitionToState: "interrupted"; interruptionReason: TraceInterruptionReason; }; onDeadline: (deadlineType: DeadlineType) => { transitionToState: "interrupted"; interruptionReason: "timeout"; } | undefined; }; debouncing: { onEnterState: (_payload: OnEnterDebouncing) => { transitionToState: "interrupted"; interruptionReason: "invalid-state-transition"; } | { transitionToState: "waiting-for-interactive"; interruptionReason?: undefined; } | undefined; onDeadline: (deadlineType: DeadlineType) => { transitionToState: "interrupted"; interruptionReason: "timeout"; } | { transitionToState: "waiting-for-interactive"; interruptionReason?: undefined; } | undefined; onProcessSpan: (spanAndAnnotation: SpanAndAnnotation<RelationSchemasT>) => { transitionToState: "interrupted"; interruptionReason: "timeout"; } | { transitionToState: "waiting-for-interactive"; interruptionReason?: undefined; } | { transitionToState: "interrupted"; interruptionReason: "idle-component-no-longer-idle"; } | undefined; onInterrupt: (reason: TraceInterruptionReason) => { transitionToState: "interrupted"; interruptionReason: TraceInterruptionReason; }; }; 'waiting-for-interactive': { onEnterState: (_payload: OnEnterWaitingForInteractive) => Transition<RelationSchemasT> | undefined; onDeadline: (deadlineType: DeadlineType) => { transitionToState: "complete"; interruptionReason: "timeout"; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleSpanAndAnnotation?: undefined; } | { transitionToState: "complete"; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT>; interruptionReason?: undefined; } | undefined; onProcessSpan: (spanAndAnnotation: SpanAndAnnotation<RelationSchemasT>) => { transitionToState: "complete"; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT>; interruptionReason?: undefined; } | { transitionToState: "complete"; interruptionReason: "timeout"; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleSpanAndAnnotation?: undefined; } | { transitionToState: "complete"; interruptionReason: "waiting-for-interactive-timeout"; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleSpanAndAnnotation?: undefined; } | { transitionToState: "complete"; interruptionReason: "matched-on-interrupt" | "matched-on-required-span-with-error"; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; cpuIdleSpanAndAnnotation?: undefined; } | undefined; onInterrupt: (reason: TraceInterruptionReason) => { transitionToState: "complete"; interruptionReason: TraceInterruptionReason; lastRequiredSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; completeSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; }; }; interrupted: { onEnterState: (transition: OnEnterInterrupted) => void; }; complete: { onEnterState: (transition: OnEnterComplete<RelationSchemasT>) => void; }; }; /** * @returns the last OnEnterState event if a transition was made */ emit<EventName extends keyof StateHandlerPayloads<SelectedRelationNameT, RelationSchemasT, VariantsT>>(event: EventName, payload: StateHandlerPayloads<SelectedRelationNameT, RelationSchemasT, VariantsT>[EventName]): OnEnterStatePayload<RelationSchemasT> | undefined; } interface PrepareAndEmitRecordingOptions<RelationSchemasT> { transition: OnEnterStatePayload<RelationSchemasT>; lastRelevantSpanAndAnnotation: SpanAndAnnotation<RelationSchemasT> | undefined; } export declare class Trace<const SelectedRelationNameT extends keyof RelationSchemasT, const RelationSchemasT extends RelationSchemasBase<RelationSchemasT>, const VariantsT extends string> implements TraceContext<SelectedRelationNameT, RelationSchemasT, VariantsT> { readonly sourceDefinition: CompleteTraceDefinition<SelectedRelationNameT, RelationSchemasT, VariantsT>; /** the final, mutable definition of this specific trace */ definition: CompleteTraceDefinition<SelectedRelationNameT, RelationSchemasT, VariantsT>; wasActivated: boolean; get activeInput(): ActiveTraceConfig<SelectedRelationNameT, RelationSchemasT, VariantsT>; set activeInput(value: ActiveTraceConfig<SelectedRelationNameT, RelationSchemasT, VariantsT>); input: DraftTraceInput<RelationSchemasT[SelectedRelationNameT], VariantsT>; readonly traceUtilities: TraceManagerUtilities<RelationSchemasT>; get isDraft(): boolean; recordedItems: Set<SpanAndAnnotation<RelationSchemasT>>; occurrenceCounters: Map<string, number>; processedPerformanceEntries: WeakMap<PerformanceEntry, SpanAndAnnotation<RelationSchemasT>>; persistedDefinitionModifications: Set<TraceDefinitionModifications<SelectedRelationNameT, RelationSchemasT, VariantsT>>; readonly recordedItemsByLabel: { [label: string]: SpanAndAnnotation<RelationSchemasT>[]; }; stateMachine: TraceStateMachine<SelectedRelationNameT, RelationSchemasT, VariantsT>; constructor(data: { definition: CompleteTraceDefinition<SelectedRelationNameT, RelationSchemasT, VariantsT>; input: DraftTraceInput<RelationSchemasT[SelectedRelationNameT], VariantsT>; definitionModifications?: TraceDefinitionModifications<SelectedRelationNameT, RelationSchemasT, VariantsT>; traceUtilities: TraceManagerUtilities<RelationSchemasT>; } | { importFrom: Trace<SelectedRelationNameT, RelationSchemasT, VariantsT>; definitionModifications: TraceDefinitionModifications<SelectedRelationNameT, RelationSchemasT, VariantsT>; }); /** * all requiredSpans implicitly interrupt the trace if they error, unless explicitly ignored */ mapRequiredSpanMatchersToInterruptOnMatchers(requiredSpans: readonly SpanMatcherFn<SelectedRelationNameT, RelationSchemasT, VariantsT>[]): readonly SpanMatcherFn<SelectedRelationNameT, RelationSchemasT, VariantsT>[]; sideEffectFns: TraceStateMachineSideEffectHandlers<RelationSchemasT>; onEnd(traceRecording: TraceRecording<SelectedRelationNameT, RelationSchemasT>): void; interrupt(reason: TraceInterruptionReason): void; transitionDraftToActive(inputAndDefinitionModifications: TraceModifications<SelectedRelationNameT, RelationSchemasT, VariantsT>): void; /** * The additions to the definition may come from either the variant at transition from draft to active * @param definitionModifications */ private applyDefinitionModifications; /** * This is used for importing spans when recreating a Trace from another Trace * if the definition was modified */ private replayItems; processSpan(span: Span<RelationSchemasT>): SpanAnnotationRecord | undefined; private getSpanLabels; } export type AllPossibleTraces<RelationSchemasT extends RelationSchemasBase<RelationSchemasT>> = Trace<any, RelationSchemasT, any>; export {};