@@ -259,15 +259,21 @@ def parse_rules_from_log(log_path: Path) -> dict[str, int]:
259259 """
260260 rule_counts : dict [str , int ] = {}
261261 current_rule : str | None = None
262+ job_rules : dict [str , str ] = {}
262263
263264 try :
264265 for line in log_path .read_text ().splitlines ():
265266 # Track current rule being executed
266- if match := RULE_START_PATTERN .match (line ):
267+ if match := RULE_START_PATTERN .match (line . lstrip () ):
267268 current_rule = match .group (1 )
268- # Count "Finished job" as rule completion
269- elif "Finished job" in line and current_rule is not None :
270- rule_counts [current_rule ] = rule_counts .get (current_rule , 0 ) + 1
269+ # Map jobid to current rule
270+ elif (match := JOBID_PATTERN .match (line )) and current_rule is not None :
271+ job_rules [match .group (1 )] = current_rule
272+ # Count finished jobs using jobid-to-rule mapping
273+ elif match := FINISHED_JOB_PATTERN .search (line ):
274+ rule = job_rules .get (match .group (1 ), current_rule )
275+ if rule is not None :
276+ rule_counts [rule ] = rule_counts .get (rule , 0 ) + 1
271277 except OSError as e :
272278 logger .info ("Could not read log file %s: %s" , log_path , e )
273279
@@ -320,7 +326,7 @@ def record_pending_error() -> None:
320326 lines = _cached_lines if _cached_lines is not None else log_path .read_text ().splitlines ()
321327 for line_num , line in enumerate (lines ):
322328 # Track current rule being executed
323- if match := RULE_START_PATTERN .match (line ):
329+ if match := RULE_START_PATTERN .match (line . lstrip () ):
324330 record_pending_error ()
325331 current_rule = match .group (1 )
326332 current_jobid = None # Reset jobid for new rule block
@@ -329,7 +335,7 @@ def record_pending_error() -> None:
329335 current_log_path = None
330336
331337 # Timestamp lines end error blocks
332- elif line . startswith ( "[" ) and TIMESTAMP_PATTERN .match (line ):
338+ elif TIMESTAMP_PATTERN .match (line . lstrip () ):
333339 record_pending_error ()
334340
335341 # Capture wildcards within rule block
@@ -484,7 +490,7 @@ def emit_pending_error() -> None:
484490 lines = _cached_lines if _cached_lines is not None else log_path .read_text ().splitlines ()
485491 for line in lines :
486492 # Track current rule - this also ends any pending error block
487- if match := RULE_START_PATTERN .match (line ):
493+ if match := RULE_START_PATTERN .match (line . lstrip () ):
488494 emit_pending_error ()
489495 current_rule = match .group (1 )
490496 current_jobid = None
@@ -493,7 +499,7 @@ def emit_pending_error() -> None:
493499 current_log_path = None
494500
495501 # Timestamp lines end error blocks
496- elif line . startswith ( "[" ) and TIMESTAMP_PATTERN .match (line ):
502+ elif TIMESTAMP_PATTERN .match (line . lstrip () ):
497503 emit_pending_error ()
498504
499505 # Capture wildcards - applies to both rule blocks and error blocks
@@ -633,12 +639,12 @@ def _get_first_log_timestamp(
633639 try :
634640 if _cached_lines is not None :
635641 for line in _cached_lines :
636- if match := TIMESTAMP_PATTERN .match (line ):
642+ if match := TIMESTAMP_PATTERN .match (line . lstrip () ):
637643 return _parse_timestamp (match .group (1 ))
638644 else :
639645 with log_path .open () as f :
640646 for line in f :
641- if match := TIMESTAMP_PATTERN .match (line ):
647+ if match := TIMESTAMP_PATTERN .match (line . lstrip () ):
642648 return _parse_timestamp (match .group (1 ))
643649 except OSError as e :
644650 logger .info ("Could not read log file %s: %s" , log_path , e )
@@ -681,11 +687,11 @@ def parse_completed_jobs_from_log(
681687 lines = _cached_lines if _cached_lines is not None else log_path .read_text ().splitlines ()
682688 for line in lines :
683689 # Check for timestamp
684- if match := TIMESTAMP_PATTERN .match (line ):
690+ if match := TIMESTAMP_PATTERN .match (line . lstrip () ):
685691 current_timestamp = _parse_timestamp (match .group (1 ))
686692
687693 # Track current rule being executed
688- elif match := RULE_START_PATTERN .match (line ):
694+ elif match := RULE_START_PATTERN .match (line . lstrip () ):
689695 current_rule = match .group (1 )
690696 current_wildcards = None
691697 current_threads = None
@@ -769,7 +775,7 @@ def parse_threads_from_log(log_path: Path) -> dict[str, int]:
769775 try :
770776 for line in log_path .read_text ().splitlines ():
771777 # Track current rule (resets context)
772- if RULE_START_PATTERN .match (line ):
778+ if RULE_START_PATTERN .match (line . lstrip () ):
773779 current_jobid = None
774780 current_threads = None
775781
@@ -826,7 +832,7 @@ def parse_all_jobs_from_log(
826832 lines = _cached_lines if _cached_lines is not None else log_path .read_text ().splitlines ()
827833 for line in lines :
828834 # Track current rule being scheduled
829- if match := RULE_START_PATTERN .match (line ):
835+ if match := RULE_START_PATTERN .match (line . lstrip () ):
830836 # Save previous job if complete
831837 if current_rule is not None and current_jobid is not None :
832838 if current_jobid not in seen_jobids :
0 commit comments