Files
arcum42-ComfyUI_SageUtils/js/shared/performanceTimer.js
T
2026-06-20 21:09:30 -07:00

208 lines
8.3 KiB
JavaScript

/**
* Performance timing utilities for SageUtils JavaScript side.
* Provides initialization timing milestones and optional runtime profiling.
*/
class PerformanceTimer {
constructor(name = "SageUtils") {
this.name = name;
this.timings = new Map();
this.initializationTimes = new Map();
this.startTimes = new Map();
this.enabled = true;
this.initStartTime = performance.now();
this.initializationComplete = false;
}
enable() { this.enabled = true; }
disable() { this.enabled = false; }
startTimer(operation) {
if (!this.enabled) return;
this.startTimes.set(operation, performance.now());
}
endTimer(operation) {
if (!this.enabled) return 0.0;
if (!this.startTimes.has(operation)) {
if (shouldLogTimingDetails()) console.warn(`[TIMING] Timer '${operation}' was never started`);
return 0.0;
}
const duration = performance.now() - this.startTimes.get(operation);
if (!this.timings.has(operation)) this.timings.set(operation, []);
this.timings.get(operation).push(duration);
this.startTimes.delete(operation);
return duration;
}
async timeFunction(operation, func) {
if (!this.enabled) return await func();
const startTime = performance.now();
try { return await func(); }
finally {
const duration = performance.now() - startTime;
if (!this.timings.has(operation)) this.timings.set(operation, []);
this.timings.get(operation).push(duration);
}
}
recordInitializationMilestone(milestone) {
if (!this.enabled) return;
const currentTime = performance.now();
const durationFromStart = currentTime - this.initStartTime;
this.initializationTimes.set(milestone, durationFromStart);
if (this.enabled && shouldLogTimingDetails()) {
console.log(`[TIMING] Initialization milestone '${milestone}': ${durationFromStart.toFixed(4)}ms from start`);
}
}
completeInitialization() {
if (!this.enabled) return 0.0;
if (this.initializationComplete) return this.getTotalInitializationTime();
const totalTime = performance.now() - this.initStartTime;
this.initializationTimes.set('__complete__', totalTime);
this.initializationComplete = true;
if (this.enabled && shouldLogTimingDetails()) {
console.log(`[TIMING] Total initialization time: ${totalTime.toFixed(4)}ms`);
}
return totalTime;
}
getTotalInitializationTime() {
if (this.initializationTimes.has('__complete__')) return this.initializationTimes.get('__complete__');
if (this.initializationTimes.size > 0) return Math.max(...Array.from(this.initializationTimes.values()));
return 0.0;
}
getStats(operation) {
const times = this.timings.get(operation);
if (!times || times.length === 0) return {};
const total = times.reduce((a, b) => a + b, 0);
return { count: times.length, total, average: total / times.length, min: Math.min(...times), max: Math.max(...times), last: times[times.length - 1] };
}
getAllStats() {
const stats = {};
for (const op of this.timings.keys()) stats[op] = this.getStats(op);
return stats;
}
getFullReport() {
const parts = [];
// Initialization report
const sortedMilestones = Array.from(this.initializationTimes.entries())
.filter(([key]) => key !== '__complete__')
.sort(([, a], [, b]) => a - b);
if (sortedMilestones.length > 0) {
parts.push(`\n=== ${this.name} Initialization Timing ===`);
let prevTime = 0;
for (const [milestone, totalTime] of sortedMilestones) {
const incremental = totalTime - prevTime;
parts.push(` ${milestone}: ${totalTime.toFixed(2)}ms (+${incremental.toFixed(2)}ms)`);
prevTime = totalTime;
}
}
if (this.initializationTimes.has('__complete__')) {
parts.push(` TOTAL: ${this.initializationTimes.get('__complete__').toFixed(2)}ms`);
}
// Runtime report
const sortedOps = Array.from(this.timings.keys())
.map(op => [op, this.getStats(op)])
.filter(([, stats]) => Object.keys(stats).length > 0)
.sort(([, a], [, b]) => (b.total || 0) - (a.total || 0));
if (sortedOps.length > 0) {
parts.push(`\n=== ${this.name} Runtime Performance ===`);
for (const [operation, stats] of sortedOps) {
parts.push(` ${operation}: calls=${stats.count} total=${stats.total.toFixed(2)}ms avg=${stats.average.toFixed(2)}ms`);
}
}
return parts.join('\n');
}
printReport() { console.log(this.getFullReport()); }
reset() {
this.timings.clear();
this.initializationTimes.clear();
this.startTimes.clear();
this.initStartTime = performance.now();
this.initializationComplete = false;
}
exportToObject() {
return {
name: this.name,
initializationTimes: Object.fromEntries(this.initializationTimes),
runtimeStats: this.getAllStats(),
totalInitializationTime: this.getTotalInitializationTime(),
initializationComplete: this.initializationComplete,
timestamp: new Date().toISOString()
};
}
async sendTimingDataToServer() {
if (!this.enabled) return;
try {
const response = await fetch('/sage_utils/timing_data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'javascript', data: this.exportToObject() })
});
if (!response.ok && shouldLogTimingDetails()) {
console.warn('[TIMING] Failed to send timing data:', response.statusText);
}
} catch (error) {
if (shouldLogTimingDetails()) console.warn('[TIMING] Error sending timing data:', error);
}
}
}
// Runtime flag readers (lazy, only called when settings dialog is opened)
function readRuntimeTimingFlags() {
const params = new URLSearchParams(window.location.search);
return {
perfMonitoring: localStorage.getItem('sageutils_perf_monitoring') === 'true' || params.get('sageutils_perf') === '1',
sendTiming: localStorage.getItem('sageutils_send_timing') === 'true' || params.get('sageutils_timing') === '1',
printTiming: localStorage.getItem('sageutils_print_timing') === 'true' || params.get('sageutils_timing_print') === '1'
};
}
function isPerfMonitoringEnabled() { return readRuntimeTimingFlags().perfMonitoring; }
function shouldSendTimingData() { return readRuntimeTimingFlags().sendTiming; }
function shouldPrintTimingReport() { return readRuntimeTimingFlags().printTiming; }
function shouldLogTimingDetails() { const f = readRuntimeTimingFlags(); return f.perfMonitoring || f.printTiming; }
// Global timer instances — disabled by default unless explicitly enabled
const javascriptTimer = new PerformanceTimer("JavaScript");
const uiTimer = new PerformanceTimer("UI");
if (!isPerfMonitoringEnabled()) {
javascriptTimer.disable();
uiTimer.disable();
}
// Convenience functions (kept for backward compatibility)
function startTimer(operation, timer = javascriptTimer) { timer.startTimer(operation); }
function endTimer(operation, timer = javascriptTimer) { return timer.endTimer(operation); }
function timeFunction(operation, func, timer = javascriptTimer) { return timer.timeFunction(operation, func); }
function recordInitializationMilestone(milestone, timer = javascriptTimer) { timer.recordInitializationMilestone(milestone); }
function completeInitialization(timer = javascriptTimer) { return timer.completeInitialization(); }
function printTimingReport(timer = javascriptTimer) { timer.printReport(); }
// Export for use in other modules
export {
PerformanceTimer,
javascriptTimer,
uiTimer,
startTimer,
endTimer,
timeFunction,
recordInitializationMilestone,
completeInitialization,
printTimingReport,
isPerfMonitoringEnabled,
shouldSendTimingData,
shouldPrintTimingReport,
shouldLogTimingDetails
};