Skip to content

Commit b96ecfd

Browse files
committed
perf: stream CUR parquet normalization in batches
1 parent e7d7e1c commit b96ecfd

2 files changed

Lines changed: 43 additions & 7 deletions

File tree

bin/benchmark_report_normalize.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,21 +222,27 @@ def _to_tags_dict(value: Any) -> dict[str, Any]:
222222
return {}
223223

224224

225-
def _normalize_cost_rows(costs_parquet: Path) -> list[dict[str, Any]]:
225+
def _iter_parquet_rows(costs_parquet: Path):
226226
try:
227227
import pyarrow.parquet as pq
228228
except ImportError as exc:
229229
raise RuntimeError("pyarrow is required to normalize CUR parquet") from exc
230230

231-
table = pq.read_table(costs_parquet)
232-
rows = table.to_pylist()
233-
cols = set(table.column_names)
231+
parquet_file = pq.ParquetFile(costs_parquet)
232+
cols = set(parquet_file.schema_arrow.names)
233+
234+
for batch in parquet_file.iter_batches():
235+
for row in batch.to_pylist():
236+
yield cols, row
234237

235-
is_map = "resource_tags" in cols and "resource_tags_user_unique_run_id" not in cols
236238

239+
def _normalize_cost_rows(costs_parquet: Path) -> list[dict[str, Any]]:
237240
grouped: dict[tuple[str, str, str], dict[str, float | str]] = {}
241+
is_map: bool | None = None
238242

239-
for row in rows:
243+
for cols, row in _iter_parquet_rows(costs_parquet):
244+
if is_map is None:
245+
is_map = "resource_tags" in cols and "resource_tags_user_unique_run_id" not in cols
240246
if is_map:
241247
tags = _to_tags_dict(row.get("resource_tags"))
242248
run_id = tags.get("user_unique_run_id") or tags.get("user_nf_unique_run_id")

modules/local/normalize_benchmark_jsonl/tests/test_normalize.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import json
22

3-
from benchmark_report_normalize import extract_runs, extract_tasks, load_run_data, normalize_jsonl
3+
import pyarrow as pa
4+
import pyarrow.parquet as pq
5+
6+
from benchmark_report_normalize import _normalize_cost_rows, extract_runs, extract_tasks, load_run_data, normalize_jsonl
47

58

69
def test_cached_count_extracted(make_run, flat_task):
@@ -38,6 +41,33 @@ def test_normalize_writes_jsonl_bundle(tmp_path, make_run, flat_task, write_run_
3841
assert task["process_short"] == "PROCESS_A"
3942

4043

44+
def test_normalize_cost_rows_reads_parquet_in_batches(tmp_path):
45+
parquet_path = tmp_path / "costs.parquet"
46+
table = pa.table(
47+
{
48+
"resource_tags_user_unique_run_id": ["run1", "run1"],
49+
"resource_tags_user_pipeline_process": ["PROC_A", "PROC_A"],
50+
"resource_tags_user_task_hash": ["abcdef12", "abcdef12"],
51+
"split_line_item_split_cost": [1.25, 2.75],
52+
"split_line_item_unused_cost": [0.25, 0.75],
53+
}
54+
)
55+
pq.write_table(table, parquet_path, row_group_size=1)
56+
57+
rows = _normalize_cost_rows(parquet_path)
58+
59+
assert rows == [
60+
{
61+
"run_id": "run1",
62+
"process": "PROC_A",
63+
"hash": "abcdef12",
64+
"cost": 5.0,
65+
"used_cost": 4.0,
66+
"unused_cost": 1.0,
67+
}
68+
]
69+
70+
4171
def test_load_run_data(tmp_path, make_run, write_run_json):
4272
data_dir = tmp_path / "data"
4373
write_run_json(data_dir, [make_run(), make_run(run_id="run2")])

0 commit comments

Comments
 (0)