blob: a1a2ef08a24f9c462d962eb7ed5fafb740f2b686 [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):
jadmanski0afbb632008-06-06 21:10:57 +0000401 # the process has not terminated within timeout,
402 # kill it via an escalating series of signals.
403 signal_queue = [signal.SIGTERM, signal.SIGKILL]
404 for sig in signal_queue:
405 try:
406 os.kill(subproc.pid, sig)
407 # The process may have died before we could kill it.
408 except OSError:
409 pass
mbligh63073c92008-03-31 16:49:32 +0000410
jadmanski0afbb632008-06-06 21:10:57 +0000411 for i in range(5):
412 rc = subproc.poll()
413 if rc != None:
414 return rc
415 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000416
417
418def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000419 # the process has not terminated within timeout,
420 # kill it via an escalating series of signals.
421 signal_queue = [signal.SIGTERM, signal.SIGKILL]
422 for sig in signal_queue:
423 try:
424 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000425
jadmanski0afbb632008-06-06 21:10:57 +0000426 # The process may have died before we could kill it.
427 except OSError:
428 pass
mbligh63073c92008-03-31 16:49:32 +0000429
jadmanski0afbb632008-06-06 21:10:57 +0000430 try:
431 for i in range(5):
432 status = os.waitpid(pid, os.WNOHANG)[0]
433 if status == pid:
434 return
435 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000436
jadmanski0afbb632008-06-06 21:10:57 +0000437 if status != pid:
438 raise error.AutoservRunError('Could not kill %d'
439 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000440
jadmanski0afbb632008-06-06 21:10:57 +0000441 # the process died before we join it.
442 except OSError:
443 pass
mbligh63073c92008-03-31 16:49:32 +0000444
445
mbligh63073c92008-03-31 16:49:32 +0000446
447def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000448 """This function returns the exit status of command."""
jadmanski0afbb632008-06-06 21:10:57 +0000449 return run(command, timeout, ignore_status,
mbligha5630a52008-09-03 22:09:50 +0000450 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
mbligh63073c92008-03-31 16:49:32 +0000451
452
mbligha5630a52008-09-03 22:09:50 +0000453def system_parallel(commands, timeout=None, ignore_status=False):
454 """This function returns a list of exit statuses for the respective
455 list of commands."""
456 return [bg_jobs.exit_status for bg_jobs in
457 run_parallel(commands, timeout, ignore_status,
458 stdout_tee=sys.stdout, stderr_tee=sys.stderr)]
mbligh849a0f62008-08-28 20:12:19 +0000459
460
mbligh8ea61e22008-05-09 18:09:37 +0000461def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000462 retain_output=False):
463 if retain_output:
464 out = run(command, timeout, ignore_status,
465 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
466 else:
467 out = run(command, timeout, ignore_status).stdout
468 if out[-1:] == '\n': out = out[:-1]
469 return out
mbligh63073c92008-03-31 16:49:32 +0000470
mbligh849a0f62008-08-28 20:12:19 +0000471
mbligha5630a52008-09-03 22:09:50 +0000472def system_output_parallel(commands, timeout=None, ignore_status=False,
473 retain_output=False):
474 if retain_output:
475 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
476 ignore_status,
477 stdout_tee=sys.stdout,
478 stderr_tee=sys.stderr)]
479 else:
480 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
481 ignore_status)]
482 for x in out:
483 if out[-1:] == '\n': out = out[:-1]
484 return out
485
486
487def get_cpu_percentage(function, *args, **dargs):
488 """Returns a tuple containing the CPU% and return value from function call.
489
490 This function calculates the usage time by taking the difference of
491 the user and system times both before and after the function call.
492 """
493 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
494 self_pre = resource.getrusage(resource.RUSAGE_SELF)
495 start = time.time()
496 to_return = function(*args, **dargs)
497 elapsed = time.time() - start
498 self_post = resource.getrusage(resource.RUSAGE_SELF)
499 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
500
501 # Calculate CPU Percentage
502 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
503 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
504 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
505
506 return cpu_percent, to_return
507
508
mblighc1cbc992008-05-27 20:01:45 +0000509"""
510This function is used when there is a need to run more than one
511job simultaneously starting exactly at the same time. It basically returns
512a modified control file (containing the synchronization code prepended)
513whenever it is ready to run the control file. The synchronization
514is done using barriers to make sure that the jobs start at the same time.
515
516Here is how the synchronization is done to make sure that the tests
517start at exactly the same time on the client.
518sc_bar is a server barrier and s_bar, c_bar are the normal barriers
519
520 Job1 Job2 ...... JobN
521 Server: | sc_bar
522 Server: | s_bar ...... s_bar
523 Server: | at.run() at.run() ...... at.run()
524 ----------|------------------------------------------------------
525 Client | sc_bar
526 Client | c_bar c_bar ...... c_bar
527 Client | <run test> <run test> ...... <run test>
528
529
530PARAMS:
531 control_file : The control file which to which the above synchronization
532 code would be prepended to
533 host_name : The host name on which the job is going to run
534 host_num (non negative) : A number to identify the machine so that we have
535 different sets of s_bar_ports for each of the machines.
536 instance : The number of the job
537 num_jobs : Total number of jobs that are going to run in parallel with
538 this job starting at the same time
539 port_base : Port number that is used to derive the actual barrier ports.
540
541RETURN VALUE:
542 The modified control file.
543
544"""
545def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000546 instance, num_jobs, port_base=63100):
547 sc_bar_port = port_base
548 c_bar_port = port_base
549 if host_num < 0:
550 print "Please provide a non negative number for the host"
551 return None
552 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
553 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000554
jadmanski0afbb632008-06-06 21:10:57 +0000555 sc_bar_timeout = 180
556 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000557
jadmanski0afbb632008-06-06 21:10:57 +0000558 # The barrier code snippet is prepended into the conrol file
559 # dynamically before at.run() is called finally.
560 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000561
jadmanski0afbb632008-06-06 21:10:57 +0000562 # jobid is the unique name used to identify the processes
563 # trying to reach the barriers
564 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000565
jadmanski0afbb632008-06-06 21:10:57 +0000566 rendv = []
567 # rendvstr is a temp holder for the rendezvous list of the processes
568 for n in range(num_jobs):
569 rendv.append("'%s#%d'" % (host_name, n))
570 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000571
jadmanski0afbb632008-06-06 21:10:57 +0000572 if instance == 0:
573 # Do the setup and wait at the server barrier
574 # Clean up the tmp and the control dirs for the first instance
575 control_new.append('if os.path.exists(job.tmpdir):')
576 control_new.append("\t system('umount -f %s > /dev/null"
577 "2> /dev/null' % job.tmpdir,"
578 "ignore_status=True)")
579 control_new.append("\t system('rm -rf ' + job.tmpdir)")
580 control_new.append(
581 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
582 % (jobid, sc_bar_timeout, sc_bar_port))
583 control_new.append(
584 'b0.rendevous_servers("PARALLEL_MASTER", "%s")'
585 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000586
jadmanski0afbb632008-06-06 21:10:57 +0000587 elif instance == 1:
588 # Wait at the server barrier to wait for instance=0
589 # process to complete setup
590 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
591 port=sc_bar_port)
592 b0.rendevous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000593
jadmanski0afbb632008-06-06 21:10:57 +0000594 if(num_jobs > 2):
595 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
596 port=s_bar_port)
597 b1.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000598
jadmanski0afbb632008-06-06 21:10:57 +0000599 else:
600 # For the rest of the clients
601 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
602 b2.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000603
jadmanski0afbb632008-06-06 21:10:57 +0000604 # Client side barrier for all the tests to start at the same time
605 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
606 % (jobid, c_bar_timeout, c_bar_port))
607 control_new.append("b1.rendevous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000608
jadmanski0afbb632008-06-06 21:10:57 +0000609 # Stick in the rest of the control file
610 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000611
jadmanski0afbb632008-06-06 21:10:57 +0000612 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000613
mbligh63073c92008-03-31 16:49:32 +0000614
mblighc5ddfd12008-08-04 17:15:00 +0000615def get_arch(run_function=run):
616 """
617 Get the hardware architecture of the machine.
618 run_function is used to execute the commands. It defaults to
619 utils.run() but a custom method (if provided) should be of the
620 same schema as utils.run. It should return a CmdResult object and
621 throw a CmdError exception.
622 """
623 arch = run_function('/bin/uname -m').stdout.rstrip()
624 if re.match(r'i\d86$', arch):
625 arch = 'i386'
626 return arch
627
628
mbligh63073c92008-03-31 16:49:32 +0000629class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000630 """
631 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000632
jadmanski0afbb632008-06-06 21:10:57 +0000633 command: String containing the command line itself
634 exit_status: Integer exit code of the process
635 stdout: String containing stdout of the process
636 stderr: String containing stderr of the process
637 duration: Elapsed wall clock time running the process
638 """
mbligh63073c92008-03-31 16:49:32 +0000639
640
jadmanski0afbb632008-06-06 21:10:57 +0000641 def __init__(self, command=None, stdout="", stderr="",
642 exit_status=None, duration=0):
643 self.command = command
644 self.exit_status = exit_status
645 self.stdout = stdout
646 self.stderr = stderr
647 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000648
649
jadmanski0afbb632008-06-06 21:10:57 +0000650 def __repr__(self):
651 wrapper = textwrap.TextWrapper(width = 78,
652 initial_indent="\n ",
653 subsequent_indent=" ")
654
655 stdout = self.stdout.rstrip()
656 if stdout:
657 stdout = "\nstdout:\n%s" % stdout
658
659 stderr = self.stderr.rstrip()
660 if stderr:
661 stderr = "\nstderr:\n%s" % stderr
662
663 return ("* Command: %s\n"
664 "Exit status: %s\n"
665 "Duration: %s\n"
666 "%s"
667 "%s"
668 % (wrapper.fill(self.command), self.exit_status,
669 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000670
671
mbligh462c0152008-03-13 15:37:10 +0000672class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000673 def __init__(self, run_sequentially=False):
674 # Run sequentially is for debugging control files
675 self.test_list = []
676 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000677
678
jadmanski0afbb632008-06-06 21:10:57 +0000679 def add(self, *args, **dargs):
680 test = (args, dargs)
681 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000682
683
jadmanski0afbb632008-06-06 21:10:57 +0000684 def run(self, fn):
685 while self.test_list:
686 test_index = random.randint(0, len(self.test_list)-1)
687 if self.run_sequentially:
688 test_index = 0
689 (args, dargs) = self.test_list.pop(test_index)
690 fn(*args, **dargs)