Skip to content

Commit 6a57e3e

Browse files
authored
Merge pull request #2285 from oesteban/fix/resource-monitor-revisions
[REF,FIX] Revision of the resource monitor
2 parents d6c4957 + ccb5403 commit 6a57e3e

File tree

8 files changed

+82
-33
lines changed

8 files changed

+82
-33
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Upcoming release
77

88
###### [Full changelog](https://github.com/nipy/nipype/milestone/13)
99

10+
* FIX+MAINT: Revision of the resource monitor (https://github.com/nipy/nipype/pull/2285)
1011
* FIX: MultiProc mishandling crashes (https://github.com/nipy/nipype/pull/2301)
1112
* MAINT: Revise use of `subprocess.Popen` (https://github.com/nipy/nipype/pull/2289)
1213
* ENH: Memorize version checks (https://github.com/nipy/nipype/pull/2274, https://github.com/nipy/nipype/pull/2295)

doc/users/config_file.rst

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,29 @@ Execution
153153
crashfiles allow portability across machines and shorter load time.
154154
(possible values: ``pklz`` and ``txt``; default value: ``pklz``)
155155

156-
*resource_monitor*
156+
157+
Resource Monitor
158+
~~~~~~~~~~~~~~~~
159+
160+
*enabled*
157161
Enables monitoring the resources occupation (possible values: ``true`` and
158-
``false``; default value: ``false``)
162+
``false``; default value: ``false``). All the following options will be
163+
dismissed if the resource monitor is not enabled.
159164

160-
*resource_monitor_frequency*
165+
*sample_frequency*
161166
Sampling period (in seconds) between measurements of resources (memory, cpus)
162-
being used by an interface. Requires ``resource_monitor`` to be ``true``.
163-
(default value: ``1``)
167+
being used by an interface (default value: ``1``)
168+
169+
*summary_file*
170+
Indicates where the summary file collecting all profiling information from the
171+
resource monitor should be stored after execution of a workflow.
172+
The ``summary_file`` does not apply to interfaces run independently.
173+
(unset by default, in which case the summary file will be written out to
174+
``<base_dir>/resource_monitor.json`` of the top-level workflow).
175+
176+
*summary_append*
177+
Append to an existing summary file (only applies to workflows).
178+
(default value: ``true``, possible values: ``true`` or ``false``).
164179

165180
Example
166181
~~~~~~~
@@ -175,6 +190,10 @@ Example
175190
hash_method = timestamp
176191
display_variable = :1
177192

193+
[monitoring]
194+
enabled = false
195+
196+
178197
Workflow.config property has a form of a nested dictionary reflecting the
179198
structure of the .cfg file.
180199

docker/files/run_examples.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ echo '[execution]' >> ${HOME}/.nipype/nipype.cfg
2020
echo 'crashfile_format = txt' >> ${HOME}/.nipype/nipype.cfg
2121

2222
if [[ "${NIPYPE_RESOURCE_MONITOR:-0}" == "1" ]]; then
23-
echo 'resource_monitor = true' >> ${HOME}/.nipype/nipype.cfg
24-
echo 'resource_monitor_frequency = 3' >> ${HOME}/.nipype/nipype.cfg
23+
echo '[monitoring]' >> ${HOME}/.nipype/nipype.cfg
24+
echo 'enabled = true' >> ${HOME}/.nipype/nipype.cfg
25+
echo 'sample_frequency = 3' >> ${HOME}/.nipype/nipype.cfg
2526
fi
2627

2728
# Set up coverage

nipype/interfaces/tests/test_resource_monitor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_cmdline_profiling(tmpdir, mem_gb, n_procs):
5454
of a CommandLine-derived interface
5555
"""
5656
from nipype import config
57-
config.set('execution', 'resource_monitor_frequency', '0.2') # Force sampling fast
57+
config.set('monitoring', 'sample_frequency', '0.2') # Force sampling fast
5858

5959
tmpdir.chdir()
6060
iface = UseResources(mem_gb=mem_gb, n_procs=n_procs)
@@ -72,7 +72,7 @@ def test_function_profiling(tmpdir, mem_gb, n_procs):
7272
of a Function interface
7373
"""
7474
from nipype import config
75-
config.set('execution', 'resource_monitor_frequency', '0.2') # Force sampling fast
75+
config.set('monitoring', 'sample_frequency', '0.2') # Force sampling fast
7676

7777
tmpdir.chdir()
7878
iface = niu.Function(function=_use_resources)

nipype/pipeline/engine/utils.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,15 +1298,24 @@ def write_workflow_prov(graph, filename=None, format='all'):
12981298
return ps.g
12991299

13001300

1301-
def write_workflow_resources(graph, filename=None):
1301+
def write_workflow_resources(graph, filename=None, append=None):
13021302
"""
13031303
Generate a JSON file with profiling traces that can be loaded
13041304
in a pandas DataFrame or processed with JavaScript like D3.js
13051305
"""
13061306
import simplejson as json
1307+
1308+
# Overwrite filename if nipype config is set
1309+
filename = config.get('monitoring', 'summary_file', filename)
1310+
1311+
# If filename still does not make sense, store in $PWD
13071312
if not filename:
13081313
filename = os.path.join(os.getcwd(), 'resource_monitor.json')
13091314

1315+
if append is None:
1316+
append = str2bool(config.get(
1317+
'monitoring', 'summary_append', 'true'))
1318+
13101319
big_dict = {
13111320
'time': [],
13121321
'name': [],
@@ -1317,14 +1326,21 @@ def write_workflow_resources(graph, filename=None):
13171326
'params': [],
13181327
}
13191328

1329+
# If file exists, just append new profile information
1330+
# If we append different runs, then we will see different
1331+
# "bursts" of timestamps corresponding to those executions.
1332+
if append and os.path.isfile(filename):
1333+
with open(filename, 'r' if PY3 else 'rb') as rsf:
1334+
big_dict = json.load(rsf)
1335+
13201336
for idx, node in enumerate(graph.nodes()):
13211337
nodename = node.fullname
13221338
classname = node._interface.__class__.__name__
13231339

13241340
params = ''
13251341
if node.parameterization:
13261342
params = '_'.join(['{}'.format(p)
1327-
for p in node.parameterization])
1343+
for p in node.parameterization])
13281344

13291345
try:
13301346
rt_list = node.result.runtime
@@ -1337,7 +1353,13 @@ def write_workflow_resources(graph, filename=None):
13371353
rt_list = [rt_list]
13381354

13391355
for subidx, runtime in enumerate(rt_list):
1340-
nsamples = len(runtime.prof_dict['time'])
1356+
try:
1357+
nsamples = len(runtime.prof_dict['time'])
1358+
except AttributeError:
1359+
logger.warning(
1360+
'Could not retrieve profiling information for node "%s" '
1361+
'(mapflow %d/%d).', nodename, subidx + 1, len(rt_list))
1362+
continue
13411363

13421364
for key in ['time', 'mem_gb', 'cpus']:
13431365
big_dict[key] += runtime.prof_dict[key]

nipype/pipeline/engine/workflows.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,11 @@ def run(self, plugin=None, plugin_args=None, updatehash=False):
598598
write_workflow_prov(execgraph, prov_base, format='all')
599599

600600
if config.resource_monitor:
601-
write_workflow_resources(execgraph)
601+
base_dir = self.base_dir or os.getcwd()
602+
write_workflow_resources(
603+
execgraph,
604+
filename=op.join(base_dir, self.name, 'resource_monitor.json')
605+
)
602606
return execgraph
603607

604608
# PRIVATE API AND FUNCTIONS

nipype/utils/config.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131

3232

3333
CONFIG_DEPRECATIONS = {
34-
'profile_runtime': ('resource_monitor', '1.0'),
35-
'filemanip_level': ('utils_level', '1.0'),
34+
'profile_runtime': ('monitoring.enabled', '1.0'),
35+
'filemanip_level': ('logging.utils_level', '1.0'),
3636
}
3737

3838
NUMPY_MMAP = LooseVersion(np.__version__) >= LooseVersion('1.12.0')
@@ -71,8 +71,11 @@
7171
parameterize_dirs = true
7272
poll_sleep_duration = 2
7373
xvfb_max_wait = 10
74-
resource_monitor = false
75-
resource_monitor_frequency = 1
74+
75+
[monitoring]
76+
enabled = false
77+
sample_frequency = 1
78+
summary_append = true
7679
7780
[check]
7881
interval = 1209600
@@ -105,12 +108,12 @@ def __init__(self, *args, **kwargs):
105108
self._config.read([config_file, 'nipype.cfg'])
106109

107110
for option in CONFIG_DEPRECATIONS:
108-
for section in ['execution', 'logging']:
111+
for section in ['execution', 'logging', 'monitoring']:
109112
if self.has_option(section, option):
110-
new_option = CONFIG_DEPRECATIONS[option][0]
111-
if not self.has_option(section, new_option):
113+
new_section, new_option = CONFIG_DEPRECATIONS[option][0].split('.')
114+
if not self.has_option(new_section, new_option):
112115
# Warn implicit in get
113-
self.set(section, new_option, self.get(section, option))
116+
self.set(new_section, new_option, self.get(section, option))
114117

115118
def set_default_config(self):
116119
self._config.readfp(StringIO(default_cfg))
@@ -138,7 +141,7 @@ def get(self, section, option, default=None):
138141
'"%s" instead.') % (option, CONFIG_DEPRECATIONS[option][1],
139142
CONFIG_DEPRECATIONS[option][0])
140143
warn(msg)
141-
option = CONFIG_DEPRECATIONS[option][0]
144+
section, option = CONFIG_DEPRECATIONS[option][0].split('.')
142145

143146
if self._config.has_option(section, option):
144147
return self._config.get(section, option)
@@ -154,7 +157,7 @@ def set(self, section, option, value):
154157
'"%s" instead.') % (option, CONFIG_DEPRECATIONS[option][1],
155158
CONFIG_DEPRECATIONS[option][0])
156159
warn(msg)
157-
option = CONFIG_DEPRECATIONS[option][0]
160+
section, option = CONFIG_DEPRECATIONS[option][0].split('.')
158161

159162
return self._config.set(section, option, value)
160163

@@ -222,8 +225,8 @@ def resource_monitor(self):
222225
return self._resource_monitor
223226

224227
# Cache config from nipype config
225-
self.resource_monitor = self._config.get(
226-
'execution', 'resource_monitor') or False
228+
self.resource_monitor = str2bool(self._config.get(
229+
'monitoring', 'enabled')) or False
227230
return self._resource_monitor
228231

229232
@resource_monitor.setter
@@ -248,7 +251,7 @@ def resource_monitor(self, value):
248251
if not self._resource_monitor:
249252
warn('Could not enable the resource monitor: psutil>=5.0'
250253
' could not be imported.')
251-
self._config.set('execution', 'resource_monitor',
254+
self._config.set('monitoring', 'enabled',
252255
('%s' % self._resource_monitor).lower())
253256

254257
def enable_resource_monitor(self):

nipype/utils/profiler.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# -*- coding: utf-8 -*-
2-
# @Author: oesteban
3-
# @Date: 2017-09-21 15:50:37
4-
# @Last Modified by: oesteban
5-
# @Last Modified time: 2017-10-20 09:12:36
2+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3+
# vi: set ft=python sts=4 ts=4 sw=4 et:
64
"""
75
Utilities to keep track of performance
86
"""
@@ -202,8 +200,8 @@ def get_max_resources_used(pid, mem_mb, num_threads, pyfunc=False):
202200
"""
203201

204202
if not resource_monitor:
205-
raise RuntimeError('Attempted to measure resources with '
206-
'"resource_monitor" set off.')
203+
raise RuntimeError('Attempted to measure resources with option '
204+
'"monitoring.enabled" set off.')
207205

208206
try:
209207
mem_mb = max(mem_mb, _get_ram_mb(pid, pyfunc=pyfunc))
@@ -320,7 +318,8 @@ def _use_cpu(x):
320318
ctr = 0
321319
while ctr < 1e7:
322320
ctr += 1
323-
x*x
321+
x * x
322+
324323

325324
# Spin multiple threads
326325
def _use_resources(n_procs, mem_gb):

0 commit comments

Comments
 (0)