Skip to content

Commit

Permalink
Add a class to parse FMS profiling data.
Browse files Browse the repository at this point in the history
  • Loading branch information
micaeljtoliveira committed Apr 9, 2024
1 parent c2a0026 commit 7f6d84c
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
70 changes: 70 additions & 0 deletions om3utils/fms_profiling.py
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
59 changes: 59 additions & 0 deletions tests/test_fms_profiling.py
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"))

0 comments on commit 7f6d84c

Please sign in to comment.