UNPKG

qnce-engine

Version:

Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization

281 lines 9.7 kB
"use strict"; // S2-T4: Profiler Event Instrumentation // PerfReporter for batched performance event collection and reporting Object.defineProperty(exports, "__esModule", { value: true }); exports.perf = exports.PerfReporter = void 0; exports.getPerfReporter = getPerfReporter; exports.shutdownPerfReporter = shutdownPerfReporter; /** * PerfReporter - Batched performance event collection and analysis * Designed to work off main thread for minimal performance impact */ class PerfReporter { events = []; config; flushTimer = null; startTime; activeSpans = new Map(); constructor(config = {}) { this.config = { batchSize: config.batchSize || 100, flushInterval: config.flushInterval || 5000, // 5 seconds enableBackgroundFlush: config.enableBackgroundFlush !== false, maxEventHistory: config.maxEventHistory || 1000, enableConsoleOutput: config.enableConsoleOutput || false }; this.startTime = performance.now(); if (this.config.enableBackgroundFlush) { this.startFlushTimer(); } } /** * Record a performance event */ record(type, metadata = {}, category = 'engine') { const event = { id: this.generateEventId(), type, timestamp: performance.now(), metadata, category }; this.events.push(event); if (this.config.enableConsoleOutput) { console.log(`[PERF] ${type}:`, metadata); } // Auto-flush if batch size reached if (this.events.length >= this.config.batchSize) { this.flush(); } return event.id; } /** * Start a performance span (for measuring duration) */ startSpan(type, metadata = {}, category = 'engine') { const spanId = this.generateEventId(); const event = { id: spanId, type, timestamp: performance.now(), metadata: { ...metadata, spanStart: true }, category }; this.activeSpans.set(spanId, event); return spanId; } /** * End a performance span and record the complete event */ endSpan(spanId, additionalMetadata = {}) { const startEvent = this.activeSpans.get(spanId); if (!startEvent) { console.warn(`[PERF] Span ${spanId} not found`); return; } const endTime = performance.now(); const duration = endTime - startEvent.timestamp; const completeEvent = { ...startEvent, duration, metadata: { ...startEvent.metadata, ...additionalMetadata, spanEnd: true } }; this.events.push(completeEvent); this.activeSpans.delete(spanId); if (this.config.enableConsoleOutput) { console.log(`[PERF] ${startEvent.type} completed in ${duration.toFixed(2)}ms`); } // Auto-flush if batch size reached if (this.events.length >= this.config.batchSize) { this.flush(); } } /** * Record flow start event (S2-T4 requirement) */ recordFlowStart(nodeId, metadata = {}) { return this.startSpan('flow-start', { nodeId, ...metadata }, 'engine'); } /** * Record flow completion event (S2-T4 requirement) */ recordFlowComplete(spanId, nextNodeId, metadata = {}) { this.endSpan(spanId, { nextNodeId, ...metadata }); } /** * Record cache hit event (S2-T4 requirement) */ recordCacheHit(cacheKey, metadata = {}) { return this.record('cache-hit', { cacheKey, ...metadata }, 'cache'); } /** * Record cache miss event (S2-T4 requirement) */ recordCacheMiss(cacheKey, metadata = {}) { return this.record('cache-miss', { cacheKey, ...metadata }, 'cache'); } /** * Record hot-reload start event (S2-T4 requirement) */ recordHotReloadStart(deltaSize, metadata = {}) { return this.startSpan('hot-reload-start', { deltaSize, ...metadata }, 'hot-reload'); } /** * Record hot-reload completion event (S2-T4 requirement) */ recordHotReloadEnd(spanId, success, metadata = {}) { this.endSpan(spanId, { success, ...metadata }); } /** * Generate performance summary for CLI dashboard */ summary() { const now = performance.now(); const eventsByType = {}; const durationsByType = {}; let cacheHits = 0; let cacheMisses = 0; let hotReloads = []; // Analyze events for (const event of this.events) { // Count events by type eventsByType[event.type] = (eventsByType[event.type] || 0) + 1; // Collect durations if (event.duration !== undefined) { if (!durationsByType[event.type]) { durationsByType[event.type] = []; } durationsByType[event.type].push(event.duration); } // Cache metrics if (event.type === 'cache-hit') cacheHits++; if (event.type === 'cache-miss') cacheMisses++; // Hot-reload metrics if (event.type === 'hot-reload-start' && event.duration !== undefined) { hotReloads.push(event.duration); } } // Calculate averages, mins, maxs const avgDurations = {}; const maxDurations = {}; const minDurations = {}; for (const [type, durations] of Object.entries(durationsByType)) { if (durations.length > 0) { avgDurations[type] = durations.reduce((sum, d) => sum + d, 0) / durations.length; maxDurations[type] = Math.max(...durations); minDurations[type] = Math.min(...durations); } } // Cache hit rate const totalCacheEvents = cacheHits + cacheMisses; const cacheHitRate = totalCacheEvents > 0 ? (cacheHits / totalCacheEvents) * 100 : 0; // Hot-reload performance const hotReloadPerformance = { avgTime: hotReloads.length > 0 ? hotReloads.reduce((sum, d) => sum + d, 0) / hotReloads.length : 0, maxTime: hotReloads.length > 0 ? Math.max(...hotReloads) : 0, totalReloads: hotReloads.length }; return { totalEvents: this.events.length, eventsByType, avgDurations, maxDurations, minDurations, cacheHitRate, hotReloadPerformance, timeRange: { start: this.startTime, end: now, duration: now - this.startTime } }; } /** * Get raw events for detailed analysis */ getEvents() { return [...this.events]; } /** * Clear event history */ clear() { this.events.length = 0; this.activeSpans.clear(); this.startTime = performance.now(); } /** * Flush events to background processing */ flush() { if (this.events.length === 0) return; // TODO: Body - integrate with ThreadPool for background processing // For now, just maintain event history with size limit if (this.events.length > this.config.maxEventHistory) { const excess = this.events.length - this.config.maxEventHistory; this.events.splice(0, excess); } if (this.config.enableConsoleOutput) { console.log(`[PERF] Flushed ${this.events.length} events`); } } /** * Start automatic flush timer */ startFlushTimer() { this.flushTimer = setInterval(() => { this.flush(); }, this.config.flushInterval); } /** * Stop automatic flush timer */ stopFlushTimer() { if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } } /** * Generate unique event ID */ generateEventId() { return `perf-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } } exports.PerfReporter = PerfReporter; // Singleton instance for global access let globalPerfReporter = null; function getPerfReporter(config) { if (!globalPerfReporter) { globalPerfReporter = new PerfReporter(config); } return globalPerfReporter; } function shutdownPerfReporter() { if (globalPerfReporter) { globalPerfReporter.stopFlushTimer(); globalPerfReporter.flush(); globalPerfReporter = null; } } // Convenience functions for common operations exports.perf = { flowStart: (nodeId, metadata) => getPerfReporter().recordFlowStart(nodeId, metadata), flowComplete: (spanId, nextNodeId, metadata) => getPerfReporter().recordFlowComplete(spanId, nextNodeId, metadata), cacheHit: (cacheKey, metadata) => getPerfReporter().recordCacheHit(cacheKey, metadata), cacheMiss: (cacheKey, metadata) => getPerfReporter().recordCacheMiss(cacheKey, metadata), hotReloadStart: (deltaSize, metadata) => getPerfReporter().recordHotReloadStart(deltaSize, metadata), hotReloadEnd: (spanId, success, metadata) => getPerfReporter().recordHotReloadEnd(spanId, success, metadata), record: (type, metadata, category) => getPerfReporter().record(type, metadata, category), summary: () => getPerfReporter().summary(), clear: () => getPerfReporter().clear() }; //# sourceMappingURL=PerfReporter.js.map