|
2 | 2 | //! |
3 | 3 | //! Query detailed failure information and provide actionable suggestions. |
4 | 4 |
|
5 | | -use charmer_parsers::run_command_allow_failure; |
| 5 | +use charmer_parsers::{ |
| 6 | + format_duration, format_duration_lsf, parse_duration_secs, parse_memory_mb, |
| 7 | + run_command_allow_failure, MemoryFormat, |
| 8 | +}; |
6 | 9 | use thiserror::Error; |
7 | 10 | use tokio::process::Command; |
8 | 11 |
|
@@ -193,19 +196,19 @@ fn parse_bhist_output(job_id: &str, output: &str) -> Result<FailureAnalysis, Fai |
193 | 196 |
|
194 | 197 | // Look for memory info |
195 | 198 | if line.contains("MAX MEM:") { |
196 | | - max_mem_mb = parse_lsf_memory(line, "MAX MEM:"); |
| 199 | + max_mem_mb = parse_lsf_memory_from_line(line, "MAX MEM:"); |
197 | 200 | } |
198 | 201 | if line.contains("MEMLIMIT") || line.contains("MEM LIMIT:") { |
199 | | - mem_limit_mb = |
200 | | - parse_lsf_memory(line, "MEMLIMIT").or_else(|| parse_lsf_memory(line, "MEM LIMIT:")); |
| 202 | + mem_limit_mb = parse_lsf_memory_from_line(line, "MEMLIMIT") |
| 203 | + .or_else(|| parse_lsf_memory_from_line(line, "MEM LIMIT:")); |
201 | 204 | } |
202 | 205 |
|
203 | 206 | // Look for runtime info |
204 | 207 | if line.contains("Run time:") || line.contains("RUN_TIME:") { |
205 | | - run_time_seconds = parse_lsf_time(line); |
| 208 | + run_time_seconds = parse_lsf_time_from_line(line); |
206 | 209 | } |
207 | 210 | if line.contains("RUNLIMIT") || line.contains("RUN LIMIT:") { |
208 | | - run_limit_seconds = parse_lsf_time(line); |
| 211 | + run_limit_seconds = parse_lsf_time_from_line(line); |
209 | 212 | } |
210 | 213 | } |
211 | 214 |
|
@@ -285,99 +288,61 @@ fn extract_number(s: &str, prefix: &str) -> Option<u64> { |
285 | 288 | } |
286 | 289 | } |
287 | 290 |
|
288 | | -/// Parse LSF memory value (e.g., "4.5 Gbytes", "1024 Mbytes"). |
289 | | -fn parse_lsf_memory(line: &str, prefix: &str) -> Option<u64> { |
| 291 | +/// Parse LSF memory value from a line with a prefix (e.g., "MAX MEM: 4.5 Gbytes"). |
| 292 | +fn parse_lsf_memory_from_line(line: &str, prefix: &str) -> Option<u64> { |
290 | 293 | if let Some(idx) = line.find(prefix) { |
291 | | - let after = &line[idx + prefix.len()..]; |
292 | | - // Find the number |
293 | | - let parts: Vec<&str> = after.split_whitespace().collect(); |
294 | | - if parts.len() >= 2 { |
295 | | - let value: f64 = parts[0].parse().ok()?; |
296 | | - let unit = parts[1].to_lowercase(); |
297 | | - return Some(if unit.starts_with('g') { |
298 | | - (value * 1024.0) as u64 |
299 | | - } else if unit.starts_with('m') { |
300 | | - value as u64 |
301 | | - } else if unit.starts_with('k') { |
302 | | - (value / 1024.0) as u64 |
303 | | - } else { |
304 | | - value as u64 |
305 | | - }); |
306 | | - } |
| 294 | + let after = &line[idx + prefix.len()..].trim(); |
| 295 | + // Extract just "4.5 Gbytes" part for the shared parser |
| 296 | + let mem_str: String = after |
| 297 | + .split_whitespace() |
| 298 | + .take(2) |
| 299 | + .collect::<Vec<_>>() |
| 300 | + .join(" "); |
| 301 | + parse_memory_mb(&mem_str, MemoryFormat::Lsf) |
| 302 | + } else { |
| 303 | + None |
307 | 304 | } |
308 | | - None |
309 | 305 | } |
310 | 306 |
|
311 | | -/// Parse LSF time value (e.g., "01:30:00", "1800 seconds"). |
312 | | -fn parse_lsf_time(line: &str) -> Option<u64> { |
313 | | - // Look for HH:MM:SS pattern |
| 307 | +/// Parse LSF time value from a line (e.g., "Run time: 01:30:00"). |
| 308 | +fn parse_lsf_time_from_line(line: &str) -> Option<u64> { |
| 309 | + // Look for HH:MM:SS pattern in the line |
314 | 310 | for word in line.split_whitespace() { |
315 | | - if word.contains(':') { |
316 | | - let parts: Vec<&str> = word.split(':').collect(); |
317 | | - if parts.len() == 3 { |
318 | | - let hours: u64 = parts[0].parse().ok()?; |
319 | | - let mins: u64 = parts[1].parse().ok()?; |
320 | | - let secs: u64 = parts[2].parse().ok()?; |
321 | | - return Some(hours * 3600 + mins * 60 + secs); |
322 | | - } |
| 311 | + if word.contains(':') && word.chars().filter(|c| *c == ':').count() == 2 { |
| 312 | + return parse_duration_secs(word); |
323 | 313 | } |
324 | 314 | } |
325 | 315 | // Look for "N seconds" pattern |
326 | 316 | if let Some(idx) = line.find("seconds") { |
327 | 317 | let before = line[..idx].trim(); |
328 | | - // Get the last number token before "seconds" |
329 | 318 | if let Some(num_str) = before.split_whitespace().last() { |
330 | | - if let Ok(secs) = num_str.parse::<f64>() { |
331 | | - return Some(secs as u64); |
| 319 | + if let Ok(secs) = num_str.parse::<u64>() { |
| 320 | + return Some(secs); |
332 | 321 | } |
333 | 322 | } |
334 | 323 | } |
335 | 324 | None |
336 | 325 | } |
337 | 326 |
|
338 | | -/// Format seconds as human-readable duration. |
339 | | -fn format_duration(seconds: u64) -> String { |
340 | | - let hours = seconds / 3600; |
341 | | - let mins = (seconds % 3600) / 60; |
342 | | - let secs = seconds % 60; |
343 | | - |
344 | | - if hours > 24 { |
345 | | - let days = hours / 24; |
346 | | - let hours = hours % 24; |
347 | | - format!("{}d {:02}:{:02}:{:02}", days, hours, mins, secs) |
348 | | - } else if hours > 0 { |
349 | | - format!("{:02}:{:02}:{:02}", hours, mins, secs) |
350 | | - } else { |
351 | | - format!("{:02}:{:02}", mins, secs) |
352 | | - } |
353 | | -} |
354 | | - |
355 | | -/// Format seconds as LSF duration format (HH:MM). |
356 | | -fn format_duration_lsf(seconds: u64) -> String { |
357 | | - let hours = seconds / 3600; |
358 | | - let mins = (seconds % 3600) / 60; |
359 | | - format!("{}:{:02}", hours, mins) |
360 | | -} |
361 | | - |
362 | 327 | #[cfg(test)] |
363 | 328 | mod tests { |
364 | 329 | use super::*; |
365 | 330 |
|
366 | 331 | #[test] |
367 | | - fn test_parse_lsf_memory() { |
| 332 | + fn test_parse_lsf_memory_from_line() { |
368 | 333 | assert_eq!( |
369 | | - parse_lsf_memory("MAX MEM: 4.5 Gbytes", "MAX MEM:"), |
| 334 | + parse_lsf_memory_from_line("MAX MEM: 4.5 GB", "MAX MEM:"), |
370 | 335 | Some(4608) |
371 | 336 | ); |
372 | 337 | assert_eq!( |
373 | | - parse_lsf_memory("MEMLIMIT 8192 Mbytes", "MEMLIMIT"), |
| 338 | + parse_lsf_memory_from_line("MEMLIMIT 8192 MB", "MEMLIMIT"), |
374 | 339 | Some(8192) |
375 | 340 | ); |
376 | 341 | } |
377 | 342 |
|
378 | 343 | #[test] |
379 | | - fn test_parse_lsf_time() { |
380 | | - assert_eq!(parse_lsf_time("Run time: 01:30:00"), Some(5400)); |
381 | | - assert_eq!(parse_lsf_time("1800 seconds"), Some(1800)); |
| 344 | + fn test_parse_lsf_time_from_line() { |
| 345 | + assert_eq!(parse_lsf_time_from_line("Run time: 01:30:00"), Some(5400)); |
| 346 | + assert_eq!(parse_lsf_time_from_line("1800 seconds"), Some(1800)); |
382 | 347 | } |
383 | 348 | } |
0 commit comments