From b9014ef0335f989b7ab8e2ff5436e1b82dd1348c Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 12:13:17 +0200 Subject: [PATCH 1/9] fix: minor improvements to monitor --- nipype/interfaces/base/support.py | 19 +++++++++--------- nipype/utils/profiler.py | 32 ++++++++++++++++++------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/nipype/interfaces/base/support.py b/nipype/interfaces/base/support.py index 14c8a55da1..88435b2a32 100644 --- a/nipype/interfaces/base/support.py +++ b/nipype/interfaces/base/support.py @@ -19,6 +19,7 @@ from ... import logging, config from ...utils.misc import is_container, rgetcwd from ...utils.filemanip import md5, hash_infile +from ...utils.profiler import ResourceMonitor, ResourceMonitorMock iflogger = logging.getLogger("nipype.interface") @@ -33,16 +34,8 @@ class RuntimeContext(AbstractContextManager): def __init__(self, resource_monitor=False, ignore_exception=False): """Initialize the context manager object.""" self._ignore_exc = ignore_exception - _proc_pid = os.getpid() - if resource_monitor: - from ...utils.profiler import ResourceMonitor - else: - from ...utils.profiler import ResourceMonitorMock as ResourceMonitor + self._resmon = resource_monitor - self._resmon = ResourceMonitor( - _proc_pid, - freq=float(config.get("execution", "resource_monitor_frequency", 1)), - ) def __call__(self, interface, cwd=None, redirect_x=False): """Generate a new runtime object.""" @@ -51,6 +44,14 @@ def __call__(self, interface, cwd=None, redirect_x=False): if cwd is None: cwd = _syscwd + _proc_pid = os.getpid() + _ResourceMonitor = ResourceMonitor if self._resmon else ResourceMonitorMock + self._resmon = _ResourceMonitor( + _proc_pid, + cwd=cwd, + freq=float(config.get("execution", "resource_monitor_frequency", 1)), + ) + self._runtime = Bunch( cwd=str(cwd), duration=None, diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index 2179b29db6..b6d816a748 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -48,23 +48,27 @@ class ResourceMonitor(threading.Thread): to a file """ - def __init__(self, pid, freq=5, fname=None, python=True): + def __init__(self, pid, freq=0.2, fname=None, cwd=None): # Make sure psutil is imported import psutil - if freq < 0.2: - raise RuntimeError("Frequency (%0.2fs) cannot be lower than 0.2s" % freq) + # Leave process initialized and make first sample + self._process = psutil.Process(pid) + _sample = self._sample(cpu_interval=0.2) - if fname is None: - fname = ".proc-%d_time-%s_freq-%0.2f" % (pid, time(), freq) - self._fname = os.path.abspath(fname) + # Continue monitor configuration + freq = max(freq, 0.2) + fname = fname or f".proc-{pid}" + self._fname = os.path.abspath( + os.path.join(cwd, fname) if cwd is not None else fname + ) self._logfile = open(self._fname, "w") self._freq = freq self._python = python - # Leave process initialized and make first sample - self._process = psutil.Process(pid) - self._sample(cpu_interval=0.2) + # Dump first sample to file + print(",".join(_sample), file=self._logfile) + self._logfile.flush() # Start thread threading.Thread.__init__(self) @@ -80,7 +84,8 @@ def stop(self): if not self._event.is_set(): self._event.set() self.join() - self._sample() + # Dump last sample to file + print(",".join(self._sample()), file=self._logfile) self._logfile.flush() self._logfile.close() @@ -132,15 +137,16 @@ def _sample(self, cpu_interval=None): except psutil.NoSuchProcess: pass - print("%f,%f,%f,%f" % (time(), cpu, rss / _MB, vms / _MB), file=self._logfile) - self._logfile.flush() + return (time(), cpu, rss / _MB, vms / _MB) def run(self): """Core monitoring function, called by start()""" start_time = time() wait_til = start_time while not self._event.is_set(): - self._sample() + # Dump sample to file + print(",".join(self._sample()), file=self._logfile) + self._logfile.flush() wait_til += self._freq self._event.wait(max(0, wait_til - time())) From 1a476b4bab4f62fe53fc56e130f8277935349acc Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 12:16:22 +0200 Subject: [PATCH 2/9] fix: maintain consistent interface of mock monitor --- nipype/utils/profiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index b6d816a748..510c05aa49 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -32,7 +32,7 @@ def fname(self): """Get/set the internal filename""" return None - def __init__(self, pid, freq=5, fname=None, python=True): + def __init__(self, pid, freq=5, fname=None, cwd=None): pass def start(self): From ae27346c5ab20c3050a2b73d60d970d567c88e57 Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 12:19:45 +0200 Subject: [PATCH 3/9] fix: remove python argument setting for monitor --- nipype/utils/profiler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index 510c05aa49..b59bcab60f 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -64,7 +64,6 @@ def __init__(self, pid, freq=0.2, fname=None, cwd=None): ) self._logfile = open(self._fname, "w") self._freq = freq - self._python = python # Dump first sample to file print(",".join(_sample), file=self._logfile) From 888e7fad9774f7a726d31f7cac3d8c8433f15f6d Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 12:26:30 +0200 Subject: [PATCH 4/9] fix: bad variable name --- nipype/utils/profiler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index b59bcab60f..a000e81c92 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -54,7 +54,7 @@ def __init__(self, pid, freq=0.2, fname=None, cwd=None): # Leave process initialized and make first sample self._process = psutil.Process(pid) - _sample = self._sample(cpu_interval=0.2) + _first_sample = self._sample(cpu_interval=0.2) # Continue monitor configuration freq = max(freq, 0.2) @@ -66,7 +66,7 @@ def __init__(self, pid, freq=0.2, fname=None, cwd=None): self._freq = freq # Dump first sample to file - print(",".join(_sample), file=self._logfile) + print(",".join(_first_sample), file=self._logfile) self._logfile.flush() # Start thread @@ -108,6 +108,7 @@ def stop(self): return retval def _sample(self, cpu_interval=None): + _time = time() cpu = 0.0 rss = 0.0 vms = 0.0 @@ -136,7 +137,7 @@ def _sample(self, cpu_interval=None): except psutil.NoSuchProcess: pass - return (time(), cpu, rss / _MB, vms / _MB) + return (_time, cpu, rss / _MB, vms / _MB) def run(self): """Core monitoring function, called by start()""" From 8ae6bc525753f364d57374ca2b9017750bf9df8c Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 14:00:58 +0200 Subject: [PATCH 5/9] fix: cast to strings before printing --- nipype/utils/profiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index a000e81c92..0dd5557116 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -66,7 +66,7 @@ def __init__(self, pid, freq=0.2, fname=None, cwd=None): self._freq = freq # Dump first sample to file - print(",".join(_first_sample), file=self._logfile) + print(",".join(f"v" for v in _first_sample), file=self._logfile) self._logfile.flush() # Start thread @@ -84,7 +84,7 @@ def stop(self): self._event.set() self.join() # Dump last sample to file - print(",".join(self._sample()), file=self._logfile) + print(",".join(f"v" for v in self._sample()), file=self._logfile) self._logfile.flush() self._logfile.close() @@ -145,7 +145,7 @@ def run(self): wait_til = start_time while not self._event.is_set(): # Dump sample to file - print(",".join(self._sample()), file=self._logfile) + print(",".join(f"v" for v in self._sample())), file=self._logfile) self._logfile.flush() wait_til += self._freq self._event.wait(max(0, wait_til - time())) From 05baec0c8374fa0177209cdebc12f6873c24a489 Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 14:03:21 +0200 Subject: [PATCH 6/9] fix: extra parenthesis --- nipype/utils/profiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index 0dd5557116..fed1f75561 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -145,7 +145,7 @@ def run(self): wait_til = start_time while not self._event.is_set(): # Dump sample to file - print(",".join(f"v" for v in self._sample())), file=self._logfile) + print(",".join(f"v" for v in self._sample()), file=self._logfile) self._logfile.flush() wait_til += self._freq self._event.wait(max(0, wait_til - time())) From 1fcc082ba808380bcf52ffbf1e8bbf79a60e8b03 Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 16:03:37 +0200 Subject: [PATCH 7/9] fix: bad string substitution --- nipype/utils/profiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index fed1f75561..324c6701ea 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -66,7 +66,7 @@ def __init__(self, pid, freq=0.2, fname=None, cwd=None): self._freq = freq # Dump first sample to file - print(",".join(f"v" for v in _first_sample), file=self._logfile) + print(",".join(f"{v}" for v in _first_sample), file=self._logfile) self._logfile.flush() # Start thread @@ -84,7 +84,7 @@ def stop(self): self._event.set() self.join() # Dump last sample to file - print(",".join(f"v" for v in self._sample()), file=self._logfile) + print(",".join(f"{v}" for v in self._sample()), file=self._logfile) self._logfile.flush() self._logfile.close() @@ -145,7 +145,7 @@ def run(self): wait_til = start_time while not self._event.is_set(): # Dump sample to file - print(",".join(f"v" for v in self._sample()), file=self._logfile) + print(",".join(f"{v}" for v in self._sample()), file=self._logfile) self._logfile.flush() wait_til += self._freq self._event.wait(max(0, wait_til - time())) From 064bd4b0057608b1640e0572eb968c2b5132abd9 Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 16:22:53 +0200 Subject: [PATCH 8/9] fix: do not use mock --- nipype/interfaces/base/support.py | 26 ++++++++++++++------------ nipype/utils/profiler.py | 18 ------------------ 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/nipype/interfaces/base/support.py b/nipype/interfaces/base/support.py index 88435b2a32..85e6869bac 100644 --- a/nipype/interfaces/base/support.py +++ b/nipype/interfaces/base/support.py @@ -19,7 +19,7 @@ from ... import logging, config from ...utils.misc import is_container, rgetcwd from ...utils.filemanip import md5, hash_infile -from ...utils.profiler import ResourceMonitor, ResourceMonitorMock +from ...utils.profiler import ResourceMonitor iflogger = logging.getLogger("nipype.interface") @@ -44,13 +44,13 @@ def __call__(self, interface, cwd=None, redirect_x=False): if cwd is None: cwd = _syscwd - _proc_pid = os.getpid() - _ResourceMonitor = ResourceMonitor if self._resmon else ResourceMonitorMock - self._resmon = _ResourceMonitor( - _proc_pid, - cwd=cwd, - freq=float(config.get("execution", "resource_monitor_frequency", 1)), - ) + if self._resmon: + _proc_pid = os.getpid() + self._resmon = ResourceMonitor( + _proc_pid, + cwd=cwd, + freq=float(config.get("execution", "resource_monitor_frequency", 1)), + ) self._runtime = Bunch( cwd=str(cwd), @@ -62,7 +62,7 @@ def __call__(self, interface, cwd=None, redirect_x=False): platform=platform.platform(), prevcwd=str(_syscwd), redirect_x=redirect_x, - resmon=self._resmon.fname or "off", + resmon=getattr(self._resmon, "fname", "off"), returncode=None, startTime=None, version=interface.version, @@ -75,7 +75,8 @@ def __enter__(self): self._runtime.environ["DISPLAY"] = config.get_display() self._runtime.startTime = dt.isoformat(dt.utcnow()) - self._resmon.start() + if self._resmon: + self._resmon.start() # TODO: Perhaps clean-up path and ensure it exists? os.chdir(self._runtime.cwd) return self._runtime @@ -88,8 +89,9 @@ def __exit__(self, exc_type, exc_value, exc_tb): timediff.days * 86400 + timediff.seconds + timediff.microseconds / 1e6 ) # Collect monitored data - for k, v in self._resmon.stop().items(): - setattr(self._runtime, k, v) + if self._resmon: + for k, v in self._resmon.stop().items(): + setattr(self._runtime, k, v) os.chdir(self._runtime.prevcwd) diff --git a/nipype/utils/profiler.py b/nipype/utils/profiler.py index 324c6701ea..266affcaec 100644 --- a/nipype/utils/profiler.py +++ b/nipype/utils/profiler.py @@ -24,24 +24,6 @@ _MB = 1024.0**2 -class ResourceMonitorMock: - """A mock class to use when the monitor is disabled.""" - - @property - def fname(self): - """Get/set the internal filename""" - return None - - def __init__(self, pid, freq=5, fname=None, cwd=None): - pass - - def start(self): - pass - - def stop(self): - return {} - - class ResourceMonitor(threading.Thread): """ A ``Thread`` to monitor a specific PID with a certain frequence From 4b17465201dff5bbc117ef72dc9af57621933d34 Mon Sep 17 00:00:00 2001 From: Oscar Esteban <code@oscaresteban.es> Date: Sun, 17 Apr 2022 21:49:45 +0200 Subject: [PATCH 9/9] fix: revise configuration of resource monitor at the interface level --- nipype/interfaces/base/core.py | 16 ++++++---------- nipype/interfaces/base/support.py | 11 ++++++----- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/nipype/interfaces/base/core.py b/nipype/interfaces/base/core.py index 69d621bbc1..781ba2ff23 100644 --- a/nipype/interfaces/base/core.py +++ b/nipype/interfaces/base/core.py @@ -173,7 +173,7 @@ class BaseInterface(Interface): _additional_metadata = [] _redirect_x = False _references = [] - resource_monitor = True # Enabled for this interface IFF enabled in the config + resource_monitor = None # Enabled for this interface IFF enabled in the config _etelemetry_version_data = None def __init__( @@ -202,9 +202,10 @@ def __init__( self.inputs.trait_set(**inputs) self.ignore_exception = ignore_exception - - if resource_monitor is not None: - self.resource_monitor = resource_monitor + self.resource_monitor = ( + resource_monitor if resource_monitor is not None + else config.resource_monitor + ) if from_file is not None: self.load_inputs_from_json(from_file, overwrite=True) @@ -376,18 +377,13 @@ def run(self, cwd=None, ignore_exception=None, **inputs): if successful, results """ - rtc = RuntimeContext( - resource_monitor=config.resource_monitor and self.resource_monitor, - ignore_exception=ignore_exception - if ignore_exception is not None - else self.ignore_exception, - ) with indirectory(cwd or os.getcwd()): self.inputs.trait_set(**inputs) self._check_mandatory_inputs() self._check_version_requirements(self.inputs) + rtc = RuntimeContext() with rtc(self, cwd=cwd, redirect_x=self._redirect_x) as runtime: # Grab inputs now, as they should not change during execution diff --git a/nipype/interfaces/base/support.py b/nipype/interfaces/base/support.py index 85e6869bac..d2c4ba7cc2 100644 --- a/nipype/interfaces/base/support.py +++ b/nipype/interfaces/base/support.py @@ -31,20 +31,21 @@ class RuntimeContext(AbstractContextManager): __slots__ = ("_runtime", "_resmon", "_ignore_exc") - def __init__(self, resource_monitor=False, ignore_exception=False): + def __init__(self): """Initialize the context manager object.""" - self._ignore_exc = ignore_exception - self._resmon = resource_monitor + self._ignore_exc = False + self._resmon = None - def __call__(self, interface, cwd=None, redirect_x=False): + def __call__(self, interface, cwd=None, redirect_x=False, ignore_exception=False): """Generate a new runtime object.""" # Tear-up: get current and prev directories _syscwd = rgetcwd(error=False) # Recover when wd does not exist if cwd is None: cwd = _syscwd - if self._resmon: + self._ignore_exc = ignore_exception or interface.ignore_exception + if interface.resource_monitor is True: _proc_pid = os.getpid() self._resmon = ResourceMonitor( _proc_pid,