Skip to content

Commit 3727b5e

Browse files
committed
add cice5 profiler
1 parent 3305cfd commit 3727b5e

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 CICE5 profiling data.
5+
The data to be parsed is written in the following form, where bloc stats are discarded:
6+
7+
Timer 1: Total 8133.37 seconds
8+
Timer stats (node): min = 8133.36 seconds
9+
max = 8133.37 seconds
10+
mean= 8133.36 seconds
11+
Timer stats(block): min = 0.00 seconds
12+
max = 0.00 seconds
13+
mean= 0.00 seconds
14+
Timer 2: TimeLoop 8133.00 seconds
15+
Timer stats (node): min = 8132.99 seconds
16+
max = 8133.00 seconds
17+
mean= 8132.99 seconds
18+
Timer stats(block): min = 0.00 seconds
19+
max = 0.00 seconds
20+
mean= 0.00 seconds
21+
"""
22+
23+
from access.parsers.profiling import ProfilingParser
24+
import re
25+
26+
27+
class CICE5ProfilingParser(ProfilingParser):
28+
"""CICE5 profiling output parser."""
29+
30+
def __init__(self):
31+
super().__init__()
32+
self._metrics = ["min", "max", "mean"]
33+
34+
@property
35+
def metrics(self) -> list:
36+
return self._metrics
37+
38+
def read(self, stream: str) -> dict:
39+
# Initialize result dictionary
40+
result = {"region": [], "min": [], "max": [], "mean": []}
41+
42+
# Regex pattern to match timer blocks
43+
# This captures the region name and the three node timing values
44+
pattern = r"Timer\s+\d+:\s+(\w+)\s+[\d.]+\s+seconds\s+Timer stats \(node\): min =\s+([\d.]+) seconds\s+max =\s+([\d.]+) seconds\s+mean=\s+([\d.]+) seconds"
45+
46+
# Find all matches
47+
matches = re.findall(pattern, stream, re.MULTILINE | re.DOTALL)
48+
49+
# Extract data from matches
50+
for match in matches:
51+
region, min_time, max_time, mean_time = match
52+
result["region"].append(region)
53+
result["min"].append(float(min_time))
54+
result["max"].append(float(max_time))
55+
result["mean"].append(float(mean_time))
56+
57+
return result

tests/test_cice5_profiling.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
6+
from access.parsers.cice5_profiling import CICE5ProfilingParser
7+
8+
9+
@pytest.fixture(scope="module")
10+
def cice5_required_metrics():
11+
return ("min", "max", "mean")
12+
13+
@pytest.fixture(scope="module")
14+
def cice5_parser():
15+
"""Fixture instantiating the CICE5 parser."""
16+
return CICE5ProfilingParser()
17+
18+
19+
@pytest.fixture(scope="module")
20+
def cice5_profiling():
21+
"""Fixture returning a dict holding the parsed FMS timing content without hits."""
22+
return {
23+
"region": ["Total", "TimeLoop"],
24+
"min": [16197.42, 16197.14],
25+
"max": [16197.47, 16197.19],
26+
"mean": [16197.44, 16197.16],
27+
}
28+
29+
30+
@pytest.fixture(scope="module")
31+
def cice5_log_file():
32+
"""Fixture returning the CICE5 timing content."""
33+
return """ --------------------------------
34+
CICE model diagnostic output
35+
--------------------------------
36+
37+
Document ice_in namelist parameters:
38+
====================================
39+
40+
runtype = continue
41+
Restart read/written 17520 58159382400.0000
42+
0.000000000000000E+000
43+
44+
Timing information:
45+
46+
Timer 1: Total 16197.47 seconds
47+
Timer stats (node): min = 16197.42 seconds
48+
max = 16197.47 seconds
49+
mean= 16197.44 seconds
50+
Timer stats(block): min = 0.00 seconds
51+
max = 0.00 seconds
52+
mean= 0.00 seconds
53+
Timer 2: TimeLoop 16197.19 seconds
54+
Timer stats (node): min = 16197.14 seconds
55+
max = 16197.19 seconds
56+
mean= 16197.16 seconds
57+
Timer stats(block): min = 0.00 seconds
58+
max = 0.00 seconds
59+
mean= 0.00 seconds
60+
"""
61+
62+
63+
def test_cice5_profiling(cice5_required_metrics, cice5_parser, cice5_log_file, cice5_profiling):
64+
"""Test the correct parsing of CICE5 timing information."""
65+
parsed_log = cice5_parser.read(cice5_log_file)
66+
67+
# check metrics are present in parser and parsed output
68+
for metric in cice5_required_metrics:
69+
assert metric in cice5_parser.metrics, f"{metric} metric not found in CICE5 parser metrics."
70+
assert metric in parsed_log, f"{metric} metric not found in CICE5 parsed log."
71+
72+
# check content for each metric is correct
73+
for idx, region in enumerate(cice5_profiling["region"]):
74+
assert region in parsed_log["region"], f"{region} not found in CICE5 parsed log"
75+
for metric in cice5_required_metrics:
76+
assert (
77+
cice5_profiling[metric][idx] == parsed_log[metric][idx]
78+
), f"Incorrect {metric} for region {region} (idx: {idx})."

0 commit comments

Comments
 (0)