Add ld_preload capability to runtime.run()
[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
10import traceback
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
50
97d494b0
JR
51 self.special_env_variables = {"LTTNG_UST_DEBUG": "1",
52 #"LTTNG_APP_SOCKET_TIMEOUT": "-1",
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
70 def subprocess_signal(self, subprocess_uuid, signal):
71 self.__subproces[subprocess_uuid].send_signal(signal)
72
73 def subprocess_terminate(self, subprocess_uuid, timeout=60):
74 process = self.__subprocess[subprocess_uuid]
75 process.terminate()
76 process.wait(timeout)
77 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
78 stdout.close()
79 stderr.close()
97d494b0 80 return process
18aedaf9
JR
81
82 def subprocess_kill(self, subprocess_uuid):
83 process = self.__subprocess[subprocess_uuid]
84 process.kill()
85 process.wait()
86 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
87 stdout.close()
88 stderr.close()
97d494b0 89 return process
18aedaf9
JR
90
91 def get_subprocess_stdout_path(self, subprocess_uuid):
92 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
93 return stdout.name
94
95 def get_subprocess_stderr_path(self, subprocess_uuid):
96 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
97 return stderr.name
98
97d494b0 99 def spawn_subprocess(self, command_line, cwd=None):
18aedaf9
JR
100 args = shlex.split(command_line)
101 env = self.get_env()
102
97d494b0
JR
103 if not os.path.isdir(self.lttng_home):
104 raise Exception("lttng home does not exist")
105
18aedaf9
JR
106 tmp_id = uuid.uuid1()
107 out_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".out")
108 err_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".err")
97d494b0
JR
109
110 stdout = open(out_path, 'w')
111 stderr = open(err_path, 'w')
112
113 env_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".env")
114 with open(env_path, 'w') as env_out:
115 pprint.pprint(env, stream=env_out)
18aedaf9
JR
116
117 p = subprocess.Popen(args, stdout=stdout, stderr=stderr, env=env)
118 self.__subprocess[tmp_id] = p
119 self.__stdout_stderr[tmp_id] = (stdout, stderr)
120 _logger.debug("Spawned sub pid: {} args: {} stdout: {} stderr{}".format(p.pid, p.args, out_path, err_path))
97d494b0 121 return tmp_id
18aedaf9 122
9f2ed047 123 def run(self, command_line, cwd=None, check_return=True, ld_preload=""):
18aedaf9
JR
124 """
125 Run the command and return a tuple of a (CompletedProcess, stdout_path,
126 stderr_path). The subprocess is already executed and returned. The
127 callecaller is responsible for checking for errors.
128 """
129 args = shlex.split(command_line)
130 env = self.get_env()
131
9f2ed047
JR
132 if ld_preload:
133 env['LD_PRELOAD'] = ld_preload
134
135
18aedaf9
JR
136 tmp_id = self._run_command_count
137 self._run_command_count += 1
138
97d494b0
JR
139 cmd_map = os.path.join(self.__runtime_log, "cmd.map")
140 with open(cmd_map, 'a') as out:
141 out.write("{}: {}\n".format(tmp_id, args))
142
18aedaf9
JR
143 out_path = os.path.join(self.__runtime_log, str(tmp_id) + ".out")
144 err_path = os.path.join(self.__runtime_log, str(tmp_id) + ".err")
145 stdout = open(out_path, "w")
146 stderr = open(err_path, "w")
147
97d494b0
JR
148 env_path = os.path.join(self.__runtime_log, str(tmp_id) + ".env")
149 with open(env_path, 'w') as env_out:
150 pprint.pprint(env, stream=env_out)
18aedaf9 151
97d494b0 152 cp = subprocess.run(args, stdout=stdout, stderr=stderr, env=env, cwd=cwd)
18aedaf9
JR
153 _logger.debug("Command #{} args: {} stdout: {} stderr{}".format(tmp_id, cp.args, out_path, err_path))
154
18aedaf9
JR
155 # Add to the global log file. This can help a little. Leave the other
156 # file available for per-run analysis
157 with open(self._runtime_log_aggregation, "a") as log:
158 with open(out_path, "r") as out:
97d494b0
JR
159 log.write("Output for command #{} {}\n".format(tmp_id, command_line))
160 log.write("Start >>>>>>>>>>>>>>>>\n")
18aedaf9 161 log.write(out.read())
97d494b0 162 log.write("End <<<<<<<<<<<<<<<<\n")
18aedaf9 163 with open(err_path, "r") as out:
97d494b0
JR
164 log.write("Error for command #{} {}\n".format(tmp_id, command_line))
165 log.write("Start >>>>>>>>>>>>>>>>\n")
18aedaf9 166 log.write(out.read())
97d494b0
JR
167 log.write("End <<<<<<<<<<<<<<<<\n")
168
169 if check_return:
170 cp.check_returncode()
18aedaf9
JR
171
172 return (cp, out_path, err_path)
173
174 def get_cppflags(self):
175 cppflags = []
176 for project in self.__projects:
177 cppflags.append(project.get_cppflags())
178 return " ".join(cppflags)
179
180 def get_ldflags(self):
181 ldflags = []
182 for project in self.__projects:
183 ldflags.append(project.get_ldflags())
184 return " ".join(ldflags)
185
186 def get_ld_library_path(self):
187 library_path = []
188 for project in self.__projects:
189 library_path.append(project.get_ld_library_path())
6aa98db5 190 return ":".join(library_path)
18aedaf9
JR
191
192 def get_bin_path(self):
193 path = []
194 for project in self.__projects:
195 path.append(project.get_bin_path())
196 return ":".join(path)
197
198 def get_env(self):
199 env = os.environ.copy()
200
201 env["LTTNG_HOME"] = self.lttng_home
202
203 env_fetch = {"CPPFLAGS": (self.get_cppflags(), " "),
204 "LDFLAGS": (self.get_ldflags(), " "),
97d494b0 205 "LD_LIBRARY_PATH": (self.get_ld_library_path(), ":"),
18aedaf9
JR
206 "PATH": (self.get_bin_path(), ":"),
207 }
208 for key, (value, delimiter) in env_fetch.items():
209 tmp_var = ""
210 if key in env:
211 tmp_var = env[key]
212 env[key] = delimiter.join([value, tmp_var])
213
97d494b0
JR
214 for var, value in self.special_env_variables.items():
215 if var in env:
216 # Raise for now since no special cases is known
217 _logger.warning("% Special var % is already defined",
218 self.label, var)
219 raise Exception("Multiple definition of a special environment variable")
220 else:
221 env[var] = value
222
18aedaf9
JR
223 for project in self.__projects:
224 for var, value in project.special_env_variables.items():
225 if var in env:
226 # Raise for now since no special cases is known
227 _logger.warning("% Special var % is already defined",
228 self.label, var)
229 raise Exception("Multiple definition of a special environment variable")
230 else:
231 env[var] = value
232 return env
233
234 def close(self):
235 for key, subp in self.__subprocess.items():
236 subp.terminate()
237 for key, subp in self.__subprocess.items():
97d494b0
JR
238 # TODO move timeout to settings
239 subp.wait(timeout=60)
18aedaf9
JR
240 for key, (stdout, stderr) in self.__stdout_stderr.items():
241 stdout.close()
242 stderr.close()
97d494b0
JR
243
244 # Copy the lttng_home used at runtime using hardlink to prevent useless
245 # data duplication
246 shutil.copytree(self.lttng_home, self.__post_runtime_lttng_home_path, copy_function=os.link)
247
This page took 0.034875 seconds and 5 git commands to generate.