qnce-engine
Version:
Core QNCE (Quantum Narrative Convergence Engine) - Framework agnostic narrative engine with performance optimization
281 lines • 9.7 kB
JavaScript
;
// 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