export class PerformanceMonitor {
private metrics: Map<string, PerformanceMetric[]> = new Map();
private observers: PerformanceObserver[] = [];
// Monitoruj Core Web Vitals
// Monitoruj wydajność React
this.monitorReactPerformance();
// Monitoruj wywołania API
// Wysyłaj metryki co 10 sekund
setInterval(() => this.sendMetrics(), 10000);
private observeWebVitals() {
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.recordMetric('lcp', lastEntry.startTime);
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
const fidObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.recordMetric('fid', entry.processingStart - entry.startTime);
fidObserver.observe({ entryTypes: ['first-input'] });
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
this.recordMetric('cls', clsValue);
clsObserver.observe({ entryTypes: ['layout-shift'] });
this.observers.push(lcpObserver, fidObserver, clsObserver);
private monitorReactPerformance() {
if (window.React && window.React.Profiler) {
// Wrap React.Profiler aby przechwycić wszystkie rendery
const originalProfiler = window.React.Profiler;
window.React.Profiler = (props) => {
const onRender = (id, phase, actualDuration, baseDuration) => {
this.recordMetric(`react-render-${id}`, actualDuration, {
timestamp: performance.now()
// Alert przy wolnych renderach
if (actualDuration > 16) { // próg 60fps
console.warn(`Wykryto wolny render: ${id} zajął ${actualDuration}ms`);
props.onRender?.(id, phase, actualDuration, baseDuration);
return originalProfiler({ ...props, onRender });
private interceptFetch() {
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const startTime = performance.now();
const url = args[0].toString();
const response = await originalFetch(...args);
const duration = performance.now() - startTime;
this.recordMetric('api-call', duration, {
method: args[1]?.method || 'GET'
const duration = performance.now() - startTime;
this.recordMetric('api-error', duration, {
private monitorMemory() {
if (!performance.memory) return;
const memoryInfo = performance.memory;
this.recordMetric('memory', memoryInfo.usedJSHeapSize, {
totalJSHeapSize: memoryInfo.totalJSHeapSize,
jsHeapSizeLimit: memoryInfo.jsHeapSizeLimit
// Alert przy skokach pamięci
const usage = memoryInfo.usedJSHeapSize / memoryInfo.jsHeapSizeLimit;
console.error('Krytyczne użycie pamięci:', `${(usage * 100).toFixed(1)}%`);
private recordMetric(name: string, value: number, metadata?: any) {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
this.metrics.get(name)!.push({
private async sendMetrics() {
const metricsToSend = {};
for (const [name, values] of this.metrics.entries()) {
metricsToSend[name] = values;
this.metrics.set(name, []); // Wyczyść wysłane metryki
if (Object.keys(metricsToSend).length > 0) {
await fetch('/api/metrics', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metricsToSend)
console.error('Nie udało się wysłać metryk:', error);