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