ATLAS Offline Software
Loading...
Searching...
No Matches
process_handling.py
Go to the documentation of this file.
1# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
2
3from .. import Logging
4from .non_blocking_stream_reader import NonBlockingStreamReader
5import subprocess
6import re
7
8
9logger = Logging.logging.getLogger("PowhegControl")
10
11
12class ProcessManager(object):
13 """! Wrapper to handle multiple Powheg subprocesses.
14
15 @author James Robinson <james.robinson@cern.ch>
16 """
17
18 def __init__(self, process_list):
19 """! Constructor.
20
21 @param process_list List of processes to manage.
22 """
23 self.__process_list = process_list
24 self.__n_initial = len(process_list)
25
26 def monitor(self):
27 """! Monitor each of the managed processes and log when they are finished."""
28 for idx, process in enumerate(self.__process_list):
29 process.id_number = idx + 1
30 while len(self.__process_list) > 0:
31 for process in list(self.__process_list):
32 if not process.has_output():
33 _return_code = process.return_code
34 self.__process_list.remove(process)
35 if _return_code == 0:
36 logger.info("Finished process #{}: there are now {}/{} running".format(process.id_number, len(self.__process_list), self.__n_initial))
37 else:
38 logger.warning("Process #{} terminated unexpectedly (return code {}): there are now {}/{} running".format(process.id_number, _return_code, len(self.__process_list), self.__n_initial))
39
40
41class SingleProcessThread(object):
42 """! Single executable running in a subprocess (usually PowhegBox).
43
44 @author James Robinson <james.robinson@cern.ch>
45 """
46
47 log_level = {"stdout": "info", "stderr": "error"}
48 __output_prefix = " | "
49 __ignore_output = []
50
51 def __init__(self, command_list, seed_index=None, stdin=None, ignore_output=None, warning_output=[], info_output=[], error_output=[]):
52 """! Constructor.
53
54 Setup underlying process together with non-blocking readers for stdout and stderr.
55
56 @param command_list Command that will be run (possibly with options).
57 @param seed_index Which seed from pwgseeds.dat to use.
58 @param stdin An open file handle providing input.
59 @param ignore_output List of strings to filter out from messages.
60 @param warning_output List of strings which would always trigger a warning only, even if produced in stderr.
61 @param info_output List of strings which would always trigger an info only, even if produced in stderr.
62 @param error_output List of strings which would always trigger an error, even if produced in stdout.
63 """
64 if not isinstance(command_list, list):
65 command_list = [command_list]
66 command_list = [str(x) for x in command_list]
67 # Set up messages to ignore
68 if ignore_output is not None:
69 self.__ignore_output = ignore_output
70 # Set up messages with special treatment
71 self.__warning_output = warning_output
72 self.__info_output = info_output
73 self.__error_output = error_output
74 # Usual case, where no open file handle is provided
75 if stdin is None:
76 self.__process = subprocess.Popen(command_list, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
77 # Write seed to stdin
78 if seed_index is not None:
79 self.__output_prefix += "Process #{}: ".format(seed_index)
80 self.__process.stdin.write(str(seed_index))
81 self.__process.stdin.close()
82 with open("pwgseeds.dat", "r") as seed_file:
83 random_seed_list = seed_file.read().splitlines()
84 self.log("Providing random seed: {}".format(random_seed_list[seed_index - 1]))
85 # Using an open file handle to provide input to stdin: remember to close this later
86 else:
87 self.__process = subprocess.Popen(command_list, stdout=subprocess.PIPE, stdin=stdin, stderr=subprocess.PIPE, text=True)
88 # Setup non-blocking stream readers for stdout and stderr
91
92
93
94 def has_output(self):
95 """! Write queued output and return process status."""
96 status = self.is_running()
98 return status
99
100 def is_running(self):
101 """! Check if the underlying process is running and finalise stream readers if not."""
102 if self.__process.poll() is not None: # process has ended
103 for nbsr in ("stdout", "stderr"):
104 getattr(self, nbsr).finalise()
105 return False
106 return True
107
108 def log(self, message, log_level="info"):
109 """! Write to the logger with appropriate log-level.
110
111 @param message The message to pass to the logger.
112 @param log_level Which level to log at.
113 """
114 for word in self.__ignore_output:
115 while word in message:
116 message = message.replace(word, "")
117 getattr(logger, log_level)("{}{}".format(self.__output_prefix, message.strip()))
118
120 """! Pass queued output to the logger."""
121 float_only = re.compile(r"^\s*[+-]?\d+\.\d+\s*$")
122 for stream in ["stdout", "stderr"]:
123 while True:
124 output, queue_size = getattr(self, stream).readline(timeout=0.1)
125 if output is not None and float_only.match(output):
126 self.log(output, "info")
127 elif output is not None and any([(pattern in output) for pattern in self.__error_output]):
128 self.log(output, "error")
129 elif output is not None and any([(pattern in output) for pattern in self.__warning_output]):
130 self.log(output, "warning")
131 elif output is not None and any([(pattern in output) for pattern in self.__info_output]):
132 self.log(output, "info")
133 elif not (output is None or len(output) == 0):
134 self.log(output, self.log_level[stream])
135 if queue_size == 0:
136 break
137
138 @property
139 def return_code(self):
140 """! Return code of underlying process."""
141 return self.__process.returncode
142
143 @property
144 def stdout(self):
145 """! stdout stream from underlying process."""
146 return self.__stdout
147
148 @property
149 def stderr(self):
150 """! stderr stream from underlying process."""
151 return self.__stderr
Wrapper to handle multiple Powheg subprocesses.
monitor(self)
Monitor each of the managed processes and log when they are finished.
Single executable running in a subprocess (usually PowhegBox).
__init__(self, command_list, seed_index=None, stdin=None, ignore_output=None, warning_output=[], info_output=[], error_output=[])
Constructor.
log(self, message, log_level="info")
Write to the logger with appropriate log-level.
stdout(self)
stdout stream from underlying process.
has_output(self)
Write queued output and return process status.
return_code(self)
Return code of underlying process.
write_queued_output(self)
Pass queued output to the logger.
stderr(self)
stderr stream from underlying process.
is_running(self)
Check if the underlying process is running and finalise stream readers if not.