blob: 443666782123dfc9c8d03e87a337e5593a0e207d [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
mblighd6d043c2008-09-27 21:00:45 +000096def get_ip_local_port_range():
97 match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
98 read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
99 return (int(match.group(1)), int(match.group(2)))
100
101
102def set_ip_local_port_range(lower, upper):
103 write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
104 '%d %d\n' % (lower, upper))
105
mbligh315b9412008-10-01 03:34:11 +0000106
107def normalize_hostname(alias):
108 ip = socket.gethostbyname(alias)
109 return socket.gethostbyaddr(ip)[0]
110
111
jadmanski5182e162008-05-13 21:48:16 +0000112def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +0000113 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +0000114
115
116def write_one_line(filename, str):
mbligh6e8840c2008-07-11 18:05:49 +0000117 open(filename, 'w').write(str.rstrip('\n') + '\n')
jadmanski5182e162008-05-13 21:48:16 +0000118
119
mblighde0d47e2008-03-28 14:37:18 +0000120def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000121 """
122 Read a key-value pair format file into a dictionary, and return it.
123 Takes either a filename or directory name as input. If it's a
124 directory name, we assume you want the file to be called keyval.
125 """
126 if os.path.isdir(path):
127 path = os.path.join(path, 'keyval')
128 keyval = {}
129 for line in open(path):
jadmanskia6014a02008-07-14 19:41:54 +0000130 line = re.sub('#.*', '', line).rstrip()
jadmanski0afbb632008-06-06 21:10:57 +0000131 if not re.search(r'^[-\w]+=', line):
132 raise ValueError('Invalid format line: %s' % line)
133 key, value = line.split('=', 1)
134 if re.search('^\d+$', value):
135 value = int(value)
136 elif re.search('^(\d+\.)?\d+$', value):
137 value = float(value)
138 keyval[key] = value
139 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000140
141
jadmanskicc549172008-05-21 18:11:51 +0000142def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000143 """
144 Write a key-value pair format file out to a file. This uses append
145 mode to open the file, so existing text will not be overwritten or
146 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000147
jadmanski0afbb632008-06-06 21:10:57 +0000148 If type_tag is None, then the key must be composed of alphanumeric
149 characters (or dashes+underscores). However, if type-tag is not
150 null then the keys must also have "{type_tag}" as a suffix. At
151 the moment the only valid values of type_tag are "attr" and "perf".
152 """
153 if os.path.isdir(path):
154 path = os.path.join(path, 'keyval')
155 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000156
jadmanski0afbb632008-06-06 21:10:57 +0000157 if type_tag is None:
158 key_regex = re.compile(r'^[-\w]+$')
159 else:
160 if type_tag not in ('attr', 'perf'):
161 raise ValueError('Invalid type tag: %s' % type_tag)
162 escaped_tag = re.escape(type_tag)
163 key_regex = re.compile(r'^[-\w]+\{%s\}$' % escaped_tag)
164 try:
165 for key, value in dictionary.iteritems():
166 if not key_regex.search(key):
167 raise ValueError('Invalid key: %s' % key)
168 keyval.write('%s=%s\n' % (key, value))
169 finally:
170 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000171
172
173def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000174 """Return true if path looks like a URL"""
175 # for now, just handle http and ftp
176 url_parts = urlparse.urlparse(path)
177 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000178
179
jadmanskied91ba92008-09-30 17:19:27 +0000180def urlopen(url, data=None, proxies=None, timeout=5):
jadmanski0afbb632008-06-06 21:10:57 +0000181 """Wrapper to urllib.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000182
jadmanski0afbb632008-06-06 21:10:57 +0000183 # Save old timeout
184 old_timeout = socket.getdefaulttimeout()
185 socket.setdefaulttimeout(timeout)
186 try:
187 return urllib.urlopen(url, data=data, proxies=proxies)
188 finally:
189 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000190
191
192def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +0000193 """Wrapper to urllib.urlretrieve with timeout addition."""
194 old_timeout = socket.getdefaulttimeout()
195 socket.setdefaulttimeout(timeout)
196 try:
197 return urllib.urlretrieve(url, filename=filename,
198 reporthook=reporthook, data=data)
199 finally:
200 socket.setdefaulttimeout(old_timeout)
201
mbligh02ff2d52008-06-03 15:00:21 +0000202
mbligh6231cd62008-02-02 19:18:33 +0000203def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000204 """Get a file from src, which can be local or a remote URL"""
205 if (src == dest):
206 return
207 if (is_url(src)):
208 print 'PWD: ' + os.getcwd()
209 print 'Fetching \n\t', src, '\n\t->', dest
210 try:
211 urllib.urlretrieve(src, dest)
212 except IOError, e:
213 raise error.AutotestError('Unable to retrieve %s (to %s)'
214 % (src, dest), e)
215 else:
216 shutil.copyfile(src, dest)
217 if permissions:
218 os.chmod(dest, permissions)
219 return dest
mbligh6231cd62008-02-02 19:18:33 +0000220
221
222def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000223 """
224 Receives either a path to a local file or a URL.
225 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000226
jadmanski0afbb632008-06-06 21:10:57 +0000227 unmap_url('/usr/src', 'foo.tar', '/tmp')
228 = '/usr/src/foo.tar'
229 unmap_url('/usr/src', 'http://site/file', '/tmp')
230 = '/tmp/file'
231 (after retrieving it)
232 """
233 if is_url(src):
234 url_parts = urlparse.urlparse(src)
235 filename = os.path.basename(url_parts[2])
236 dest = os.path.join(destdir, filename)
237 return get_file(src, dest)
238 else:
239 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000240
241
242def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000243 *args, **dargs):
244 """
245 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000246
jadmanski0afbb632008-06-06 21:10:57 +0000247 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000248
jadmanski0afbb632008-06-06 21:10:57 +0000249 In the preserve_srcdir case, we just check it's up to date,
250 and if not, we rerun install, without removing srcdir
251 """
252 versionfile = os.path.join(srcdir, '.version')
253 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000254
jadmanski0afbb632008-06-06 21:10:57 +0000255 if os.path.exists(versionfile):
256 old_version = pickle.load(open(versionfile))
257 if old_version == new_version:
258 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000259
jadmanski0afbb632008-06-06 21:10:57 +0000260 if install_needed:
261 if not preserve_srcdir and os.path.exists(srcdir):
262 shutil.rmtree(srcdir)
263 install(*args, **dargs)
264 if os.path.exists(srcdir):
265 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000266
267
mbligh63073c92008-03-31 16:49:32 +0000268def run(command, timeout=None, ignore_status=False,
mblighbd96b452008-09-03 23:14:27 +0000269 stdout_tee=None, stderr_tee=None, verbose=True):
jadmanski0afbb632008-06-06 21:10:57 +0000270 """
271 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000272
jadmanski0afbb632008-06-06 21:10:57 +0000273 Args:
274 command: the command line string
275 timeout: time limit in seconds before attempting to
276 kill the running process. The run() function
277 will take a few seconds longer than 'timeout'
278 to complete if it has to kill the process.
279 ignore_status: do not raise an exception, no matter what
280 the exit code of the command is.
281 stdout_tee: optional file-like object to which stdout data
282 will be written as it is generated (data will still
283 be stored in result.stdout)
284 stderr_tee: likewise for stderr
mbligh63073c92008-03-31 16:49:32 +0000285
jadmanski0afbb632008-06-06 21:10:57 +0000286 Returns:
287 a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000288
jadmanski0afbb632008-06-06 21:10:57 +0000289 Raises:
290 CmdError: the exit code of the command
291 execution was not 0
292 """
mblighbd96b452008-09-03 23:14:27 +0000293 bg_job = join_bg_jobs((BgJob(command, stdout_tee, stderr_tee, verbose),),
mbligh849a0f62008-08-28 20:12:19 +0000294 timeout)[0]
295 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000296 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000297 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000298
mbligh849a0f62008-08-28 20:12:19 +0000299 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000300
mbligha5630a52008-09-03 22:09:50 +0000301def run_parallel(commands, timeout=None, ignore_status=False,
302 stdout_tee=None, stderr_tee=None):
303 """Beahves the same as run with the following exceptions:
304
305 - commands is a list of commands to run in parallel.
306 - ignore_status toggles whether or not an exception should be raised
307 on any error.
308
309 returns a list of CmdResult objects
310 """
311 bg_jobs = []
312 for command in commands:
313 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee))
314
315 # Updates objects in bg_jobs list with their process information
316 join_bg_jobs(bg_jobs, timeout)
317
318 for bg_job in bg_jobs:
319 if not ignore_status and bg_job.result.exit_status:
320 raise error.CmdError(command, bg_job.result,
321 "Command returned non-zero exit status")
322
323 return [bg_job.result for bg_job in bg_jobs]
324
325
mbligh849a0f62008-08-28 20:12:19 +0000326@deprecated
mbligh63073c92008-03-31 16:49:32 +0000327def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000328 """Function deprecated. Please use BgJob class instead."""
329 bg_job = BgJob(command)
330 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000331
332
mbligh849a0f62008-08-28 20:12:19 +0000333def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000334 """Joins the bg_jobs with the current thread.
335
336 Returns the same list of bg_jobs objects that was passed in.
337 """
mbligh849a0f62008-08-28 20:12:19 +0000338 ret, timeouterr = 0, False
339 for bg_job in bg_jobs:
340 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000341
jadmanski0afbb632008-06-06 21:10:57 +0000342 try:
343 # We are holding ends to stdin, stdout pipes
344 # hence we need to be sure to close those fds no mater what
345 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000346 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
347
348 for bg_job in bg_jobs:
349 # Process stdout and stderr
350 bg_job.process_output(stdout=True,final_read=True)
351 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000352 finally:
353 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000354 for bg_job in bg_jobs:
355 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000356
mbligh849a0f62008-08-28 20:12:19 +0000357 if timeout_error:
358 # TODO: This needs to be fixed to better represent what happens when
359 # running in parallel. However this is backwards compatable, so it will
360 # do for the time being.
361 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
362 "Command(s) did not complete within %d seconds"
363 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000364
mbligh63073c92008-03-31 16:49:32 +0000365
mbligh849a0f62008-08-28 20:12:19 +0000366 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000367
mbligh849a0f62008-08-28 20:12:19 +0000368
369def _wait_for_commands(bg_jobs, start_time, timeout):
370 # This returns True if it must return due to a timeout, otherwise False.
371
mblighf0b4a0a2008-09-03 20:46:16 +0000372 # To check for processes which terminate without producing any output
373 # a 1 second timeout is used in select.
374 SELECT_TIMEOUT = 1
375
mbligh849a0f62008-08-28 20:12:19 +0000376 select_list = []
377 reverse_dict = {}
378 for bg_job in bg_jobs:
379 select_list.append(bg_job.sp.stdout)
380 select_list.append(bg_job.sp.stderr)
381 reverse_dict[bg_job.sp.stdout] = (bg_job,True)
382 reverse_dict[bg_job.sp.stderr] = (bg_job,False)
383
jadmanski0afbb632008-06-06 21:10:57 +0000384 if timeout:
385 stop_time = start_time + timeout
386 time_left = stop_time - time.time()
387 else:
388 time_left = None # so that select never times out
389 while not timeout or time_left > 0:
390 # select will return when stdout is ready (including when it is
391 # EOF, that is the process has terminated).
mblighf0b4a0a2008-09-03 20:46:16 +0000392 ready, _, _ = select.select(select_list, [], [], SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000393
jadmanski0afbb632008-06-06 21:10:57 +0000394 # os.read() has to be used instead of
395 # subproc.stdout.read() which will otherwise block
mbligh849a0f62008-08-28 20:12:19 +0000396 for fileno in ready:
397 bg_job,stdout = reverse_dict[fileno]
398 bg_job.process_output(stdout)
mbligh63073c92008-03-31 16:49:32 +0000399
mbligh849a0f62008-08-28 20:12:19 +0000400 remaining_jobs = [x for x in bg_jobs if x.result.exit_status is None]
401 if len(remaining_jobs) == 0:
402 return False
403 for bg_job in remaining_jobs:
404 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000405
jadmanski0afbb632008-06-06 21:10:57 +0000406 if timeout:
407 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000408
mbligh849a0f62008-08-28 20:12:19 +0000409 # Kill all processes which did not complete prior to timeout
410 for bg_job in [x for x in bg_jobs if x.result.exit_status is None]:
411 nuke_subprocess(bg_job.sp)
mbligh8ea61e22008-05-09 18:09:37 +0000412
mbligh849a0f62008-08-28 20:12:19 +0000413 return True
mbligh63073c92008-03-31 16:49:32 +0000414
415
mbligh63073c92008-03-31 16:49:32 +0000416def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000417 # check if the subprocess is still alive, first
418 if subproc.poll() is not None:
419 return subproc.poll()
420
jadmanski0afbb632008-06-06 21:10:57 +0000421 # the process has not terminated within timeout,
422 # kill it via an escalating series of signals.
423 signal_queue = [signal.SIGTERM, signal.SIGKILL]
424 for sig in signal_queue:
425 try:
426 os.kill(subproc.pid, sig)
427 # The process may have died before we could kill it.
428 except OSError:
429 pass
mbligh63073c92008-03-31 16:49:32 +0000430
jadmanski0afbb632008-06-06 21:10:57 +0000431 for i in range(5):
432 rc = subproc.poll()
433 if rc != None:
434 return rc
435 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000436
437
438def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000439 # the process has not terminated within timeout,
440 # kill it via an escalating series of signals.
441 signal_queue = [signal.SIGTERM, signal.SIGKILL]
442 for sig in signal_queue:
443 try:
444 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000445
jadmanski0afbb632008-06-06 21:10:57 +0000446 # The process may have died before we could kill it.
447 except OSError:
448 pass
mbligh63073c92008-03-31 16:49:32 +0000449
jadmanski0afbb632008-06-06 21:10:57 +0000450 try:
451 for i in range(5):
452 status = os.waitpid(pid, os.WNOHANG)[0]
453 if status == pid:
454 return
455 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000456
jadmanski0afbb632008-06-06 21:10:57 +0000457 if status != pid:
458 raise error.AutoservRunError('Could not kill %d'
459 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000460
jadmanski0afbb632008-06-06 21:10:57 +0000461 # the process died before we join it.
462 except OSError:
463 pass
mbligh63073c92008-03-31 16:49:32 +0000464
465
mbligh63073c92008-03-31 16:49:32 +0000466
467def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000468 """This function returns the exit status of command."""
jadmanski0afbb632008-06-06 21:10:57 +0000469 return run(command, timeout, ignore_status,
mbligha5630a52008-09-03 22:09:50 +0000470 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
mbligh63073c92008-03-31 16:49:32 +0000471
472
mbligha5630a52008-09-03 22:09:50 +0000473def system_parallel(commands, timeout=None, ignore_status=False):
474 """This function returns a list of exit statuses for the respective
475 list of commands."""
476 return [bg_jobs.exit_status for bg_jobs in
477 run_parallel(commands, timeout, ignore_status,
478 stdout_tee=sys.stdout, stderr_tee=sys.stderr)]
mbligh849a0f62008-08-28 20:12:19 +0000479
480
mbligh8ea61e22008-05-09 18:09:37 +0000481def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000482 retain_output=False):
483 if retain_output:
484 out = run(command, timeout, ignore_status,
485 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
486 else:
487 out = run(command, timeout, ignore_status).stdout
488 if out[-1:] == '\n': out = out[:-1]
489 return out
mbligh63073c92008-03-31 16:49:32 +0000490
mbligh849a0f62008-08-28 20:12:19 +0000491
mbligha5630a52008-09-03 22:09:50 +0000492def system_output_parallel(commands, timeout=None, ignore_status=False,
493 retain_output=False):
494 if retain_output:
495 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
496 ignore_status,
497 stdout_tee=sys.stdout,
498 stderr_tee=sys.stderr)]
499 else:
500 out = [bg_job.stdout for bg_job in run_parallel(commands, timeout,
501 ignore_status)]
502 for x in out:
503 if out[-1:] == '\n': out = out[:-1]
504 return out
505
506
507def get_cpu_percentage(function, *args, **dargs):
508 """Returns a tuple containing the CPU% and return value from function call.
509
510 This function calculates the usage time by taking the difference of
511 the user and system times both before and after the function call.
512 """
513 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
514 self_pre = resource.getrusage(resource.RUSAGE_SELF)
515 start = time.time()
516 to_return = function(*args, **dargs)
517 elapsed = time.time() - start
518 self_post = resource.getrusage(resource.RUSAGE_SELF)
519 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
520
521 # Calculate CPU Percentage
522 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
523 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
524 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
525
526 return cpu_percent, to_return
527
528
mblighc1cbc992008-05-27 20:01:45 +0000529"""
530This function is used when there is a need to run more than one
531job simultaneously starting exactly at the same time. It basically returns
532a modified control file (containing the synchronization code prepended)
533whenever it is ready to run the control file. The synchronization
534is done using barriers to make sure that the jobs start at the same time.
535
536Here is how the synchronization is done to make sure that the tests
537start at exactly the same time on the client.
538sc_bar is a server barrier and s_bar, c_bar are the normal barriers
539
540 Job1 Job2 ...... JobN
541 Server: | sc_bar
542 Server: | s_bar ...... s_bar
543 Server: | at.run() at.run() ...... at.run()
544 ----------|------------------------------------------------------
545 Client | sc_bar
546 Client | c_bar c_bar ...... c_bar
547 Client | <run test> <run test> ...... <run test>
548
549
550PARAMS:
551 control_file : The control file which to which the above synchronization
552 code would be prepended to
553 host_name : The host name on which the job is going to run
554 host_num (non negative) : A number to identify the machine so that we have
555 different sets of s_bar_ports for each of the machines.
556 instance : The number of the job
557 num_jobs : Total number of jobs that are going to run in parallel with
558 this job starting at the same time
559 port_base : Port number that is used to derive the actual barrier ports.
560
561RETURN VALUE:
562 The modified control file.
563
564"""
565def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000566 instance, num_jobs, port_base=63100):
567 sc_bar_port = port_base
568 c_bar_port = port_base
569 if host_num < 0:
570 print "Please provide a non negative number for the host"
571 return None
572 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
573 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000574
jadmanski0afbb632008-06-06 21:10:57 +0000575 sc_bar_timeout = 180
576 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000577
jadmanski0afbb632008-06-06 21:10:57 +0000578 # The barrier code snippet is prepended into the conrol file
579 # dynamically before at.run() is called finally.
580 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000581
jadmanski0afbb632008-06-06 21:10:57 +0000582 # jobid is the unique name used to identify the processes
583 # trying to reach the barriers
584 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000585
jadmanski0afbb632008-06-06 21:10:57 +0000586 rendv = []
587 # rendvstr is a temp holder for the rendezvous list of the processes
588 for n in range(num_jobs):
589 rendv.append("'%s#%d'" % (host_name, n))
590 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000591
jadmanski0afbb632008-06-06 21:10:57 +0000592 if instance == 0:
593 # Do the setup and wait at the server barrier
594 # Clean up the tmp and the control dirs for the first instance
595 control_new.append('if os.path.exists(job.tmpdir):')
596 control_new.append("\t system('umount -f %s > /dev/null"
597 "2> /dev/null' % job.tmpdir,"
598 "ignore_status=True)")
599 control_new.append("\t system('rm -rf ' + job.tmpdir)")
600 control_new.append(
601 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
602 % (jobid, sc_bar_timeout, sc_bar_port))
603 control_new.append(
604 'b0.rendevous_servers("PARALLEL_MASTER", "%s")'
605 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000606
jadmanski0afbb632008-06-06 21:10:57 +0000607 elif instance == 1:
608 # Wait at the server barrier to wait for instance=0
609 # process to complete setup
610 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
611 port=sc_bar_port)
612 b0.rendevous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000613
jadmanski0afbb632008-06-06 21:10:57 +0000614 if(num_jobs > 2):
615 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
616 port=s_bar_port)
617 b1.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000618
jadmanski0afbb632008-06-06 21:10:57 +0000619 else:
620 # For the rest of the clients
621 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
622 b2.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000623
jadmanski0afbb632008-06-06 21:10:57 +0000624 # Client side barrier for all the tests to start at the same time
625 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
626 % (jobid, c_bar_timeout, c_bar_port))
627 control_new.append("b1.rendevous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000628
jadmanski0afbb632008-06-06 21:10:57 +0000629 # Stick in the rest of the control file
630 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000631
jadmanski0afbb632008-06-06 21:10:57 +0000632 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000633
mbligh63073c92008-03-31 16:49:32 +0000634
mblighc5ddfd12008-08-04 17:15:00 +0000635def get_arch(run_function=run):
636 """
637 Get the hardware architecture of the machine.
638 run_function is used to execute the commands. It defaults to
639 utils.run() but a custom method (if provided) should be of the
640 same schema as utils.run. It should return a CmdResult object and
641 throw a CmdError exception.
642 """
643 arch = run_function('/bin/uname -m').stdout.rstrip()
644 if re.match(r'i\d86$', arch):
645 arch = 'i386'
646 return arch
647
648
mbligh63073c92008-03-31 16:49:32 +0000649class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000650 """
651 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000652
jadmanski0afbb632008-06-06 21:10:57 +0000653 command: String containing the command line itself
654 exit_status: Integer exit code of the process
655 stdout: String containing stdout of the process
656 stderr: String containing stderr of the process
657 duration: Elapsed wall clock time running the process
658 """
mbligh63073c92008-03-31 16:49:32 +0000659
660
jadmanski0afbb632008-06-06 21:10:57 +0000661 def __init__(self, command=None, stdout="", stderr="",
662 exit_status=None, duration=0):
663 self.command = command
664 self.exit_status = exit_status
665 self.stdout = stdout
666 self.stderr = stderr
667 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000668
669
jadmanski0afbb632008-06-06 21:10:57 +0000670 def __repr__(self):
671 wrapper = textwrap.TextWrapper(width = 78,
672 initial_indent="\n ",
673 subsequent_indent=" ")
674
675 stdout = self.stdout.rstrip()
676 if stdout:
677 stdout = "\nstdout:\n%s" % stdout
678
679 stderr = self.stderr.rstrip()
680 if stderr:
681 stderr = "\nstderr:\n%s" % stderr
682
683 return ("* Command: %s\n"
684 "Exit status: %s\n"
685 "Duration: %s\n"
686 "%s"
687 "%s"
688 % (wrapper.fill(self.command), self.exit_status,
689 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000690
691
mbligh462c0152008-03-13 15:37:10 +0000692class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000693 def __init__(self, run_sequentially=False):
694 # Run sequentially is for debugging control files
695 self.test_list = []
696 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000697
698
jadmanski0afbb632008-06-06 21:10:57 +0000699 def add(self, *args, **dargs):
700 test = (args, dargs)
701 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000702
703
jadmanski0afbb632008-06-06 21:10:57 +0000704 def run(self, fn):
705 while self.test_list:
706 test_index = random.randint(0, len(self.test_list)-1)
707 if self.run_sequentially:
708 test_index = 0
709 (args, dargs) = self.test_list.pop(test_index)
710 fn(*args, **dargs)