UNPKG

lag-monitor

Version:

A lightweight utility for monitoring JavaScript event loop lag in real-time

227 lines (224 loc) 5.98 kB
// src/CircularBuffer.ts var CircularBuffer = class { constructor(capacity) { this.capacity = capacity; this.index = 0; this.filled = 0; this.buffer = new Array(capacity); } push(item) { this.buffer[this.index] = item; this.index = (this.index + 1) % this.capacity; this.filled = Math.min(this.filled + 1, this.capacity); } get latest() { if (this.filled === 0) return void 0; const lastIndex = (this.index - 1 + this.capacity) % this.capacity; return this.buffer[lastIndex]; } toArray() { if (this.filled < this.capacity) { return this.buffer.slice(0, this.filled); } return [ ...this.buffer.slice(this.index), ...this.buffer.slice(0, this.index) ]; } get length() { return this.filled; } clear() { this.index = 0; this.filled = 0; } }; var CircularBuffer_default = CircularBuffer; // src/LagMonitor.ts var DEFAULT_OPTIONS = { lagType: "setTimeout", sampleRate: 100, sampleCount: 10, callback: void 0, autoStart: true }; var LagMonitor = class { constructor(options = DEFAULT_OPTIONS) { this.isActive = false; this.timerId = null; this.lastTimestamp = void 0; this.options = Object.assign({}, DEFAULT_OPTIONS, options); this.userMetrics = options.userMetrics || {}; this.lagSamples = new CircularBuffer_default( this.options.sampleCount || DEFAULT_OPTIONS.sampleCount ); if (this.options.autoStart) { this.start(); } } // Built-in getters get latest() { return this.lagSamples.latest || 0; } get average() { return this.lagSamples.toArray().reduce((sum, val) => sum + val, 0) / this.lagSamples.length; } get min() { return Math.min(...this.lagSamples.toArray()); } get max() { return Math.max(...this.lagSamples.toArray()); } scheduleNext(fn, delay) { this.clearTimer(); if (this.options.lagType === "requestAnimationFrame") { if (typeof requestAnimationFrame !== "undefined") { return requestAnimationFrame(fn); } throw new Error( "requestAnimationFrame is not supported in this environment. Use setTimeout or setInterval instead." ); } else if (this.options.lagType === "setInterval") { return setInterval(fn, delay); } else { return setTimeout(fn, delay); } } tick() { if (!this.isActive) return; const now = performance.now(); const lag = this.lastTimestamp ? Math.max( 0, now - this.lastTimestamp - (this.options.sampleRate || DEFAULT_OPTIONS.sampleRate) ) : 0; this.lastTimestamp = now; this.lagSamples.push(lag); if (this.options.callback) { try { this.options.callback(lag, this.snapshot()); } catch (error) { console.warn("Lag monitor callback error:", error); } } this.clearTimer(); this.timerId = this.scheduleNext( () => this.tick(), // Anon fxn in order to preserve `this` context this.options.sampleRate || DEFAULT_OPTIONS.sampleRate ); } clearTimer() { if (this.timerId) { if (this.options.lagType === "requestAnimationFrame") { cancelAnimationFrame(this.timerId); } else if (this.options.lagType === "setInterval") { clearInterval(this.timerId); } else { clearTimeout(this.timerId); } this.timerId = null; } } /** * Get a metric value by name - handles both built-in and user-defined metrics */ getMetricValue(metric) { const userMetric = this.userMetrics[metric]; if (userMetric) { try { return userMetric(this.lagSamples.toArray()); } catch (error) { console.warn(`Error computing user metric '${metric}':`, error); return void 0; } } switch (metric) { case "latest": return this.latest; case "average": return this.average; case "min": return this.min; case "max": return this.max; case "samples": return this.lagSamples.length; } return void 0; } /** * Get all available metric functions (built-in + user-defined) */ getAvailableMetrics() { const builtInMetrics = ["latest", "average", "min", "max", "samples"]; const userMetricNames = Object.keys(this.userMetrics); return [...builtInMetrics, ...userMetricNames]; } /** * Compute a specific metric or all metrics */ snapshot(metric) { if (metric) { const value = this.getMetricValue(metric); return { [metric]: value }; } const result = {}; this.getAvailableMetrics().forEach((metricName) => { result[metricName] = this.getMetricValue(metricName); }); return result; } start() { if (this.isActive) { console.warn("Lag monitor is already running"); return; } this.isActive = true; this.lastTimestamp = performance.now(); this.timerId = this.scheduleNext( () => this.tick(), this.options.sampleRate || DEFAULT_OPTIONS.sampleRate ); } stop() { if (!this.isActive) { console.warn("Lag monitor is not running"); return; } this.isActive = false; this.clearTimer(); this.lastTimestamp = void 0; } reset() { this.lagSamples.clear(); this.lastTimestamp = performance.now(); this.isActive = false; this.clearTimer(); this.timerId = null; } restart() { this.reset(); this.start(); } isRunning() { return this.isActive; } }; var LagMonitor_default = LagMonitor; // src/index.ts var index_default = LagMonitor_default; export { CircularBuffer_default as CircularBuffer, LagMonitor_default as LagMonitor, index_default as default }; /** * Event Loop Lag Monitor * * A lightweight utility for monitoring JavaScript event loop lag in real-time. * Useful for performance monitoring, debugging, and alerting on event loop blocking. * * @author Your Name * @version 1.0.0 * @license MIT */