blob: b67810c6ce51005b5b9ca54b30d27bf8b8d2b78c [file] [log] [blame]
mbligh63073c92008-03-31 16:49:32 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. Released under the GPL v2
4
mbligh849a0f62008-08-28 20:12:19 +00005import os, pickle, random, re, resource, select, shutil, signal, StringIO
6import socket, struct, subprocess, sys, time, textwrap, urllib, urlparse
7import warnings
mbligh81edd792008-08-26 16:54:02 +00008from autotest_lib.client.common_lib import error, barrier
9
mbligh849a0f62008-08-28 20:12:19 +000010def deprecated(func):
11 """This is a decorator which can be used to mark functions as deprecated.
12 It will result in a warning being emmitted when the function is used."""
13 def new_func(*args, **dargs):
14 warnings.warn("Call to deprecated function %s." % func.__name__,
15 category=DeprecationWarning)
16 return func(*args, **dargs)
17 new_func.__name__ = func.__name__
18 new_func.__doc__ = func.__doc__
19 new_func.__dict__.update(func.__dict__)
20 return new_func
21
22
23class BgJob(object):
mblighbd96b452008-09-03 23:14:27 +000024 def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True):
mbligh849a0f62008-08-28 20:12:19 +000025 self.command = command
26 self.stdout_tee = stdout_tee
27 self.stderr_tee = stderr_tee
28 self.result = CmdResult(command)
mblighbd96b452008-09-03 23:14:27 +000029 if verbose:
30 print "running: %s" % command
mbligh849a0f62008-08-28 20:12:19 +000031 self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
32 stderr=subprocess.PIPE,
33 preexec_fn=self._reset_sigpipe, shell=True,
34 executable="/bin/bash")
35
36
37 def output_prepare(self, stdout_file=None, stderr_file=None):
38 self.stdout_file = stdout_file
39 self.stderr_file = stderr_file
40
41 def process_output(self, stdout=True, final_read=False):
42 """output_prepare must be called prior to calling this"""
43 if stdout:
44 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
45 else:
46 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
47
48 if final_read:
49 # read in all the data we can from pipe and then stop
50 data = []
51 while select.select([pipe], [], [], 0)[0]:
52 data.append(os.read(pipe.fileno(), 1024))
53 if len(data[-1]) == 0:
54 break
55 data = "".join(data)
56 else:
57 # perform a single read
58 data = os.read(pipe.fileno(), 1024)
59 buf.write(data)
60 if tee:
61 tee.write(data)
62 tee.flush()
63
64
65 def cleanup(self):
66 self.sp.stdout.close()
67 self.sp.stderr.close()
68 self.result.stdout = self.stdout_file.getvalue()
69 self.result.stderr = self.stderr_file.getvalue()
70
71
72 def _reset_sigpipe(self):
73 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
74
mbligh81edd792008-08-26 16:54:02 +000075
76def ip_to_long(ip):
77 # !L is a long in network byte order
78 return struct.unpack('!L', socket.inet_aton(ip))[0]
79
80
81def long_to_ip(number):
82 # See above comment.
83 return socket.inet_ntoa(struct.pack('!L', number))
84
85
86def create_subnet_mask(bits):
87 # ~ does weird things in python...but this does work
88 return (1 << 32) - (1 << 32-bits)
89
90
91def format_ip_with_mask(ip, mask_bits):
92 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
93 return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
mbligh6231cd62008-02-02 19:18:33 +000094
mblighde0d47e2008-03-28 14:37:18 +000095
jadmanski5182e162008-05-13 21:48:16 +000096def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +000097 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +000098
99
100def write_one_line(filename, str):
mbligh6e8840c2008-07-11 18:05:49 +0000101 open(filename, 'w').write(str.rstrip('\n') + '\n')
jadmanski5182e162008-05-13 21:48:16 +0000102
103
mblighde0d47e2008-03-28 14:37:18 +0000104def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000105 """
106 Read a key-value pair format file into a dictionary, and return it.
107 Takes either a filename or directory name as input. If it's a
108 directory name, we assume you want the file to be called keyval.
109 """
110 if os.path.isdir(path):
111 path = os.path.join(path, 'keyval')
112 keyval = {}
113 for line in open(path):
jadmanskia6014a02008-07-14 19:41:54 +0000114 line = re.sub('#.*', '', line).rstrip()
jadmanski0afbb632008-06-06 21:10:57 +0000115 if not re.search(r'^[-\w]+=', line):
116 raise ValueError('Invalid format line: %s' % line)
117 key, value = line.split('=', 1)
118 if re.search('^\d+$', value):
119 value = int(value)
120 elif re.search('^(\d+\.)?\d+$', value):
121 value = float(value)
122 keyval[key] = value
123 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000124
125
jadmanskicc549172008-05-21 18:11:51 +0000126def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000127 """
128 Write a key-value pair format file out to a file. This uses append
129 mode to open the file, so existing text will not be overwritten or
130 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000131
jadmanski0afbb632008-06-06 21:10:57 +0000132 If type_tag is None, then the key must be composed of alphanumeric
133 characters (or dashes+underscores). However, if type-tag is not
134 null then the keys must also have "{type_tag}" as a suffix. At
135 the moment the only valid values of type_tag are "attr" and "perf".
136 """
137 if os.path.isdir(path):
138 path = os.path.join(path, 'keyval')
139 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000140
jadmanski0afbb632008-06-06 21:10:57 +0000141 if type_tag is None:
142 key_regex = re.compile(r'^[-\w]+$')
143 else:
144 if type_tag not in ('attr', 'perf'):
145 raise ValueError('Invalid type tag: %s' % type_tag)
146 escaped_tag = re.escape(type_tag)
147 key_regex = re.compile(r'^[-\w]+\{%s\}$' % escaped_tag)
148 try:
149 for key, value in dictionary.iteritems():
150 if not key_regex.search(key):
151 raise ValueError('Invalid key: %s' % key)
152 keyval.write('%s=%s\n' % (key, value))
153 finally:
154 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000155
156
157def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000158 """Return true if path looks like a URL"""
159 # for now, just handle http and ftp
160 url_parts = urlparse.urlparse(path)
161 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000162
163
mbligh02ff2d52008-06-03 15:00:21 +0000164def urlopen(url, data=None, proxies=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +0000165 """Wrapper to urllib.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000166
jadmanski0afbb632008-06-06 21:10:57 +0000167 # Save old timeout
168 old_timeout = socket.getdefaulttimeout()
169 socket.setdefaulttimeout(timeout)
170 try:
171 return urllib.urlopen(url, data=data, proxies=proxies)
172 finally:
173 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000174
175
176def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +0000177 """Wrapper to urllib.urlretrieve with timeout addition."""
178 old_timeout = socket.getdefaulttimeout()
179 socket.setdefaulttimeout(timeout)
180 try:
181 return urllib.urlretrieve(url, filename=filename,
182 reporthook=reporthook, data=data)
183 finally:
184 socket.setdefaulttimeout(old_timeout)
185
mbligh02ff2d52008-06-03 15:00:21 +0000186
mbligh6231cd62008-02-02 19:18:33 +0000187def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000188 """Get a file from src, which can be local or a remote URL"""
189 if (src == dest):
190 return
191 if (is_url(src)):
192 print 'PWD: ' + os.getcwd()
193 print 'Fetching \n\t', src, '\n\t->', dest
194 try:
195 urllib.urlretrieve(src, dest)
196 except IOError, e:
197 raise error.AutotestError('Unable to retrieve %s (to %s)'
198 % (src, dest), e)
199 else:
200 shutil.copyfile(src, dest)
201 if permissions:
202 os.chmod(dest, permissions)
203 return dest
mbligh6231cd62008-02-02 19:18:33 +0000204
205
206def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000207 """
208 Receives either a path to a local file or a URL.
209 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000210
jadmanski0afbb632008-06-06 21:10:57 +0000211 unmap_url('/usr/src', 'foo.tar', '/tmp')
212 = '/usr/src/foo.tar'
213 unmap_url('/usr/src', 'http://site/file', '/tmp')
214 = '/tmp/file'
215 (after retrieving it)
216 """
217 if is_url(src):
218 url_parts = urlparse.urlparse(src)
219 filename = os.path.basename(url_parts[2])
220 dest = os.path.join(destdir, filename)
221 return get_file(src, dest)
222 else:
223 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000224
225
226def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000227 *args, **dargs):
228 """
229 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000230
jadmanski0afbb632008-06-06 21:10:57 +0000231 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000232
jadmanski0afbb632008-06-06 21:10:57 +0000233 In the preserve_srcdir case, we just check it's up to date,
234 and if not, we rerun install, without removing srcdir
235 """
236 versionfile = os.path.join(srcdir, '.version')
237 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000238
jadmanski0afbb632008-06-06 21:10:57 +0000239 if os.path.exists(versionfile):
240 old_version = pickle.load(open(versionfile))
241 if old_version == new_version:
242 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000243
jadmanski0afbb632008-06-06 21:10:57 +0000244 if install_needed:
245 if not preserve_srcdir and os.path.exists(srcdir):
246 shutil.rmtree(srcdir)
247 install(*args, **dargs)
248 if os.path.exists(srcdir):
249 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000250
251
mbligh63073c92008-03-31 16:49:32 +0000252def run(command, timeout=None, ignore_status=False,
mblighbd96b452008-09-03 23:14:27 +0000253 stdout_tee=None, stderr_tee=None, verbose=True):
jadmanski0afbb632008-06-06 21:10:57 +0000254 """
255 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000256
jadmanski0afbb632008-06-06 21:10:57 +0000257 Args:
258 command: the command line string
259 timeout: time limit in seconds before attempting to
260 kill the running process. The run() function
261 will take a few seconds longer than 'timeout'
262 to complete if it has to kill the process.
263 ignore_status: do not raise an exception, no matter what
264 the exit code of the command is.
265 stdout_tee: optional file-like object to which stdout data
266 will be written as it is generated (data will still
267 be stored in result.stdout)
268 stderr_tee: likewise for stderr
mbligh63073c92008-03-31 16:49:32 +0000269
jadmanski0afbb632008-06-06 21:10:57 +0000270 Returns:
271 a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000272
jadmanski0afbb632008-06-06 21:10:57 +0000273 Raises:
274 CmdError: the exit code of the command
275 execution was not 0
276 """
mblighbd96b452008-09-03 23:14:27 +0000277 bg_job = join_bg_jobs((BgJob(command, stdout_tee, stderr_tee, verbose),),
mbligh849a0f62008-08-28 20:12:19 +0000278 timeout)[0]
279 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000280 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000281 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000282
mbligh849a0f62008-08-28 20:12:19 +0000283 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000284
mbligha5630a52008-09-03 22:09:50 +0000285def run_parallel(commands, timeout=None, ignore_status=False,
286 stdout_tee=None, stderr_tee=None):
287 """Beahves the same as run with the following exceptions:
288
289 - commands is a list of commands to run in parallel.
290 - ignore_status toggles whether or not an exception should be raised
291 on any error.
292
293 returns a list of CmdResult objects
294 """
295 bg_jobs = []
296 for command in commands:
297 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee))
298
299 # Updates objects in bg_jobs list with their process information
300 join_bg_jobs(bg_jobs, timeout)
301
302 for bg_job in bg_jobs:
303 if not ignore_status and bg_job.result.exit_status:
304 raise error.CmdError(command, bg_job.result,
305 "Command returned non-zero exit status")
306
307 return [bg_job.result for bg_job in bg_jobs]
308
309
mbligh849a0f62008-08-28 20:12:19 +0000310@deprecated
mbligh63073c92008-03-31 16:49:32 +0000311def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000312 """Function deprecated. Please use BgJob class instead."""
313 bg_job = BgJob(command)
314 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000315
316
mbligh849a0f62008-08-28 20:12:19 +0000317def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000318 """Joins the bg_jobs with the current thread.
319
320 Returns the same list of bg_jobs objects that was passed in.
321 """
mbligh849a0f62008-08-28 20:12:19 +0000322 ret, timeouterr = 0, False
323 for bg_job in bg_jobs:
324 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000325
jadmanski0afbb632008-06-06 21:10:57 +0000326 try:
327 # We are holding ends to stdin, stdout pipes
328 # hence we need to be sure to close those fds no mater what
329 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000330 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
331
332 for bg_job in bg_jobs:
333 # Process stdout and stderr
334 bg_job.process_output(stdout=True,final_read=True)
335 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000336 finally:
337 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000338 for bg_job in bg_jobs:
339 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000340
mbligh849a0f62008-08-28 20:12:19 +0000341 if timeout_error:
342 # TODO: This needs to be fixed to better represent what happens when
343 # running in parallel. However this is backwards compatable, so it will
344 # do for the time being.
345 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
346 "Command(s) did not complete within %d seconds"
347 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000348
mbligh63073c92008-03-31 16:49:32 +0000349
mbligh849a0f62008-08-28 20:12:19 +0000350 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000351
mbligh849a0f62008-08-28 20:12:19 +0000352
353def _wait_for_commands(bg_jobs, start_time, timeout):
354 # This returns True if it must return due to a timeout, otherwise False.
355
mblighf0b4a0a2008-09-03 20:46:16 +0000356 # To check for processes which terminate without producing any output
357 # a 1 second timeout is used in select.
358 SELECT_TIMEOUT = 1
359
mbligh849a0f62008-08-28 20:12:19 +0000360 select_list = []
361 reverse_dict = {}
362 for bg_job in bg_jobs:
363 select_list.append(bg_job.sp.stdout)
364 select_list.append(bg_job.sp.stderr)
365 reverse_dict[bg_job.sp.stdout] = (bg_job,True)
366 reverse_dict[bg_job.sp.stderr] = (bg_job,False)
367
jadmanski0afbb632008-06-06 21:10:57 +0000368 if timeout:
369 stop_time = start_time + timeout
370 time_left = stop_time - time.time()
371 else:
372 time_left = None # so that select never times out
373 while not timeout or time_left > 0:
374 # select will return when stdout is ready (including when it is
375 # EOF, that is the process has terminated).
mblighf0b4a0a2008-09-03 20:46:16 +0000376 ready, _, _ = select.select(select_list, [], [], SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000377
jadmanski0afbb632008-06-06 21:10:57 +0000378 # os.read() has to be used instead of
379 # subproc.stdout.read() which will otherwise block
mbligh849a0f62008-08-28 20:12:19 +0000380 for fileno in ready:
381 bg_job,stdout = reverse_dict[fileno]
382 bg_job.process_output(stdout)
mbligh63073c92008-03-31 16:49:32 +0000383
mbligh849a0f62008-08-28 20:12:19 +0000384 remaining_jobs = [x for x in bg_jobs if x.result.exit_status is None]
385 if len(remaining_jobs) == 0:
386 return False
387 for bg_job in remaining_jobs:
388 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000389
jadmanski0afbb632008-06-06 21:10:57 +0000390 if timeout:
391 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000392
mbligh849a0f62008-08-28 20:12:19 +0000393 # Kill all processes which did not complete prior to timeout
394 for bg_job in [x for x in bg_jobs if x.result.exit_status is None]:
395 nuke_subprocess(bg_job.sp)
mbligh8ea61e22008-05-09 18:09:37 +0000396
mbligh849a0f62008-08-28 20:12:19 +0000397 return True
mbligh63073c92008-03-31 16:49:32 +0000398
399
mbligh63073c92008-03-31 16:49:32 +0000400def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000401 # check if the subprocess is still alive, first
402 if subproc.poll() is not None:
403 return subproc.poll()
404
jadmanski0afbb632008-06-06 21:10:57 +0000405 # the process has not terminated within timeout,
406 # kill it via an escalating series of signals.
407 signal_queue = [signal.SIGTERM, signal.SIGKILL]
408 for sig in signal_queue:
409 try:
410 os.kill(subproc.pid, sig)
411 # The process may have died before we could kill it.
412 except OSError:
413 pass
mbligh63073c92008-03-31 16:49:32 +0000414
jadmanski0afbb632008-06-06 21:10:57 +0000415 for i in range(5):
416 rc = subproc.poll()
417 if rc != None:
418 return rc
419 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000420
421
422def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000423 # the process has not terminated within timeout,
424 # kill it via an escalating series of signals.
425 signal_queue = [signal.SIGTERM, signal.SIGKILL]
426 for sig in signal_queue:
427 try:
428 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000429
jadmanski0afbb632008-06-06 21:10:57 +0000430 # The process may have died before we could kill it.
431 except OSError:
432 pass
mbligh63073c92008-03-31 16:49:32 +0000433
jadmanski0afbb632008-06-06 21:10:57 +0000434 try:
435 for i in range(5):
436 status = os.waitpid(pid, os.WNOHANG)[0]
437 if status == pid:
438 return
439 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000440
jadmanski0afbb632008-06-06 21:10:57 +0000441 if status != pid:
442 raise error.AutoservRunError('Could not kill %d'
443 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000444
jadmanski0afbb632008-06-06 21:10:57 +0000445 # the process died before we join it.
446 except OSError:
447 pass
mbligh63073c92008-03-31 16:49:32 +0000448
449
mbligh63073c92008-03-31 16:49:32 +0000450
451def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000452 """This function returns the exit status of command."""
jadmanski0afbb632008-06-06 21:10:57 +0000453 return run(command, timeout, ignore_status,
mbligha5630a52008-09-03 22:09:50 +0000454 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
mbligh63073c92008-03-31 16:49:32 +0000455
456
mbligha5630a52008-09-03 22:09:50 +0000457def system_parallel(commands, timeout=None, ignore_status=False):
458 """This function returns a list of exit statuses for the respective
459 list of commands."""
460 return [bg_jobs.exit_status for bg_jobs in
461 run_parallel(commands, timeout, ignore_status,
462 stdout_tee=sys.stdout, stderr_tee=sys.stderr)]
mbligh849a0f62008-08-28 20:12:19 +0000463
464
mbligh8ea61e22008-05-09 18:09:37 +0000465def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000466 retain_output=False):
467 if retain_output:
468 out = run(command, timeout, ignore_status,
469 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
470 else:
471 out = run(command, timeout, ignore_status).stdout
472 if out[-1:] == '\n': out = out[:-1]
473 return out
mbligh63073c92008-03-31 16:49:32 +0000474
mbligh849a0f62008-08-28 20:12:19 +0000475
mbligha5630a52008-09-03 22:09:50 +0000476def system_output_parallel(commands, timeout=None, ignore_status=False,
477 retain_output=False):
478 if retain_output:
479 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
480 ignore_status,
481 stdout_tee=sys.stdout,
482 stderr_tee=sys.stderr)]
483 else:
484 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
485 ignore_status)]
486 for x in out:
487 if out[-1:] == '\n': out = out[:-1]
488 return out
489
490
491def get_cpu_percentage(function, *args, **dargs):
492 """Returns a tuple containing the CPU% and return value from function call.
493
494 This function calculates the usage time by taking the difference of
495 the user and system times both before and after the function call.
496 """
497 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
498 self_pre = resource.getrusage(resource.RUSAGE_SELF)
499 start = time.time()
500 to_return = function(*args, **dargs)
501 elapsed = time.time() - start
502 self_post = resource.getrusage(resource.RUSAGE_SELF)
503 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
504
505 # Calculate CPU Percentage
506 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
507 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
508 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
509
510 return cpu_percent, to_return
511
512
mblighc1cbc992008-05-27 20:01:45 +0000513"""
514This function is used when there is a need to run more than one
515job simultaneously starting exactly at the same time. It basically returns
516a modified control file (containing the synchronization code prepended)
517whenever it is ready to run the control file. The synchronization
518is done using barriers to make sure that the jobs start at the same time.
519
520Here is how the synchronization is done to make sure that the tests
521start at exactly the same time on the client.
522sc_bar is a server barrier and s_bar, c_bar are the normal barriers
523
524 Job1 Job2 ...... JobN
525 Server: | sc_bar
526 Server: | s_bar ...... s_bar
527 Server: | at.run() at.run() ...... at.run()
528 ----------|------------------------------------------------------
529 Client | sc_bar
530 Client | c_bar c_bar ...... c_bar
531 Client | <run test> <run test> ...... <run test>
532
533
534PARAMS:
535 control_file : The control file which to which the above synchronization
536 code would be prepended to
537 host_name : The host name on which the job is going to run
538 host_num (non negative) : A number to identify the machine so that we have
539 different sets of s_bar_ports for each of the machines.
540 instance : The number of the job
541 num_jobs : Total number of jobs that are going to run in parallel with
542 this job starting at the same time
543 port_base : Port number that is used to derive the actual barrier ports.
544
545RETURN VALUE:
546 The modified control file.
547
548"""
549def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000550 instance, num_jobs, port_base=63100):
551 sc_bar_port = port_base
552 c_bar_port = port_base
553 if host_num < 0:
554 print "Please provide a non negative number for the host"
555 return None
556 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
557 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000558
jadmanski0afbb632008-06-06 21:10:57 +0000559 sc_bar_timeout = 180
560 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000561
jadmanski0afbb632008-06-06 21:10:57 +0000562 # The barrier code snippet is prepended into the conrol file
563 # dynamically before at.run() is called finally.
564 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000565
jadmanski0afbb632008-06-06 21:10:57 +0000566 # jobid is the unique name used to identify the processes
567 # trying to reach the barriers
568 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000569
jadmanski0afbb632008-06-06 21:10:57 +0000570 rendv = []
571 # rendvstr is a temp holder for the rendezvous list of the processes
572 for n in range(num_jobs):
573 rendv.append("'%s#%d'" % (host_name, n))
574 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000575
jadmanski0afbb632008-06-06 21:10:57 +0000576 if instance == 0:
577 # Do the setup and wait at the server barrier
578 # Clean up the tmp and the control dirs for the first instance
579 control_new.append('if os.path.exists(job.tmpdir):')
580 control_new.append("\t system('umount -f %s > /dev/null"
581 "2> /dev/null' % job.tmpdir,"
582 "ignore_status=True)")
583 control_new.append("\t system('rm -rf ' + job.tmpdir)")
584 control_new.append(
585 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
586 % (jobid, sc_bar_timeout, sc_bar_port))
587 control_new.append(
588 'b0.rendevous_servers("PARALLEL_MASTER", "%s")'
589 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000590
jadmanski0afbb632008-06-06 21:10:57 +0000591 elif instance == 1:
592 # Wait at the server barrier to wait for instance=0
593 # process to complete setup
594 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
595 port=sc_bar_port)
596 b0.rendevous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000597
jadmanski0afbb632008-06-06 21:10:57 +0000598 if(num_jobs > 2):
599 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
600 port=s_bar_port)
601 b1.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000602
jadmanski0afbb632008-06-06 21:10:57 +0000603 else:
604 # For the rest of the clients
605 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
606 b2.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000607
jadmanski0afbb632008-06-06 21:10:57 +0000608 # Client side barrier for all the tests to start at the same time
609 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
610 % (jobid, c_bar_timeout, c_bar_port))
611 control_new.append("b1.rendevous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000612
jadmanski0afbb632008-06-06 21:10:57 +0000613 # Stick in the rest of the control file
614 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000615
jadmanski0afbb632008-06-06 21:10:57 +0000616 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000617
mbligh63073c92008-03-31 16:49:32 +0000618
mblighc5ddfd12008-08-04 17:15:00 +0000619def get_arch(run_function=run):
620 """
621 Get the hardware architecture of the machine.
622 run_function is used to execute the commands. It defaults to
623 utils.run() but a custom method (if provided) should be of the
624 same schema as utils.run. It should return a CmdResult object and
625 throw a CmdError exception.
626 """
627 arch = run_function('/bin/uname -m').stdout.rstrip()
628 if re.match(r'i\d86$', arch):
629 arch = 'i386'
630 return arch
631
632
mbligh63073c92008-03-31 16:49:32 +0000633class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000634 """
635 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000636
jadmanski0afbb632008-06-06 21:10:57 +0000637 command: String containing the command line itself
638 exit_status: Integer exit code of the process
639 stdout: String containing stdout of the process
640 stderr: String containing stderr of the process
641 duration: Elapsed wall clock time running the process
642 """
mbligh63073c92008-03-31 16:49:32 +0000643
644
jadmanski0afbb632008-06-06 21:10:57 +0000645 def __init__(self, command=None, stdout="", stderr="",
646 exit_status=None, duration=0):
647 self.command = command
648 self.exit_status = exit_status
649 self.stdout = stdout
650 self.stderr = stderr
651 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000652
653
jadmanski0afbb632008-06-06 21:10:57 +0000654 def __repr__(self):
655 wrapper = textwrap.TextWrapper(width = 78,
656 initial_indent="\n ",
657 subsequent_indent=" ")
658
659 stdout = self.stdout.rstrip()
660 if stdout:
661 stdout = "\nstdout:\n%s" % stdout
662
663 stderr = self.stderr.rstrip()
664 if stderr:
665 stderr = "\nstderr:\n%s" % stderr
666
667 return ("* Command: %s\n"
668 "Exit status: %s\n"
669 "Duration: %s\n"
670 "%s"
671 "%s"
672 % (wrapper.fill(self.command), self.exit_status,
673 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000674
675
mbligh462c0152008-03-13 15:37:10 +0000676class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000677 def __init__(self, run_sequentially=False):
678 # Run sequentially is for debugging control files
679 self.test_list = []
680 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000681
682
jadmanski0afbb632008-06-06 21:10:57 +0000683 def add(self, *args, **dargs):
684 test = (args, dargs)
685 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000686
687
jadmanski0afbb632008-06-06 21:10:57 +0000688 def run(self, fn):
689 while self.test_list:
690 test_index = random.randint(0, len(self.test_list)-1)
691 if self.run_sequentially:
692 test_index = 0
693 (args, dargs) = self.test_list.pop(test_index)
694 fn(*args, **dargs)