-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
phlop runtime process monitoring #881
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||||||
from pathlib import Path | ||||||||||
|
||||||||||
|
||||||||||
def have_phlop(): | ||||||||||
from importlib.util import find_spec | ||||||||||
|
||||||||||
try: | ||||||||||
return find_spec("phlop.dict") is not None | ||||||||||
except (ImportError, ModuleNotFoundError): | ||||||||||
return False | ||||||||||
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Simplify the The Apply this diff to simplify the function: def have_phlop():
from importlib.util import find_spec
- try:
- return find_spec("phlop.dict") is not None
- except (ImportError, ModuleNotFoundError):
- return False
+ return find_spec("phlop.dict") is not None 📝 Committable suggestion
Suggested change
|
||||||||||
|
||||||||||
|
||||||||||
def valdict(**kwargs): | ||||||||||
if not have_phlop(): | ||||||||||
return dict | ||||||||||
Comment on lines
+14
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return a dictionary instance instead of the In the Apply this diff to fix the issue: def valdict(**kwargs):
if not have_phlop():
- return dict
+ return dict(**kwargs) 📝 Committable suggestion
Suggested change
|
||||||||||
|
||||||||||
from phlop.dict import ValDict # pylint: disable=import-error | ||||||||||
|
||||||||||
return ValDict(**kwargs) | ||||||||||
|
||||||||||
|
||||||||||
_globals = valdict(stats_man=None) | ||||||||||
|
||||||||||
|
||||||||||
def monitoring_yaml_file(cpplib): | ||||||||||
path = Path(".phare") / "stats" / f"rank.{cpplib.mpi_rank()}.yaml" | ||||||||||
path.parent.mkdir(exist_ok=True, parents=True) | ||||||||||
return path | ||||||||||
|
||||||||||
|
||||||||||
def setup_monitoring(cpplib, interval=10): | ||||||||||
if not have_phlop(): | ||||||||||
return | ||||||||||
|
||||||||||
from phlop.app import stats_man as sm # pylint: disable=import-error | ||||||||||
|
||||||||||
_globals.stats_man = sm.AttachableRuntimeStatsManager( | ||||||||||
valdict(yaml=monitoring_yaml_file(cpplib), interval=interval), | ||||||||||
dict(rank=cpplib.mpi_rank()), | ||||||||||
).start() | ||||||||||
|
||||||||||
|
||||||||||
def monitoring_shutdown(cpplib): | ||||||||||
if not have_phlop(): | ||||||||||
return | ||||||||||
|
||||||||||
if _globals.stats_man: | ||||||||||
_globals.stats_man.kill().join() | ||||||||||
Comment on lines
+47
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Access Similar to the previous comment, ensure that you access the Apply this diff to fix the access: -if _globals.stats_man:
- _globals.stats_man.kill().join()
+if _globals['stats_man']:
+ _globals['stats_man'].kill().join() 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,14 +2,19 @@ | |||||||||||||||||
# | ||||||||||||||||||
# | ||||||||||||||||||
|
||||||||||||||||||
import os | ||||||||||||||||||
import datetime | ||||||||||||||||||
import atexit | ||||||||||||||||||
import time as timem | ||||||||||||||||||
import numpy as np | ||||||||||||||||||
import pyphare.pharein as ph | ||||||||||||||||||
from pathlib import Path | ||||||||||||||||||
from . import monitoring as mon | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
life_cycles = {} | ||||||||||||||||||
SIM_MONITOR = os.getenv("PHARE_SIM_MON", "False").lower() in ("true", "1", "t") | ||||||||||||||||||
SCOPE_TIMING = os.getenv("PHARE_SCOPE_TIMING", "False").lower() in ("true", "1", "t") | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
@atexit.register | ||||||||||||||||||
|
@@ -24,6 +29,9 @@ def simulator_shutdown(): | |||||||||||||||||
def make_cpp_simulator(dim, interp, nbrRefinedPart, hier): | ||||||||||||||||||
from pyphare.cpp import cpp_lib | ||||||||||||||||||
|
||||||||||||||||||
if SCOPE_TIMING: | ||||||||||||||||||
Path(".phare/timings").mkdir(exist_ok=True) | ||||||||||||||||||
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling when creating the directory While creating the Apply this diff to handle potential exceptions: +try:
if SCOPE_TIMING:
Path(".phare/timings").mkdir(exist_ok=True)
+except Exception as e:
+ print(f"Failed to create directory '.phare/timings': {e}")
+ # Consider additional error handling or logging as needed 📝 Committable suggestion
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
make_sim = f"make_simulator_{dim}_{interp}_{nbrRefinedPart}" | ||||||||||||||||||
return getattr(cpp_lib(), make_sim)(hier) | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -127,6 +135,7 @@ def initialize(self): | |||||||||||||||||
|
||||||||||||||||||
self.cpp_sim.initialize() | ||||||||||||||||||
self._auto_dump() # first dump might be before first advance | ||||||||||||||||||
|
||||||||||||||||||
return self | ||||||||||||||||||
except: | ||||||||||||||||||
import sys | ||||||||||||||||||
|
@@ -140,7 +149,6 @@ def initialize(self): | |||||||||||||||||
|
||||||||||||||||||
def _throw(self, e): | ||||||||||||||||||
import sys | ||||||||||||||||||
from pyphare.cpp import cpp_lib | ||||||||||||||||||
|
||||||||||||||||||
print_rank0(e) | ||||||||||||||||||
sys.exit(1) | ||||||||||||||||||
|
@@ -170,12 +178,19 @@ def times(self): | |||||||||||||||||
self.timeStep(), | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
def run(self, plot_times=False): | ||||||||||||||||||
def run(self, plot_times=False, monitoring=None): | ||||||||||||||||||
"""monitoring requires phlop""" | ||||||||||||||||||
from pyphare.cpp import cpp_lib | ||||||||||||||||||
|
||||||||||||||||||
self._check_init() | ||||||||||||||||||
|
||||||||||||||||||
if monitoring is None: # check env | ||||||||||||||||||
monitoring = SIM_MONITOR | ||||||||||||||||||
|
||||||||||||||||||
if self.simulation.dry_run: | ||||||||||||||||||
return self | ||||||||||||||||||
if monitoring: | ||||||||||||||||||
mon.setup_monitoring(cpp_lib()) | ||||||||||||||||||
perf = [] | ||||||||||||||||||
end_time = self.cpp_sim.endTime() | ||||||||||||||||||
t = self.cpp_sim.currentTime() | ||||||||||||||||||
|
@@ -197,6 +212,7 @@ def run(self, plot_times=False): | |||||||||||||||||
if plot_times: | ||||||||||||||||||
plot_timestep_time(perf) | ||||||||||||||||||
|
||||||||||||||||||
mon.monitoring_shutdown(cpp_lib()) | ||||||||||||||||||
return self.reset() | ||||||||||||||||||
|
||||||||||||||||||
def _auto_dump(self): | ||||||||||||||||||
|
@@ -263,13 +279,10 @@ def _log_to_file(self): | |||||||||||||||||
DATETIME_FILES - logfile with starting datetime timestamp per rank | ||||||||||||||||||
NONE - no logging files, display to cout | ||||||||||||||||||
""" | ||||||||||||||||||
import os | ||||||||||||||||||
|
||||||||||||||||||
if "PHARE_LOG" not in os.environ: | ||||||||||||||||||
os.environ["PHARE_LOG"] = "RANK_FILES" | ||||||||||||||||||
from pyphare.cpp import cpp_lib | ||||||||||||||||||
|
||||||||||||||||||
if os.environ["PHARE_LOG"] != "NONE" and cpp_lib().mpi_rank() == 0: | ||||||||||||||||||
from pathlib import Path | ||||||||||||||||||
|
||||||||||||||||||
Path(".log").mkdir(exist_ok=True) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,21 +2,19 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
# parsing PHARE scope funtion timers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import argparse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import numpy as np | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
from dataclasses import dataclass, field | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
from pyphare.pharesee.run import Run | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
from pyphare.pharesee.hierarchy import hierarchy_from | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
from phlop.timing.scope_timer import ScopeTimerFile as phScopeTimerFile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
from phlop.timing.scope_timer import file_parser as phfile_parser | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
from phlop.timing import scope_timer as st | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
substeps_per_finer_level = 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
@dataclass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
class ScopeTimerFile(phScopeTimerFile): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
class ScopeTimerFile(st.ScopeTimerFile): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
run: Run | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
rank: str | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
advances: list = field(default_factory=lambda: []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -124,20 +122,67 @@ def normalised_times_for_L(self, ilvl): | |||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Normalise substep time against particle count for that level | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
at the most recent coarse time, no refined timesteps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Particle counts may include init dump, so be one bigger. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
times = self.advance_times_for_L(ilvl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
counts = len(self.particles_per_level_per_time_step[ilvl]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
# trim init particle count for lvl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Li_times = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.particles_per_level_per_time_step[ilvl] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if counts == len(times) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
else self.particles_per_level_per_time_step[ilvl][1:] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ilvl == 0: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return times / self.particles_per_level_per_time_step[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return times / Li_times | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent potential division by zero When Consider adding a check to prevent division by zero. For example: if ilvl == 0:
return np.divide(times, Li_times, out=np.zeros_like(times), where=Li_times!=0) This approach will return 0 for any element where |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
substeps = self.steps_per_coarse_timestep_for_L(ilvl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
norm_times = times.copy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
norm_times.reshape(int(times.shape[0] / substeps), substeps) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
/ self.particles_per_level_per_time_step[ilvl].reshape( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.particles_per_level_per_time_step[ilvl].shape[0], 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
/ Li_times.reshape(Li_times.shape[0], 1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+125
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Approve changes with a note on potential division by zero The changes to handle particle counts and normalization look good. However, there's still a potential issue with division by zero on line 138. Consider adding a check to prevent division by zero. For example: if ilvl == 0:
return np.divide(times, Li_times, out=np.zeros_like(times), where=Li_times!=0) This approach will return 0 for any element where |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
).reshape(times.shape[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
def file_parser(run, rank, times_filepath): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
supe = phfile_parser(times_filepath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
supe = st.file_parser(times_filepath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ScopeTimerFile(supe.id_keys, supe.roots, run, str(rank)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
def write_root_as_csv(scope_timer_file, outfile, headers=None, regex=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
from contextlib import redirect_stdout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
with open(outfile, "w") as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
with redirect_stdout(f): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+154
to
+155
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Combine nested Consider combining the nested Apply this diff to refactor the code: -with open(outfile, "w") as f:
- with redirect_stdout(f):
- print_root_as_csv(scope_timer_file, headers, regex)
+with open(outfile, "w") as f, redirect_stdout(f):
+ print_root_as_csv(scope_timer_file, headers, regex) 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
print_root_as_csv(scope_timer_file, headers, regex) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
def print_root_as_csv(scope_timer_file, n_parts, headers=None, regex=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
stf = scope_timer_file # alias | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
stf = file_parser(stf) if isinstance(stf, str) else stf | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if headers: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
print(",".join(headers)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
for root in stf.roots: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
s = stf(root.k) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if regex and regex not in s: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
bits = s.split(",") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"{s}{root.t},{root.t/n_parts}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+159
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent potential division by zero in The variable Consider adding a check at the beginning of the function: def print_root_as_csv(scope_timer_file, n_parts, headers=None, regex=None):
+ if n_parts == 0:
+ raise ValueError("n_parts must be a non-zero value")
stf = scope_timer_file # alias 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
def print_variance_across(scope_timer_filepath=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if scope_timer_filepath is None: # assume cli | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
parser = argparse.ArgumentParser() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
parser.add_argument("-f", "--file", default=None, help="timer file") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
scope_timer_filepath = parser.parse_args().file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not scope_timer_filepath: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
parser.print_help() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
sys.exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
st.print_variance_across(scope_timer_filepath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if len(sys.argv) > 1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
fn = sys.argv[1] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
sys.argv = [sys.argv[0]] + sys.argv[2:] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
globals()[fn]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider moving the MPI barrier for better synchronization
The current placement of the MPI barrier might not fully prevent race conditions. Other ranks could potentially proceed to create their rank-specific directories before rank 0 has finished creating the parent directory.
Consider moving the MPI barrier after the creation of
rank_info_dir
:This change ensures that all ranks wait until the parent directory is created before proceeding, preventing potential issues in directory creation.
📝 Committable suggestion