Files
worklenz/worklenz-frontend/src/utils/enhanced-performance-monitoring.ts
shancds 7f71e8952b Enhance EnhancedKanbanBoardNativeDnD with task priority updates and socket integration
- Added functionality to handle task priority changes, emitting updates via socket for real-time synchronization.
- Updated the EnhancedKanbanBoardNativeDnD component to include new logic for managing task priorities within the drag-and-drop interface.
- Cleaned up console log statements in the useTaskSocketHandlers hook for improved performance monitoring.
2025-07-17 12:52:40 +05:30

680 lines
19 KiB
TypeScript

// Enhanced performance monitoring for Worklenz application
// Performance monitoring constants
export const PERFORMANCE_CONFIG = {
// Measurement thresholds
THRESHOLDS: {
FCP: 1800, // First Contentful Paint (ms)
LCP: 2500, // Largest Contentful Paint (ms)
FID: 100, // First Input Delay (ms)
CLS: 0.1, // Cumulative Layout Shift
TTFB: 600, // Time to First Byte (ms)
INP: 200, // Interaction to Next Paint (ms)
},
// Monitoring intervals
INTERVALS: {
METRICS_COLLECTION: 5000, // 5 seconds
PERFORMANCE_REPORT: 30000, // 30 seconds
CLEANUP_THRESHOLD: 300000, // 5 minutes
},
// Buffer sizes
BUFFERS: {
MAX_ENTRIES: 1000,
MAX_RESOURCE_ENTRIES: 500,
MAX_NAVIGATION_ENTRIES: 100,
},
} as const;
// Performance metrics interface
export interface PerformanceMetrics {
// Core Web Vitals
fcp?: number;
lcp?: number;
fid?: number;
cls?: number;
ttfb?: number;
inp?: number;
// Custom metrics
domContentLoaded?: number;
windowLoad?: number;
firstByte?: number;
// Application-specific metrics
taskLoadTime?: number;
projectSwitchTime?: number;
filterApplyTime?: number;
bulkActionTime?: number;
// Memory and performance
memoryUsage?: {
usedJSHeapSize: number;
totalJSHeapSize: number;
jsHeapSizeLimit: number;
};
// Timing information
timestamp: number;
url: string;
userAgent: string;
}
// Performance monitoring class
export class EnhancedPerformanceMonitor {
private static instance: EnhancedPerformanceMonitor;
private metrics: PerformanceMetrics[] = [];
private observers: PerformanceObserver[] = [];
private intervalIds: NodeJS.Timeout[] = [];
private isMonitoring = false;
// Singleton pattern
static getInstance(): EnhancedPerformanceMonitor {
if (!this.instance) {
this.instance = new EnhancedPerformanceMonitor();
}
return this.instance;
}
// Start comprehensive performance monitoring
startMonitoring(): void {
if (this.isMonitoring) return;
this.isMonitoring = true;
this.setupObservers();
this.collectInitialMetrics();
this.startPeriodicCollection();
// console.log('🚀 Enhanced performance monitoring started');
}
// Stop monitoring and cleanup
stopMonitoring(): void {
if (!this.isMonitoring) return;
this.isMonitoring = false;
this.cleanupObservers();
this.clearIntervals();
// console.log('🛑 Enhanced performance monitoring stopped');
}
// Setup performance observers
private setupObservers(): void {
if (!('PerformanceObserver' in window)) return;
// Core Web Vitals observer
try {
const vitalsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.processVitalMetric(entry);
}
});
vitalsObserver.observe({
type: 'largest-contentful-paint',
buffered: true
});
vitalsObserver.observe({
type: 'first-input',
buffered: true
});
vitalsObserver.observe({
type: 'layout-shift',
buffered: true
});
this.observers.push(vitalsObserver);
} catch (error) {
console.warn('Failed to setup vitals observer:', error);
}
// Navigation timing observer
try {
const navigationObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.processNavigationMetric(entry as PerformanceNavigationTiming);
}
});
navigationObserver.observe({
type: 'navigation',
buffered: true
});
this.observers.push(navigationObserver);
} catch (error) {
console.warn('Failed to setup navigation observer:', error);
}
// Resource timing observer
try {
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.processResourceMetric(entry as PerformanceResourceTiming);
}
});
resourceObserver.observe({
type: 'resource',
buffered: true
});
this.observers.push(resourceObserver);
} catch (error) {
console.warn('Failed to setup resource observer:', error);
}
// Measure observer
try {
const measureObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.processCustomMeasure(entry as PerformanceMeasure);
}
});
measureObserver.observe({
type: 'measure',
buffered: true
});
this.observers.push(measureObserver);
} catch (error) {
console.warn('Failed to setup measure observer:', error);
}
}
// Process Core Web Vitals metrics
private processVitalMetric(entry: PerformanceEntry): void {
const metric: Partial<PerformanceMetrics> = {
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
};
switch (entry.entryType) {
case 'largest-contentful-paint':
metric.lcp = entry.startTime;
break;
case 'first-input':
metric.fid = (entry as any).processingStart - entry.startTime;
break;
case 'layout-shift':
if (!(entry as any).hadRecentInput) {
metric.cls = (metric.cls || 0) + (entry as any).value;
}
break;
}
this.addMetric(metric as PerformanceMetrics);
}
// Process navigation timing metrics
private processNavigationMetric(entry: PerformanceNavigationTiming): void {
const metric: PerformanceMetrics = {
fcp: this.getFCP(),
ttfb: entry.responseStart - entry.requestStart,
domContentLoaded: entry.domContentLoadedEventEnd - entry.startTime,
windowLoad: entry.loadEventEnd - entry.startTime,
firstByte: entry.responseStart - entry.startTime,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
};
this.addMetric(metric);
}
// Process resource timing metrics
private processResourceMetric(entry: PerformanceResourceTiming): void {
// Track slow resources
const duration = entry.responseEnd - entry.requestStart;
if (duration > 1000) { // Resources taking more than 1 second
console.warn(`Slow resource detected: ${entry.name} (${duration.toFixed(2)}ms)`);
}
// Track render-blocking resources (check if property exists)
if ((entry as any).renderBlockingStatus === 'blocking') {
console.warn(`Render-blocking resource: ${entry.name}`);
}
}
// Process custom performance measures
private processCustomMeasure(entry: PerformanceMeasure): void {
const metric: Partial<PerformanceMetrics> = {
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
};
// Map custom measures to metrics
switch (entry.name) {
case 'task-load-time':
metric.taskLoadTime = entry.duration;
break;
case 'project-switch-time':
metric.projectSwitchTime = entry.duration;
break;
case 'filter-apply-time':
metric.filterApplyTime = entry.duration;
break;
case 'bulk-action-time':
metric.bulkActionTime = entry.duration;
break;
}
if (Object.keys(metric).length > 3) {
this.addMetric(metric as PerformanceMetrics);
}
}
// Get First Contentful Paint
private getFCP(): number | undefined {
const fcpEntry = performance.getEntriesByType('paint')
.find(entry => entry.name === 'first-contentful-paint');
return fcpEntry?.startTime;
}
// Collect initial metrics
private collectInitialMetrics(): void {
const metric: PerformanceMetrics = {
fcp: this.getFCP(),
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
};
// Add memory information if available
if ('memory' in performance) {
metric.memoryUsage = {
usedJSHeapSize: (performance as any).memory.usedJSHeapSize,
totalJSHeapSize: (performance as any).memory.totalJSHeapSize,
jsHeapSizeLimit: (performance as any).memory.jsHeapSizeLimit,
};
}
this.addMetric(metric);
}
// Start periodic metrics collection
private startPeriodicCollection(): void {
// Collect metrics every 5 seconds
const metricsInterval = setInterval(() => {
this.collectPeriodicMetrics();
}, PERFORMANCE_CONFIG.INTERVALS.METRICS_COLLECTION);
// Generate performance report every 30 seconds
const reportInterval = setInterval(() => {
this.generatePerformanceReport();
}, PERFORMANCE_CONFIG.INTERVALS.PERFORMANCE_REPORT);
// Cleanup old metrics every 5 minutes
const cleanupInterval = setInterval(() => {
this.cleanupOldMetrics();
}, PERFORMANCE_CONFIG.INTERVALS.CLEANUP_THRESHOLD);
this.intervalIds.push(metricsInterval, reportInterval, cleanupInterval);
}
// Collect periodic metrics
private collectPeriodicMetrics(): void {
const metric: PerformanceMetrics = {
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
};
// Add memory information if available
if ('memory' in performance) {
metric.memoryUsage = {
usedJSHeapSize: (performance as any).memory.usedJSHeapSize,
totalJSHeapSize: (performance as any).memory.totalJSHeapSize,
jsHeapSizeLimit: (performance as any).memory.jsHeapSizeLimit,
};
}
this.addMetric(metric);
}
// Add metric to collection
private addMetric(metric: PerformanceMetrics): void {
this.metrics.push(metric);
// Limit buffer size
if (this.metrics.length > PERFORMANCE_CONFIG.BUFFERS.MAX_ENTRIES) {
this.metrics = this.metrics.slice(-PERFORMANCE_CONFIG.BUFFERS.MAX_ENTRIES);
}
}
// Generate performance report
private generatePerformanceReport(): void {
if (this.metrics.length === 0) return;
const recent = this.metrics.slice(-10); // Last 10 metrics
const report = this.analyzeMetrics(recent);
// console.log('📊 Performance Report:', report);
// Check for performance issues
this.checkPerformanceIssues(report);
}
// Analyze metrics and generate insights
private analyzeMetrics(metrics: PerformanceMetrics[]): any {
const validMetrics = metrics.filter(m => m);
if (validMetrics.length === 0) return {};
const report: any = {
timestamp: Date.now(),
sampleSize: validMetrics.length,
};
// Analyze each metric
['fcp', 'lcp', 'fid', 'cls', 'ttfb', 'taskLoadTime', 'projectSwitchTime'].forEach(metric => {
const values = validMetrics
.map(m => (m as any)[metric])
.filter(v => v !== undefined);
if (values.length > 0) {
report[metric] = {
avg: values.reduce((a, b) => a + b, 0) / values.length,
min: Math.min(...values),
max: Math.max(...values),
latest: values[values.length - 1],
};
}
});
// Memory analysis
const memoryMetrics = validMetrics
.map(m => m.memoryUsage)
.filter(m => m !== undefined);
if (memoryMetrics.length > 0) {
const latest = memoryMetrics[memoryMetrics.length - 1];
report.memory = {
usedMB: (latest.usedJSHeapSize / 1024 / 1024).toFixed(2),
totalMB: (latest.totalJSHeapSize / 1024 / 1024).toFixed(2),
usage: ((latest.usedJSHeapSize / latest.totalJSHeapSize) * 100).toFixed(2) + '%',
};
}
return report;
}
// Check for performance issues
private checkPerformanceIssues(report: any): void {
const issues: string[] = [];
// Check Core Web Vitals
if (report.fcp?.latest > PERFORMANCE_CONFIG.THRESHOLDS.FCP) {
issues.push(`FCP is slow: ${report.fcp.latest.toFixed(2)}ms (threshold: ${PERFORMANCE_CONFIG.THRESHOLDS.FCP}ms)`);
}
if (report.lcp?.latest > PERFORMANCE_CONFIG.THRESHOLDS.LCP) {
issues.push(`LCP is slow: ${report.lcp.latest.toFixed(2)}ms (threshold: ${PERFORMANCE_CONFIG.THRESHOLDS.LCP}ms)`);
}
if (report.fid?.latest > PERFORMANCE_CONFIG.THRESHOLDS.FID) {
issues.push(`FID is high: ${report.fid.latest.toFixed(2)}ms (threshold: ${PERFORMANCE_CONFIG.THRESHOLDS.FID}ms)`);
}
if (report.cls?.latest > PERFORMANCE_CONFIG.THRESHOLDS.CLS) {
issues.push(`CLS is high: ${report.cls.latest.toFixed(3)} (threshold: ${PERFORMANCE_CONFIG.THRESHOLDS.CLS})`);
}
// Check application-specific metrics
if (report.taskLoadTime?.latest > 1000) {
issues.push(`Task loading is slow: ${report.taskLoadTime.latest.toFixed(2)}ms`);
}
if (report.projectSwitchTime?.latest > 500) {
issues.push(`Project switching is slow: ${report.projectSwitchTime.latest.toFixed(2)}ms`);
}
// Check memory usage
if (report.memory && parseFloat(report.memory.usage) > 80) {
issues.push(`High memory usage: ${report.memory.usage}`);
}
// Log issues
if (issues.length > 0) {
console.warn('⚠️ Performance Issues Detected:');
issues.forEach(issue => console.warn(` - ${issue}`));
}
}
// Cleanup old metrics
private cleanupOldMetrics(): void {
const fiveMinutesAgo = Date.now() - PERFORMANCE_CONFIG.INTERVALS.CLEANUP_THRESHOLD;
this.metrics = this.metrics.filter(metric => metric.timestamp > fiveMinutesAgo);
}
// Cleanup observers
private cleanupObservers(): void {
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
}
// Clear intervals
private clearIntervals(): void {
this.intervalIds.forEach(id => clearInterval(id));
this.intervalIds = [];
}
// Get current metrics
getMetrics(): PerformanceMetrics[] {
return [...this.metrics];
}
// Get performance summary
getPerformanceSummary(): any {
return this.analyzeMetrics(this.metrics);
}
// Export metrics for analysis
exportMetrics(): string {
return JSON.stringify({
timestamp: Date.now(),
metrics: this.metrics,
summary: this.getPerformanceSummary(),
}, null, 2);
}
}
// Custom performance measurement utilities
export class CustomPerformanceMeasurer {
private static marks = new Map<string, number>();
// Mark start of operation
static mark(name: string): void {
if ('performance' in window && 'mark' in performance) {
performance.mark(`${name}-start`);
}
this.marks.set(name, Date.now());
}
// Measure operation duration
static measure(name: string): number {
const startTime = this.marks.get(name);
const endTime = Date.now();
if (startTime) {
const duration = endTime - startTime;
if ('performance' in window && 'measure' in performance) {
try {
performance.measure(name, `${name}-start`);
} catch (error) {
console.warn(`Failed to create performance measure for ${name}:`, error);
}
}
this.marks.delete(name);
return duration;
}
return 0;
}
// Measure async operation
static async measureAsync<T>(name: string, operation: () => Promise<T>): Promise<T> {
this.mark(name);
try {
const result = await operation();
this.measure(name);
return result;
} catch (error) {
this.measure(name);
throw error;
}
}
// Measure function execution
static measureFunction<T extends any[], R>(
name: string,
fn: (...args: T) => R
): (...args: T) => R {
return (...args: T): R => {
this.mark(name);
try {
const result = fn(...args);
this.measure(name);
return result;
} catch (error) {
this.measure(name);
throw error;
}
};
}
}
// Performance optimization recommendations
export class PerformanceOptimizer {
// Analyze and provide optimization recommendations
static analyzeAndRecommend(metrics: PerformanceMetrics[]): string[] {
const recommendations: string[] = [];
const latest = metrics[metrics.length - 1];
if (!latest) return recommendations;
// FCP recommendations
if (latest.fcp && latest.fcp > PERFORMANCE_CONFIG.THRESHOLDS.FCP) {
recommendations.push(
'Consider optimizing critical rendering path: inline critical CSS, reduce render-blocking resources'
);
}
// LCP recommendations
if (latest.lcp && latest.lcp > PERFORMANCE_CONFIG.THRESHOLDS.LCP) {
recommendations.push(
'Optimize Largest Contentful Paint: compress images, preload critical resources, improve server response times'
);
}
// Memory recommendations
if (latest.memoryUsage) {
const usagePercent = (latest.memoryUsage.usedJSHeapSize / latest.memoryUsage.totalJSHeapSize) * 100;
if (usagePercent > 80) {
recommendations.push(
'High memory usage detected: implement cleanup routines, check for memory leaks, optimize data structures'
);
}
}
// Task loading recommendations
if (latest.taskLoadTime && latest.taskLoadTime > 1000) {
recommendations.push(
'Task loading is slow: implement pagination, optimize database queries, add loading states'
);
}
return recommendations;
}
// Get optimization priority
static getOptimizationPriority(metrics: PerformanceMetrics[]): Array<{metric: string, priority: 'high' | 'medium' | 'low', value: number}> {
const latest = metrics[metrics.length - 1];
if (!latest) return [];
const priorities: Array<{metric: string, priority: 'high' | 'medium' | 'low', value: number}> = [];
// Check each metric against thresholds
if (latest.fcp) {
const ratio = latest.fcp / PERFORMANCE_CONFIG.THRESHOLDS.FCP;
priorities.push({
metric: 'First Contentful Paint',
priority: ratio > 2 ? 'high' : ratio > 1.5 ? 'medium' : 'low',
value: latest.fcp,
});
}
if (latest.lcp) {
const ratio = latest.lcp / PERFORMANCE_CONFIG.THRESHOLDS.LCP;
priorities.push({
metric: 'Largest Contentful Paint',
priority: ratio > 2 ? 'high' : ratio > 1.5 ? 'medium' : 'low',
value: latest.lcp,
});
}
if (latest.cls) {
const ratio = latest.cls / PERFORMANCE_CONFIG.THRESHOLDS.CLS;
priorities.push({
metric: 'Cumulative Layout Shift',
priority: ratio > 3 ? 'high' : ratio > 2 ? 'medium' : 'low',
value: latest.cls,
});
}
return priorities.sort((a, b) => {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
}
}
// Track if performance monitoring has been initialized
let isInitialized = false;
// Initialize performance monitoring
export const initializePerformanceMonitoring = (): void => {
// Prevent duplicate initialization
if (isInitialized) {
console.warn('Performance monitoring already initialized');
return;
}
isInitialized = true;
const monitor = EnhancedPerformanceMonitor.getInstance();
monitor.startMonitoring();
// Cleanup on page unload
const cleanup = () => {
monitor.stopMonitoring();
isInitialized = false;
};
window.addEventListener('beforeunload', cleanup);
// Also cleanup on page visibility change (tab switching)
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
cleanup();
}
});
};
// Export global performance utilities
export const performanceUtils = {
monitor: EnhancedPerformanceMonitor.getInstance(),
measurer: CustomPerformanceMeasurer,
optimizer: PerformanceOptimizer,
initialize: initializePerformanceMonitoring,
};