Fix: be aggressive on closing to prevent hanging sessiond
[deliverable/lttng-ivc.git] / lttng_ivc / utils / runtime.py
CommitLineData
18aedaf9 1import os
97d494b0 2import sys
18aedaf9
JR
3import shlex
4import subprocess
5import uuid
6import logging
97d494b0
JR
7import shutil
8import contextlib
9import pprint
18aedaf9 10
97d494b0
JR
11from tempfile import TemporaryDirectory
12
13import lttng_ivc.settings as Settings
18aedaf9
JR
14_logger = logging.getLogger("Runtime")
15
16
97d494b0
JR
17@contextlib.contextmanager
18def get_runtime(runtime_dir):
19 runtime = Runtime(runtime_dir)
20 try:
21 yield runtime
22 finally:
23 runtime.close()
24
25
18aedaf9
JR
26class Runtime(object):
27 def __init__(self, runtime_dir):
28 """
29 A dictionary of popen object eg. lttng-sessiond, relayd,
30 anything really. Key is a uuid.
31 """
32 self.__subprocess = {}
33 self.__stdout_stderr = {}
34 self.__projects = []
35
36 self.__runtime_log = os.path.join(runtime_dir, "log")
37 self.__runtime_log_sub = os.path.join(self.__runtime_log, "subprocess")
38
97d494b0
JR
39 """
40 Path of the copy of lttng_home folder after Runtime.close() is issued. This is
41 to be used for post runtime analysis and mostly debugging on error.
42 """
43 self.__post_runtime_lttng_home_path = os.path.join(runtime_dir,
44 "lttng_home")
45
18aedaf9
JR
46 self._runtime_log_aggregation = os.path.join(self.__runtime_log, "runtime.log")
47
48 self._run_command_count = 0
1cd816fd 49 self._is_test_modules_loaded = False
18aedaf9 50
97d494b0 51 self.special_env_variables = {"LTTNG_UST_DEBUG": "1",
d96b684b 52 "LTTNG_APP_SOCKET_TIMEOUT": "-1",
97d494b0
JR
53 #"LTTNG_UST_REGISTER_TIMEOUT": "-1",
54 "LTTNG_NETWORK_SOCKET_TIMEOUT": "-1"}
18aedaf9 55
97d494b0
JR
56 # Keep a reference on the object to keep it alive. It will close/clean on
57 # exit.
58 self.__lttng_home_dir = TemporaryDirectory(prefix=Settings.tmp_object_prefix)
59 self.lttng_home = self.__lttng_home_dir.name
60
61 if len(self.lttng_home) > 88:
62 raise Exception("TemporaryDirectory for lttng_home is to long. Use a short TMPDIR")
63
64 os.makedirs(self.__runtime_log)
65 os.makedirs(self.__runtime_log_sub)
18aedaf9
JR
66
67 def add_project(self, project):
68 self.__projects.append(project)
69
ee0fecde
JR
70 def remove_project(self, project):
71 self.__projects.remove(project)
72
18aedaf9
JR
73 def subprocess_signal(self, subprocess_uuid, signal):
74 self.__subproces[subprocess_uuid].send_signal(signal)
75
da0b3c5a 76 def subprocess_terminate(self, subprocess_uuid, timeout=60, check_return=True):
18aedaf9
JR
77 process = self.__subprocess[subprocess_uuid]
78 process.terminate()
da0b3c5a
JR
79 try:
80 process.wait(timeout)
81 except subprocess.TimeoutExpired:
82 # Force kill
acb9586e 83 return self.subprocess_kill(subprocess_uuid)
18aedaf9
JR
84 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
85 stdout.close()
86 stderr.close()
da0b3c5a
JR
87 if check_return:
88 if process.returncode != 0:
89 raise subprocess.CalledProcessError(process.returncode, process.args)
97d494b0 90 return process
18aedaf9
JR
91
92 def subprocess_kill(self, subprocess_uuid):
93 process = self.__subprocess[subprocess_uuid]
94 process.kill()
95 process.wait()
96 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
97 stdout.close()
98 stderr.close()
97d494b0 99 return process
18aedaf9 100
da0b3c5a
JR
101 def subprocess_wait(self, subprocess_uuid, check_return=True):
102 process = self.__subprocess[subprocess_uuid]
103 process.wait()
104 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
105 stdout.close()
106 stderr.close()
107 if check_return:
108 if process.returncode != 0:
109 raise subprocess.CalledProcessError(process.returncode, process.args)
110 return process
111
18aedaf9
JR
112 def get_subprocess_stdout_path(self, subprocess_uuid):
113 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
114 return stdout.name
115
116 def get_subprocess_stderr_path(self, subprocess_uuid):
117 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
118 return stderr.name
119
97d494b0 120 def spawn_subprocess(self, command_line, cwd=None):
18aedaf9
JR
121 args = shlex.split(command_line)
122 env = self.get_env()
123
97d494b0
JR
124 if not os.path.isdir(self.lttng_home):
125 raise Exception("lttng home does not exist")
126
18aedaf9
JR
127 tmp_id = uuid.uuid1()
128 out_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".out")
129 err_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".err")
97d494b0
JR
130
131 stdout = open(out_path, 'w')
132 stderr = open(err_path, 'w')
133
134 env_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".env")
135 with open(env_path, 'w') as env_out:
136 pprint.pprint(env, stream=env_out)
18aedaf9 137
bde0c540 138 p = subprocess.Popen(args, stdout=stdout, stderr=stderr, env=env, cwd=cwd)
18aedaf9
JR
139 self.__subprocess[tmp_id] = p
140 self.__stdout_stderr[tmp_id] = (stdout, stderr)
141 _logger.debug("Spawned sub pid: {} args: {} stdout: {} stderr{}".format(p.pid, p.args, out_path, err_path))
97d494b0 142 return tmp_id
18aedaf9 143
686e86ab 144 def run(self, command_line, cwd=None, check_return=True, ld_preload="", classpath="", timeout=None):
18aedaf9
JR
145 """
146 Run the command and return a tuple of a (CompletedProcess, stdout_path,
147 stderr_path). The subprocess is already executed and returned. The
148 callecaller is responsible for checking for errors.
149 """
150 args = shlex.split(command_line)
151 env = self.get_env()
152
9f2ed047
JR
153 if ld_preload:
154 env['LD_PRELOAD'] = ld_preload
74eb7096
JR
155 if classpath:
156 env['CLASSPATH'] = classpath
9f2ed047
JR
157
158
18aedaf9
JR
159 tmp_id = self._run_command_count
160 self._run_command_count += 1
161
97d494b0
JR
162 cmd_map = os.path.join(self.__runtime_log, "cmd.map")
163 with open(cmd_map, 'a') as out:
164 out.write("{}: {}\n".format(tmp_id, args))
165
18aedaf9
JR
166 out_path = os.path.join(self.__runtime_log, str(tmp_id) + ".out")
167 err_path = os.path.join(self.__runtime_log, str(tmp_id) + ".err")
168 stdout = open(out_path, "w")
169 stderr = open(err_path, "w")
170
97d494b0
JR
171 env_path = os.path.join(self.__runtime_log, str(tmp_id) + ".env")
172 with open(env_path, 'w') as env_out:
0a7e963a
JR
173 for key, value in env.items():
174 env_out.write('{}={}\n'.format(key, value))
18aedaf9 175
686e86ab
JR
176 cp = subprocess.run(args, stdout=stdout, stderr=stderr, env=env,
177 cwd=cwd, timeout=timeout)
18aedaf9
JR
178 _logger.debug("Command #{} args: {} stdout: {} stderr{}".format(tmp_id, cp.args, out_path, err_path))
179
18aedaf9
JR
180 # Add to the global log file. This can help a little. Leave the other
181 # file available for per-run analysis
182 with open(self._runtime_log_aggregation, "a") as log:
183 with open(out_path, "r") as out:
97d494b0
JR
184 log.write("Output for command #{} {}\n".format(tmp_id, command_line))
185 log.write("Start >>>>>>>>>>>>>>>>\n")
18aedaf9 186 log.write(out.read())
97d494b0 187 log.write("End <<<<<<<<<<<<<<<<\n")
18aedaf9 188 with open(err_path, "r") as out:
97d494b0
JR
189 log.write("Error for command #{} {}\n".format(tmp_id, command_line))
190 log.write("Start >>>>>>>>>>>>>>>>\n")
18aedaf9 191 log.write(out.read())
97d494b0
JR
192 log.write("End <<<<<<<<<<<<<<<<\n")
193
194 if check_return:
195 cp.check_returncode()
18aedaf9
JR
196
197 return (cp, out_path, err_path)
198
199 def get_cppflags(self):
200 cppflags = []
201 for project in self.__projects:
202 cppflags.append(project.get_cppflags())
203 return " ".join(cppflags)
204
205 def get_ldflags(self):
206 ldflags = []
207 for project in self.__projects:
208 ldflags.append(project.get_ldflags())
209 return " ".join(ldflags)
210
211 def get_ld_library_path(self):
212 library_path = []
213 for project in self.__projects:
214 library_path.append(project.get_ld_library_path())
6aa98db5 215 return ":".join(library_path)
18aedaf9
JR
216
217 def get_bin_path(self):
218 path = []
219 for project in self.__projects:
220 path.append(project.get_bin_path())
221 return ":".join(path)
222
223 def get_env(self):
224 env = os.environ.copy()
225
226 env["LTTNG_HOME"] = self.lttng_home
227
228 env_fetch = {"CPPFLAGS": (self.get_cppflags(), " "),
229 "LDFLAGS": (self.get_ldflags(), " "),
97d494b0 230 "LD_LIBRARY_PATH": (self.get_ld_library_path(), ":"),
18aedaf9
JR
231 "PATH": (self.get_bin_path(), ":"),
232 }
233 for key, (value, delimiter) in env_fetch.items():
234 tmp_var = ""
235 if key in env:
236 tmp_var = env[key]
237 env[key] = delimiter.join([value, tmp_var])
238
97d494b0
JR
239 for var, value in self.special_env_variables.items():
240 if var in env:
241 # Raise for now since no special cases is known
242 _logger.warning("% Special var % is already defined",
243 self.label, var)
244 raise Exception("Multiple definition of a special environment variable")
245 else:
246 env[var] = value
247
18aedaf9
JR
248 for project in self.__projects:
249 for var, value in project.special_env_variables.items():
250 if var in env:
251 # Raise for now since no special cases is known
252 _logger.warning("% Special var % is already defined",
253 self.label, var)
254 raise Exception("Multiple definition of a special environment variable")
255 else:
256 env[var] = value
257 return env
258
ab63b97e
JR
259 def load_test_module(self):
260 # Base directory is provided by env
261 self.run("modprobe lttng-test")
1cd816fd 262 self._is_test_modules_loaded = True
ab63b97e
JR
263
264 def unload_test_module(self, check_return=True):
265 # Base directory is provided by env
1cd816fd
JR
266 if self._is_test_modules_loaded:
267 self.run("modprobe -r lttng-test", check_return=check_return)
ab63b97e 268
18aedaf9
JR
269 def close(self):
270 for key, subp in self.__subprocess.items():
69e4e7d8 271 self.subprocess_kill(key)
97d494b0 272
ab63b97e
JR
273 # Always try to remove test module but do not perform check on return
274 # value.
275 self.unload_test_module(False)
276
97d494b0
JR
277 # Copy the lttng_home used at runtime using hardlink to prevent useless
278 # data duplication
279 shutil.copytree(self.lttng_home, self.__post_runtime_lttng_home_path, copy_function=os.link)
This page took 0.038095 seconds and 5 git commands to generate.