Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Code formatting
uses: psf/black@stable
uses: astral-sh/ruff-action@v3
with:
options: "--check --verbose --diff"
src: "./src ./tests"
args: "format --check --diff"

pypa-build:
name: PyPA build
Expand Down Expand Up @@ -80,12 +79,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install '.[devel,test]'
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Lint with ruff
uses: astral-sh/ruff-action@v3
- name: Test with pytest
run: |
python -m pytest
Expand Down
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.13.2
hooks:
# Run the linter.
- id: ruff-check
args: [ --fix ]
# Run the formatter.
- id: ruff-format
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ build-backend = "setuptools.build_meta"

[project.optional-dependencies]
devel = [
"flake8",
"black",
"ruff",
"pre-commit",
]
test = [
Expand All @@ -39,10 +38,10 @@ addopts = ["--cov=access.profiling", "--cov-report=term", "--cov-report=html", "
testpaths = ["tests"]

[tool.coverage.run]
omit = ["src/access/profiling/__init__.py"]
omit = ["setup.py"]

[tool.black]
[tool.ruff]
line-length = 120

[tool.flake8]
max-line-length = 120
[tool.ruff.lint]
select = ["E", "F", "B", "C4", "SIM", "PTH", "UP", "I", "N", "C901"]
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file is only needed for the automatic versioning in the conda recipe
from setuptools import setup
import setuptools_scm
from setuptools import setup

setup(version=setuptools_scm.get_version())
16 changes: 7 additions & 9 deletions src/access/profiling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@
access-profiling package.
"""

__version__ = "0.1.0"
from importlib.metadata import version, PackageNotFoundError
from contextlib import suppress
from importlib.metadata import PackageNotFoundError, version

try:
__version__ = "unknown"
with suppress(PackageNotFoundError):
__version__ = version("access-profiling")
except PackageNotFoundError:
# package is not installed
pass

from access.profiling.parser import ProfilingParser
from access.profiling.cice5_parser import CICE5ProfilingParser
from access.profiling.fms_parser import FMSProfilingParser
from access.profiling.um_parser import UMProfilingParser
from access.profiling.parser import ProfilingParser
from access.profiling.payujson_parser import PayuJSONProfilingParser
from access.profiling.cice5_parser import CICE5ProfilingParser
from access.profiling.um_parser import UMProfilingParser

__all__ = [
"ProfilingParser",
Expand Down
8 changes: 6 additions & 2 deletions src/access/profiling/cice5_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
For example, ESM1.6 has 17 timers printed at the end of ice_diag.d output log.
"""

from access.profiling.parser import ProfilingParser
import re

from access.profiling.parser import ProfilingParser


class CICE5ProfilingParser(ProfilingParser):
"""CICE5 profiling output parser."""
Expand Down Expand Up @@ -60,7 +61,10 @@ def read(self, stream: str) -> dict:

# Regex pattern to match timer blocks
# This captures the region name and the three node timing values
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"
pattern = (
r"Timer\s+\d+:\s+(\w+)\s+[\d.]+\s+seconds\s+Timer stats \(node\): min =\s+([\d.]+) seconds\s+max ="
r"\s+([\d.]+) seconds\s+mean=\s+([\d.]+) seconds"
)

# Find all matches
matches = re.findall(pattern, stream, re.MULTILINE | re.DOTALL)
Expand Down
20 changes: 10 additions & 10 deletions src/access/profiling/fms_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
"""Parser for FMS profiling data, such as output by MOM5 and 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
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
"""

from access.profiling.parser import ProfilingParser, _convert_from_string
import re

from access.profiling.parser import ProfilingParser, _convert_from_string


class FMSProfilingParser(ProfilingParser):
"""FMS profiling output parser."""
Expand All @@ -40,7 +41,6 @@ def metrics(self) -> list:
return self._metrics

def read(self, stream: str) -> dict:

# Regular expression to extract the profiling section from the file
header = r"\s*" + r"\s*".join(self._metrics) + r"\s*"
footer = r" MPP_STACK high water mark=\s*\d*"
Expand All @@ -57,7 +57,7 @@ def read(self, stream: str) -> dict:
stats = {"region": []}
stats.update({m: [] for m in self.metrics})
match = profiling_section_p.search(stream)
if match == None:
if match is None:
raise ValueError("No FMS profiling data found")
else:
profiling_section = match.group(1)
Expand Down
8 changes: 5 additions & 3 deletions src/access/profiling/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ class ProfilingParser(ABC):
values, one for each profiling region. Therefore, 'val1a', is the value for metric a of region 1.
"""

@abstractmethod
def __init__(self):
pass
"""Instantiate a ProfilingParser."""

@property
@abstractmethod
Expand All @@ -47,7 +48,8 @@ def read(self, stream: str) -> dict:


def _convert_from_string(value: str) -> Any:
"""Tries to convert a string to the most appropriate numeric type. Leaves it unchanged if conversion does not succeed.
"""Tries to convert a string to the most appropriate numeric type. Leaves it unchanged if conversion does not
succeed.

Args:
value (str): string to convert.
Expand All @@ -58,6 +60,6 @@ def _convert_from_string(value: str) -> Any:
for type_conversion in (int, float):
try:
return type_conversion(value)
except:
except Exception:
continue
return value
7 changes: 4 additions & 3 deletions src/access/profiling/payujson_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
}
"""

from access.profiling.parser import ProfilingParser
import json

from access.profiling.parser import ProfilingParser


class PayuJSONProfilingParser(ProfilingParser):
"""Payu JSON job output profiling parser."""
Expand Down Expand Up @@ -58,8 +59,8 @@ def read(self, stream: str) -> dict:

try:
timings = json.loads(stream)["timings"]
except:
raise ValueError(errmsg)
except Exception as e:
raise ValueError(errmsg) from e

# remove known keys not relevant to profiling
for unwanted_key in ("payu_start_time", "payu_finish_time"):
Expand Down
6 changes: 3 additions & 3 deletions src/access/profiling/um_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@

"""

from access.profiling.parser import ProfilingParser, _convert_from_string

import re
import logging
import re

from access.profiling.parser import ProfilingParser, _convert_from_string

logger = logging.getLogger(__name__)

Expand Down
6 changes: 3 additions & 3 deletions tests/test_cice5_profiling.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ def test_cice5_profiling(cice5_required_metrics, cice5_parser, cice5_log_file, c
for idx, region in enumerate(cice5_profiling["region"]):
assert region in parsed_log["region"], f"{region} not found in CICE5 parsed log"
for metric in cice5_required_metrics:
assert (
cice5_profiling[metric][idx] == parsed_log[metric][idx]
), f"Incorrect {metric} for region {region} (idx: {idx})."
assert cice5_profiling[metric][idx] == parsed_log[metric][idx], (
f"Incorrect {metric} for region {region} (idx: {idx})."
)


def test_cice5_incorrect_profiling(cice5_parser, cice5_incorrect_log_file):
Expand Down
62 changes: 31 additions & 31 deletions tests/test_fms_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ def fms_nohits_log_file():

Tabulating mpp_clock statistics across 49 PEs...

tmin tmax tavg tstd tfrac grain pemin pemax
Total runtime 16282.797785 16282.797792 16282.797789 0.000001 1.000 0 0 48
Ocean 15969.542784 16000.704550 15986.765795 8.643639 0.982 1 0 48
(Ocean initialization) 4.288529 4.296586 4.291991 0.001470 0.000 11 0 48
(Ocean ODA) 0.000000 0.000000 0.000000 0.000000 0.000 11 0 48
(Red Sea/Gulf Bay salinity fix) 0.024143 0.077235 0.040902 0.013836 0.000 31 0 48
OASIS init 0.231678 0.232671 0.232397 0.000242 0.000 1 0 48
oasis_recv 168.797136 171.648384 170.460762 0.650894 0.010 31 0 48
oasis_send 2.468914 2.756777 2.593809 0.079459 0.000 31 0 48
MPP_STACK high water mark= 0
tmin tmax tavg tstd tfrac grain pemin pemax
Total runtime 16282.797785 16282.797792 16282.797789 0.000001 1.000 0 0 48
Ocean 15969.542784 16000.704550 15986.765795 8.643639 0.982 1 0 48
(Ocean initialization) 4.288529 4.296586 4.291991 0.001470 0.000 11 0 48
(Ocean ODA) 0.000000 0.000000 0.000000 0.000000 0.000 11 0 48
(Red Sea/Gulf Bay salinity fix) 0.024143 0.077235 0.040902 0.013836 0.000 31 0 48
OASIS init 0.231678 0.232671 0.232397 0.000242 0.000 1 0 48
oasis_recv 168.797136 171.648384 170.460762 0.650894 0.010 31 0 48
oasis_send 2.468914 2.756777 2.593809 0.079459 0.000 31 0 48
MPP_STACK high water mark= 0
MOM5: --- completed ---
"""

Expand Down Expand Up @@ -128,19 +128,19 @@ def fms_hits_log_file():

Tabulating mpp_clock statistics across 1 PEs...

hits tmin tmax tavg tstd tfrac grain pemin pemax
Total runtime 1 100.641190 100.641190 100.641190 0.000000 1.000 0 0 0
Initialization 1 0.987726 0.987726 0.987726 0.000000 0.010 0 0 0
Main loop 1 98.930085 98.930085 98.930085 0.000000 0.983 0 0 0
Termination 1 0.718969 0.718969 0.718969 0.000000 0.007 0 0 0
Ocean Initialization 2 1.529830 1.529830 1.529830 0.000000 0.015 11 0 0
Ocean 24 98.279247 98.279247 98.279247 0.000000 0.977 1 0 0
Ocean dynamics 192 84.799971 84.799971 84.799971 0.000000 0.843 11 0 0
Ocean thermodynamics and tracers 72 11.512013 11.512013 11.512013 0.000000 0.114 11 0 0
Ocean grid generation and remapp 0 0.000000 0.000000 0.000000 0.000000 0.000 11 0 0
Ocean Other 192 1.710326 1.710326 1.710326 0.000000 0.017 11 0 0
(Ocean tracer advection) 48 4.427230 4.427230 4.427230 0.000000 0.044 21 0 0
MPP_STACK high water mark= 0
hits tmin tmax tavg tstd tfrac grain pemin pemax
Total runtime 1 100.641190 100.641190 100.641190 0.000000 1.000 0 0 0
Initialization 1 0.987726 0.987726 0.987726 0.000000 0.010 0 0 0
Main loop 1 98.930085 98.930085 98.930085 0.000000 0.983 0 0 0
Termination 1 0.718969 0.718969 0.718969 0.000000 0.007 0 0 0
Ocean Initialization 2 1.529830 1.529830 1.529830 0.000000 0.015 11 0 0
Ocean 24 98.279247 98.279247 98.279247 0.000000 0.977 1 0 0
Ocean dynamics 192 84.799971 84.799971 84.799971 0.000000 0.843 11 0 0
Ocean thermodynamics and tracers 72 11.512013 11.512013 11.512013 0.000000 0.114 11 0 0
Ocean grid generation and remapp 0 0.000000 0.000000 0.000000 0.000000 0.000 11 0 0
Ocean Other 192 1.710326 1.710326 1.710326 0.000000 0.017 11 0 0
(Ocean tracer advection) 48 4.427230 4.427230 4.427230 0.000000 0.044 21 0 0
MPP_STACK high water mark= 0
"""


Expand All @@ -151,8 +151,8 @@ def fms_incorrect_log_file():

Tabulating mpp_clock statistics across 1 PEs...

hits tmax tavg tstd tfrac grain pemin pemax
Total runtime 1 100.641190 100.641190 100.641190 0.000000 1.000 0 0 0
hits tmax tavg tstd tfrac grain pemin pemax
Total runtime 1 100.641190 100.641190 100.641190 0.000000 1.000 0 0 0
high water mark= 0
"""

Expand All @@ -163,9 +163,9 @@ def test_fms_nohits_profiling(fms_nohits_parser, fms_nohits_log_file, fms_nohits
for idx, region in enumerate(fms_nohits_profiling.keys()):
assert region in parsed_log, f"{region} not found in mom5 parsed log"
for metric in ("tmin", "tmax", "tavg", "tstd"):
assert (
fms_nohits_profiling[metric][idx] == parsed_log[metric][idx]
), f"Incorrect {metric} for region {region} (idx: {idx})."
assert fms_nohits_profiling[metric][idx] == parsed_log[metric][idx], (
f"Incorrect {metric} for region {region} (idx: {idx})."
)


def test_fms_hits_profiling(fms_hits_parser, fms_hits_log_file, fms_hits_profiling):
Expand All @@ -174,9 +174,9 @@ def test_fms_hits_profiling(fms_hits_parser, fms_hits_log_file, fms_hits_profili
for idx, region in enumerate(fms_hits_profiling.keys()):
assert region in parsed_log, f"{region} not found in mom6 parsed log"
for metric in ("hits", "tmin", "tmax", "tavg", "tstd"):
assert (
fms_hits_profiling[metric][idx] == parsed_log[metric][idx]
), f"Incorrect {metric} for region {region} (idx: {idx})."
assert fms_hits_profiling[metric][idx] == parsed_log[metric][idx], (
f"Incorrect {metric} for region {region} (idx: {idx})."
)


def test_fms_incorrect_profiling(fms_hits_parser, fms_incorrect_log_file):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ def test_base_parser(profiling_data):
def test_str2num():
"""Tests conversion of numbers to most appropriate type."""
str2int = _convert_from_string("42")
assert type(str2int) == int
assert type(str2int) is int
assert str2int == 42
str2float = _convert_from_string("-1.23")
assert type(str2float) == float
assert type(str2float) is float
assert str2float == -1.23
str2float = _convert_from_string("0.00000")
assert str2float == 0.0
str2str = _convert_from_string("somestr")
assert type(str2str) == str
assert type(str2str) is str
assert str2str == "somestr"
6 changes: 3 additions & 3 deletions tests/test_payujson_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def test_payujson_profiling(payujson_parser, payujson_log_file, payujson_profili
parsed_log = payujson_parser.read(payujson_log_file)
for idx, region in enumerate(payujson_profiling.keys()):
assert region in parsed_log, f"{region} not found in Payu JSON parsed log."
assert (
payujson_profiling["walltime"][idx] == parsed_log["walltime"][idx]
), f"Incorrect walltime for region {region} (idx: {idx})."
assert payujson_profiling["walltime"][idx] == parsed_log["walltime"][idx], (
f"Incorrect walltime for region {region} (idx: {idx})."
)


def test_payujson_incorrect_profiling(payujson_parser):
Expand Down
Loading
Loading