Skip to content

Commit b5ed669

Browse files
committed
fix: track more platform vars, integration with sentry
1 parent 68bf998 commit b5ed669

File tree

3 files changed

+57
-56
lines changed

3 files changed

+57
-56
lines changed

fmriprep/cli/workflow.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ def build_workflow(config_file, retval):
3434
if msg is not None:
3535
build_log.warning(msg)
3636

37+
dset_desc_path = config.execution.bids_dir / 'dataset_description.json'
38+
if dset_desc_path.exists():
39+
from hashlib import sha256
40+
desc_content = dset_desc_path.read_bytes()
41+
config.execution.bids_description_hash = sha256(desc_content).hexdigest()
42+
3743
# First check that bids_dir looks like a BIDS folder
3844
config.init_layout()
3945
subject_list = collect_participants(

fmriprep/config.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,14 @@ def redirect_warnings(message, category, filename, lineno, file=None, line=None)
141141
DEFAULT_MEMORY_MIN_GB = 0.01
142142

143143
_exec_env = os.name
144+
_docker_ver = None
144145
# special variable set in the container
145146
if os.getenv('IS_DOCKER_8395080871'):
146147
_exec_env = 'singularity'
147148
_cgroup = Path('/proc/1/cgroup')
148149
if _cgroup.exists() and 'docker' in _cgroup.read_text():
149-
_exec_env = 'docker'
150-
if os.getenv('DOCKER_VERSION_8395080871'):
151-
_exec_env = 'fmriprep-docker'
150+
_docker_ver = os.getenv('DOCKER_VERSION_8395080871')
151+
_exec_env = 'fmriprep-docker' if _docker_ver else 'docker'
152152
del _cgroup
153153

154154
_fs_license = os.getenv('FS_LICENSE')
@@ -160,6 +160,32 @@ def redirect_warnings(message, category, filename, lineno, file=None, line=None)
160160
os.path.join(os.getenv('HOME'), '.cache', 'templateflow'))
161161
)
162162

163+
try:
164+
from psutil import virtual_memory
165+
_free_mem_at_start = round(virtual_memory().free / 1024**3, 1)
166+
except Exception:
167+
_free_mem_at_start = None
168+
169+
_oc_limit = 'n/a'
170+
_oc_policy = 'n/a'
171+
try:
172+
# Memory policy may have a large effect on types of errors experienced
173+
_proc_oc_path = Path('/proc/sys/vm/overcommit_memory')
174+
if _proc_oc_path.exists():
175+
_oc_policy = {
176+
'0': 'heuristic', '1': 'always', '2': 'never'
177+
}.get(_proc_oc_path.read_text().strip(), 'unknown')
178+
if _oc_policy != 'never':
179+
_proc_oc_kbytes = Path('/proc/sys/vm/overcommit_kbytes')
180+
if _proc_oc_kbytes.exists():
181+
_oc_limit = _proc_oc_kbytes.read_text().strip()
182+
if _oc_limit in ('0', 'n/a') and Path('/proc/sys/vm/overcommit_ratio').exists():
183+
_oc_limit = '{}%'.format(
184+
Path('/proc/sys/vm/overcommit_ratio').read_text().strip()
185+
)
186+
except Exception:
187+
pass
188+
163189

164190
class _Config:
165191
"""An abstract class forbidding instantiation."""
@@ -206,6 +232,8 @@ class nipype(_Config):
206232

207233
crashfile_format = 'txt'
208234
"""The file format for crashfiles, either text or pickle."""
235+
free_mem = _free_mem_at_start
236+
"""Free memory at start."""
209237
get_linked_libs = False
210238
"""Run NiPype's tool to enlist linked libraries for every interface."""
211239
memory_gb = None
@@ -214,6 +242,10 @@ class nipype(_Config):
214242
"""Number of processes (compute tasks) that can be run in parallel (multiprocessing only)."""
215243
omp_nthreads = os.cpu_count()
216244
"""Number of CPUs a single process can access for multithreaded execution."""
245+
overcommit_policy = _oc_policy
246+
"""Linux's kernel virtual memory overcommit policy."""
247+
overcommit_limit = _oc_limit
248+
"""Linux's kernel virtual memory overcommit limits."""
217249
plugin = 'MultiProc'
218250
"""NiPype's execution plugin."""
219251
plugin_args = {
@@ -255,6 +287,8 @@ class execution(_Config):
255287
"""Run in sloppy mode (meaning, suboptimal parameters that minimize run-time)."""
256288
echo_idx = None
257289
"""Select a particular echo for multi-echo EPI datasets."""
290+
exec_docker_version = _docker_ver
291+
"""Version of Docker Engine."""
258292
exec_env = _exec_env
259293
"""A string representing the execution platform."""
260294
fs_license_file = _fs_license
@@ -288,6 +322,7 @@ class execution(_Config):
288322
templateflow_home = _templateflow_home
289323
"""The root folder of the TemplateFlow client."""
290324
templateflow_version = _tf_ver
325+
"""The TemplateFlow client version installed."""
291326
version = __version__
292327
"""*fMRIPrep*'s version."""
293328
work_dir = Path('work').absolute()
@@ -315,6 +350,9 @@ class execution(_Config):
315350
del _nipype_ver
316351
del _templateflow_home
317352
del _tf_ver
353+
del _free_mem_at_start
354+
del _oc_limit
355+
del _oc_policy
318356

319357

320358
class workflow(_Config):

fmriprep/utils/sentry.py

Lines changed: 10 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
22
# vi: set ft=python sts=4 ts=4 sw=4 et:
3-
"""Stripped out routines for Sentry"""
3+
"""Stripped out routines for Sentry."""
44
import os
5-
from pathlib import Path
65
import re
76
from niworkflows.utils.misc import read_crashfile
87
import sentry_sdk
@@ -43,66 +42,24 @@
4342

4443

4544
def sentry_setup():
46-
from os import cpu_count
47-
import psutil
48-
import hashlib
49-
50-
exec_env = config.execution.exec_env
51-
environment = "prod"
52-
release = config.execution.version
53-
if not release:
54-
environment = "dev"
55-
release = "dev"
56-
elif int(os.getenv('FMRIPREP_DEV', '0')) or ('+' in release):
57-
environment = "dev"
45+
"""Set-up sentry."""
46+
release = config.execution.version or "dev"
47+
environment = "dev" if (
48+
os.getenv('FMRIPREP_DEV', '').lower in ('1', 'on', 'yes', 'y', 'true')
49+
or ('+' in release)
50+
) else "prod"
5851

5952
sentry_sdk.init("https://[email protected]/1137693",
6053
release=release,
6154
environment=environment,
6255
before_send=before_send)
6356
with sentry_sdk.configure_scope() as scope:
64-
scope.set_tag('exec_env', exec_env)
65-
66-
if exec_env == 'fmriprep-docker':
67-
scope.set_tag('docker_version', os.getenv('DOCKER_VERSION_8395080871'))
68-
69-
dset_desc_path = config.execution.bids_dir / 'dataset_description.json'
70-
if dset_desc_path.exists():
71-
desc_content = dset_desc_path.read_bytes()
72-
scope.set_tag('dset_desc_sha256', hashlib.sha256(desc_content).hexdigest())
73-
74-
free_mem_at_start = round(psutil.virtual_memory().free / 1024**3, 1)
75-
scope.set_tag('free_mem_at_start', free_mem_at_start)
76-
scope.set_tag('cpu_count', cpu_count())
77-
78-
# Memory policy may have a large effect on types of errors experienced
79-
overcommit_memory = Path('/proc/sys/vm/overcommit_memory')
80-
if overcommit_memory.exists():
81-
policy = {'0': 'heuristic',
82-
'1': 'always',
83-
'2': 'never'}.get(overcommit_memory.read_text().strip(), 'unknown')
84-
scope.set_tag('overcommit_memory', policy)
85-
if policy == 'never':
86-
overcommit_kbytes = Path('/proc/sys/vm/overcommit_memory')
87-
kb = overcommit_kbytes.read_text().strip()
88-
if kb != '0':
89-
limit = '{}kB'.format(kb)
90-
else:
91-
overcommit_ratio = Path('/proc/sys/vm/overcommit_ratio')
92-
limit = '{}%'.format(overcommit_ratio.read_text().strip())
93-
scope.set_tag('overcommit_limit', limit)
94-
else:
95-
scope.set_tag('overcommit_limit', 'n/a')
96-
else:
97-
scope.set_tag('overcommit_memory', 'n/a')
98-
scope.set_tag('overcommit_limit', 'n/a')
99-
10057
for k, v in config.get(flat=True).items():
10158
scope.set_tag(k, v)
10259

10360

10461
def process_crashfile(crashfile):
105-
"""Parse the contents of a crashfile and submit sentry messages"""
62+
"""Parse the contents of a crashfile and submit sentry messages."""
10663
crash_info = read_crashfile(str(crashfile))
10764
with sentry_sdk.push_scope() as scope:
10865
scope.level = 'fatal'
@@ -169,7 +126,7 @@ def process_crashfile(crashfile):
169126

170127

171128
def before_send(event, hints):
172-
# Filtering log messages about crashed nodes
129+
"""Filter log messages about crashed nodes."""
173130
if 'logentry' in event and 'message' in event['logentry']:
174131
msg = event['logentry']['message']
175132
if msg.startswith("could not run node:"):
@@ -193,7 +150,7 @@ def before_send(event, hints):
193150

194151
def _chunks(string, length=CHUNK_SIZE):
195152
"""
196-
Splits a string into smaller chunks
153+
Split a string into smaller chunks.
197154
198155
>>> list(_chunks('some longer string.', length=3))
199156
['som', 'e l', 'ong', 'er ', 'str', 'ing', '.']

0 commit comments

Comments
 (0)