/** * * Copyright 2020-2026 Splunk Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import { Tracer } from '@opentelemetry/api'; import { SplunkOtelWebConfig } from '../../types'; export type ThrashedCursorOptions = Partial | true; type ResolvedThrashedCursorConfig = { ignoreUrls: Array; maxConfinedAreaSize: number; maxVelocity: number; minAverageVelocity: number; minDirectionChangeDegrees: number; minDirectionChanges: number; minMovementDistance: number; minTotalDistance: number; scoreWeightConfinedArea: number; scoreWeightDirectionChanges: number; scoreWeightVelocity: number; thrashingScoreThreshold: number; throttleMs: number; timeWindowMs: number; }; export declare class ThrashedCursorDetector { private tracer; private otelConfig; private config; /** * Reusable result objects, mutated in place on each analysis pass. * * Performance: These objects are allocated once and reused across all * mousemove events instead of creating new objects on each call. * Since the analysis runs synchronously and results are consumed before * the next event, there is no risk of data races. */ private readonly analysisResult; private readonly boundingBoxResult; /** * Ring buffer capacity, computed from config at construction time. * Sized to hold 2x the number of samples that fit in the analysis time window * at the configured throttle rate, ensuring enough headroom for samples * that haven't aged out yet. */ private readonly bufferCapacity; /** * Cached dead zone distance, scaled by devicePixelRatio. * Movements smaller than this threshold are ignored to filter out noise/jitter. * Computed once on enable() to avoid repeated multiplication on every sample. */ private cachedDeadZone; private hasLastMouse; private lastMouseX; private lastMouseY; private lastSampleTime; private lastThrashingTime; private listener?; private readonly metricsResult; /** * Pre-allocated fixed-size circular buffer for mouse position samples. * * Performance: Using a ring buffer instead of Array.push() + Array.filter() * avoids per-event heap allocations and GC pressure. The mousemove handler * can fire at ~60 events/sec, so eliminating object creation on each event * prevents frequent minor GC pauses that could cause UI jank. */ private ringBuffer; private sampleCount; private writeIndex; private constructor(); static create(tracer: Tracer, otelConfig: SplunkOtelWebConfig): ThrashedCursorDetector | undefined; disable(): void; enable(): void; private static normalizeConfig; private static resolveConfig; private addSample; /** * Single-pass analysis over the ring buffer. * Computes metrics, bounding box, and scoring without temporary allocations. * Returns the reusable analysisResult (consumed synchronously by the caller * before the next mousemove overwrites it). */ private analyzeThrashing; /** * Single-pass metrics and bounding box calculation over windowed ring buffer samples. * * Performance: computes distance, velocity, direction changes, and bounding box * in a single iteration over the buffer instead of separate passes. This reduces * the iteration count from 3-4x to 1x, which matters at ~60 calls/sec. * Mutates metricsResult and boundingBoxResult in place (see reusable result objects). */ private calculateMetrics; private calculateThrashingScore; private getSampleIndex; private handleMouseMove; private initRingBuffer; private onThrashingDetected; private resetState; private setAnalysisResult; } export {};