Skip to content

Commit b91e4eb

Browse files
authored
Add CI heartbeat logs for long dataset builds (#333)
* Add CI heartbeat logs for dataset builds * Format CI heartbeat progress logging
1 parent c6b8c11 commit b91e4eb

5 files changed

Lines changed: 86 additions & 1 deletion

File tree

.github/workflows/pull_request.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ jobs:
3232
runs-on: ubuntu-latest
3333
env:
3434
HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }}
35+
PYTHONUNBUFFERED: "1"
3536
steps:
3637
- name: Checkout code
3738
uses: actions/checkout@v4

.github/workflows/push.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
id-token: "write"
3636
env:
3737
HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }}
38+
PYTHONUNBUFFERED: "1"
3839
steps:
3940
- name: Checkout code
4041
uses: actions/checkout@v4
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make long-running dataset builds emit plain CI heartbeat logs so release workflows are less likely to die silently during calibration.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from policyengine_uk_data.utils.progress import ProcessingProgress
2+
3+
4+
def test_track_dataset_creation_logs_in_ci(monkeypatch, capsys):
5+
monkeypatch.setenv("GITHUB_ACTIONS", "true")
6+
7+
progress = ProcessingProgress()
8+
9+
with progress.track_dataset_creation(["Build base", "Save final"]) as (
10+
update_dataset,
11+
nested_progress,
12+
):
13+
assert nested_progress is None
14+
update_dataset("Build base", "processing")
15+
update_dataset("Build base", "completed")
16+
update_dataset("Save final", "processing")
17+
update_dataset("Save final", "completed")
18+
19+
output = capsys.readouterr().out
20+
assert "[dataset] starting: Build base" in output
21+
assert "[dataset] completed (1/2): Build base" in output
22+
assert "[dataset] completed (2/2): Save final" in output
23+
24+
25+
def test_track_calibration_logs_heartbeats_in_ci(monkeypatch, capsys):
26+
monkeypatch.setenv("CI", "true")
27+
28+
progress = ProcessingProgress()
29+
30+
with progress.track_calibration(12) as update_calibration:
31+
for iteration in range(1, 13):
32+
update_calibration(iteration, calculating_loss=True)
33+
update_calibration(iteration, loss_value=iteration / 10)
34+
35+
output = capsys.readouterr().out
36+
assert "[calibration] epoch 1/12: calculating loss" in output
37+
assert "[calibration] epoch 10/12: loss=1.000000" in output
38+
assert "[calibration] epoch 12/12: loss=1.200000" in output

policyengine_uk_data/utils/progress.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"""
77

88
from contextlib import contextmanager
9+
import os
910
from typing import Any, Dict, List, Optional, Union
10-
import time
1111

1212
from rich.console import Console
1313
from rich.progress import (
@@ -187,6 +187,12 @@ def __init__(self, console: Optional[Console] = None):
187187
"""
188188
self.console = console or Console()
189189
self.progress_manager: Optional[RichProgress] = None
190+
self._plain_output = (
191+
os.environ.get("GITHUB_ACTIONS") == "true" or os.environ.get("CI") == "true"
192+
)
193+
194+
def _emit(self, message: str):
195+
print(message, flush=True)
190196

191197
@contextmanager
192198
def track_dataset_creation(self, datasets: List[str]):
@@ -198,6 +204,23 @@ def track_dataset_creation(self, datasets: List[str]):
198204
Yields:
199205
Tuple of (update_dataset function, progress manager for nested tasks).
200206
"""
207+
if self._plain_output:
208+
completed_count = 0
209+
210+
def update_dataset(dataset_name: str, status: str = "processing"):
211+
nonlocal completed_count
212+
213+
if status == "processing":
214+
self._emit(f"[dataset] starting: {dataset_name}")
215+
elif status == "completed":
216+
completed_count += 1
217+
self._emit(
218+
f"[dataset] completed ({completed_count}/{len(datasets)}): {dataset_name}"
219+
)
220+
221+
yield update_dataset, None
222+
return
223+
201224
with create_progress(self.console) as progress:
202225
# Main dataset creation progress
203226
main_task = progress.add_task(
@@ -265,6 +288,27 @@ def track_calibration(self, iterations: int, nested_progress=None):
265288
Yields:
266289
Function to update calibration progress.
267290
"""
291+
if self._plain_output:
292+
293+
def update_calibration(
294+
iteration: int,
295+
loss_value: Optional[float] = None,
296+
calculating_loss: bool = False,
297+
):
298+
if calculating_loss:
299+
self._emit(
300+
f"[calibration] epoch {iteration}/{iterations}: calculating loss"
301+
)
302+
elif loss_value is not None and (
303+
iteration == 1 or iteration == iterations or iteration % 10 == 0
304+
):
305+
self._emit(
306+
f"[calibration] epoch {iteration}/{iterations}: loss={loss_value:.6f}"
307+
)
308+
309+
yield update_calibration
310+
return
311+
268312
if nested_progress:
269313
# Add calibration as a nested task in existing progress
270314
calibration_task = nested_progress.add_task(

0 commit comments

Comments
 (0)