ATLAS Offline Software
Toggle main menu visibility
Loading...
Searching...
No Matches
HLT
Trigger
TrigControl
TrigCommon
bin
athenaEF_tdaq_infra.py
Go to the documentation of this file.
1
#!/usr/bin/env python3
2
# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3
#
4
# Start a private TDAQ online infrastructure for testing the athenaEF OH
5
# publication path (athenaEF -M) in an offline job.
6
#
7
# athenaEF decides the online environment: partition name, webis host/port and OH server name.
8
# TDAQ_WEBDAQ_BASE has to be available.
9
# This script only reports readiness.
10
#
11
# The script first sources the TDAQ release via cm_setup.sh and re-execs itself.
12
# exec preserves the PID, so the PR_SET_PDEATHSIG that athenaEF set on this process stays valid across the environment setup.
13
#
14
# Started applications:
15
# ipc_server initial partition
16
# ipc_server -p <part> job partition
17
# is_server DF, RunParams and the OH server
18
# rdb_server ISRepository, only if --schema is given
19
# webproxy Used by WebdaqHistSvc on webdaq-port
20
#
21
# IS publication (WebdaqInfoSvc) needs rdb_server serving the ISRepository (needed by webproxy to resolve the object type).
22
# OH publication does not need it, so no --schema means no rdb_server.
23
#
24
# NB: webproxy MUST be used, NOT webis_server: webproxy deserialises OH POSTs as
25
# binary TBufferFile (matching webdaq::oh::put), while webis_server expects
26
# JSON - it answers 201 anyway and then silently drops every publication.
27
#
28
# Once all servers are up the script prints ATHENAEF_INFRA_READY on stdout, then
29
# stays alive polling them and exits non-zero if any of them dies.
30
# Server log files are written to --log-dir (default: cwd).
31
#
32
# All child processes are spawned with PR_SET_PDEATHSIG=SIGKILL so they die with this script.
33
# On SIGTERM/SIGINT the histograms are dumped with oh_cp and the partitions are removed cleanly with ipc_rm.
34
#
35
36
import
argparse
37
import
os
38
import
signal
39
import
subprocess
40
import
sys
41
import
time
42
import
socket
43
44
import
logging
45
logging.basicConfig(stream=sys.stdout, level=logging.INFO,
46
format=
'%(asctime)s %(name)s %(levelname)-8s %(message)s'
)
47
log = logging.getLogger(
'athenaEF_infra'
)
48
49
# TDAQ release setup (hardcoded for now, see --tdaq-release)
50
CM_SETUP =
'/cvmfs/atlas.cern.ch/repo/sw/tdaq/tools/cmake_tdaq/bin/cm_setup.sh'
51
DEFAULT_TDAQ_RELEASE =
'tdaq-14-00-00'
52
53
# Environment marker distinguishing stage 2 from stage 1
54
STAGE2_ENV =
'ATHENAEF_INFRA_STAGE2'
55
56
# Printed on stdout once the infrastructure is up
57
READY_MARKER =
'ATHENAEF_INFRA_READY'
58
59
60
def
parse_args
():
61
parser = argparse.ArgumentParser(
62
description=
'Start a private TDAQ infrastructure (IPC/IS/webproxy) for athenaEF online-monitoring tests'
)
63
parser.add_argument(
'--partition'
, metavar=
'NAME'
, required=
True
,
64
help=
'partition name (TDAQ_PARTITION)'
)
65
parser.add_argument(
'--webdaq-port'
, metavar=
'PORT'
, type=int, required=
True
,
66
help=
'port the webproxy listens on'
)
67
parser.add_argument(
'--oh-server'
, metavar=
'NAME'
, required=
True
,
68
help=
'name of the OH IS server (TDAQ_OH_SERVER)'
)
69
parser.add_argument(
'--run-number'
, metavar=
'N'
, type=int, required=
True
,
70
help=
'run number, used to name the output file'
)
71
parser.add_argument(
'--schema'
, metavar=
'FILE'
, action=
'append'
, default=[],
72
help=
'IS schema file (absolute path) to load into the ISRepository, '
73
'repeatable. If none is given rdb_server is not started'
)
74
parser.add_argument(
'--log-dir'
, metavar=
'DIR'
, default=
'.'
,
75
help=
'directory for infrastructure log files (default: cwd)'
)
76
parser.add_argument(
'--tdaq-release'
, metavar=
'REL'
, default=DEFAULT_TDAQ_RELEASE,
77
help=
'TDAQ release to source via cm_setup.sh (default: %(default)s)'
)
78
return
parser.parse_args()
79
80
81
def
setup_tdaq_and_reexec
(args):
82
"""re-exec this script through bash after sourcing the TDAQ release."""
83
if
not
os.path.exists(CM_SETUP):
84
log.error(
'TDAQ release setup not found at %s'
, CM_SETUP)
85
sys.exit(1)
86
87
log.info(
'Sourcing TDAQ release %s via %s'
, args.tdaq_release, CM_SETUP)
88
os.environ[STAGE2_ENV] =
'1'
89
script = os.path.abspath(__file__)
90
cmd = (f
'source {CM_SETUP} {args.tdaq_release} || exit 66; '
91
f
'exec python3 "{script}" "$@"'
)
92
os.execv(
'/bin/bash'
, [
'/bin/bash'
,
'-c'
, cmd, script] + sys.argv[1:])
93
94
95
class
Infrastructure
:
96
"""Manage the private TDAQ infrastructure (adapted from HLTMPPy.runner.Infrastructure)"""
97
98
sigs = [signal.SIGFPE, signal.SIGHUP, signal.SIGQUIT, signal.SIGSEGV,
99
signal.SIGTERM, signal.SIGINT]
100
101
def
__init__
(self, args):
102
self.
args
= args
103
self.
processes
= []
# (name, subprocess.Popen)
104
self.
pid
= os.getpid()
# Distinguish mother from children after forking
105
self.
ready
=
False
# True once all servers are up
106
self.
register_handlers
()
107
108
def
__del__
(self):
109
"""Stop infrastructure in the mother process, in case program exits before stop()"""
110
if
os.getpid() == self.
pid
:
111
self.
stop
()
112
113
def
register_handlers
(self):
114
self.
prehandlers
= {}
115
for
s
in
self.
sigs
:
116
self.
prehandlers
[s] = signal.getsignal(s)
117
signal.signal(s, self.
_handle_quit
)
118
119
def
_handle_quit
(self, signum, frame):
120
log.info(
'Caught signal %d. Cleaning up the infrastructure and exiting'
, signum)
121
self.
stop
()
122
prehandler = self.
prehandlers
.pop(signum, signal.SIG_DFL)
123
signal.signal(signum, prehandler)
124
sys.exit(0)
125
126
def
_implant_bomb
(self):
127
"""preexec_fn ensuring infrastructure processes exit when this process dies"""
128
from
ctypes
import
cdll
129
PR_SET_PDEATHSIG = 1
130
try
:
131
return
lambda
: cdll[
'libc.so.6'
].prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
132
except
Exception:
133
log.error(
'Error setting PR_SET_PDEATHSIG for infrastructure processes. '
134
'Using a dummy function instead'
)
135
return
lambda
: 1
136
137
def
_launch
(self, name, cmd):
138
logbase = os.path.join(self.
args
.log_dir, f
'{name}_{self.args.partition}'
)
139
proc = subprocess.Popen(cmd, preexec_fn=self.
_implant_bomb
(),
140
stdout=open(logbase +
'.out'
,
'w'
),
141
stderr=open(logbase +
'.err'
,
'w'
),
142
close_fds=
True
)
143
log.info(
'Started %s (pid %d): %s'
, name, proc.pid,
' '
.join(cmd))
144
self.
processes
.append((name, proc))
145
return
proc
146
147
def
start
(self):
148
from
ispy
import
IPCPartition
149
150
partition = self.
args
.partition
151
152
# Private IPC domain: reference file local to this job's directory
153
ipc_ref =
'file:'
+ os.path.join(os.getcwd(),
'ipc_init.ref'
)
154
log.info(
'Setting TDAQ_IPC_INIT_REF: %s'
, ipc_ref)
155
os.environ[
'TDAQ_IPC_INIT_REF'
] = ipc_ref
156
157
log.info(
'Initializing OH monitoring infrastructure for partition %s'
, partition)
158
159
self.
_launch
(
'ipc_initial'
, [
'ipc_server'
])
160
while
not
IPCPartition(
'initial'
).
isValid
():
161
log.info(
'Waiting until initial partition is available...'
)
162
time.sleep(1)
163
164
self.
_launch
(
'ipc_partition'
, [
'ipc_server'
,
'-p'
, partition])
165
while
not
IPCPartition(partition).
isValid
():
166
log.info(
'Waiting until partition %s is available...'
, partition)
167
time.sleep(1)
168
169
for
server
in
[
'DF'
,
'RunParams'
, self.
args
.oh_server]:
170
self.
_launch
(f
'is_{server}'
, [
'is_server'
,
'-p'
, partition,
'-n'
, server])
171
172
self.
start_rdb
()
173
self.
start_webproxy
()
174
self.
ready
=
True
175
176
def
start_rdb
(self):
177
"""Start rdb_server serving the IS type schema"""
178
if
not
self.
args
.schema:
179
log.info(
'No --schema given, not starting rdb_server '
180
'(IS publication will be rejected with HTTP 400)'
)
181
return
182
183
missing = [f
for
f
in
self.
args
.schema
if
not
os.path.exists(f)]
184
if
missing:
185
log.error(
'IS schema file(s) not found: %s'
,
', '
.join(missing))
186
self.
stop
()
187
sys.exit(1)
188
189
log.info(
'Starting rdb_server with IS schema: %s'
,
', '
.join(self.
args
.schema))
190
self.
_launch
(
'rdb'
, [
'rdb_server'
,
'-p'
, self.
args
.partition,
191
'-d'
,
'ISRepository'
,
'-s'
,
192
'-D'
] + self.
args
.schema)
193
194
def
start_webproxy
(self):
195
"""Start the webproxy REST server"""
196
port = self.args.webdaq_port
197
webproxy = self._launch('webproxy', ['webproxy', '-p', str(port)])
198
199
timeout = 60
200
for _ in range(timeout):
201
if webproxy.poll() is not None:
202
log.error('webproxy exited early (code %s); see webproxy_%s.err',
203
webproxy.returncode, self.args.partition)
204
self.stop()
205
sys.exit(1)
206
try:
207
with socket.create_connection(('localhost', port), timeout=1):
208
log.info('webproxy listening on http://localhost:%d', port)
209
return
210
except OSError:
211
time.sleep(1)
212
213
log.error('webproxy is not listening on localhost:%d after %d s', port, timeout)
214
self.stop()
215
sys.exit(1)
216
217
def check_alive(self):
218
"""
Return False if any infrastructure process has exited"""
219
for name, proc in self.processes:
220
ret = proc.poll()
221
if ret is not None:
222
log.error('Infrastructure process %s (pid %d) exited with code %s',
223
name, proc.pid, ret)
224
return False
225
return True
226
227
def copy_histograms(self):
228
fname = f'r{self.args.run_number:010d}_{self.args.partition}_{self.args.oh_server}.root'
229
log.info('Copying histograms into %s (oh_cp)', fname)
230
subprocess.call(['oh_cp', '-p', self.args.partition, '-s', self.args.oh_server,
231
'-n', '.*', '-o', '.*', '-O',
232
'-r', str(self.args.run_number), '-f', fname])
233
234
def dump_is_content(self):
235
fname = f'r{self.args.run_number:010d}_{self.args.partition}_DF.txt'
236
log.info('Writing content of DF IS server to %s', fname)
237
with open(fname, "w") as f:
238
subprocess.call(['is_ls', '-p', self.args.partition, '-n', 'DF',
239
'-R', '.*', '-TNv'],
240
stdout=f, stderr=subprocess.STDOUT, text=True)
241
242
def stop(self):
243
if not self.processes:
244
return
245
246
if self.ready: # Nothing was ever published if we did not fully start
247
self.copy_histograms()
248
self.dump_is_content()
249
250
log.info('Finalizing OH monitoring infrastructure')
251
for part in [self.args.partition, 'initial']:
252
log.info('Destroying partition: %s', part)
253
subprocess.call(['ipc_rm', '-f', '-p', part, '-i', '".*"', '-n', '".*"'],
254
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
255
256
for name, proc in self.processes:
257
while proc.poll() is None:
258
proc.kill()
259
time.sleep(0.1)
260
self.processes = []
261
log.info('Terminated all infrastructure processes')
262
263
264
def main():
265
args = parse_args()
266
267
if STAGE2_ENV not in os.environ:
268
setup_tdaq_and_reexec(args) # does not return
269
270
os.makedirs(args.log_dir, exist_ok=True)
271
272
infra = Infrastructure(args)
273
infra.start()
274
print(READY_MARKER, flush=True)
275
276
while infra.check_alive():
277
time.sleep(2)
278
279
infra.stop()
280
return 1
281
282
283
if __name__ == '__main__':
284
sys.exit(main())
isValid
bool isValid() const
Test to see if the link can be dereferenced.
athenaEF_tdaq_infra.Infrastructure
Definition
athenaEF_tdaq_infra.py:95
athenaEF_tdaq_infra.Infrastructure._launch
_launch(self, name, cmd)
Definition
athenaEF_tdaq_infra.py:137
athenaEF_tdaq_infra.Infrastructure.start_webproxy
start_webproxy(self)
Definition
athenaEF_tdaq_infra.py:194
athenaEF_tdaq_infra.Infrastructure.register_handlers
register_handlers(self)
Definition
athenaEF_tdaq_infra.py:113
athenaEF_tdaq_infra.Infrastructure.sigs
list sigs
Definition
athenaEF_tdaq_infra.py:98
athenaEF_tdaq_infra.Infrastructure.ready
bool ready
Definition
athenaEF_tdaq_infra.py:105
athenaEF_tdaq_infra.Infrastructure.pid
pid
Definition
athenaEF_tdaq_infra.py:104
athenaEF_tdaq_infra.Infrastructure.start
start(self)
Definition
athenaEF_tdaq_infra.py:147
athenaEF_tdaq_infra.Infrastructure.prehandlers
dict prehandlers
Definition
athenaEF_tdaq_infra.py:114
athenaEF_tdaq_infra.Infrastructure._handle_quit
_handle_quit
Definition
athenaEF_tdaq_infra.py:117
athenaEF_tdaq_infra.Infrastructure.__init__
__init__(self, args)
Definition
athenaEF_tdaq_infra.py:101
athenaEF_tdaq_infra.Infrastructure.processes
list processes
Definition
athenaEF_tdaq_infra.py:103
athenaEF_tdaq_infra.Infrastructure._implant_bomb
_implant_bomb(self)
Definition
athenaEF_tdaq_infra.py:126
athenaEF_tdaq_infra.Infrastructure.args
args
Definition
athenaEF_tdaq_infra.py:102
athenaEF_tdaq_infra.Infrastructure.stop
stop(self)
Definition
athenaEF_tdaq_infra.py:242
athenaEF_tdaq_infra.Infrastructure.__del__
__del__(self)
Definition
athenaEF_tdaq_infra.py:108
athenaEF_tdaq_infra.Infrastructure.start_rdb
start_rdb(self)
Definition
athenaEF_tdaq_infra.py:176
athenaEF_tdaq_infra.setup_tdaq_and_reexec
setup_tdaq_and_reexec(args)
Definition
athenaEF_tdaq_infra.py:81
athenaEF_tdaq_infra.parse_args
parse_args()
Definition
athenaEF_tdaq_infra.py:60
Generated on
for ATLAS Offline Software by
1.17.0