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