import { Injectable } from '@angular/core'; interface PerformanceSample { duration: number; success: boolean; timestamp: number; } interface BottleneckAnalysis { slowOperations: Array<{ operation: string; avgDuration: number; failureRate: number; }>; frequentOperations: Array<{ operation: string; callCount: number; }>; } @Injectable({ providedIn: 'root' }) export class PerformanceProfilerService { private metrics = new Map(); private readonly maxSamples = 100; // Mesurer une opération asynchrone async measureAsync( operationName: string, operation: () => Promise ): Promise { const start = performance.now(); try { const result = await operation(); const duration = performance.now() - start; this.recordSample(operationName, duration, true); return result; } catch (error) { const duration = performance.now() - start; this.recordSample(operationName, duration, false); throw error; } } // Mesurer une opération synchrone measureSync( operationName: string, operation: () => T ): T { const start = performance.now(); try { const result = operation(); const duration = performance.now() - start; this.recordSample(operationName, duration, true); return result; } catch (error) { const duration = performance.now() - start; this.recordSample(operationName, duration, false); throw error; } } // Analyser les goulots d'étranglement analyzeBottlenecks(): BottleneckAnalysis { const analysis: BottleneckAnalysis = { slowOperations: [], frequentOperations: [] }; for (const [operation, samples] of this.metrics.entries()) { const avgDuration = samples.reduce((sum, s) => sum + s.duration, 0) / samples.length; const failureRate = samples.filter(s => !s.success).length / samples.length; // Opérations lentes (> 100ms) if (avgDuration > 100) { analysis.slowOperations.push({ operation, avgDuration: Math.round(avgDuration * 100) / 100, failureRate: Math.round(failureRate * 10000) / 100 }); } // Opérations fréquentes (> 50 appels) if (samples.length > 50) { analysis.frequentOperations.push({ operation, callCount: samples.length }); } } return analysis; } // Obtenir les métriques brutes getMetrics() { const result: Record = {}; for (const [operation, samples] of this.metrics.entries()) { const durations = samples.map(s => s.duration); const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length; const minDuration = Math.min(...durations); const maxDuration = Math.max(...durations); const p95Duration = this.percentile(durations, 95); result[operation] = { sampleCount: samples.length, avgDuration: Math.round(avgDuration * 100) / 100, minDuration: Math.round(minDuration * 100) / 100, maxDuration: Math.round(maxDuration * 100) / 100, p95Duration: Math.round(p95Duration * 100) / 100, failureRate: samples.filter(s => !s.success).length / samples.length }; } return result; } // Exporter les métriques pour analyse exportMetrics() { return { timestamp: new Date().toISOString(), userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown', metrics: this.getMetrics(), bottlenecks: this.analyzeBottlenecks(), memory: typeof performance !== 'undefined' && (performance as any).memory ? { used: (performance as any).memory.usedJSHeapSize, total: (performance as any).memory.totalJSHeapSize, limit: (performance as any).memory.jsHeapSizeLimit } : null }; } private recordSample(operation: string, duration: number, success: boolean) { if (!this.metrics.has(operation)) { this.metrics.set(operation, []); } const samples = this.metrics.get(operation)!; samples.push({ duration, success, timestamp: Date.now() }); // Garder seulement les derniers échantillons if (samples.length > this.maxSamples) { samples.shift(); } } private percentile(values: number[], p: number): number { const sorted = [...values].sort((a, b) => a - b); const index = (p / 100) * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); if (lower === upper) { return sorted[lower]; } return sorted[lower] + (sorted[upper] - sorted[lower]) * (index - lower); } // Réinitialiser les métriques reset() { this.metrics.clear(); } }