lag-monitor
Version:
A lightweight utility for monitoring JavaScript event loop lag in real-time
255 lines (250 loc) • 7.02 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
CircularBuffer: () => CircularBuffer_default,
LagMonitor: () => LagMonitor_default,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
// 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;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CircularBuffer,
LagMonitor
});
/**
* 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
*/