feat(performance): implement extensive performance optimizations across task management components

- Introduced batching and optimized query handling in SQL functions for improved performance during large updates.
- Enhanced task sorting functions with batching to reduce load times and improve responsiveness.
- Implemented performance monitoring utilities to track render times, memory usage, and long tasks, providing insights for further optimizations.
- Added performance analysis component to visualize metrics and identify bottlenecks in task management.
- Optimized drag-and-drop functionality with CSS enhancements to ensure smooth interactions and reduce layout thrashing.
- Refined task row rendering logic to minimize DOM updates and improve loading behavior for large lists.
- Introduced aggressive virtualization and memoization strategies to enhance rendering performance in task lists.
This commit is contained in:
chamiakJ
2025-06-30 07:48:32 +05:30
parent e3324f0707
commit 7fdea2a285
11 changed files with 2003 additions and 457 deletions

View File

@@ -0,0 +1,284 @@
// Debug utility for identifying performance bottlenecks with 400 tasks
export const debugPerformance = {
// Log component render times
logComponentRender: (componentName: string, startTime: number) => {
const renderTime = performance.now() - startTime;
if (renderTime > 16) { // Log slow renders (>16ms)
console.warn(`Slow render detected: ${componentName} took ${renderTime.toFixed(2)}ms`);
}
},
// Log Redux selector performance
logSelectorPerformance: (selectorName: string, startTime: number) => {
const executionTime = performance.now() - startTime;
if (executionTime > 5) { // Log slow selectors (>5ms)
console.warn(`Slow selector detected: ${selectorName} took ${executionTime.toFixed(2)}ms`);
}
},
// Log memory usage
logMemoryUsage: () => {
if ('memory' in performance) {
const memory = (performance as any).memory;
const usedMB = memory.usedJSHeapSize / 1024 / 1024;
const totalMB = memory.totalJSHeapSize / 1024 / 1024;
const limitMB = memory.jsHeapSizeLimit / 1024 / 1024;
console.log(`Memory Usage: ${usedMB.toFixed(1)}MB / ${totalMB.toFixed(1)}MB (${limitMB.toFixed(1)}MB limit)`);
if (usedMB > 100) {
console.warn(`High memory usage detected: ${usedMB.toFixed(1)}MB`);
}
}
},
// Log DOM node count
logDOMNodes: () => {
const nodeCount = document.querySelectorAll('*').length;
console.log(`Total DOM nodes: ${nodeCount}`);
if (nodeCount > 1000) {
console.warn(`High DOM node count detected: ${nodeCount} nodes`);
}
},
// Log React component count
logReactComponents: () => {
// This is a rough estimate - React DevTools would be more accurate
const reactComponents = document.querySelectorAll('[data-reactroot], [data-reactid]').length;
console.log(`React components (estimate): ${reactComponents}`);
},
// Log scroll performance
logScrollPerformance: () => {
let lastScrollTime = 0;
let scrollCount = 0;
const handleScroll = () => {
const currentTime = performance.now();
const timeSinceLastScroll = currentTime - lastScrollTime;
if (timeSinceLastScroll < 16) { // Less than 60fps
scrollCount++;
if (scrollCount > 5) {
console.warn(`Poor scroll performance detected: ${timeSinceLastScroll.toFixed(2)}ms between scrolls`);
}
} else {
scrollCount = 0;
}
lastScrollTime = currentTime;
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
};
},
// Log long tasks
logLongTasks: () => {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
console.warn(`Long task detected: ${entry.duration.toFixed(2)}ms`, entry);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
return () => {
observer.disconnect();
};
}
return () => {};
},
// NEW: Monitor hover performance specifically
logHoverPerformance: () => {
let hoverStartTime = 0;
let hoverCount = 0;
let totalHoverTime = 0;
const handleMouseEnter = () => {
hoverStartTime = performance.now();
};
const handleMouseLeave = () => {
if (hoverStartTime > 0) {
const hoverDuration = performance.now() - hoverStartTime;
totalHoverTime += hoverDuration;
hoverCount++;
if (hoverDuration > 50) { // Log slow hover operations (>50ms)
console.warn(`Slow hover operation detected: ${hoverDuration.toFixed(2)}ms`);
}
// Log average hover time every 10 hovers
if (hoverCount % 10 === 0) {
const avgHoverTime = totalHoverTime / hoverCount;
console.log(`Average hover time: ${avgHoverTime.toFixed(2)}ms (${hoverCount} hovers)`);
}
hoverStartTime = 0;
}
};
// Monitor hover events on task rows specifically
const taskRows = document.querySelectorAll('.task-row-optimized, .task-row');
taskRows.forEach(row => {
row.addEventListener('mouseenter', handleMouseEnter, { passive: true });
row.addEventListener('mouseleave', handleMouseLeave, { passive: true });
});
return () => {
taskRows.forEach(row => {
row.removeEventListener('mouseenter', handleMouseEnter);
row.removeEventListener('mouseleave', handleMouseLeave);
});
};
},
// NEW: Monitor CSS transitions and animations
logCSSPerformance: () => {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'measure') {
const duration = entry.duration;
if (duration > 16) { // Log slow CSS operations (>16ms)
console.warn(`Slow CSS operation detected: ${duration.toFixed(2)}ms - ${entry.name}`);
}
}
}
});
observer.observe({ entryTypes: ['measure'] });
return () => {
observer.disconnect();
};
}
return () => {};
},
// Comprehensive performance check
runPerformanceCheck: () => {
console.group('🔍 Performance Check');
// Memory usage
debugPerformance.logMemoryUsage();
// DOM nodes
debugPerformance.logDOMNodes();
// React components
debugPerformance.logReactComponents();
// Start monitoring
const stopScroll = debugPerformance.logScrollPerformance();
const stopLongTasks = debugPerformance.logLongTasks();
const stopHover = debugPerformance.logHoverPerformance();
const stopCSS = debugPerformance.logCSSPerformance();
console.groupEnd();
return () => {
stopScroll();
stopLongTasks();
stopHover();
stopCSS();
};
},
// Monitor specific component
monitorComponent: (componentName: string) => {
const startTime = performance.now();
return () => {
debugPerformance.logComponentRender(componentName, startTime);
};
},
// Monitor Redux selector
monitorSelector: (selectorName: string, selectorFn: () => any) => {
const startTime = performance.now();
const result = selectorFn();
debugPerformance.logSelectorPerformance(selectorName, startTime);
return result;
},
// NEW: Quick hover performance test
testHoverPerformance: () => {
console.group('🧪 Hover Performance Test');
const taskRows = document.querySelectorAll('.task-row-optimized, .task-row');
console.log(`Found ${taskRows.length} task rows to test`);
let totalHoverTime = 0;
let hoverCount = 0;
const testHover = (row: Element) => {
return new Promise<void>((resolve) => {
const startTime = performance.now();
// Simulate hover
row.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
setTimeout(() => {
row.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }));
const hoverTime = performance.now() - startTime;
totalHoverTime += hoverTime;
hoverCount++;
if (hoverTime > 50) {
console.warn(`Slow hover on row ${hoverCount}: ${hoverTime.toFixed(2)}ms`);
}
resolve();
}, 100);
});
};
// Test first 5 rows
const testRows = Array.from(taskRows).slice(0, 5);
Promise.all(testRows.map(testHover)).then(() => {
const avgHoverTime = totalHoverTime / hoverCount;
console.log(`Average hover time: ${avgHoverTime.toFixed(2)}ms (${hoverCount} tests)`);
if (avgHoverTime > 30) {
console.error(`🚨 Poor hover performance detected: ${avgHoverTime.toFixed(2)}ms average`);
} else if (avgHoverTime > 16) {
console.warn(`⚠️ Suboptimal hover performance: ${avgHoverTime.toFixed(2)}ms average`);
} else {
console.log(`✅ Good hover performance: ${avgHoverTime.toFixed(2)}ms average`);
}
console.groupEnd();
});
}
};
// Auto-run performance check in development
if (process.env.NODE_ENV === 'development') {
// Run initial check after page load
window.addEventListener('load', () => {
setTimeout(() => {
debugPerformance.runPerformanceCheck();
// Run hover performance test after 3 seconds
setTimeout(() => {
debugPerformance.testHoverPerformance();
}, 3000);
}, 2000); // Wait for initial render
});
}
// Export for manual use
export default debugPerformance;

View File

@@ -0,0 +1,304 @@
import React from 'react';
// Performance monitoring utility for task list performance analysis
export class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: Map<string, number[]> = new Map();
private marks: Map<string, number> = new Map();
private observers: Map<string, PerformanceObserver> = new Map();
static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
// Mark a performance point
mark(name: string): void {
this.marks.set(name, performance.now());
performance.mark(name);
}
// Measure time between two marks
measure(name: string, startMark: string, endMark: string): void {
try {
performance.measure(name, startMark, endMark);
const measure = performance.getEntriesByName(name, 'measure')[0];
if (measure) {
this.addMetric(name, measure.duration);
}
} catch (error) {
console.warn(`Failed to measure ${name}:`, error);
}
}
// Add a metric value
addMetric(name: string, value: number): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name)!.push(value);
}
// Get average for a metric
getAverage(name: string): number {
const values = this.metrics.get(name);
if (!values || values.length === 0) return 0;
return values.reduce((sum, val) => sum + val, 0) / values.length;
}
// Get all metrics
getMetrics(): Record<string, { average: number; count: number; min: number; max: number }> {
const result: Record<string, { average: number; count: number; min: number; max: number }> = {};
this.metrics.forEach((values, name) => {
if (values.length > 0) {
result[name] = {
average: this.getAverage(name),
count: values.length,
min: Math.min(...values),
max: Math.max(...values),
};
}
});
return result;
}
// Monitor React component render times
monitorComponentRender(componentName: string): () => void {
const startMark = `${componentName}-render-start`;
const endMark = `${componentName}-render-end`;
this.mark(startMark);
return () => {
this.mark(endMark);
this.measure(`${componentName}-render-time`, startMark, endMark);
};
}
// Monitor Redux selector performance
monitorSelector(selectorName: string, selectorFn: () => any): any {
const startTime = performance.now();
const result = selectorFn();
const endTime = performance.now();
this.addMetric(`${selectorName}-execution-time`, endTime - startTime);
return result;
}
// Monitor DOM operations
monitorDOMOperation(operationName: string, operation: () => void): void {
const startTime = performance.now();
operation();
const endTime = performance.now();
this.addMetric(`${operationName}-dom-time`, endTime - startTime);
}
// Monitor memory usage
monitorMemory(): void {
if ('memory' in performance) {
const memory = (performance as any).memory;
this.addMetric('memory-used', memory.usedJSHeapSize);
this.addMetric('memory-total', memory.totalJSHeapSize);
this.addMetric('memory-limit', memory.jsHeapSizeLimit);
}
}
// Monitor frame rate
startFrameRateMonitoring(): () => void {
let frameCount = 0;
let lastTime = performance.now();
const measureFrameRate = () => {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) { // Every second
const fps = frameCount / ((currentTime - lastTime) / 1000);
this.addMetric('fps', fps);
frameCount = 0;
lastTime = currentTime;
}
requestAnimationFrame(measureFrameRate);
};
const animationId = requestAnimationFrame(measureFrameRate);
return () => {
cancelAnimationFrame(animationId);
};
}
// Monitor long tasks
startLongTaskMonitoring(): () => void {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
this.addMetric('long-task-duration', entry.duration);
console.warn('Long task detected:', entry);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
this.observers.set('longtask', observer);
return () => {
observer.disconnect();
this.observers.delete('longtask');
};
}
return () => {};
}
// Monitor layout thrashing
startLayoutThrashingMonitoring(): () => void {
let layoutCount = 0;
let lastLayoutTime = 0;
const monitor = this;
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = function() {
const currentTime = performance.now();
if (currentTime - lastLayoutTime < 16) { // Less than 16ms between calls
layoutCount++;
monitor.addMetric('layout-thrashing-count', layoutCount);
}
lastLayoutTime = currentTime;
return originalGetBoundingClientRect.call(this);
};
return () => {
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
};
}
// Generate performance report
generateReport(): string {
const metrics = this.getMetrics();
const report = {
timestamp: new Date().toISOString(),
metrics,
summary: this.generateSummary(metrics),
recommendations: this.generateRecommendations(metrics),
};
return JSON.stringify(report, null, 2);
}
private generateSummary(metrics: Record<string, any>): Record<string, string> {
const summary: Record<string, string> = {};
// Component render times
const renderTimes = Object.keys(metrics).filter(key => key.includes('render-time'));
if (renderTimes.length > 0) {
const avgRenderTime = renderTimes.reduce((sum, key) => sum + metrics[key].average, 0) / renderTimes.length;
summary.renderPerformance = avgRenderTime > 16 ? 'Poor' : avgRenderTime > 8 ? 'Fair' : 'Good';
}
// FPS
if (metrics.fps) {
summary.frameRate = metrics.fps.average > 55 ? 'Good' : metrics.fps.average > 30 ? 'Fair' : 'Poor';
}
// Memory usage
if (metrics['memory-used'] && metrics['memory-limit']) {
const memoryUsage = (metrics['memory-used'].average / metrics['memory-limit'].average) * 100;
summary.memoryUsage = memoryUsage > 80 ? 'High' : memoryUsage > 50 ? 'Moderate' : 'Low';
}
return summary;
}
private generateRecommendations(metrics: Record<string, any>): string[] {
const recommendations: string[] = [];
// Check for slow component renders
const slowRenders = Object.keys(metrics).filter(key =>
key.includes('render-time') && metrics[key].average > 16
);
if (slowRenders.length > 0) {
recommendations.push(`Optimize component renders: ${slowRenders.join(', ')}`);
}
// Check for layout thrashing
if (metrics['layout-thrashing-count'] && metrics['layout-thrashing-count'].count > 10) {
recommendations.push('Reduce layout thrashing by batching DOM reads and writes');
}
// Check for long tasks
if (metrics['long-task-duration'] && metrics['long-task-duration'].count > 0) {
recommendations.push('Break down long tasks into smaller chunks');
}
// Check for low FPS
if (metrics.fps && metrics.fps.average < 30) {
recommendations.push('Optimize rendering performance to maintain 60fps');
}
// Check for high memory usage
if (metrics['memory-used'] && metrics['memory-limit']) {
const memoryUsage = (metrics['memory-used'].average / metrics['memory-limit'].average) * 100;
if (memoryUsage > 80) {
recommendations.push('Reduce memory usage to prevent performance degradation');
}
}
return recommendations;
}
// Clear all metrics
clear(): void {
this.metrics.clear();
this.marks.clear();
performance.clearMarks();
performance.clearMeasures();
}
// Stop all monitoring
stop(): void {
this.observers.forEach((observer) => {
observer.disconnect();
});
this.observers.clear();
}
}
// Convenience functions
export const performanceMonitor = PerformanceMonitor.getInstance();
// React hook for monitoring component performance
export const usePerformanceMonitor = (componentName: string) => {
const endMonitoring = React.useCallback(() => {
return performanceMonitor.monitorComponentRender(componentName);
}, [componentName]);
React.useEffect(() => {
const cleanup = endMonitoring();
return cleanup;
}, [endMonitoring]);
};
// Redux middleware for monitoring selector performance
export const performanceMiddleware = (store: any) => (next: any) => (action: any) => {
const startTime = performance.now();
const result = next(action);
const endTime = performance.now();
performanceMonitor.addMetric(`redux-action-${action.type}`, endTime - startTime);
return result;
};
// Export for global access
if (typeof window !== 'undefined') {
(window as any).performanceMonitor = performanceMonitor;
}

View File

@@ -0,0 +1,297 @@
// Performance optimization utility for reducing long tasks and improving frame rate
import React from 'react';
interface PerformanceMetrics {
longTaskCount: number;
averageLongTaskDuration: number;
frameRate: number;
memoryUsage: number;
layoutThrashingCount: number;
}
class PerformanceOptimizer {
private longTaskObserver: PerformanceObserver | null = null;
private frameRateObserver: PerformanceObserver | null = null;
private layoutThrashingCount = 0;
private metrics: PerformanceMetrics = {
longTaskCount: 0,
averageLongTaskDuration: 0,
frameRate: 0,
memoryUsage: 0,
layoutThrashingCount: 0,
};
constructor() {
this.initializeObservers();
}
private initializeObservers() {
// Monitor long tasks
if ('PerformanceObserver' in window) {
this.longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
this.metrics.longTaskCount++;
this.metrics.averageLongTaskDuration =
(this.metrics.averageLongTaskDuration * (this.metrics.longTaskCount - 1) + entry.duration) / this.metrics.longTaskCount;
console.warn(`🚨 Long task detected: ${entry.duration.toFixed(2)}ms - Consider chunking this operation`);
}
}
});
this.longTaskObserver.observe({ entryTypes: ['longtask'] });
}
// Monitor frame rate
this.startFrameRateMonitoring();
}
private startFrameRateMonitoring() {
let frameCount = 0;
let lastTime = performance.now();
const measureFrameRate = () => {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
this.metrics.frameRate = frameCount;
frameCount = 0;
lastTime = currentTime;
if (this.metrics.frameRate < 30) {
console.warn(`⚠️ Low frame rate detected: ${this.metrics.frameRate}fps`);
}
}
requestAnimationFrame(measureFrameRate);
};
requestAnimationFrame(measureFrameRate);
}
// Chunk large operations to prevent long tasks
static chunkOperation<T>(
items: T[],
operation: (item: T, index: number) => void,
chunkSize: number = 10,
delay: number = 16
): Promise<void> {
return new Promise((resolve) => {
let index = 0;
const processChunk = () => {
const startTime = performance.now();
const endIndex = Math.min(index + chunkSize, items.length);
// Process items in this chunk
for (let i = index; i < endIndex; i++) {
operation(items[i], i);
}
index = endIndex;
// Check if we need to yield to prevent long tasks
const processingTime = performance.now() - startTime;
if (processingTime > 16) {
console.warn(`⚠️ Chunk processing took ${processingTime.toFixed(2)}ms - consider smaller chunks`);
}
if (index < items.length) {
// Schedule next chunk with delay to prevent blocking
setTimeout(processChunk, delay);
} else {
resolve();
}
};
processChunk();
});
}
// Optimize DOM operations to prevent layout thrashing
static batchDOMOperations(operations: (() => void)[]): void {
// Use requestAnimationFrame to batch DOM updates
requestAnimationFrame(() => {
// Force layout read first
document.body.offsetHeight;
// Perform all write operations
operations.forEach(operation => operation());
});
}
// Debounce function for expensive operations
static debounce<T extends (...args: any[]) => void>(
func: T,
delay: number
): T {
let timeoutId: NodeJS.Timeout;
return ((...args: any[]) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
}) as T;
}
// Throttle function for frequent operations
static throttle<T extends (...args: any[]) => void>(
func: T,
delay: number
): T {
let lastExecTime = 0;
return ((...args: any[]) => {
const currentTime = performance.now();
if (currentTime - lastExecTime > delay) {
func(...args);
lastExecTime = currentTime;
}
}) as T;
}
// Optimize list rendering with virtualization hints
static optimizeListRendering<T>(
items: T[],
renderItem: (item: T, index: number) => React.ReactNode,
options: {
chunkSize?: number;
virtualizationThreshold?: number;
overscanCount?: number;
} = {}
): React.ReactNode[] {
const {
chunkSize = 50,
virtualizationThreshold = 100,
overscanCount = 5
} = options;
// For small lists, render everything
if (items.length <= virtualizationThreshold) {
return items.map((item, index) => renderItem(item, index));
}
// For large lists, use chunked rendering
const chunks: React.ReactNode[] = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
chunks.push(
React.createElement('div', { key: `chunk-${i}`, className: 'virtualized-chunk' },
chunk.map((item, index) => renderItem(item, i + index))
)
);
}
return chunks;
}
// Monitor memory usage
static getMemoryUsage(): { used: number; total: number; limit: number } {
if ('memory' in performance) {
const memory = (performance as any).memory;
return {
used: memory.usedJSHeapSize / 1024 / 1024,
total: memory.totalJSHeapSize / 1024 / 1024,
limit: memory.jsHeapSizeLimit / 1024 / 1024
};
}
return { used: 0, total: 0, limit: 0 };
}
// Optimize scroll performance
static optimizeScroll(container: HTMLElement, handler: (event: Event) => void): () => void {
let ticking = false;
const optimizedHandler = (event: Event) => {
if (!ticking) {
requestAnimationFrame(() => {
handler(event);
ticking = false;
});
ticking = true;
}
};
container.addEventListener('scroll', optimizedHandler, { passive: true });
return () => {
container.removeEventListener('scroll', optimizedHandler);
};
}
// Optimize hover performance
static optimizeHover(
element: HTMLElement,
onEnter: () => void,
onLeave: () => void,
delay: number = 50
): () => void {
let enterTimeout: NodeJS.Timeout;
let leaveTimeout: NodeJS.Timeout;
const handleMouseEnter = () => {
clearTimeout(leaveTimeout);
enterTimeout = setTimeout(onEnter, delay);
};
const handleMouseLeave = () => {
clearTimeout(enterTimeout);
leaveTimeout = setTimeout(onLeave, delay);
};
element.addEventListener('mouseenter', handleMouseEnter, { passive: true });
element.addEventListener('mouseleave', handleMouseLeave, { passive: true });
return () => {
clearTimeout(enterTimeout);
clearTimeout(leaveTimeout);
element.removeEventListener('mouseenter', handleMouseEnter);
element.removeEventListener('mouseleave', handleMouseLeave);
};
}
// Get current performance metrics
getMetrics(): PerformanceMetrics {
const memory = PerformanceOptimizer.getMemoryUsage();
this.metrics.memoryUsage = memory.used;
this.metrics.layoutThrashingCount = this.layoutThrashingCount;
return { ...this.metrics };
}
// Reset metrics
resetMetrics(): void {
this.metrics = {
longTaskCount: 0,
averageLongTaskDuration: 0,
frameRate: 0,
memoryUsage: 0,
layoutThrashingCount: 0,
};
}
// Cleanup observers
destroy(): void {
if (this.longTaskObserver) {
this.longTaskObserver.disconnect();
}
if (this.frameRateObserver) {
this.frameRateObserver.disconnect();
}
}
}
// Create global instance
const performanceOptimizer = new PerformanceOptimizer();
// Auto-cleanup on page unload
window.addEventListener('beforeunload', () => {
performanceOptimizer.destroy();
});
export { PerformanceOptimizer, performanceOptimizer };
export default PerformanceOptimizer;