ATLAS Offline Software
Toggle main menu visibility
Loading...
Searching...
No Matches
Tools
PyJobTransforms
python
trfMPITools.py
Go to the documentation of this file.
1
# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3
8
9
from
enum
import
Enum
10
from
copy
import
deepcopy
11
import
os
12
import
re
13
import
logging
14
import
pprint
15
import
itertools
as
it
16
from
time
import
sleep
17
18
from
PyJobTransforms.trfExitCodes
import
trfExit
19
import
PyJobTransforms.trfExceptions
as
trfExceptions
20
21
msg = logging.getLogger(__name__)
22
23
mpiConfig =
None
24
25
26
class
MPIType
(Enum):
27
"""MPI master, MPI worker, or not using MPI"""
28
29
NOMPI = 0
30
MPIMASTER = 1
31
MPIWORKER = 2
32
33
34
def
signalError
(message):
35
msg.error(message)
36
raise
trfExceptions.TransformSetupException
(
37
trfExit.nameToCode(
"TRF_SETUP"
), message
38
)
39
40
41
def
getMPIRank
():
42
"""Return MPI rank"""
43
if
mpiConfig
is
not
None
:
44
return
int(mpiConfig[
"rank"
])
45
if
"RANK"
not
in
os.environ:
46
return
-1
47
else
:
48
try
:
49
return
int(os.environ[
"RANK"
])
50
except
ValueError:
51
signalError
(
"$RANK environment variable is not an integer"
)
52
return
-2
# Only here to placate PyRight
53
54
55
def
getMPIType
():
56
"""Return MPI type"""
57
if
mpiConfig
is
not
None
:
58
return
mpiConfig[
"type"
]
59
if
"RANK"
not
in
os.environ:
60
return
MPIType.NOMPI
61
if
getMPIRank
() == 0:
62
return
MPIType.MPIMASTER
63
else
:
64
return
MPIType.MPIWORKER
65
66
67
def
setupMPIConfig
(output, dataDict):
68
"""Check environment is correct if we are in MPI mode, and setup dictionaries"""
69
global
mpiConfig
70
if
"RANK"
not
in
os.environ:
71
signalError
(
72
"Running in MPI mode but the $RANK environment variable is not set!"
73
)
74
rank =
getMPIRank
()
75
if
not
os.getcwd().endswith(
"rank-{}"
.format(
getMPIRank
())):
76
signalError
(
77
"Running in MPI mode with rank {0} but working directory is not called rank-{0}"
.format(
78
getMPIRank
()
79
)
80
)
81
mpiType =
getMPIType
()
82
mpiConfig = {}
83
mpiConfig[
"rank"
] = rank
84
mpiConfig[
"type"
] = mpiType
85
mpiConfig[
"outputs"
] = {
86
dataType: deepcopy(dataDict[dataType])
for
dataType
in
output
87
}
88
# expand any [ ] lists in output filenames
89
output_proc_regex = re.compile(
r"(.+)\[(.*)](.*)"
)
90
for
v
in
mpiConfig[
"outputs"
].values():
91
v.multipleOK =
True
92
new_list = []
93
list_to_remove = []
94
for
fn
in
v.value:
95
if
(
"["
in
fn)
and
(
"]"
in
fn):
96
match = output_proc_regex.match(fn)
97
new_list.extend(
98
[
99
f
"{match.group(1)}{it}{match.group(3)}"
100
for
it
in
match.group(2).
split
(
","
)
101
]
102
)
103
list_to_remove.append(match.group(1))
104
else
:
105
new_list.append(fn)
106
list_to_remove.append(fn)
107
v.value = new_list
108
v.list_to_remove = list(
set
(list_to_remove))
109
110
111
def
mpiShouldValidate
():
112
if
getMPIType
() == MPIType.NOMPI:
113
return
True
# validate if we're not in MPI mode
114
if
getMPIRank
() == 0:
115
return
True
# validate in rank 0
116
return
False
# don't validate in other ranks
117
118
119
def
mpiOutputs
():
120
return
mpiConfig[
"outputs"
].values()
121
122
123
def
mergeOutputs
():
124
"""Merge outputs into rank 0"""
125
if
mpiConfig
is
None
:
126
msg.warning(
"trfMPITools.mergeOutputs called when we are not in MPI mode"
)
127
return
128
rank_dir_regex = re.compile(
"rank-([0-9]+)$"
)
129
rank_dirs = {
130
int(m.group(1)): m.string
131
for
m
in
(rank_dir_regex.search(d.path)
for
d
in
os.scandir(
".."
)
if
d.is_dir())
132
if
m
and
int(m.group(1)) > 0
133
}
134
num_ranks = len(rank_dirs) + 1
135
# First wait for all ranks to reach this point so we don't start merging before some outputs are fully closed
136
open(
"athena_done"
,
"a"
).close()
137
files_to_check = [
138
(rank, f
"../rank-{rank}/athena_done"
)
for
rank
in
range(0, num_ranks)
139
]
140
count = 0
141
files_to_check = list(
142
it.filterfalse(
lambda
f: os.path.exists(f[1]), files_to_check)
143
)
144
while
files_to_check:
145
if
count % 10 == 0
and
getMPIRank
() == 0:
146
msg.info(
147
f
"{count // 10 + 1}: Waiting for all ranks to finish athena: {list(map(lambda x: x[0], files_to_check))}"
148
)
149
count += 1
150
sleep(6)
151
files_to_check = list(
152
it.filterfalse(
lambda
f: os.path.exists(f[1]), files_to_check)
153
)
154
# Now continue: First the logs
155
if
getMPIRank
() == 0:
156
import
sqlite3
as
sq3
157
from
glob
import
glob
158
159
# Merge log databases
160
conn = sq3.connect(
"mpilog.db"
)
161
cur = conn.cursor()
162
tables = [
"ranks"
,
"files"
,
"event_log"
]
163
for
db
in
glob(
"../rank-[1-9]*/mpilog.db"
):
164
cur.execute(
"ATTACH DATABASE ? as db"
, (db,))
165
for
table
in
tables:
166
upsert =
"INSERT OR IGNORE"
if
table ==
"files"
else
"INSERT"
167
cur.execute(f
"{upsert} INTO {table} SELECT * from db.{table}"
)
168
conn.commit()
169
cur.execute(
"DETACH DATABASE db"
)
170
conn.close()
171
# Then everything else
172
msg.info(
"Rank output directories are:\n{}"
.format(pprint.pformat(rank_dirs)))
173
all_merge_inputs = list(
174
map
(
175
lambda
f: f.path,
176
filter(
177
lambda
f: f.is_file(),
178
it.chain.from_iterable(
map
(os.scandir, rank_dirs.values())),
179
),
180
)
181
)
182
# Remove PoolFileCatalog
183
try
:
184
os.remove(
"PoolFileCatalog.xml"
)
185
except
FileNotFoundError:
186
pass
187
for
dtype, defn
in
mpiConfig[
"outputs"
].items():
188
if
getMPIRank
() == 0:
189
msg.info(f
"Output type is {dtype}"
)
190
merge_helper = deepcopy(defn)
191
merge_helper.multipleOK =
True
192
if
getMPIRank
() == 0:
193
for
fn
in
defn.list_to_remove:
194
# remove empty files from rank 0
195
try
:
196
os.remove(fn)
197
except
FileNotFoundError:
198
pass
199
merge_lists = []
200
for
fn
in
defn.value:
201
merge_inputs = sorted(filter(
lambda
s: s.endswith(fn), all_merge_inputs))
202
# Add to list
203
merge_helper.value.extend(merge_inputs)
204
merge_lists.append((fn, merge_inputs))
205
# Remove non-existent output files from mpiOutputs
206
defn.value = [x[0]
for
x
in
merge_lists
if
len(x[1]) >= 1]
207
# Merge each final output in a different rank
208
if
getMPIRank
() >= len(merge_lists):
209
msg.info(f
"In rank {getMPIRank()}, not merging"
)
210
continue
211
for
idx
in
range(
getMPIRank
(), len(merge_lists), num_ranks):
212
my_merge = merge_lists[idx]
213
if
len(my_merge[1]) < 1:
214
msg.info(
215
f
"In rank {getMPIRank()}, no inputs for ../rank-0/{my_merge[0]}"
216
)
217
continue
218
msg.info(
219
f
"In rank {getMPIRank()}, merging into ../rank-0/{my_merge[0]}. Inputs are \n{pprint.pformat(my_merge[1])}"
220
)
221
try
:
222
merge_helper.selfMerge(f
"../rank-0/{my_merge[0]}"
, my_merge[1])
223
except
Exception
as
e:
224
msg.error(
225
f
"Merge failure in rank {getMPIRank()} merging into {my_merge[0]}: {e}"
226
)
227
with
open(
"../rank-0/merge_failure"
,
"a"
)
as
f:
228
f.write(
229
f
"Merge failure in rank {getMPIRank()} merging into {my_merge[0]}: {e}\n"
230
)
231
# Create a file to indicate we are done
232
open(
"done_merging"
,
"a"
).close()
233
if
getMPIRank
() == 0:
234
# In rank 0, wait until all other ranks have finished merging
235
files_to_check = [
236
(rank, f
"../rank-{rank}/done_merging"
)
for
rank
in
range(0, num_ranks)
237
]
238
count = 0
239
files_to_check = list(
240
it.filterfalse(
lambda
f: os.path.exists(f[1]), files_to_check)
241
)
242
while
files_to_check:
243
if
count % 10 == 0:
244
msg.info(
245
f
"Waiting for all ranks to finish merging: {list(map(lambda x: x[0], files_to_check))}"
246
)
247
count += 1
248
sleep(6)
249
files_to_check = list(
250
it.filterfalse(
lambda
f: os.path.exists(f[1]), files_to_check)
251
)
252
if
not
os.path.exists(
"merge_failure"
):
253
msg.info(
"All ranks done merging"
)
254
else
:
255
msg.error(
"ERRORS WHILE MERGING"
)
256
raise
RuntimeError(
"Output merging error"
)
map
STL class.
python.trfExceptions.TransformSetupException
Setup exceptions.
Definition
trfExceptions.py:43
python.trfMPITools.MPIType
Definition
trfMPITools.py:26
set
STL class.
split
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition
hcg.cxx:179
PyJobTransforms.trfExitCodes
Module for transform exit codes.
python.trfMPITools.mpiShouldValidate
mpiShouldValidate()
Definition
trfMPITools.py:111
python.trfMPITools.getMPIRank
getMPIRank()
Definition
trfMPITools.py:41
python.trfMPITools.mpiOutputs
mpiOutputs()
Definition
trfMPITools.py:119
python.trfMPITools.signalError
signalError(message)
Definition
trfMPITools.py:34
python.trfMPITools.setupMPIConfig
setupMPIConfig(output, dataDict)
Definition
trfMPITools.py:67
python.trfMPITools.mergeOutputs
mergeOutputs()
Definition
trfMPITools.py:123
python.trfMPITools.getMPIType
getMPIType()
Definition
trfMPITools.py:55
Generated on
for ATLAS Offline Software by
1.17.0