Skip to content

Commit

Permalink
Generate report zip (PHAREHUB#801)
Browse files Browse the repository at this point in the history
* report zip gen

* consistent cmake version required

* bind to none for better // of multiple mpirun calls

* report zip gen

* more

* correct python cmake var name

* revert default configurator option

* PR comments / move ompi bind to none to configurator

* default report writing on / disabled if testing / simulator retval consistency
  • Loading branch information
PhilipDeegan authored May 12, 2024
1 parent ea898ac commit ced43dc
Show file tree
Hide file tree
Showing 28 changed files with 514 additions and 82 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ tools/cmake.sh
perf.*
**/*.h5
.vscode

.phare*
PHARE_REPORT.zip
18 changes: 17 additions & 1 deletion ISSUES.TXT
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@


# Having Issues

Please run:

> ./tools/report.sh # or "python3 tools/report.py" with the correct PYTHONPATH

This will build a zip archive "REPORT_INFO.zip", which you should either email to us, or
Log an issue on github via https://github.com/PHAREHUB/PHARE/issues/new
Outline the context of your issue, and upload the zip




# Known Issues

1. OMPI symbol resolution with python3.
Affects: OMPI versions < 3
Source: https://github.com/open-mpi/ompi/issues/3705
Expand All @@ -10,4 +25,5 @@
2. Python launching, with C++ MPI init, doesn't work so well with MPI4PY
Source: https://bitbucket.org/mpi4py/mpi4py/issues/154/attempting-to-use-an-mpi-routine-before
Solution:
LD_PRELOAD=/path/to/your/libmpi.so python3 $SCRIPT
LD_PRELOAD=/path/to/your/libmpi.so python3 $SCRIPT

59 changes: 59 additions & 0 deletions pyphare/pyphare/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#
#
#
# program help looks like
"""
usage: phare_sim.py [-h] [-d]
options:
-h, --help show this help message and exit
-d, --dry-run Validate but do not run simulations
"""

import sys
import dataclasses


def disabled_for_testing():
# check if any module is loaded from PHARE tests directory
from pathlib import Path

test_dir = Path(__file__).resolve().parent.parent.parent.parent / "tests"
if test_dir.exists():
test_dir = str(test_dir)
for k, mod in sys.modules.items():
if hasattr(mod, "__file__") and mod.__file__ and test_dir in mod.__file__:
return True
return False


@dataclasses.dataclass
class CliArgs:
dry_run: bool = dataclasses.field(default_factory=lambda: False)
write_reports: bool = dataclasses.field(default_factory=lambda: False)


def parse_cli_args():
default_off = len(sys.argv) == 1 and disabled_for_testing()
if default_off:
return CliArgs()

import argparse

parser = argparse.ArgumentParser()
parser.add_argument(
"-d",
"--dry-run",
help="Validate but do not run simulations",
action="store_true",
default=False,
)
parser.add_argument(
"-w",
"--write_reports",
help="Write build and runtime configs to disk",
action="store_true",
default=True,
)

return CliArgs(**vars(parser.parse_args()))
15 changes: 15 additions & 0 deletions pyphare/pyphare/cpp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#
#
#


import json

# continue to use override if set
_cpp_lib_override = None

Expand Down Expand Up @@ -25,6 +32,14 @@ def cpp_etc_lib():
return importlib.import_module("pybindlibs.cpp_etc")


def build_config():
return cpp_etc_lib().phare_build_config()


def build_config_as_json():
return json.dumps(build_config())


def splitter_type(dim, interp, n_particles):
return getattr(cpp_lib(), f"Splitter_{dim}_{interp}_{n_particles}")

Expand Down
109 changes: 109 additions & 0 deletions pyphare/pyphare/cpp/validate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
PHARE build and runtime validation checks
"""

import os
import sys
import json
import dataclasses
from pathlib import Path

from pyphare.core import phare_utilities
from pyphare import cpp

DOT_PHARE_DIR = Path(os.getcwd()) / ".phare"


ERROR_MESSAGES = dict(
on_phare_config_error="""
Warning: PHARE was not built with the configurator active""",
on_python3_version_mismatch="""
Inconsistency detected!
Python during build and now are not the same!
Build python version : {}
Current python version: {}""",
on_build_config_check_runtime_error="""
Could not interrogate python versions
Please see 'Having Issues' Section of ISSUES.TXT
Actual error (consider adding to issue report): {}""",
)


def print_error(key, *args):
print(ERROR_MESSAGES[key].format(*args).strip())


def python_version_from(binary):
return phare_utilities.decode_bytes(
phare_utilities.run_cli_cmd(f"{binary} -V", check=True).stdout.strip()
)


def check_build_config_is_runtime_compatible(strict=True):
try:
build_config: dict = cpp.build_config()

if "PHARE_CONFIG_ERROR" in build_config:
print_error("on_phare_config_error")
return

build_python_version = build_config["PYTHON_VERSION"]
current_python_version = python_version_from(sys.executable)
if build_python_version != current_python_version:
print_error(
"on_python3_version_mismatch",
build_python_version,
current_python_version,
)

raise ValueError("Python version mismatch!")

except RuntimeError as e:
print_error("on_build_config_check_runtime_error", e)

except ValueError as e:
print(e)
if strict:
raise e


@dataclasses.dataclass
class RuntimeSettings:
python_version: str
python_binary: str


def try_system_binary(cli, log_to):
with open(log_to, "w") as f:
try:
proc = phare_utilities.run_cli_cmd(cli, check=True)
f.write(phare_utilities.decode_bytes(proc.stdout).strip())
except Exception as e:
f.write(f"failed to run cli command {cli}\n")
f.write(f"error {e}")


def try_system_binaries(log_dir):
try_system_binary("free -g", log_dir / "free_dash_g.txt")
try_system_binary("lscpu", log_dir / "lscpu.txt")
try_system_binary("hwloc-info", log_dir / "hwloc_info.txt")


def log_runtime_config():
cpp_lib = cpp.cpp_lib()

settings = RuntimeSettings(
python_binary=sys.executable, python_version=python_version_from(sys.executable)
)

if cpp_lib.mpi_rank() == 0:
DOT_PHARE_DIR.mkdir(exist_ok=True, parents=True)
cpp_lib.mpi_barrier()

rank_dir = DOT_PHARE_DIR / f"rank_{cpp_lib.mpi_rank()}"
rank_dir.mkdir(exist_ok=True)

with open(rank_dir / "runtime_config.json", "w") as f:
json.dump(dataclasses.asdict(settings), f)

try_system_binaries(rank_dir)
3 changes: 2 additions & 1 deletion pyphare/pyphare/pharein/maxwellian_fluid_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def __init__(self, bx=None, by=None, bz=None, **kwargs):
for population in self.populations:
self.add_population(population, **kwargs[population])

self.validate(global_vars.sim)
if not global_vars.sim.dry_run:
self.validate(global_vars.sim)

global_vars.sim.set_model(self)

Expand Down
9 changes: 9 additions & 0 deletions pyphare/pyphare/pharein/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from . import global_vars
from ..core import box as boxm
from ..core.box import Box
from ..core import parse_cli_args

CLI_ARGS = parse_cli_args()


# ------------------------------------------------------------------------------


def supported_dimensions():
Expand Down Expand Up @@ -671,6 +677,9 @@ def wrapper(simulation_object, **kwargs):

kwargs["hyper_resistivity"] = check_hyper_resistivity(**kwargs)

# kwargs["dry_run"] = CLI_ARGS.dry_run
# kwargs["write_reports"] = CLI_ARGS.reports

return func(simulation_object, **kwargs)

return wrapper
Expand Down
27 changes: 22 additions & 5 deletions pyphare/pyphare/simulator/simulator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#
#
#


import atexit
import time as timem
import numpy as np
import pyphare.pharein as ph

CLI_ARGS = ph.simulation.CLI_ARGS

life_cycles = {}

Expand Down Expand Up @@ -31,8 +38,6 @@ def startMPI():

class Simulator:
def __init__(self, simulation, auto_dump=True, **kwargs):
import pyphare.pharein as ph

assert isinstance(simulation, ph.Simulation) # pylint: disable=no-member
self.simulation = simulation
self.cpp_hier = None # HERE
Expand All @@ -58,9 +63,16 @@ def setup(self):
# mostly to detach C++ class construction/dict parsing from C++ Simulator::init
try:
from pyphare.cpp import cpp_lib
import pyphare.pharein as ph

startMPI()

import pyphare.cpp.validate as validate_cpp

if all([not CLI_ARGS.dry_run, CLI_ARGS.write_reports]):
# not necessary during testing
validate_cpp.log_runtime_config()
validate_cpp.check_build_config_is_runtime_compatible()

if self.log_to_file:
self._log_to_file()
ph.populateDict()
Expand Down Expand Up @@ -90,6 +102,9 @@ def initialize(self):
if self.cpp_hier is None:
self.setup()

if CLI_ARGS.dry_run:
return self

self.cpp_sim.initialize()
self._auto_dump() # first dump might be before first advance
return self
Expand All @@ -113,6 +128,8 @@ def _throw(self, e):

def advance(self, dt=None):
self._check_init()
if CLI_ARGS.dry_run:
return self
if dt is None:
dt = self.timeStep()

Expand All @@ -138,6 +155,8 @@ def run(self):
from pyphare.cpp import cpp_lib

self._check_init()
if CLI_ARGS.dry_run:
return self
perf = []
end_time = self.cpp_sim.endTime()
t = self.cpp_sim.currentTime()
Expand Down Expand Up @@ -180,8 +199,6 @@ def data_wrangler(self):

def reset(self):
if self.cpp_sim is not None:
import pyphare.pharein as ph

ph.clearDict()
if self.cpp_dw is not None:
self.cpp_dw.kill()
Expand Down
Loading

0 comments on commit ced43dc

Please sign in to comment.