diff --git a/application_events.json b/application_events.json index 4eea360..6658233 100644 --- a/application_events.json +++ b/application_events.json @@ -4231,8 +4231,83 @@ "trigger_variable": null, "auto_started": true } + }, + { + "timestamp": "2025-08-15T00:46:30.290892", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-15T00:46:30.340169", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-15T00:46:30.349167", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: Fast", + "details": { + "dataset_id": "Fast", + "variables_count": 2, + "streaming_count": 1, + "prefix": "fast" + } + }, + { + "timestamp": "2025-08-15T00:46:30.355834", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 2 datasets activated", + "details": { + "activated_datasets": 2, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-15T00:49:03.086267", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_1", + "variables": [ + "UR29_Brix", + "UR29_ma", + "AUX Blink_1.0S" + ], + "time_window": 3600, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-15T00:50:02.569962", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_1", + "variables": [ + "UR29_Brix", + "UR29_ma", + "AUX Blink_1.0S" + ], + "time_window": 3600, + "trigger_variable": null, + "auto_started": true + } } ], - "last_updated": "2025-08-15T00:41:32.191310", - "total_entries": 381 + "last_updated": "2025-08-15T00:50:02.569962", + "total_entries": 387 } \ No newline at end of file diff --git a/frontend/src/components/ChartjsPlot.jsx b/frontend/src/components/ChartjsPlot.jsx index d584638..541a5d0 100644 --- a/frontend/src/components/ChartjsPlot.jsx +++ b/frontend/src/components/ChartjsPlot.jsx @@ -70,6 +70,14 @@ const ChartjsPlot = ({ session, height = '400px' }) => { // Data preservation system for zoom operations const dataBackupRef = useRef(new Map()); + + // Chart health monitoring + const chartHealthRef = useRef({ + lastDataTimestamp: 0, + consecutiveErrors: 0, + isHealthy: true, + lastHealthCheck: 0 + }); const bgColor = useColorModeValue('white', 'gray.800'); const textColor = useColorModeValue('gray.600', 'gray.300'); @@ -145,6 +153,113 @@ const ChartjsPlot = ({ session, height = '400px' }) => { return []; }, [getColor]); + // Chart health monitoring and auto-recovery + const checkChartHealth = useCallback(() => { + if (!chartRef.current) return false; + + const health = chartHealthRef.current; + const now = Date.now(); + + // Check if chart is receiving data + const timeSinceLastData = now - health.lastDataTimestamp; + const hasRecentData = timeSinceLastData < 30000; // 30 seconds + + // Check if streaming is properly configured + const realtimeOptions = chartRef.current.options?.scales?.x?.realtime; + const isStreamingConfigured = !!realtimeOptions; + + // Check if datasets exist and are properly configured + const hasDatasets = chartRef.current.data?.datasets?.length > 0; + + const isHealthy = hasRecentData && isStreamingConfigured && hasDatasets; + + if (!isHealthy && health.isHealthy) { + console.warn('⚠️ Chart health check failed:', { + hasRecentData, + isStreamingConfigured, + hasDatasets, + timeSinceLastData, + consecutiveErrors: health.consecutiveErrors + }); + } + + health.isHealthy = isHealthy; + health.lastHealthCheck = now; + + return isHealthy; + }, []); + + const attemptAutoRecovery = useCallback(async () => { + console.log('🔄 Attempting chart auto-recovery...'); + + try { + // Clear all data directly + if (chartRef.current) { + chartRef.current.data.datasets.forEach(dataset => { + if (dataset.data) { + dataset.data.length = 0; + } + }); + chartRef.current.update('quiet'); + setDataPointsCount(0); + } + + // Wait a moment + await new Promise(resolve => setTimeout(resolve, 100)); + + // Recreate chart if session is active + if (session?.is_active && !session?.is_paused) { + // Trigger a recreation by clearing refs and letting useEffect handle it + chartRef.current = null; + setTimeout(() => { + // The useEffect will recreate the chart + console.log('✅ Chart auto-recovery completed'); + }, 100); + + // Reset health counters + chartHealthRef.current.consecutiveErrors = 0; + chartHealthRef.current.isHealthy = true; + + return true; + } + } catch (error) { + console.error('❌ Chart auto-recovery failed:', error); + chartHealthRef.current.consecutiveErrors++; + } + + return false; + }, [session]); + + // Diagnostic function to help identify issues + const runDiagnostics = useCallback(() => { + console.log('🔍 Chart Diagnostics:', { + chartExists: !!chartRef.current, + canvasExists: !!canvasRef.current, + sessionId: session?.session_id, + sessionActive: session?.is_active, + sessionPaused: session?.is_paused, + dataBackupSize: dataBackupRef.current.size, + health: chartHealthRef.current, + datasets: chartRef.current?.data?.datasets?.length || 0, + realtimeConfig: !!chartRef.current?.options?.scales?.x?.realtime + }); + + // Check for common issues + const issues = []; + if (!chartRef.current) issues.push('Chart not initialized'); + if (!session?.is_active) issues.push('Session not active'); + if (session?.is_paused) issues.push('Session is paused'); + if (chartHealthRef.current.consecutiveErrors > 5) issues.push(`${chartHealthRef.current.consecutiveErrors} consecutive errors`); + + if (issues.length > 0) { + console.warn('⚠️ Issues detected:', issues); + } else { + console.log('✅ No issues detected'); + } + + return issues; + }, [session]); + // Load historical data from CSV files const loadHistoricalData = useCallback(async (variables, timeWindow) => { try { @@ -872,6 +987,10 @@ const ChartjsPlot = ({ session, height = '400px' }) => { if (pointsAdded > 0) { chart.update('quiet'); + // Update health monitoring + chartHealthRef.current.lastDataTimestamp = Date.now(); + chartHealthRef.current.consecutiveErrors = 0; + // Backup data periodically when significant data is added if (pointsAdded > 5 && dataBackupRef.current) { const backup = chart.data.datasets.map(dataset => ({ @@ -883,6 +1002,20 @@ const ChartjsPlot = ({ session, height = '400px' }) => { dataBackupRef.current.set('current', backup); console.log(`📦 Data backed up: ${backup.reduce((total, ds) => total + ds.data.length, 0)} points`); } + } else { + // No new data received - increment error counter + chartHealthRef.current.consecutiveErrors++; + + // Check if we need auto-recovery after multiple failed attempts + if (chartHealthRef.current.consecutiveErrors > 10) { + console.warn(`⚠️ No data received for ${chartHealthRef.current.consecutiveErrors} consecutive attempts`); + + // Attempt auto-recovery if too many consecutive errors + if (chartHealthRef.current.consecutiveErrors > 20) { + console.warn('🚑 Triggering auto-recovery due to consecutive data failures'); + setTimeout(() => attemptAutoRecovery(), 100); + } + } } // Clear NaN insertion flag @@ -890,7 +1023,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { sessionData.insertNaNOnNextIngest = false; } return pointsAdded; - }, []); + }, [attemptAutoRecovery]); // Fallback cleanup removed per requirement: always realtime; we pause instead when no data @@ -1235,6 +1368,28 @@ const ChartjsPlot = ({ session, height = '400px' }) => { } }, [session?.isFullscreen, createStreamingChart]); + // Periodic health monitoring + useEffect(() => { + if (!session?.session_id || !chartRef.current) return; + + const healthCheckInterval = setInterval(() => { + const isHealthy = checkChartHealth(); + + // Log health status periodically for debugging + if (!isHealthy) { + console.warn('📊 Chart health check:', { + sessionId: session.session_id, + isActive: session.is_active, + isPaused: session.is_paused, + consecutiveErrors: chartHealthRef.current.consecutiveErrors, + timeSinceLastData: Date.now() - chartHealthRef.current.lastDataTimestamp + }); + } + }, 15000); // Check every 15 seconds + + return () => clearInterval(healthCheckInterval); + }, [session?.session_id, checkChartHealth]); + // Initialize chart when config is resolved - simplified approach useEffect(() => { console.log(`🔍 useEffect triggered - sessionId: ${session?.session_id}, hasCanvas: ${!!canvasRef.current}, hasChart: ${!!chartRef.current}`); @@ -1460,6 +1615,8 @@ const ChartjsPlot = ({ session, height = '400px' }) => { bottom={2} right={2} zIndex={10} + display="flex" + gap={2} > + + {/* Auto-recovery button when chart health is poor */} + {!chartHealthRef.current.isHealthy && ( + <> + + + + + )} )} diff --git a/system_state.json b/system_state.json index 1491628..024e8b7 100644 --- a/system_state.json +++ b/system_state.json @@ -9,5 +9,5 @@ ] }, "auto_recovery_enabled": true, - "last_update": "2025-08-15T00:35:28.773668" + "last_update": "2025-08-15T00:46:30.361074" } \ No newline at end of file