Files
michaelschiemer/resources/js/modules/performance-profiler/profiler.js
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

501 lines
14 KiB
JavaScript

/**
* LiveComponents Performance Profiler
*
* Provides detailed performance profiling with flamegraph and timeline visualization.
*
* Features:
* - Real-time performance metrics collection
* - Flamegraph generation for call stack visualization
* - Timeline visualization for event sequencing
* - Export to Chrome DevTools format
* - Integration with Performance API
*
* @module PerformanceProfiler
*/
export class PerformanceProfiler {
constructor(options = {}) {
this.enabled = options.enabled ?? false;
this.maxSamples = options.maxSamples ?? 1000;
this.samplingInterval = options.samplingInterval ?? 10; // ms
this.autoStart = options.autoStart ?? false;
this.samples = [];
this.timeline = [];
this.marks = new Map();
this.measures = new Map();
this.isRecording = false;
this.recordingStartTime = null;
if (this.autoStart && this.enabled) {
this.start();
}
}
/**
* Start performance profiling
*/
start() {
if (this.isRecording) {
console.warn('[PerformanceProfiler] Already recording');
return;
}
this.isRecording = true;
this.recordingStartTime = performance.now();
this.samples = [];
this.timeline = [];
console.log('[PerformanceProfiler] Started recording');
}
/**
* Stop performance profiling
* @returns {Object} Profiling results
*/
stop() {
if (!this.isRecording) {
console.warn('[PerformanceProfiler] Not recording');
return null;
}
this.isRecording = false;
const duration = performance.now() - this.recordingStartTime;
const results = {
duration,
samples: this.samples,
timeline: this.timeline,
marks: Array.from(this.marks.entries()),
measures: Array.from(this.measures.entries()),
summary: this.generateSummary()
};
console.log('[PerformanceProfiler] Stopped recording', results);
return results;
}
/**
* Mark a specific point in time
* @param {string} name - Mark name
* @param {Object} metadata - Additional metadata
*/
mark(name, metadata = {}) {
if (!this.isRecording && !this.enabled) return;
const timestamp = performance.now();
const mark = {
name,
timestamp,
relativeTime: timestamp - this.recordingStartTime,
metadata
};
this.marks.set(name, mark);
// Also use native Performance API
if (performance.mark) {
performance.mark(name);
}
// Add to timeline
this.timeline.push({
type: 'mark',
...mark
});
}
/**
* Measure duration between two marks
* @param {string} name - Measure name
* @param {string} startMark - Start mark name
* @param {string} endMark - End mark name (optional, defaults to now)
*/
measure(name, startMark, endMark = null) {
if (!this.isRecording && !this.enabled) return;
const start = this.marks.get(startMark);
if (!start) {
console.warn(`[PerformanceProfiler] Start mark "${startMark}" not found`);
return;
}
const endTime = endMark
? this.marks.get(endMark)?.timestamp
: performance.now();
if (!endTime) {
console.warn(`[PerformanceProfiler] End mark "${endMark}" not found`);
return;
}
const duration = endTime - start.timestamp;
const measure = {
name,
startMark,
endMark,
duration,
startTime: start.timestamp,
endTime,
relativeStartTime: start.relativeTime,
relativeEndTime: endTime - this.recordingStartTime
};
this.measures.set(name, measure);
// Native Performance API
if (performance.measure && endMark) {
try {
performance.measure(name, startMark, endMark);
} catch (e) {
// Ignore if marks don't exist in native API
}
}
// Add to timeline
this.timeline.push({
type: 'measure',
...measure
});
}
/**
* Record a sample for flamegraph
* @param {string} functionName - Function name
* @param {Array<string>} stackTrace - Call stack
* @param {number} duration - Execution duration
*/
sample(functionName, stackTrace = [], duration = 0) {
if (!this.isRecording) return;
if (this.samples.length >= this.maxSamples) {
console.warn('[PerformanceProfiler] Max samples reached');
return;
}
this.samples.push({
timestamp: performance.now(),
relativeTime: performance.now() - this.recordingStartTime,
functionName,
stackTrace,
duration
});
}
/**
* Generate flamegraph data
* @returns {Object} Flamegraph data structure
*/
generateFlamegraph() {
if (this.samples.length === 0) {
return null;
}
// Build call tree from samples
const root = {
name: '(root)',
value: 0,
children: []
};
for (const sample of this.samples) {
let currentNode = root;
const stack = [sample.functionName, ...sample.stackTrace].reverse();
for (const funcName of stack) {
let child = currentNode.children.find(c => c.name === funcName);
if (!child) {
child = {
name: funcName,
value: 0,
children: []
};
currentNode.children.push(child);
}
child.value += sample.duration || 1;
currentNode = child;
}
}
return root;
}
/**
* Generate timeline data for visualization
* @returns {Array} Timeline events
*/
generateTimeline() {
return this.timeline.map(event => ({
...event,
category: this.categorizeEvent(event),
color: this.getEventColor(event)
}));
}
/**
* Generate summary statistics
* @returns {Object} Summary data
*/
generateSummary() {
const durations = Array.from(this.measures.values()).map(m => m.duration);
if (durations.length === 0) {
return {
totalMeasures: 0,
totalSamples: this.samples.length,
totalDuration: 0
};
}
const totalDuration = durations.reduce((sum, d) => sum + d, 0);
const avgDuration = totalDuration / durations.length;
const maxDuration = Math.max(...durations);
const minDuration = Math.min(...durations);
// Calculate percentiles
const sortedDurations = [...durations].sort((a, b) => a - b);
const p50 = sortedDurations[Math.floor(sortedDurations.length * 0.5)];
const p90 = sortedDurations[Math.floor(sortedDurations.length * 0.9)];
const p99 = sortedDurations[Math.floor(sortedDurations.length * 0.99)];
return {
totalMeasures: this.measures.size,
totalSamples: this.samples.length,
totalDuration,
avgDuration,
maxDuration,
minDuration,
percentiles: { p50, p90, p99 }
};
}
/**
* Export to Chrome DevTools Performance format
* @returns {Object} Chrome DevTools trace event format
*/
exportToChromeTrace() {
const events = [];
// Add marks
for (const [name, mark] of this.marks.entries()) {
events.push({
name,
cat: 'mark',
ph: 'R', // Instant event
ts: mark.timestamp * 1000, // microseconds
pid: 1,
tid: 1,
args: mark.metadata
});
}
// Add measures as duration events
for (const [name, measure] of this.measures.entries()) {
events.push({
name,
cat: 'measure',
ph: 'B', // Begin
ts: measure.startTime * 1000,
pid: 1,
tid: 1
});
events.push({
name,
cat: 'measure',
ph: 'E', // End
ts: measure.endTime * 1000,
pid: 1,
tid: 1
});
}
// Add samples
for (const sample of this.samples) {
events.push({
name: sample.functionName,
cat: 'sample',
ph: 'X', // Complete event
ts: sample.timestamp * 1000,
dur: (sample.duration || 1) * 1000,
pid: 1,
tid: 1,
args: {
stackTrace: sample.stackTrace
}
});
}
return {
traceEvents: events.sort((a, b) => a.ts - b.ts),
displayTimeUnit: 'ms',
metadata: {
'clock-domain': 'PERFORMANCE_NOW'
}
};
}
/**
* Categorize timeline event
* @private
*/
categorizeEvent(event) {
if (event.type === 'mark') {
if (event.name.includes('action')) return 'action';
if (event.name.includes('render')) return 'render';
if (event.name.includes('state')) return 'state';
return 'other';
}
if (event.type === 'measure') {
if (event.duration > 100) return 'slow';
if (event.duration > 16) return 'normal';
return 'fast';
}
return 'unknown';
}
/**
* Get color for event visualization
* @private
*/
getEventColor(event) {
const colorMap = {
action: '#4CAF50',
render: '#2196F3',
state: '#FF9800',
slow: '#F44336',
normal: '#FFC107',
fast: '#8BC34A',
other: '#9E9E9E',
unknown: '#607D8B'
};
return colorMap[this.categorizeEvent(event)] || '#607D8B';
}
/**
* Clear all profiling data
*/
clear() {
this.samples = [];
this.timeline = [];
this.marks.clear();
this.measures.clear();
this.recordingStartTime = null;
// Clear native Performance API
if (performance.clearMarks) {
performance.clearMarks();
}
if (performance.clearMeasures) {
performance.clearMeasures();
}
}
/**
* Get current profiling status
* @returns {Object} Status information
*/
getStatus() {
return {
enabled: this.enabled,
isRecording: this.isRecording,
samplesCount: this.samples.length,
marksCount: this.marks.size,
measuresCount: this.measures.size,
timelineEventsCount: this.timeline.length,
recordingDuration: this.isRecording
? performance.now() - this.recordingStartTime
: 0
};
}
}
/**
* LiveComponents Performance Integration
*
* Automatic profiling for LiveComponents actions and lifecycle events
*/
export class LiveComponentsProfiler extends PerformanceProfiler {
constructor(component, options = {}) {
super(options);
this.component = component;
this.actionCallDepth = 0;
if (this.enabled) {
this.instrumentComponent();
}
}
/**
* Instrument component for automatic profiling
* @private
*/
instrumentComponent() {
// Hook into action calls
const originalCall = this.component.call.bind(this.component);
this.component.call = (actionName, params, options) => {
this.actionCallDepth++;
const markName = `action:${actionName}:${this.actionCallDepth}`;
this.mark(`${markName}:start`, {
actionName,
params,
depth: this.actionCallDepth
});
const result = originalCall(actionName, params, options);
// Handle promise results
if (result instanceof Promise) {
return result.finally(() => {
this.mark(`${markName}:end`);
this.measure(`action:${actionName}`, `${markName}:start`, `${markName}:end`);
this.actionCallDepth--;
});
}
this.mark(`${markName}:end`);
this.measure(`action:${actionName}`, `${markName}:start`, `${markName}:end`);
this.actionCallDepth--;
return result;
};
// Hook into state updates
if (this.component.state) {
const originalSet = this.component.state.set.bind(this.component.state);
this.component.state.set = (key, value) => {
this.mark(`state:set:${key}`, { key, value });
return originalSet(key, value);
};
}
// Hook into renders
if (this.component.render) {
const originalRender = this.component.render.bind(this.component);
this.component.render = () => {
this.mark('render:start');
const result = originalRender();
if (result instanceof Promise) {
return result.finally(() => {
this.mark('render:end');
this.measure('render', 'render:start', 'render:end');
});
}
this.mark('render:end');
this.measure('render', 'render:start', 'render:end');
return result;
};
}
}
}
// Export default instance
export default PerformanceProfiler;