speedact
Version:
A modern library for measuring React component performance
411 lines (409 loc) • 14.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { jsx, Fragment } from "react/jsx-runtime";
import { useEffect, useCallback, Profiler } from "react";
var __defProp2 = Object.defineProperty;
var __defNormalProp = /* @__PURE__ */ __name((obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value, "__defNormalProp");
var __publicField = /* @__PURE__ */ __name((obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value), "__publicField");
const _MetricsCalculator = class _MetricsCalculator {
constructor(maxSamples = 100) {
__publicField(this, "renders", []);
__publicField(this, "maxSamples");
__publicField(this, "head", 0);
__publicField(this, "size", 0);
this.maxSamples = Math.max(1, Math.min(1e3, maxSamples));
this.renders = new Array(this.maxSamples);
}
addRender(renderInfo) {
try {
if (!renderInfo || typeof renderInfo.duration !== "number") {
if (process.env.NODE_ENV !== "production") {
console.warn("SpeedAct: Invalid render info provided");
}
return;
}
if (renderInfo.duration < 0 || !isFinite(renderInfo.duration)) {
if (process.env.NODE_ENV !== "production") {
console.warn(
"SpeedAct: Invalid render duration:",
renderInfo.duration
);
}
return;
}
this.renders[this.head] = { ...renderInfo };
this.head = (this.head + 1) % this.maxSamples;
if (this.size < this.maxSamples) {
this.size++;
}
} catch (error) {
console.error("SpeedAct: Error adding render info:", error);
}
}
getMetrics(componentName) {
var _a, _b;
try {
if (this.size === 0) {
return this.getEmptyMetrics(componentName);
}
const durations = this.getValidDurations();
if (durations.length === 0) {
return this.getEmptyMetrics(componentName);
}
const totalRenderTime = durations.reduce(
(sum, duration) => sum + duration,
0
);
const averageRenderTime = totalRenderTime / durations.length;
const longestRenderTime = Math.max(...durations);
const shortestRenderTime = Math.min(...durations);
const lastRenderTime = (_b = (_a = this.getLastValidRender()) == null ? void 0 : _a.duration) != null ? _b : 0;
const metrics = {
renderCount: this.size,
totalRenderTime: Number(totalRenderTime.toFixed(3)),
averageRenderTime: Number(averageRenderTime.toFixed(3)),
longestRenderTime: Number(longestRenderTime.toFixed(3)),
shortestRenderTime: Number(shortestRenderTime.toFixed(3)),
lastRenderTime: Number(lastRenderTime.toFixed(3)),
timestamp: Date.now()
};
if (componentName) {
metrics.componentName = componentName;
}
return metrics;
} catch (error) {
console.error("SpeedAct: Error calculating metrics:", error);
return this.getEmptyMetrics(componentName);
}
}
getValidDurations() {
const durations = [];
const actualSize = Math.min(this.size, this.maxSamples);
for (let i = 0; i < actualSize; i++) {
const render = this.renders[i];
if (render && typeof render.duration === "number" && render.duration >= 0 && isFinite(render.duration)) {
durations.push(render.duration);
}
}
return durations;
}
getLastValidRender() {
const actualSize = Math.min(this.size, this.maxSamples);
for (let i = 1; i <= actualSize; i++) {
const index = (this.head - i + this.maxSamples) % this.maxSamples;
const render = this.renders[index];
if (render && typeof render.duration === "number" && render.duration >= 0 && isFinite(render.duration)) {
return render;
}
}
return null;
}
getEmptyMetrics(componentName) {
const metrics = {
renderCount: 0,
totalRenderTime: 0,
averageRenderTime: 0,
longestRenderTime: 0,
shortestRenderTime: 0,
lastRenderTime: 0,
timestamp: Date.now()
};
if (componentName) {
metrics.componentName = componentName;
}
return metrics;
}
reset() {
try {
this.renders = new Array(this.maxSamples);
this.head = 0;
this.size = 0;
} catch (error) {
console.error("SpeedAct: Error resetting metrics calculator:", error);
}
}
getRenderHistory() {
try {
const history = [];
const actualSize = Math.min(this.size, this.maxSamples);
for (let i = 0; i < actualSize; i++) {
const render = this.renders[i];
if (render) {
history.push({ ...render });
}
}
return history.sort((a, b) => a.startTime - b.startTime);
} catch (error) {
console.error("SpeedAct: Error getting render history:", error);
return [];
}
}
getMemoryUsage() {
try {
const usedSlots = this.size;
const totalSlots = this.maxSamples;
const memoryEfficiency = totalSlots > 0 ? usedSlots / totalSlots * 100 : 0;
return {
usedSlots,
totalSlots,
memoryEfficiency: Number(memoryEfficiency.toFixed(2))
};
} catch (error) {
console.error("SpeedAct: Error calculating memory usage:", error);
return { usedSlots: 0, totalSlots: 0, memoryEfficiency: 0 };
}
}
};
__name(_MetricsCalculator, "MetricsCalculator");
let MetricsCalculator = _MetricsCalculator;
const _PerformanceLogger = class _PerformanceLogger {
static log(metrics, threshold) {
var _a;
try {
if (!metrics || typeof metrics.lastRenderTime !== "number") {
return;
}
const logLevel = this.determineLogLevel(
metrics.lastRenderTime,
threshold
);
const message = `SpeedAct [${(_a = metrics.componentName) != null ? _a : "Unknown"}]: ${metrics.lastRenderTime.toFixed(
2
)}ms (avg: ${metrics.averageRenderTime.toFixed(2)}ms, renders: ${metrics.renderCount})`;
switch (logLevel) {
case "error":
console.error(`🔴 ${message}`);
break;
case "warn":
console.warn(`🟡 ${message}`);
break;
case "info":
console.log(`🟢 ${message}`);
break;
default:
console.log(message);
}
if (threshold && metrics.lastRenderTime > threshold * 2) {
console.group("Performance Analysis");
console.log(this.generateAnalysis(metrics));
console.groupEnd();
}
} catch (error) {
console.error("SpeedAct: Error logging performance metrics:", error);
}
}
static determineLogLevel(duration, threshold) {
const effectiveThreshold = threshold != null ? threshold : 16;
if (duration > effectiveThreshold * 3) return "error";
if (duration > effectiveThreshold) return "warn";
return "info";
}
static generateReport(metrics) {
var _a;
try {
const componentName = (_a = metrics.componentName) != null ? _a : "Unknown Component";
let report = `Performance Report: ${componentName}
`;
report += "=".repeat(40) + "\n";
report += `Render Count: ${metrics.renderCount}
`;
report += `Total Time: ${metrics.totalRenderTime.toFixed(2)}ms
`;
report += `Average Time: ${metrics.averageRenderTime.toFixed(2)}ms
`;
report += `Longest Render: ${metrics.longestRenderTime.toFixed(2)}ms
`;
report += `Shortest Render: ${metrics.shortestRenderTime.toFixed(2)}ms
`;
report += `Last Render: ${metrics.lastRenderTime.toFixed(2)}ms
`;
report += `Timestamp: ${new Date(metrics.timestamp).toISOString()}
`;
report += this.generateAnalysis(metrics);
return report;
} catch (error) {
console.error("SpeedAct: Error generating report:", error);
return "Error generating performance report";
}
}
static generateAnalysis(metrics) {
try {
const analysis = [];
const grade = this.calculateGrade(metrics.averageRenderTime);
analysis.push(`Performance Grade: ${grade}`);
const variance = metrics.longestRenderTime - metrics.shortestRenderTime;
const consistency = this.calculateConsistency(variance);
analysis.push(`Consistency: ${consistency}`);
if (metrics.averageRenderTime > 16) {
analysis.push("⚠️ Consider optimizing render performance");
}
if (variance > 50) {
analysis.push("⚠️ High render time variance detected");
}
if (metrics.renderCount > 100) {
analysis.push("ℹ️ High render count - consider memoization");
}
return analysis.join("\n");
} catch (error) {
console.error("SpeedAct: Error generating analysis:", error);
return "Analysis unavailable";
}
}
static calculateGrade(averageTime) {
if (averageTime < 16) return "A (Excellent)";
if (averageTime < 33) return "B (Good)";
if (averageTime < 50) return "C (Fair)";
return "D (Poor)";
}
static calculateConsistency(variance) {
if (variance < 5) return "Excellent";
if (variance < 20) return "Good";
if (variance < 50) return "Fair";
return "Poor";
}
};
__name(_PerformanceLogger, "PerformanceLogger");
let PerformanceLogger = _PerformanceLogger;
function isPerformanceAPIAvailable() {
try {
return typeof performance !== "undefined" && typeof performance.now === "function" && typeof performance.mark === "function" && typeof performance.measure === "function";
} catch (e) {
return false;
}
}
__name(isPerformanceAPIAvailable, "isPerformanceAPIAvailable");
const componentMetrics = /* @__PURE__ */ new Map();
const componentCleanupTimers = /* @__PURE__ */ new Map();
const CLEANUP_DELAY = 5 * 60 * 1e3;
function scheduleCleanup(componentName) {
const existingTimer = componentCleanupTimers.get(componentName);
if (existingTimer) {
clearTimeout(existingTimer);
}
const timer = setTimeout(() => {
componentMetrics.delete(componentName);
componentCleanupTimers.delete(componentName);
}, CLEANUP_DELAY);
componentCleanupTimers.set(componentName, timer);
}
__name(scheduleCleanup, "scheduleCleanup");
function cancelCleanup(componentName) {
const timer = componentCleanupTimers.get(componentName);
if (timer) {
clearTimeout(timer);
componentCleanupTimers.delete(componentName);
}
}
__name(cancelCleanup, "cancelCleanup");
function PerformanceMonitor({
children,
componentName = "PerformanceMonitor",
onMetricsUpdate,
logToConsole = false,
threshold = 16,
enabled = true,
maxSamples = 100
}) {
useEffect(() => {
if (enabled && !componentMetrics.has(componentName)) {
componentMetrics.set(componentName, new MetricsCalculator(maxSamples));
}
if (enabled) {
cancelCleanup(componentName);
}
return () => {
if (enabled) {
scheduleCleanup(componentName);
}
};
}, [componentName, maxSamples, enabled]);
const onRenderCallback = useCallback(
(_id, _phase, actualDuration, _baseDuration, startTime, commitTime) => {
try {
if (!enabled || !isPerformanceAPIAvailable()) {
return;
}
const calculator = componentMetrics.get(componentName);
if (!calculator) return;
const renderInfo = {
startTime,
endTime: commitTime,
duration: actualDuration,
renderCount: calculator.getMetrics().renderCount + 1,
componentName
};
calculator.addRender(renderInfo);
const metrics = calculator.getMetrics(componentName);
if (logToConsole) {
PerformanceLogger.log(metrics, threshold);
}
if (onMetricsUpdate) {
onMetricsUpdate(metrics);
}
} catch (error) {
console.error("Error in PerformanceMonitor callback:", error);
}
},
[componentName, enabled, logToConsole, threshold, onMetricsUpdate]
);
if (!enabled) {
return /* @__PURE__ */ jsx(Fragment, { children });
}
return /* @__PURE__ */ jsx(Profiler, { id: componentName, onRender: onRenderCallback, children });
}
__name(PerformanceMonitor, "PerformanceMonitor");
const PerformanceUtils = {
getComponentMetrics: /* @__PURE__ */ __name((componentName) => {
const calculator = componentMetrics.get(componentName);
return calculator ? calculator.getMetrics(componentName) : null;
}, "getComponentMetrics"),
resetComponentMetrics: /* @__PURE__ */ __name((componentName) => {
const calculator = componentMetrics.get(componentName);
if (calculator) {
calculator.reset();
}
}, "resetComponentMetrics"),
getAllComponentNames: /* @__PURE__ */ __name(() => {
return Array.from(componentMetrics.keys());
}, "getAllComponentNames"),
getAllMetrics: /* @__PURE__ */ __name(() => {
const allMetrics = {};
for (const [name, calculator] of componentMetrics.entries()) {
allMetrics[name] = calculator.getMetrics(name);
}
return allMetrics;
}, "getAllMetrics"),
resetAllMetrics: /* @__PURE__ */ __name(() => {
for (const calculator of componentMetrics.values()) {
calculator.reset();
}
}, "resetAllMetrics"),
generateGlobalReport: /* @__PURE__ */ __name(() => {
const allMetrics = PerformanceUtils.getAllMetrics();
let report = "Global Performance Report\n";
report += "========================\n\n";
for (const [, metrics] of Object.entries(allMetrics)) {
report += PerformanceLogger.generateReport(metrics);
report += "\n\n";
}
return report;
}, "generateGlobalReport"),
forceCleanup: /* @__PURE__ */ __name(() => {
componentMetrics.clear();
for (const timer of componentCleanupTimers.values()) {
clearTimeout(timer);
}
componentCleanupTimers.clear();
}, "forceCleanup"),
getActiveComponentsCount: /* @__PURE__ */ __name(() => {
return componentMetrics.size;
}, "getActiveComponentsCount")
};
export {
MetricsCalculator,
PerformanceLogger,
PerformanceMonitor,
PerformanceUtils,
isPerformanceAPIAvailable
};
//# sourceMappingURL=index.es.js.map