trustformers_debug/utilities/
health.rs1use crate::{DebugConfig, DebugSession, QuickDebugLevel, SimplifiedDebugResult};
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Serialize, Deserialize)]
9pub struct HealthCheckResult {
10 pub overall_score: f64,
11 pub status: String,
12 pub issues: Vec<String>,
13 pub critical_issues: bool,
14 pub timestamp: chrono::DateTime<chrono::Utc>,
15}
16
17#[derive(Debug, Serialize, Deserialize)]
19pub struct DebugSummary {
20 pub config_hash: String,
21 pub total_debug_runs: usize,
22 pub total_issues: usize,
23 pub critical_issues: usize,
24 pub recommendations: Vec<String>,
25 pub timestamp: chrono::DateTime<chrono::Utc>,
26}
27
28#[derive(Debug, Clone)]
30pub enum ExportFormat {
31 Json,
32 Csv,
33 Html,
34}
35
36#[derive(Debug, Clone)]
38pub enum DebugTemplate {
39 Development,
40 Production,
41 Training,
42 Research,
43}
44
45pub struct HealthChecker;
47
48impl HealthChecker {
49 pub async fn quick_health_check<T>(model: &T) -> Result<HealthCheckResult> {
51 let result = crate::quick_debug(model, QuickDebugLevel::Light).await?;
52
53 let health_score = match &result {
54 SimplifiedDebugResult::Light(health) => health.score,
55 SimplifiedDebugResult::Standard { health, .. } => health.score,
56 SimplifiedDebugResult::Deep(report) => {
57 let summary = report.summary();
58 Self::health_score_from_counts(summary.critical_issues, summary.total_issues)
59 },
60 SimplifiedDebugResult::Production(anomaly) => {
61 100.0 - (anomaly.anomaly_count as f64 * 10.0)
62 },
63 };
64
65 Ok(HealthCheckResult {
66 overall_score: health_score,
67 status: Self::score_to_status(health_score),
68 issues: result.recommendations(),
69 critical_issues: result.has_critical_issues(),
70 timestamp: chrono::Utc::now(),
71 })
72 }
73
74 pub fn score_to_status(score: f64) -> String {
76 match score {
77 s if s >= 90.0 => "Excellent".to_string(),
78 s if s >= 75.0 => "Good".to_string(),
79 s if s >= 50.0 => "Fair".to_string(),
80 s if s >= 25.0 => "Poor".to_string(),
81 _ => "Critical".to_string(),
82 }
83 }
84
85 pub fn generate_debug_summary(
87 config: &DebugConfig,
88 results: &[SimplifiedDebugResult],
89 ) -> DebugSummary {
90 let mut total_issues = 0;
91 let mut critical_issues = 0;
92 let mut all_recommendations = Vec::new();
93
94 for result in results {
95 match result {
96 SimplifiedDebugResult::Light(health) => {
97 if health.score < 50.0 {
98 critical_issues += 1;
99 }
100 total_issues += 1;
101 },
102 SimplifiedDebugResult::Standard { health, .. } => {
103 if health.score < 50.0 {
104 critical_issues += 1;
105 }
106 total_issues += 1;
107 },
108 SimplifiedDebugResult::Deep(report) => {
109 let summary = report.summary();
110 total_issues += summary.total_issues;
111 critical_issues += summary.critical_issues;
112 },
113 SimplifiedDebugResult::Production(anomaly) => {
114 total_issues += anomaly.anomaly_count;
115 if anomaly.severity_level.to_lowercase().contains("critical")
116 || anomaly.severity_level.to_lowercase().contains("high")
117 {
118 critical_issues += 1;
119 }
120 },
121 }
122
123 all_recommendations.extend(result.recommendations());
124 }
125
126 all_recommendations.dedup();
127
128 DebugSummary {
129 config_hash: Self::hash_config(config),
130 total_debug_runs: results.len(),
131 total_issues,
132 critical_issues,
133 recommendations: all_recommendations,
134 timestamp: chrono::Utc::now(),
135 }
136 }
137
138 pub async fn export_debug_data(
140 session: &DebugSession,
141 format: ExportFormat,
142 output_path: &str,
143 ) -> Result<String> {
144 let report = session.generate_snapshot().await?;
145
146 match format {
147 ExportFormat::Json => {
148 let json_data = serde_json::to_string_pretty(&report)?;
149 tokio::fs::write(output_path, json_data).await?;
150 },
151 ExportFormat::Csv => {
152 let csv_data = Self::report_to_csv(&report)?;
153 tokio::fs::write(output_path, csv_data).await?;
154 },
155 ExportFormat::Html => {
156 let html_data = Self::report_to_html(&report)?;
157 tokio::fs::write(output_path, html_data).await?;
158 },
159 }
160
161 Ok(format!("Debug data exported to {}", output_path))
162 }
163
164 pub fn create_debug_template(template_type: DebugTemplate) -> DebugConfig {
166 match template_type {
167 DebugTemplate::Development => DebugConfig {
168 enable_tensor_inspection: true,
169 enable_gradient_debugging: true,
170 enable_model_diagnostics: true,
171 enable_visualization: true,
172 enable_memory_profiling: true,
173 enable_computation_graph_analysis: true,
174 max_tracked_tensors: 1000,
175 max_gradient_history: 100,
176 sampling_rate: 1.0,
177 ..Default::default()
178 },
179 DebugTemplate::Production => DebugConfig {
180 enable_tensor_inspection: false,
181 enable_gradient_debugging: false,
182 enable_model_diagnostics: false,
183 enable_visualization: false,
184 enable_memory_profiling: true,
185 enable_computation_graph_analysis: false,
186 max_tracked_tensors: 10,
187 max_gradient_history: 10,
188 sampling_rate: 0.1,
189 ..Default::default()
190 },
191 DebugTemplate::Training => DebugConfig {
192 enable_tensor_inspection: true,
193 enable_gradient_debugging: true,
194 enable_model_diagnostics: true,
195 enable_visualization: false,
196 enable_memory_profiling: true,
197 enable_computation_graph_analysis: true,
198 max_tracked_tensors: 500,
199 max_gradient_history: 50,
200 sampling_rate: 0.5,
201 ..Default::default()
202 },
203 DebugTemplate::Research => DebugConfig {
204 enable_tensor_inspection: true,
205 enable_gradient_debugging: true,
206 enable_model_diagnostics: true,
207 enable_visualization: true,
208 enable_memory_profiling: true,
209 enable_computation_graph_analysis: true,
210 max_tracked_tensors: 2000,
211 max_gradient_history: 200,
212 sampling_rate: 1.0,
213 ..Default::default()
214 },
215 }
216 }
217
218 fn hash_config(config: &DebugConfig) -> String {
220 use std::collections::hash_map::DefaultHasher;
221 use std::hash::{Hash, Hasher};
222
223 let mut hasher = DefaultHasher::new();
224 format!("{:?}", config).hash(&mut hasher);
225 format!("{:x}", hasher.finish())
226 }
227
228 fn report_to_csv(report: &crate::DebugReport) -> Result<String> {
236 let summary = report.summary();
237 let score = Self::health_score_from_counts(summary.critical_issues, summary.total_issues);
238 Ok(format!(
239 "timestamp,score,issues\n{},{:.1},{}",
240 chrono::Utc::now().to_rfc3339(),
241 score,
242 summary.total_issues
243 ))
244 }
245
246 fn health_score_from_counts(critical_issues: usize, total_issues: usize) -> f64 {
251 100.0 - (critical_issues as f64 * 20.0 + total_issues as f64 * 5.0)
252 }
253
254 fn report_to_html(report: &crate::DebugReport) -> Result<String> {
256 Ok(format!(
260 r#"
261<!DOCTYPE html>
262<html>
263<head>
264 <title>Debug Report</title>
265 <style>
266 body {{ font-family: Arial, sans-serif; margin: 40px; }}
267 .report {{ background: #f5f5f5; padding: 20px; border-radius: 5px; }}
268 </style>
269</head>
270<body>
271 <h1>TrustformeRS Debug Report</h1>
272 <div class="report">
273 <pre>{}</pre>
274 </div>
275</body>
276</html>
277 "#,
278 serde_json::to_string_pretty(report)?
279 ))
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::tensor_inspector::{
287 AlertSeverity as TensorAlertSeverity, TensorAlert, TensorAlertType, TensorInspectionReport,
288 };
289 use std::collections::HashMap;
290 use uuid::Uuid;
291
292 #[tokio::test]
298 async fn test_report_to_csv_reflects_real_summary_not_hardcoded_zeros() {
299 let mut session = DebugSession::new(DebugConfig::default());
300 session.start().await.expect("session should start");
301 let mut report = session.stop().await.expect("session should stop");
302
303 report.tensor_report = Some(TensorInspectionReport {
304 total_tensors: 1,
305 tensors_with_issues: 1,
306 total_memory_usage: 0,
307 alerts: vec![TensorAlert {
308 id: Uuid::new_v4(),
309 tensor_id: Uuid::new_v4(),
310 tensor_name: "test_tensor".to_string(),
311 alert_type: TensorAlertType::NaNValues,
312 severity: TensorAlertSeverity::Critical,
313 message: "NaN detected".to_string(),
314 timestamp: chrono::Utc::now(),
315 }],
316 comparisons: Vec::new(),
317 summary_stats: HashMap::new(),
318 });
319
320 let csv = HealthChecker::report_to_csv(&report).expect("csv conversion should succeed");
321 let data_row = csv.lines().nth(1).expect("csv must have a data row after the header");
322 let fields: Vec<&str> = data_row.split(',').collect();
323 assert_eq!(fields.len(), 3, "expected timestamp,score,issues columns");
324
325 let score: f64 = fields[1].parse().expect("score column must be numeric");
326 let issues: usize = fields[2].parse().expect("issues column must be numeric");
327
328 assert_eq!(
331 issues, 1,
332 "issues must reflect the real NaN alert, not the old hardcoded 0"
333 );
334 assert_eq!(
335 score, 75.0,
336 "score must be computed from report.summary(), not the old hardcoded 0"
337 );
338 }
339
340 #[tokio::test]
341 async fn test_report_to_csv_clean_report_has_zero_issues_and_full_score() {
342 let mut session = DebugSession::new(DebugConfig::default());
343 session.start().await.expect("session should start");
344 let report = session.stop().await.expect("session should stop");
345
346 let csv = HealthChecker::report_to_csv(&report).expect("csv conversion should succeed");
347 let data_row = csv.lines().nth(1).expect("csv must have a data row after the header");
348 let fields: Vec<&str> = data_row.split(',').collect();
349
350 let score: f64 = fields[1].parse().expect("score column must be numeric");
351 let issues: usize = fields[2].parse().expect("issues column must be numeric");
352 assert_eq!(issues, 0);
353 assert_eq!(score, 100.0);
354 }
355}