Skip to content

Commit 95b4855

Browse files
committed
add Payu JSON profiling parser
1 parent d178fb9 commit 95b4855

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2025 ACCESS-NRI and contributors. See the top-level COPYRIGHT file for details.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Parser for payu JSON walltime data generated by payu.
5+
The data to be parsed is written in the following form:
6+
7+
{
8+
"scheduler_job_id": "149764665.gadi-pbs",
9+
"scheduler_type": "pbs",
10+
# ... many more fields ...
11+
"timings": {
12+
"payu_start_time": "2025-09-16T08:52:50.748807",
13+
"payu_setup_duration_seconds": 47.73822930175811,
14+
"payu_model_run_duration_seconds": 6776.044810215011,
15+
"payu_run_duration_seconds": 6779.385873348918,
16+
"payu_archive_duration_seconds": 8.063649574294686,
17+
"payu_finish_time": "2025-09-16T10:46:48.974451",
18+
"payu_total_duration_seconds": 6838.225644
19+
},
20+
# ... more fields
21+
}
22+
"""
23+
24+
from access.parsers.profiling import ProfilingParser
25+
import json
26+
27+
28+
class PayuJSONProfilingParser(ProfilingParser):
29+
"""Payu JSON job output profiling parser."""
30+
31+
def __init__(self):
32+
"""Instantiate Payu JSON profiling parser."""
33+
super().__init__()
34+
self._metrics = ["walltime"]
35+
36+
@property
37+
def metrics(self) -> list:
38+
return self._metrics
39+
40+
def read(self, stream: str) -> dict:
41+
42+
try:
43+
timings = json.loads(stream)["timings"]
44+
except KeyError:
45+
raise KeyError('"timings" key missing in stream.')
46+
except json.JSONDecodeError:
47+
raise ValueError("Invalid JSON supplied.")
48+
49+
# remove known keys not relevant to profiling
50+
for unwanted_key in ("payu_start_time", "payu_finish_time"):
51+
try:
52+
del timings[unwanted_key]
53+
except KeyError:
54+
continue
55+
56+
result = {"regions": [], "walltime": []}
57+
58+
# transpose dict to be consistent with other profiling parsers.
59+
for k, v in timings.items():
60+
result["regions"].append(k)
61+
result["walltime"].append(v)
62+
63+
return result

tests/test_payujson_profiling.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright 2025 ACCESS-NRI and contributors. See the top-level COPYRIGHT file for details.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import pytest
5+
import datetime
6+
7+
from access.parsers.payujson_profiling import PayuJSONProfilingParser
8+
9+
10+
@pytest.fixture(scope="module")
11+
def payujson_parser():
12+
"""Fixture instantiating the Payu JSON parser."""
13+
return PayuJSONProfilingParser()
14+
15+
16+
@pytest.fixture(scope="module")
17+
def payujson_profiling():
18+
"""Fixture returning a dict holding the parsed FMS timing content without hits."""
19+
return {
20+
"regions": [
21+
"payu_setup_duration_seconds",
22+
"payu_model_run_duration_seconds",
23+
"payu_run_duration_seconds",
24+
"payu_archive_duration_seconds",
25+
"payu_total_duration_seconds",
26+
],
27+
"walltime": [47.73822930175811, 6776.044810215011, 6779.385873348918, 8.063649574294686, 6838.225644],
28+
}
29+
30+
31+
@pytest.fixture(scope="module")
32+
def payujson_log_file():
33+
"""Fixture returning the FMS timing content without hits column."""
34+
return """{
35+
"scheduler_job_id": "149764665.gadi-pbs",
36+
"timings": {
37+
"payu_start_time": "2025-09-16T08:52:50.748807",
38+
"payu_setup_duration_seconds": 47.73822930175811,
39+
"payu_model_run_duration_seconds": 6776.044810215011,
40+
"payu_run_duration_seconds": 6779.385873348918,
41+
"payu_archive_duration_seconds": 8.063649574294686,
42+
"payu_finish_time": "2025-09-16T10:46:48.974451",
43+
"payu_total_duration_seconds": 6838.225644
44+
},
45+
"payu_run_id": "5c9027104cc39a5d39814624537c21440b68beb7",
46+
"payu_model_run_status": 0,
47+
"model_finish_time": "1844-01-01T00:00:00",
48+
"model_start_time": "1843-01-01T00:00:00",
49+
"model_calendar": "proleptic_gregorian",
50+
"payu_run_status": 0
51+
}
52+
"""
53+
54+
55+
def test_payujson_profiling(payujson_parser, payujson_log_file, payujson_profiling):
56+
"""Test the correct parsing of Payu JSON timing information."""
57+
parsed_log = payujson_parser.read(payujson_log_file)
58+
for idx, region in enumerate(payujson_profiling.keys()):
59+
assert region in parsed_log, f"{region} not found in Payu JSON parsed log"
60+
assert (
61+
payujson_profiling["walltime"][idx] == parsed_log["walltime"][idx]
62+
), f"Incorrect walltime for region {region} (idx: {idx})."

0 commit comments

Comments
 (0)