UNPKG

speedact

Version:

A modern library for measuring React component performance

411 lines (409 loc) 14.2 kB
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