1010from datetime import datetime
1111from pathlib import Path
1212
13- from .colors import BOLD , CYAN , GREEN , MAGENTA , RESET , YELLOW , _paint
13+ from .colors import BOLD , CYAN , GREEN , RESET , YELLOW , _paint
1414from .fs import _next_available_path , _preview_pythonpath
1515from .keys import _KeyReader , _clear_screen
1616from .metrics import CSV_FIELDS , _extract_attempt_metrics , _upsert_csv_row
@@ -71,6 +71,12 @@ def _window_title(attempt_number: int, total_attempts: int) -> str:
7171 return f"Pacman - Attempt { attempt_number } /{ total_attempts } "
7272
7373
74+ def _format_duration_mm_ss (duration_seconds : float ) -> str :
75+ total_seconds = max (0 , int (duration_seconds ))
76+ minutes , seconds = divmod (total_seconds , 60 )
77+ return f"{ minutes :02d} :{ seconds :02d} "
78+
79+
7480def _create_attempt_state (attempt_number : int , total_attempts : int ) -> dict [str , object ]:
7581 return {
7682 "attempt_display" : _attempt_display (attempt_number , total_attempts ),
@@ -79,8 +85,7 @@ def _create_attempt_state(attempt_number: int, total_attempts: int) -> dict[str,
7985 "stdout_lines" : 0 ,
8086 "stderr_lines" : 0 ,
8187 "started_at" : "" ,
82- "finished_at" : "" ,
83- "duration_seconds" : "" ,
88+ "duration" : "" ,
8489 "score" : "" ,
8590 "result" : "" ,
8691 "return_code" : "" ,
@@ -124,6 +129,7 @@ def _refresh_batch_console(batch_artifacts: dict[str, object]) -> None:
124129 attempt_number : dict (state )
125130 for attempt_number , state in batch_artifacts ["attempt_states" ].items ()
126131 }
132+ refresh_started_at = datetime .now ()
127133
128134 counts = {"pending" : 0 , "running" : 0 , "completed" : 0 , "failed" : 0 , "interrupted" : 0 }
129135 for state in attempt_states .values ():
@@ -173,10 +179,16 @@ def _refresh_batch_console(batch_artifacts: dict[str, object]) -> None:
173179
174180 for attempt_number in range (1 , total_attempts + 1 ):
175181 state = attempt_states .get (attempt_number , _create_attempt_state (attempt_number , total_attempts ))
182+ duration_display = str (state .get ("duration" , "" ))
183+ if state .get ("started_at" ) and state .get ("status" ) == "running" :
184+ started_at = datetime .fromisoformat (str (state ["started_at" ]))
185+ duration_display = _format_duration_mm_ss ((refresh_started_at - started_at ).total_seconds ())
176186 line = (
177187 f"[{ attempt_number } ] { state ['status' ]} | "
178188 f"stdout={ state .get ('stdout_lines' , 0 )} | stderr={ state .get ('stderr_lines' , 0 )} "
179189 )
190+ if duration_display :
191+ line += f" | elapsed={ duration_display } "
180192 if state .get ("return_code" ) not in ("" , None ):
181193 line += f" | exit={ state ['return_code' ]} "
182194 if state .get ("score" ):
@@ -441,15 +453,15 @@ def _run_game_attempt(
441453
442454 if cancel_event .is_set ():
443455 finished_at = started_at
456+ duration_display = _format_duration_mm_ss ((finished_at - started_at ).total_seconds ())
444457 status_label = "interrupted (launcher stop)"
445458 _update_attempt_state (
446459 batch_artifacts ,
447460 attempt_number ,
448461 status = status_label ,
449462 return_code = 130 ,
450463 started_at = started_at .isoformat (timespec = "seconds" ),
451- finished_at = finished_at .isoformat (timespec = "seconds" ),
452- duration_seconds = "0.000" ,
464+ duration = duration_display ,
453465 )
454466 _upsert_csv_row (
455467 csv_path ,
@@ -464,8 +476,7 @@ def _run_game_attempt(
464476 "status" : status_label ,
465477 "return_code" : 130 ,
466478 "started_at" : started_at .isoformat (timespec = "seconds" ),
467- "finished_at" : finished_at .isoformat (timespec = "seconds" ),
468- "duration_seconds" : "0.000" ,
479+ "duration" : duration_display ,
469480 "agent" : selected_agent ,
470481 "layout" : selected_layout ,
471482 "ghosts" : selected_ghosts ,
@@ -483,7 +494,7 @@ def _run_game_attempt(
483494 "stdout" : "" ,
484495 "stderr" : "" ,
485496 "started_at" : started_at ,
486- "finished_at " : finished_at ,
497+ "duration " : duration_display ,
487498 "interrupted" : True ,
488499 "window_title" : window_title ,
489500 }
@@ -605,7 +616,7 @@ def _run_game_attempt(
605616 else "failed"
606617 )
607618 metrics = _extract_attempt_metrics (stdout_text )
608- duration_seconds = ( finished_at - started_at ).total_seconds ()
619+ duration_display = _format_duration_mm_ss (( finished_at - started_at ).total_seconds () )
609620
610621 _append_text (
611622 log_path ,
@@ -616,7 +627,7 @@ def _run_game_attempt(
616627 f"Status: { status_label } " ,
617628 f"Return code: { return_code } " ,
618629 f"Finished: { finished_at .isoformat (timespec = 'seconds' )} " ,
619- f"Duration seconds : { duration_seconds :.3f } " ,
630+ f"Duration: { duration_display } " ,
620631 f"Score: { metrics ['score' ] or '<unknown>' } " ,
621632 f"Result: { metrics ['result' ] or '<unknown>' } " ,
622633 "" ,
@@ -637,8 +648,7 @@ def _run_game_attempt(
637648 "status" : status_label ,
638649 "return_code" : return_code ,
639650 "started_at" : started_at .isoformat (timespec = "seconds" ),
640- "finished_at" : finished_at .isoformat (timespec = "seconds" ),
641- "duration_seconds" : f"{ duration_seconds :.3f} " ,
651+ "duration" : duration_display ,
642652 "agent" : selected_agent ,
643653 "layout" : selected_layout ,
644654 "ghosts" : selected_ghosts ,
@@ -652,7 +662,7 @@ def _run_game_attempt(
652662 status = status_label ,
653663 return_code = return_code ,
654664 finished_at = finished_at .isoformat (timespec = "seconds" ),
655- duration_seconds = f" { duration_seconds :.3f } " ,
665+ duration = duration_display ,
656666 score = metrics ["score" ],
657667 result = metrics ["result" ],
658668 )
@@ -671,6 +681,7 @@ def _run_game_attempt(
671681 "stderr" : stderr_text ,
672682 "started_at" : started_at ,
673683 "finished_at" : finished_at ,
684+ "duration" : duration_display ,
674685 "interrupted" : interrupted ,
675686 "window_title" : window_title ,
676687 }
0 commit comments