@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
TypeScript
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 {};