-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a class to parse FMS profiling data.
- Loading branch information
1 parent
c2a0026
commit 7f6d84c
Showing
2 changed files
with
129 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"""FMS profiling data parser.""" | ||
|
||
from pathlib import Path | ||
import re | ||
|
||
from om3utils.utils import convert_from_string | ||
from om3utils.profiling import ProfilingParser | ||
|
||
|
||
class FMSProfilingParser(ProfilingParser): | ||
"""Defines a ProfilingParser for FMS profiling data, such as output by MOM6. | ||
The data to be parsed is written in the following form: | ||
hits tmin tmax tavg tstd tfrac grain pemin pemax | ||
Total runtime 1 138.600364 138.600366 138.600365 0.000001 1.000 0 0 11 | ||
Ocean Initialization 2 2.344926 2.345701 2.345388 0.000198 0.017 11 0 11 | ||
Ocean 23 86.869466 86.871652 86.870450 0.000744 0.627 1 0 11 | ||
Ocean dynamics 96 43.721019 44.391032 43.957944 0.244785 0.317 11 0 11 | ||
Ocean thermodynamics and tracers 72 27.377185 33.281659 29.950144 1.792324 0.216 11 0 11 | ||
MPP_STACK high water mark= 0 | ||
""" | ||
|
||
def __init__(self, filename: str): | ||
super().__init__() | ||
|
||
# FMS provides the following metrics: | ||
self._metrics = ["hits", "tmin", "tmax", "tavg", "tstd", "tfrac", "grain", "pemin", "pemax"] | ||
self._filename = filename | ||
|
||
@property | ||
def metrics(self) -> list: | ||
return self._metrics | ||
|
||
def read(self, path: Path) -> dict: | ||
"""Given a file path, returns a dictionary holding the parsed data. | ||
Args: | ||
path (Path): File to parse. | ||
Returns: | ||
dict: Profiling data. | ||
""" | ||
profiling_file = path / self._filename | ||
if not profiling_file.is_file(): | ||
raise FileNotFoundError(f"File not found: {profiling_file.as_posix()}") | ||
|
||
# Regular expression to extract the profiling section from the file | ||
header = r"\s*hits\s*tmin\s*tmax\s*tavg\s*tstd\s*tfrac\s*grain\s*pemin\s*pemax\s*" | ||
footer = r" MPP_STACK high water mark=\s*\d*" | ||
profiling_section_p = re.compile(header + r"(.*)" + footer, re.DOTALL) | ||
|
||
# Regular expression to parse the data for each region | ||
profile_line = r"^\s*(?P<region>[a-zA-Z:()_/\-*&\s]+(?<!\s))" | ||
for metric in self.metrics: | ||
profile_line += r"\s+(?P<" + metric + r">[0-9.]+)" | ||
profile_line += r"$" | ||
profiling_region_p = re.compile(profile_line, re.MULTILINE) | ||
|
||
# Parse data | ||
stats = {"region": []} | ||
stats.update(dict(zip(self.metrics, [[] for _ in self.metrics]))) | ||
with open(profiling_file, "r") as f: | ||
profiling_section = profiling_section_p.search(f.read()).group(1) | ||
for line in profiling_region_p.finditer(profiling_section): | ||
stats["region"].append(line.group("region")) | ||
for metric in self.metrics: | ||
stats[str(metric)].append(convert_from_string(line.group(metric))) | ||
|
||
return stats |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import pytest | ||
from pathlib import Path | ||
|
||
from test_utils import MockFile | ||
from om3utils.fms_profiling import FMSProfilingParser | ||
|
||
|
||
@pytest.fixture() | ||
def simple_fms_stats(): | ||
return { | ||
"region": [ | ||
"Total runtime", | ||
"Ocean Initialization", | ||
"Ocean", | ||
"Ocean dynamics", | ||
"Ocean thermodynamics and tracers", | ||
], | ||
"hits": [1.0, 2.0, 23.0, 96.0, 72.0], | ||
"tmin": [138.600364, 2.344926, 86.869466, 43.721019, 27.377185], | ||
"tmax": [138.600366, 2.345701, 86.871652, 44.391032, 33.281659], | ||
"tavg": [138.600365, 2.345388, 86.87045, 43.957944, 29.950144], | ||
"tstd": [1e-06, 0.000198, 0.000744, 0.244785, 1.792324], | ||
"tfrac": [1.0, 0.017, 0.627, 0.317, 0.216], | ||
"grain": [0.0, 11.0, 1.0, 11.0, 11.0], | ||
"pemin": [0.0, 0.0, 0.0, 0.0, 0.0], | ||
"pemax": [11.0, 11.0, 11.0, 11.0, 11.0], | ||
} | ||
|
||
|
||
@pytest.fixture() | ||
def simple_fms_output_file(tmp_path): | ||
file = tmp_path / "simple_output_file " | ||
output_str = """Some random stuff | ||
over a few lines | ||
with some number: 1, 2, 3 | ||
and special symbols: | /* # | ||
then the actual profiling output: | ||
hits tmin tmax tavg tstd tfrac grain pemin pemax | ||
Total runtime 1 138.600364 138.600366 138.600365 0.000001 1.000 0 0 11 | ||
Ocean Initialization 2 2.344926 2.345701 2.345388 0.000198 0.017 11 0 11 | ||
Ocean 23 86.869466 86.871652 86.870450 0.000744 0.627 1 0 11 | ||
Ocean dynamics 96 43.721019 44.391032 43.957944 0.244785 0.317 11 0 11 | ||
Ocean thermodynamics and tracers 72 27.377185 33.281659 29.950144 1.792324 0.216 11 0 11 | ||
MPP_STACK high water mark= 0 | ||
""" | ||
return MockFile(file, output_str) | ||
|
||
|
||
def test_fms_profiling_read(tmp_path, simple_fms_output_file, simple_fms_stats): | ||
parser = FMSProfilingParser(simple_fms_output_file.file.name) | ||
stats = parser.read(tmp_path) | ||
|
||
assert stats == simple_fms_stats | ||
|
||
|
||
def test_read_missing_fms_profiling_file(): | ||
with pytest.raises(FileNotFoundError): | ||
parser = FMSProfilingParser("benchmark") | ||
parser.read(Path("garbage")) |