Skip to main content

rust_ethernet_ip/client/
diagnostics.rs

1use super::EipClient;
2use tokio::time::Duration;
3
4impl EipClient {
5    /// Quick connection health check (no I/O).
6    ///
7    /// Returns `true` if the session handle is valid and there has been activity
8    /// within the last 150 seconds. Use this for cheap periodic checks; for a
9    /// definitive check that the PLC is still responding, use [`check_health_detailed`](Self::check_health_detailed).
10    pub async fn check_health(&self) -> bool {
11        self.session_handle != 0
12            && self.last_activity.lock().await.elapsed() < Duration::from_secs(150)
13    }
14
15    /// Builds a lightweight diagnostics snapshot from the current client state.
16    pub async fn get_diagnostics_snapshot(&self) -> crate::DiagnosticsSnapshot {
17        self.build_diagnostics_snapshot(crate::HealthCheckMode::Passive, self.check_health().await)
18            .await
19    }
20
21    /// Verifies the connection by sending a keep-alive (and re-registering if needed).
22    ///
23    /// Use this when you need to confirm the PLC is still reachable (e.g. after
24    /// a long idle or before a critical operation). On failure, consider
25    /// reconnecting; check [`EtherNetIpError::is_retriable`](crate::error::EtherNetIpError::is_retriable) on errors.
26    pub async fn check_health_detailed(&mut self) -> crate::error::Result<bool> {
27        if self.session_handle == 0 {
28            return Ok(false);
29        }
30
31        // Try sending a lightweight keep-alive command
32        match self.send_keep_alive().await {
33            Ok(()) => Ok(true),
34            Err(_) => {
35                // If keep-alive fails, try re-registering the session
36                match self.register_session().await {
37                    Ok(()) => Ok(true),
38                    Err(_) => Ok(false),
39                }
40            }
41        }
42    }
43
44    /// Builds a verified diagnostics snapshot by actively checking PLC connectivity.
45    pub async fn get_diagnostics_snapshot_detailed(
46        &mut self,
47    ) -> crate::error::Result<crate::DiagnosticsSnapshot> {
48        let is_healthy = self.check_health_detailed().await?;
49        Ok(self
50            .build_diagnostics_snapshot(crate::HealthCheckMode::Verified, is_healthy)
51            .await)
52    }
53
54    async fn build_diagnostics_snapshot(
55        &self,
56        health_mode: crate::HealthCheckMode,
57        is_healthy: bool,
58    ) -> crate::DiagnosticsSnapshot {
59        let now = std::time::SystemTime::now();
60        let session_active = self.session_handle != 0;
61        let last_activity_elapsed = self.last_activity.lock().await.elapsed();
62        let last_success_time = if is_healthy || session_active {
63            Some(
64                now.checked_sub(last_activity_elapsed)
65                    .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
66            )
67        } else {
68            None
69        };
70
71        let error_category = if session_active && !is_healthy {
72            Some(crate::ErrorCategory::Session)
73        } else {
74            None
75        };
76
77        crate::DiagnosticsSnapshot {
78            captured_at: now,
79            connections: crate::ConnectionMetrics {
80                active_connections: if session_active { 1 } else { 0 },
81                total_connections: if session_active { 1 } else { 0 },
82                failed_connections: 0,
83                connection_uptime_avg: Duration::ZERO,
84                last_connection_time: last_success_time,
85            },
86            operations: crate::OperationMetrics {
87                total_reads: 0,
88                total_writes: 0,
89                successful_reads: 0,
90                successful_writes: 0,
91                failed_reads: 0,
92                failed_writes: 0,
93                batch_operations: 0,
94                subscription_updates: 0,
95                partial_batch_failures: 0,
96                last_successful_read_time: None,
97                last_failed_read_time: None,
98                last_successful_write_time: None,
99                last_failed_write_time: None,
100            },
101            performance: crate::PerformanceMetrics {
102                avg_read_latency_ms: 0.0,
103                avg_write_latency_ms: 0.0,
104                max_read_latency_ms: 0.0,
105                max_write_latency_ms: 0.0,
106                reads_per_second: 0.0,
107                writes_per_second: 0.0,
108                memory_usage_mb: 0.0,
109                cpu_usage_percent: 0.0,
110            },
111            errors: crate::ErrorMetrics {
112                network_errors: 0,
113                protocol_errors: 0,
114                timeout_errors: 0,
115                tag_not_found_errors: 0,
116                data_type_errors: 0,
117                session_errors: if error_category == Some(crate::ErrorCategory::Session) {
118                    1
119                } else {
120                    0
121                },
122                route_path_errors: 0,
123                embedded_service_errors: 0,
124                known_controller_limitation_errors: 0,
125                retriable_errors: if error_category == Some(crate::ErrorCategory::Session) {
126                    1
127                } else {
128                    0
129                },
130                non_retriable_errors: 0,
131                last_error_time: if error_category.is_some() {
132                    Some(now)
133                } else {
134                    None
135                },
136                last_error_message: error_category.map(|_| {
137                    "Detailed health check reported session-level connectivity failure".to_string()
138                }),
139                last_error_category: error_category,
140                last_retriable_error_time: if error_category == Some(crate::ErrorCategory::Session)
141                {
142                    Some(now)
143                } else {
144                    None
145                },
146            },
147            health: crate::HealthMetrics {
148                overall_health: if is_healthy {
149                    crate::HealthStatus::Healthy
150                } else if session_active {
151                    crate::HealthStatus::Critical
152                } else {
153                    crate::HealthStatus::Unknown
154                },
155                last_health_check: now,
156                health_mode,
157                last_verified_health_check: if health_mode == crate::HealthCheckMode::Verified {
158                    Some(now)
159                } else {
160                    None
161                },
162                consecutive_failures: if is_healthy { 0 } else { 1 },
163                recovery_attempts: 0,
164                system_uptime: Duration::ZERO,
165                last_success_time,
166                last_failure_time: if session_active && !is_healthy {
167                    Some(now)
168                } else {
169                    None
170                },
171            },
172            system_metrics_are_placeholders: true,
173        }
174    }
175}