diff --git a/src/hooks/usePerformanceMonitor.ts b/src/hooks/usePerformanceMonitor.ts new file mode 100644 index 0000000..aa357e4 --- /dev/null +++ b/src/hooks/usePerformanceMonitor.ts @@ -0,0 +1,124 @@ +import { useEffect, useRef } from 'react'; +import { eventBuilders, analytics } from '@/lib/analytics'; + +interface PerformanceThresholds { + renderTime?: number; // ms + memoryUsage?: number; // MB +} + +const DEFAULT_THRESHOLDS: PerformanceThresholds = { + renderTime: 16, // 60fps threshold + memoryUsage: 50, // 50MB +}; + +/** + * Hook to monitor component performance and track bottlenecks + */ +export function usePerformanceMonitor( + componentName: string, + thresholds: PerformanceThresholds = DEFAULT_THRESHOLDS +) { + const renderCount = useRef(0); + const lastRenderTime = useRef(performance.now()); + const mountTime = useRef(performance.now()); + + useEffect(() => { + renderCount.current += 1; + const currentTime = performance.now(); + const renderTime = currentTime - lastRenderTime.current; + lastRenderTime.current = currentTime; + + // Skip first render (mount) + if (renderCount.current === 1) return; + + // Check render performance + if (thresholds.renderTime && renderTime > thresholds.renderTime) { + const event = eventBuilders.performanceBottleneck({ + operation_type: `render.${componentName}`, + duration_ms: renderTime, + data_size: renderCount.current, + threshold_exceeded: true, + }); + analytics.track(event.event, event.properties); + } + + // Check memory usage if available + if ('memory' in performance && (performance as any).memory && thresholds.memoryUsage) { + const memoryMB = (performance as any).memory.usedJSHeapSize / (1024 * 1024); + if (memoryMB > thresholds.memoryUsage) { + const event = eventBuilders.memoryWarning({ + component: componentName, + memory_mb: memoryMB, + threshold_exceeded: true, + gc_count: undefined, + }); + analytics.track(event.event, event.properties); + } + } + }); + + // Track component unmount metrics + useEffect(() => { + return () => { + const lifetime = performance.now() - mountTime.current; + + // Only track if component lived for more than 5 seconds and had many renders + if (lifetime > 5000 && renderCount.current > 100) { + const avgRenderTime = lifetime / renderCount.current; + + // Track if average render time is high + if (avgRenderTime > 10) { + const event = eventBuilders.performanceBottleneck({ + operation_type: `lifecycle.${componentName}`, + duration_ms: avgRenderTime, + data_size: renderCount.current, + threshold_exceeded: true, + }); + analytics.track(event.event, event.properties); + } + } + }; + }, [componentName]); +} + +/** + * Hook to track async operation performance + */ +export function useAsyncPerformanceTracker(operationName: string) { + const operationStart = useRef(null); + + const startTracking = () => { + operationStart.current = performance.now(); + }; + + const endTracking = (success: boolean = true, dataSize?: number) => { + if (!operationStart.current) return; + + const duration = performance.now() - operationStart.current; + operationStart.current = null; + + // Track if operation took too long + if (duration > 3000) { + const event = eventBuilders.performanceBottleneck({ + operation_type: `async.${operationName}`, + duration_ms: duration, + data_size: dataSize, + threshold_exceeded: true, + }); + analytics.track(event.event, event.properties); + } + + // Track errors + if (!success) { + const event = eventBuilders.apiError({ + endpoint: operationName, + error_code: 'async_operation_failed', + retry_count: 0, + response_time_ms: duration, + }); + analytics.track(event.event, event.properties); + } + }; + + return { startTracking, endTracking }; +} \ No newline at end of file diff --git a/src/lib/analytics/resourceMonitor.ts b/src/lib/analytics/resourceMonitor.ts new file mode 100644 index 0000000..7f37b1a --- /dev/null +++ b/src/lib/analytics/resourceMonitor.ts @@ -0,0 +1,210 @@ +import { analytics, eventBuilders } from '@/lib/analytics'; +import type { ResourceUsageProperties } from './types'; + +/** + * Resource monitoring utility for tracking system resource usage and performance + * Helps identify performance bottlenecks and resource-intensive operations + */ +export class ResourceMonitor { + private static instance: ResourceMonitor; + private monitoringInterval: NodeJS.Timeout | null = null; + private isMonitoring = false; + private sampleCount = 0; + private highUsageThresholds = { + memory: 500, // MB + cpu: 80, // percent + networkRequests: 50, // per interval + }; + + private constructor() {} + + static getInstance(): ResourceMonitor { + if (!ResourceMonitor.instance) { + ResourceMonitor.instance = new ResourceMonitor(); + } + return ResourceMonitor.instance; + } + + /** + * Start monitoring resource usage with periodic sampling + * @param intervalMs - Sampling interval in milliseconds (default: 60000ms = 1 minute) + */ + startMonitoring(intervalMs: number = 60000): void { + if (this.isMonitoring) { + console.warn('Resource monitoring is already active'); + return; + } + + this.isMonitoring = true; + this.sampleCount = 0; + + // Initial sample + this.collectAndReportMetrics(); + + // Set up periodic sampling + this.monitoringInterval = setInterval(() => { + this.collectAndReportMetrics(); + }, intervalMs); + + console.log(`Resource monitoring started with ${intervalMs}ms interval`); + } + + /** + * Stop resource monitoring + */ + stopMonitoring(): void { + if (this.monitoringInterval) { + clearInterval(this.monitoringInterval); + this.monitoringInterval = null; + } + this.isMonitoring = false; + console.log('Resource monitoring stopped'); + } + + /** + * Collect current resource metrics + */ + private collectResourceMetrics(): ResourceUsageProperties { + const metrics: ResourceUsageProperties = { + memory_usage_mb: this.getMemoryUsage(), + network_requests_count: this.getNetworkRequestsCount(), + active_connections: this.getActiveConnections(), + }; + + // Add CPU usage if available + const cpuUsage = this.getCPUUsage(); + if (cpuUsage !== null) { + metrics.cpu_usage_percent = cpuUsage; + } + + // Add cache hit rate if available + const cacheHitRate = this.getCacheHitRate(); + if (cacheHitRate !== null) { + metrics.cache_hit_rate = cacheHitRate; + } + + return metrics; + } + + /** + * Collect metrics and report to analytics + */ + private collectAndReportMetrics(): void { + try { + const metrics = this.collectResourceMetrics(); + this.sampleCount++; + + // Always send sampled data every 10th sample for baseline tracking + if (this.sampleCount % 10 === 0) { + const event = eventBuilders.resourceUsageSampled(metrics); + analytics.track(event.event, event.properties); + } + + // Check for high usage conditions + const isHighUsage = + metrics.memory_usage_mb > this.highUsageThresholds.memory || + (metrics.cpu_usage_percent && metrics.cpu_usage_percent > this.highUsageThresholds.cpu) || + metrics.network_requests_count > this.highUsageThresholds.networkRequests; + + if (isHighUsage) { + const event = eventBuilders.resourceUsageHigh(metrics); + analytics.track(event.event, event.properties); + } + } catch (error) { + console.error('Failed to collect resource metrics:', error); + } + } + + /** + * Get current memory usage in MB + */ + private getMemoryUsage(): number { + if ('memory' in performance && (performance as any).memory) { + return (performance as any).memory.usedJSHeapSize / (1024 * 1024); + } + + // Fallback: estimate based on performance timing + return 0; + } + + /** + * Get CPU usage percentage (if available) + */ + private getCPUUsage(): number | null { + // This is a placeholder - actual CPU usage would require native APIs + // In a Tauri app, you could call a Rust function to get real CPU usage + return null; + } + + /** + * Get count of active network requests + */ + private getNetworkRequestsCount(): number { + // Count active fetch requests if performance observer is available + if ('PerformanceObserver' in window) { + const entries = performance.getEntriesByType('resource'); + const recentEntries = entries.filter(entry => + entry.startTime > performance.now() - 60000 // Last minute + ); + return recentEntries.length; + } + return 0; + } + + /** + * Get number of active connections (WebSocket, SSE, etc.) + */ + private getActiveConnections(): number { + // This would need to be tracked by your connection management code + // For now, return a placeholder + return 0; + } + + /** + * Get cache hit rate if available + */ + private getCacheHitRate(): number | null { + // This would need to be calculated based on your caching implementation + return null; + } + + /** + * Set custom thresholds for high usage detection + */ + setThresholds(thresholds: Partial): void { + this.highUsageThresholds = { + ...this.highUsageThresholds, + ...thresholds, + }; + } + + /** + * Get current thresholds + */ + getThresholds(): typeof ResourceMonitor.prototype.highUsageThresholds { + return { ...this.highUsageThresholds }; + } + + /** + * Force a single metric collection and report + */ + collectOnce(): ResourceUsageProperties { + const metrics = this.collectResourceMetrics(); + + // Check for high usage + const isHighUsage = + metrics.memory_usage_mb > this.highUsageThresholds.memory || + (metrics.cpu_usage_percent && metrics.cpu_usage_percent > this.highUsageThresholds.cpu) || + metrics.network_requests_count > this.highUsageThresholds.networkRequests; + + if (isHighUsage) { + const event = eventBuilders.resourceUsageHigh(metrics); + analytics.track(event.event, event.properties); + } + + return metrics; + } +} + +// Export singleton instance +export const resourceMonitor = ResourceMonitor.getInstance(); diff --git a/src/lib/api-tracker.ts b/src/lib/api-tracker.ts new file mode 100644 index 0000000..af1ccc4 --- /dev/null +++ b/src/lib/api-tracker.ts @@ -0,0 +1,115 @@ +import { api as originalApi } from './api'; +import { analytics, eventBuilders } from './analytics'; + +// Performance thresholds (in milliseconds) +const PERFORMANCE_THRESHOLDS = { + fast: 100, + normal: 500, + slow: 2000, + bottleneck: 5000, +}; + +// Memory threshold (in MB) +const MEMORY_WARNING_THRESHOLD = 100; + +/** + * Wraps an API method with error and performance tracking + */ +function wrapApiMethod Promise>( + methodName: string, + method: T +): T { + return (async (...args: any[]) => { + const startTime = performance.now(); + const startMemory = ('memory' in performance ? (performance as any).memory?.usedJSHeapSize : 0) || 0; + let retryCount = 0; + + const trackPerformance = (success: boolean, error?: any) => { + const duration = performance.now() - startTime; + const memoryUsed = ((('memory' in performance ? (performance as any).memory?.usedJSHeapSize : 0) || 0) - startMemory) / (1024 * 1024); // Convert to MB + + // Track API errors + if (!success && error) { + const event = eventBuilders.apiError({ + endpoint: methodName, + error_code: error.code || error.status || 'unknown', + retry_count: retryCount, + response_time_ms: duration, + }); + analytics.track(event.event, event.properties); + } + + // Track performance bottlenecks + if (duration > PERFORMANCE_THRESHOLDS.bottleneck) { + const event = eventBuilders.performanceBottleneck({ + operation_type: `api.${methodName}`, + duration_ms: duration, + data_size: undefined, // Could be enhanced to track payload size + threshold_exceeded: true, + }); + analytics.track(event.event, event.properties); + } + + // Track network performance + const connectionQuality = + duration < PERFORMANCE_THRESHOLDS.fast ? 'excellent' : + duration < PERFORMANCE_THRESHOLDS.normal ? 'good' : 'poor'; + + if (success) { + const networkEvent = eventBuilders.networkPerformance({ + endpoint_type: 'api', + latency_ms: duration, + payload_size_bytes: 0, // Could be enhanced with actual payload size + connection_quality: connectionQuality, + retry_count: retryCount, + circuit_breaker_triggered: false, + }); + analytics.track(networkEvent.event, networkEvent.properties); + } + + // Track memory warnings + if (memoryUsed > MEMORY_WARNING_THRESHOLD) { + const event = eventBuilders.memoryWarning({ + component: `api.${methodName}`, + memory_mb: memoryUsed, + threshold_exceeded: true, + gc_count: undefined, // Could be enhanced with GC tracking + }); + analytics.track(event.event, event.properties); + } + }; + + try { + const result = await method(...args); + trackPerformance(true); + return result; + } catch (error) { + trackPerformance(false, error); + throw error; + } + }) as T; +} + +/** + * Creates a tracked version of the API object + */ +function createTrackedApi() { + const trackedApi: any = {}; + + // Wrap each method in the original API + for (const [key, value] of Object.entries(originalApi)) { + if (typeof value === 'function') { + trackedApi[key] = wrapApiMethod(key, value); + } else { + trackedApi[key] = value; + } + } + + return trackedApi as typeof originalApi; +} + +// Export the tracked API +export const api = createTrackedApi(); + +// Re-export types from the original API module +export * from './api';