base_utils.py revision 4f8df4306022e5805fa0092dd32373abcc1a7d67
1# 2# Copyright 2008 Google Inc. Released under the GPL v2 3 4# pylint: disable-msg=C0111 5 6import os, pickle, random, re, resource, select, shutil, signal, StringIO 7import socket, struct, subprocess, time, textwrap, urlparse 8import warnings, smtplib, logging, urllib2 9import itertools 10from threading import Thread, Event 11try: 12 import hashlib 13except ImportError: 14 import md5, sha 15from autotest_lib.client.common_lib import error, logging_manager 16 17def deprecated(func): 18 """This is a decorator which can be used to mark functions as deprecated. 19 It will result in a warning being emmitted when the function is used.""" 20 def new_func(*args, **dargs): 21 warnings.warn("Call to deprecated function %s." % func.__name__, 22 category=DeprecationWarning) 23 return func(*args, **dargs) 24 new_func.__name__ = func.__name__ 25 new_func.__doc__ = func.__doc__ 26 new_func.__dict__.update(func.__dict__) 27 return new_func 28 29 30class _NullStream(object): 31 def write(self, data): 32 pass 33 34 35 def flush(self): 36 pass 37 38 39TEE_TO_LOGS = object() 40_the_null_stream = _NullStream() 41 42DEFAULT_STDOUT_LEVEL = logging.DEBUG 43DEFAULT_STDERR_LEVEL = logging.ERROR 44 45# prefixes for logging stdout/stderr of commands 46STDOUT_PREFIX = '[stdout] ' 47STDERR_PREFIX = '[stderr] ' 48 49def custom_warning_handler(message, category, filename, lineno, file=None, 50 line=None): 51 """Custom handler to log at the WARNING error level. Ignores |file|.""" 52 logging.warning(warnings.formatwarning(message, category, filename, lineno, 53 line)) 54 55warnings.showwarning = custom_warning_handler 56 57def get_stream_tee_file(stream, level, prefix=''): 58 if stream is None: 59 return _the_null_stream 60 if stream is TEE_TO_LOGS: 61 return logging_manager.LoggingFile(level=level, prefix=prefix) 62 return stream 63 64 65def _join_with_nickname(base_string, nickname): 66 if nickname: 67 return '%s BgJob "%s" ' % (base_string, nickname) 68 return base_string 69 70 71# TODO: Cleanup and possibly eliminate no_pipes, which is only used 72# in our master-ssh connection process, while fixing underlying 73# semantics problem in BgJob. See crbug.com/279312 74class BgJob(object): 75 def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True, 76 stdin=None, stderr_level=DEFAULT_STDERR_LEVEL, nickname=None, 77 no_pipes=False): 78 """Create and start a new BgJob. 79 80 This constructor creates a new BgJob, and uses Popen to start a new 81 subprocess with given command. It returns without blocking on execution 82 of the subprocess. 83 84 After starting a new BgJob, use output_prepare to connect the process's 85 stdout and stderr pipes to the stream of your choice. 86 87 When the job is running, the jobs's output streams are only read from 88 when process_output is called. 89 90 @param command: command to be executed in new subprocess. May be either 91 a list, or a string (in which case Popen will be called 92 with shell=True) 93 @param stdout_tee: Optional additional stream that the process's stdout 94 stream output will be written to. Or, specify 95 base_utils.TEE_TO_LOGS and the output will handled by 96 the standard logging_manager. 97 @param stderr_tee: Same as stdout_tee, but for stderr. 98 @param verbose: Boolean, make BgJob logging more verbose. 99 @param stdin: Stream object, will be passed to Popen as the new 100 process's stdin. 101 @param stderr_level: A logging level value. If stderr_tee was set to 102 base_utils.TEE_TO_LOGS, sets the level that tee'd 103 stderr output will be logged at. Ignored 104 otherwise. 105 @param nickname: Optional string, to be included in logging messages 106 @param no_pipes: Boolean, default False. If True, this subprocess 107 created by this BgJob does NOT use subprocess.PIPE 108 for its stdin or stderr streams. Instead, these 109 streams are connected to the logging manager 110 (regardless of the values of stdout_tee and 111 stderr_tee). 112 If no_pipes is True, then calls to output_prepare, 113 process_output, and cleanup will result in an 114 InvalidBgJobCall exception. no_pipes should be 115 True for BgJobs that do not interact via stdout/stderr 116 with other BgJobs, or long runing background jobs that 117 will never be joined with join_bg_jobs, such as the 118 master-ssh connection BgJob. 119 """ 120 self.command = command 121 self._no_pipes = no_pipes 122 if no_pipes: 123 stdout_tee = TEE_TO_LOGS 124 stderr_tee = TEE_TO_LOGS 125 self.stdout_tee = get_stream_tee_file(stdout_tee, DEFAULT_STDOUT_LEVEL, 126 prefix=_join_with_nickname(STDOUT_PREFIX, nickname)) 127 self.stderr_tee = get_stream_tee_file(stderr_tee, stderr_level, 128 prefix=_join_with_nickname(STDERR_PREFIX, nickname)) 129 self.result = CmdResult(command) 130 131 # allow for easy stdin input by string, we'll let subprocess create 132 # a pipe for stdin input and we'll write to it in the wait loop 133 if isinstance(stdin, basestring): 134 self.string_stdin = stdin 135 stdin = subprocess.PIPE 136 else: 137 self.string_stdin = None 138 139 140 if no_pipes: 141 stdout_param = self.stdout_tee 142 stderr_param = self.stderr_tee 143 else: 144 stdout_param = subprocess.PIPE 145 stderr_param = subprocess.PIPE 146 147 if verbose: 148 logging.debug("Running '%s'", command) 149 if type(command) == list: 150 self.sp = subprocess.Popen(command, 151 stdout=stdout_param, 152 stderr=stderr_param, 153 preexec_fn=self._reset_sigpipe, 154 stdin=stdin) 155 else: 156 self.sp = subprocess.Popen(command, stdout=stdout_param, 157 stderr=stderr_param, 158 preexec_fn=self._reset_sigpipe, shell=True, 159 executable="/bin/bash", 160 stdin=stdin) 161 162 self._output_prepare_called = False 163 self._process_output_warned = False 164 self._cleanup_called = False 165 self.stdout_file = _the_null_stream 166 self.stderr_file = _the_null_stream 167 168 def output_prepare(self, stdout_file=_the_null_stream, 169 stderr_file=_the_null_stream): 170 """Connect the subprocess's stdout and stderr to streams. 171 172 Subsequent calls to output_prepare are permitted, and will reassign 173 the streams. However, this will have the side effect that the ultimate 174 call to cleanup() will only remember the stdout and stderr data up to 175 the last output_prepare call when saving this data to BgJob.result. 176 177 @param stdout_file: Stream that output from the process's stdout pipe 178 will be written to. Default: a null stream. 179 @param stderr_file: Stream that output from the process's stdout pipe 180 will be written to. Default: a null stream. 181 """ 182 if self._no_pipes: 183 raise error.InvalidBgJobCall('Cannot call output_prepare on a ' 184 'job with no_pipes=True.') 185 if self._output_prepare_called: 186 logging.warning('BgJob [%s] received a duplicate call to ' 187 'output prepare. Allowing, but this may result ' 188 'in data missing from BgJob.result.') 189 self.stdout_file = stdout_file 190 self.stderr_file = stderr_file 191 self._output_prepare_called = True 192 193 194 def process_output(self, stdout=True, final_read=False): 195 """Read from process's output stream, and write data to destinations. 196 197 This function reads up to 1024 bytes from the background job's 198 stdout or stderr stream, and writes the resulting data to the BgJob's 199 output tee and to the stream set up in output_prepare. 200 201 Warning: Calls to process_output will block on reads from the 202 subprocess stream, and will block on writes to the configured 203 destination stream. 204 205 @param stdout: True = read and process data from job's stdout. 206 False = from stderr. 207 Default: True 208 @param final_read: Do not read only 1024 bytes from stream. Instead, 209 read and process all data until end of the stream. 210 211 """ 212 if self._no_pipes: 213 raise error.InvalidBgJobCall('Cannot call process_output on ' 214 'a job with no_pipes=True') 215 if not self._output_prepare_called and not self._process_output_warned: 216 logging.warning('BgJob with command [%s] handled a process_output ' 217 'call before output_prepare was called. Some output ' 218 'data discarded. Future warnings suppressed.', 219 self.command) 220 self._process_output_warned = True 221 if stdout: 222 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee 223 else: 224 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee 225 226 if final_read: 227 # read in all the data we can from pipe and then stop 228 data = [] 229 while select.select([pipe], [], [], 0)[0]: 230 data.append(os.read(pipe.fileno(), 1024)) 231 if len(data[-1]) == 0: 232 break 233 data = "".join(data) 234 else: 235 # perform a single read 236 data = os.read(pipe.fileno(), 1024) 237 buf.write(data) 238 tee.write(data) 239 240 241 def cleanup(self): 242 """Clean up after BgJob. 243 244 Flush the stdout_tee and stderr_tee buffers, close the 245 subprocess stdout and stderr buffers, and saves data from 246 the configured stdout and stderr destination streams to 247 self.result. Duplicate calls ignored with a warning. 248 """ 249 if self._no_pipes: 250 raise error.InvalidBgJobCall('Cannot call cleanup on ' 251 'a job with no_pipes=True') 252 if self._cleanup_called: 253 logging.warning('BgJob [%s] received a duplicate call to ' 254 'cleanup. Ignoring.', self.command) 255 return 256 try: 257 self.stdout_tee.flush() 258 self.stderr_tee.flush() 259 self.sp.stdout.close() 260 self.sp.stderr.close() 261 self.result.stdout = self.stdout_file.getvalue() 262 self.result.stderr = self.stderr_file.getvalue() 263 finally: 264 self._cleanup_called = True 265 266 267 def _reset_sigpipe(self): 268 signal.signal(signal.SIGPIPE, signal.SIG_DFL) 269 270 271def ip_to_long(ip): 272 # !L is a long in network byte order 273 return struct.unpack('!L', socket.inet_aton(ip))[0] 274 275 276def long_to_ip(number): 277 # See above comment. 278 return socket.inet_ntoa(struct.pack('!L', number)) 279 280 281def create_subnet_mask(bits): 282 return (1 << 32) - (1 << 32-bits) 283 284 285def format_ip_with_mask(ip, mask_bits): 286 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits) 287 return "%s/%s" % (long_to_ip(masked_ip), mask_bits) 288 289 290def normalize_hostname(alias): 291 ip = socket.gethostbyname(alias) 292 return socket.gethostbyaddr(ip)[0] 293 294 295def get_ip_local_port_range(): 296 match = re.match(r'\s*(\d+)\s*(\d+)\s*$', 297 read_one_line('/proc/sys/net/ipv4/ip_local_port_range')) 298 return (int(match.group(1)), int(match.group(2))) 299 300 301def set_ip_local_port_range(lower, upper): 302 write_one_line('/proc/sys/net/ipv4/ip_local_port_range', 303 '%d %d\n' % (lower, upper)) 304 305 306def send_email(mail_from, mail_to, subject, body): 307 """ 308 Sends an email via smtp 309 310 mail_from: string with email address of sender 311 mail_to: string or list with email address(es) of recipients 312 subject: string with subject of email 313 body: (multi-line) string with body of email 314 """ 315 if isinstance(mail_to, str): 316 mail_to = [mail_to] 317 msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to), 318 subject, body) 319 try: 320 mailer = smtplib.SMTP('localhost') 321 try: 322 mailer.sendmail(mail_from, mail_to, msg) 323 finally: 324 mailer.quit() 325 except Exception, e: 326 # Emails are non-critical, not errors, but don't raise them 327 print "Sending email failed. Reason: %s" % repr(e) 328 329 330def read_one_line(filename): 331 return open(filename, 'r').readline().rstrip('\n') 332 333 334def read_file(filename): 335 f = open(filename) 336 try: 337 return f.read() 338 finally: 339 f.close() 340 341 342def get_field(data, param, linestart="", sep=" "): 343 """ 344 Parse data from string. 345 @param data: Data to parse. 346 example: 347 data: 348 cpu 324 345 34 5 345 349 cpu0 34 11 34 34 33 350 ^^^^ 351 start of line 352 params 0 1 2 3 4 353 @param param: Position of parameter after linestart marker. 354 @param linestart: String to which start line with parameters. 355 @param sep: Separator between parameters regular expression. 356 """ 357 search = re.compile(r"(?<=^%s)\s*(.*)" % linestart, re.MULTILINE) 358 find = search.search(data) 359 if find != None: 360 return re.split("%s" % sep, find.group(1))[param] 361 else: 362 print "There is no line which starts with %s in data." % linestart 363 return None 364 365 366def write_one_line(filename, line): 367 open_write_close(filename, str(line).rstrip('\n') + '\n') 368 369 370def open_write_close(filename, data): 371 f = open(filename, 'w') 372 try: 373 f.write(data) 374 finally: 375 f.close() 376 377 378def locate_file(path, base_dir=None): 379 """Locates a file. 380 381 @param path: The path of the file being located. Could be absolute or relative 382 path. For relative path, it tries to locate the file from base_dir. 383 @param base_dir (optional): Base directory of the relative path. 384 385 @returns Absolute path of the file if found. None if path is None. 386 @raises error.TestFail if the file is not found. 387 """ 388 if path is None: 389 return None 390 391 if not os.path.isabs(path) and base_dir is not None: 392 # Assume the relative path is based in autotest directory. 393 path = os.path.join(base_dir, path) 394 if not os.path.isfile(path): 395 raise error.TestFail('ERROR: Unable to find %s' % path) 396 return path 397 398 399def matrix_to_string(matrix, header=None): 400 """ 401 Return a pretty, aligned string representation of a nxm matrix. 402 403 This representation can be used to print any tabular data, such as 404 database results. It works by scanning the lengths of each element 405 in each column, and determining the format string dynamically. 406 407 @param matrix: Matrix representation (list with n rows of m elements). 408 @param header: Optional tuple or list with header elements to be displayed. 409 """ 410 if type(header) is list: 411 header = tuple(header) 412 lengths = [] 413 if header: 414 for column in header: 415 lengths.append(len(column)) 416 for row in matrix: 417 for i, column in enumerate(row): 418 column = unicode(column).encode("utf-8") 419 cl = len(column) 420 try: 421 ml = lengths[i] 422 if cl > ml: 423 lengths[i] = cl 424 except IndexError: 425 lengths.append(cl) 426 427 lengths = tuple(lengths) 428 format_string = "" 429 for length in lengths: 430 format_string += "%-" + str(length) + "s " 431 format_string += "\n" 432 433 matrix_str = "" 434 if header: 435 matrix_str += format_string % header 436 for row in matrix: 437 matrix_str += format_string % tuple(row) 438 439 return matrix_str 440 441 442def read_keyval(path, type_tag=None): 443 """ 444 Read a key-value pair format file into a dictionary, and return it. 445 Takes either a filename or directory name as input. If it's a 446 directory name, we assume you want the file to be called keyval. 447 448 @param path: Full path of the file to read from. 449 @param type_tag: If not None, only keyvals with key ending 450 in a suffix {type_tag} will be collected. 451 """ 452 if os.path.isdir(path): 453 path = os.path.join(path, 'keyval') 454 if not os.path.exists(path): 455 return {} 456 457 if type_tag: 458 pattern = r'^([-\.\w]+)\{%s\}=(.*)$' % type_tag 459 else: 460 pattern = r'^([-\.\w]+)=(.*)$' 461 462 keyval = {} 463 f = open(path) 464 for line in f: 465 line = re.sub('#.*', '', line).rstrip() 466 if not line: 467 continue 468 match = re.match(pattern, line) 469 if match: 470 key = match.group(1) 471 value = match.group(2) 472 if re.search('^\d+$', value): 473 value = int(value) 474 elif re.search('^(\d+\.)?\d+$', value): 475 value = float(value) 476 keyval[key] = value 477 else: 478 raise ValueError('Invalid format line: %s' % line) 479 f.close() 480 return keyval 481 482 483def write_keyval(path, dictionary, type_tag=None, tap_report=None): 484 """ 485 Write a key-value pair format file out to a file. This uses append 486 mode to open the file, so existing text will not be overwritten or 487 reparsed. 488 489 If type_tag is None, then the key must be composed of alphanumeric 490 characters (or dashes+underscores). However, if type-tag is not 491 null then the keys must also have "{type_tag}" as a suffix. At 492 the moment the only valid values of type_tag are "attr" and "perf". 493 494 @param path: full path of the file to be written 495 @param dictionary: the items to write 496 @param type_tag: see text above 497 """ 498 if os.path.isdir(path): 499 path = os.path.join(path, 'keyval') 500 keyval = open(path, 'a') 501 502 if type_tag is None: 503 key_regex = re.compile(r'^[-\.\w]+$') 504 else: 505 if type_tag not in ('attr', 'perf'): 506 raise ValueError('Invalid type tag: %s' % type_tag) 507 escaped_tag = re.escape(type_tag) 508 key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag) 509 try: 510 for key in sorted(dictionary.keys()): 511 if not key_regex.search(key): 512 raise ValueError('Invalid key: %s' % key) 513 keyval.write('%s=%s\n' % (key, dictionary[key])) 514 finally: 515 keyval.close() 516 517 # same for tap 518 if tap_report is not None and tap_report.do_tap_report: 519 tap_report.record_keyval(path, dictionary, type_tag=type_tag) 520 521class FileFieldMonitor(object): 522 """ 523 Monitors the information from the file and reports it's values. 524 525 It gather the information at start and stop of the measurement or 526 continuously during the measurement. 527 """ 528 class Monitor(Thread): 529 """ 530 Internal monitor class to ensure continuous monitor of monitored file. 531 """ 532 def __init__(self, master): 533 """ 534 @param master: Master class which control Monitor 535 """ 536 Thread.__init__(self) 537 self.master = master 538 539 def run(self): 540 """ 541 Start monitor in thread mode 542 """ 543 while not self.master.end_event.isSet(): 544 self.master._get_value(self.master.logging) 545 time.sleep(self.master.time_step) 546 547 548 def __init__(self, status_file, data_to_read, mode_diff, continuously=False, 549 contlogging=False, separator=" +", time_step=0.1): 550 """ 551 Initialize variables. 552 @param status_file: File contain status. 553 @param mode_diff: If True make a difference of value, else average. 554 @param data_to_read: List of tuples with data position. 555 format: [(start_of_line,position in params)] 556 example: 557 data: 558 cpu 324 345 34 5 345 559 cpu0 34 11 34 34 33 560 ^^^^ 561 start of line 562 params 0 1 2 3 4 563 @param mode_diff: True to subtract old value from new value, 564 False make average of the values. 565 @parma continuously: Start the monitoring thread using the time_step 566 as the measurement period. 567 @param contlogging: Log data in continuous run. 568 @param separator: Regular expression of separator. 569 @param time_step: Time period of the monitoring value. 570 """ 571 self.end_event = Event() 572 self.start_time = 0 573 self.end_time = 0 574 self.test_time = 0 575 576 self.status_file = status_file 577 self.separator = separator 578 self.data_to_read = data_to_read 579 self.num_of_params = len(self.data_to_read) 580 self.mode_diff = mode_diff 581 self.continuously = continuously 582 self.time_step = time_step 583 584 self.value = [0 for i in range(self.num_of_params)] 585 self.old_value = [0 for i in range(self.num_of_params)] 586 self.log = [] 587 self.logging = contlogging 588 589 self.started = False 590 self.num_of_get_value = 0 591 self.monitor = None 592 593 594 def _get_value(self, logging=True): 595 """ 596 Return current values. 597 @param logging: If true log value in memory. There can be problem 598 with long run. 599 """ 600 data = read_file(self.status_file) 601 value = [] 602 for i in range(self.num_of_params): 603 value.append(int(get_field(data, 604 self.data_to_read[i][1], 605 self.data_to_read[i][0], 606 self.separator))) 607 608 if logging: 609 self.log.append(value) 610 if not self.mode_diff: 611 value = map(lambda x, y: x + y, value, self.old_value) 612 613 self.old_value = value 614 self.num_of_get_value += 1 615 return value 616 617 618 def start(self): 619 """ 620 Start value monitor. 621 """ 622 if self.started: 623 self.stop() 624 self.old_value = [0 for i in range(self.num_of_params)] 625 self.num_of_get_value = 0 626 self.log = [] 627 self.end_event.clear() 628 self.start_time = time.time() 629 self._get_value() 630 self.started = True 631 if (self.continuously): 632 self.monitor = FileFieldMonitor.Monitor(self) 633 self.monitor.start() 634 635 636 def stop(self): 637 """ 638 Stop value monitor. 639 """ 640 if self.started: 641 self.started = False 642 self.end_time = time.time() 643 self.test_time = self.end_time - self.start_time 644 self.value = self._get_value() 645 if (self.continuously): 646 self.end_event.set() 647 self.monitor.join() 648 if (self.mode_diff): 649 self.value = map(lambda x, y: x - y, self.log[-1], self.log[0]) 650 else: 651 self.value = map(lambda x: x / self.num_of_get_value, 652 self.value) 653 654 655 def get_status(self): 656 """ 657 @return: Status of monitored process average value, 658 time of test and array of monitored values and time step of 659 continuous run. 660 """ 661 if self.started: 662 self.stop() 663 if self.mode_diff: 664 for i in range(len(self.log) - 1): 665 self.log[i] = (map(lambda x, y: x - y, 666 self.log[i + 1], self.log[i])) 667 self.log.pop() 668 return (self.value, self.test_time, self.log, self.time_step) 669 670 671def is_url(path): 672 """Return true if path looks like a URL""" 673 # for now, just handle http and ftp 674 url_parts = urlparse.urlparse(path) 675 return (url_parts[0] in ('http', 'ftp')) 676 677 678def urlopen(url, data=None, timeout=5): 679 """Wrapper to urllib2.urlopen with timeout addition.""" 680 681 # Save old timeout 682 old_timeout = socket.getdefaulttimeout() 683 socket.setdefaulttimeout(timeout) 684 try: 685 return urllib2.urlopen(url, data=data) 686 finally: 687 socket.setdefaulttimeout(old_timeout) 688 689 690def urlretrieve(url, filename, data=None, timeout=300): 691 """Retrieve a file from given url.""" 692 logging.debug('Fetching %s -> %s', url, filename) 693 694 src_file = urlopen(url, data=data, timeout=timeout) 695 try: 696 dest_file = open(filename, 'wb') 697 try: 698 shutil.copyfileobj(src_file, dest_file) 699 finally: 700 dest_file.close() 701 finally: 702 src_file.close() 703 704 705def hash(type, input=None): 706 """ 707 Returns an hash object of type md5 or sha1. This function is implemented in 708 order to encapsulate hash objects in a way that is compatible with python 709 2.4 and python 2.6 without warnings. 710 711 Note that even though python 2.6 hashlib supports hash types other than 712 md5 and sha1, we are artificially limiting the input values in order to 713 make the function to behave exactly the same among both python 714 implementations. 715 716 @param input: Optional input string that will be used to update the hash. 717 """ 718 if type not in ['md5', 'sha1']: 719 raise ValueError("Unsupported hash type: %s" % type) 720 721 try: 722 hash = hashlib.new(type) 723 except NameError: 724 if type == 'md5': 725 hash = md5.new() 726 elif type == 'sha1': 727 hash = sha.new() 728 729 if input: 730 hash.update(input) 731 732 return hash 733 734 735def get_file(src, dest, permissions=None): 736 """Get a file from src, which can be local or a remote URL""" 737 if src == dest: 738 return 739 740 if is_url(src): 741 urlretrieve(src, dest) 742 else: 743 shutil.copyfile(src, dest) 744 745 if permissions: 746 os.chmod(dest, permissions) 747 return dest 748 749 750def unmap_url(srcdir, src, destdir='.'): 751 """ 752 Receives either a path to a local file or a URL. 753 returns either the path to the local file, or the fetched URL 754 755 unmap_url('/usr/src', 'foo.tar', '/tmp') 756 = '/usr/src/foo.tar' 757 unmap_url('/usr/src', 'http://site/file', '/tmp') 758 = '/tmp/file' 759 (after retrieving it) 760 """ 761 if is_url(src): 762 url_parts = urlparse.urlparse(src) 763 filename = os.path.basename(url_parts[2]) 764 dest = os.path.join(destdir, filename) 765 return get_file(src, dest) 766 else: 767 return os.path.join(srcdir, src) 768 769 770def update_version(srcdir, preserve_srcdir, new_version, install, 771 *args, **dargs): 772 """ 773 Make sure srcdir is version new_version 774 775 If not, delete it and install() the new version. 776 777 In the preserve_srcdir case, we just check it's up to date, 778 and if not, we rerun install, without removing srcdir 779 """ 780 versionfile = os.path.join(srcdir, '.version') 781 install_needed = True 782 783 if os.path.exists(versionfile): 784 old_version = pickle.load(open(versionfile)) 785 if old_version == new_version: 786 install_needed = False 787 788 if install_needed: 789 if not preserve_srcdir and os.path.exists(srcdir): 790 shutil.rmtree(srcdir) 791 install(*args, **dargs) 792 if os.path.exists(srcdir): 793 pickle.dump(new_version, open(versionfile, 'w')) 794 795 796def get_stderr_level(stderr_is_expected): 797 if stderr_is_expected: 798 return DEFAULT_STDOUT_LEVEL 799 return DEFAULT_STDERR_LEVEL 800 801 802def run(command, timeout=None, ignore_status=False, 803 stdout_tee=None, stderr_tee=None, verbose=True, stdin=None, 804 stderr_is_expected=None, args=(), nickname=None, ignore_timeout=False): 805 """ 806 Run a command on the host. 807 808 @param command: the command line string. 809 @param timeout: time limit in seconds before attempting to kill the 810 running process. The run() function will take a few seconds 811 longer than 'timeout' to complete if it has to kill the process. 812 @param ignore_status: do not raise an exception, no matter what the exit 813 code of the command is. 814 @param ignore_timeout: If True, timeouts are ignored otherwise if a 815 timeout occurs it will raise CmdTimeoutError. 816 @param stdout_tee: optional file-like object to which stdout data 817 will be written as it is generated (data will still be stored 818 in result.stdout). 819 @param stderr_tee: likewise for stderr. 820 @param verbose: if True, log the command being run. 821 @param stdin: stdin to pass to the executed process (can be a file 822 descriptor, a file object of a real file or a string). 823 @param args: sequence of strings of arguments to be given to the command 824 inside " quotes after they have been escaped for that; each 825 element in the sequence will be given as a separate command 826 argument 827 @param nickname: Short string that will appear in logging messages 828 associated with this command. 829 830 @return a CmdResult object or None if the command timed out and 831 ignore_timeout is True 832 833 @raise CmdError: the exit code of the command execution was not 0 834 @raise CmdTimeoutError: the command timed out and ignore_timeout is False. 835 """ 836 if isinstance(args, basestring): 837 raise TypeError('Got a string for the "args" keyword argument, ' 838 'need a sequence.') 839 840 for arg in args: 841 command += ' "%s"' % sh_escape(arg) 842 if stderr_is_expected is None: 843 stderr_is_expected = ignore_status 844 845 try: 846 bg_job = join_bg_jobs( 847 (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin, 848 stderr_level=get_stderr_level(stderr_is_expected), 849 nickname=nickname),), timeout)[0] 850 except error.CmdTimeoutError: 851 if not ignore_timeout: 852 raise 853 return None 854 855 if not ignore_status and bg_job.result.exit_status: 856 raise error.CmdError(command, bg_job.result, 857 "Command returned non-zero exit status") 858 859 return bg_job.result 860 861 862def run_parallel(commands, timeout=None, ignore_status=False, 863 stdout_tee=None, stderr_tee=None, 864 nicknames=[]): 865 """ 866 Behaves the same as run() with the following exceptions: 867 868 - commands is a list of commands to run in parallel. 869 - ignore_status toggles whether or not an exception should be raised 870 on any error. 871 872 @return: a list of CmdResult objects 873 """ 874 bg_jobs = [] 875 for (command, nickname) in itertools.izip_longest(commands, nicknames): 876 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee, 877 stderr_level=get_stderr_level(ignore_status), 878 nickname=nickname)) 879 880 # Updates objects in bg_jobs list with their process information 881 join_bg_jobs(bg_jobs, timeout) 882 883 for bg_job in bg_jobs: 884 if not ignore_status and bg_job.result.exit_status: 885 raise error.CmdError(command, bg_job.result, 886 "Command returned non-zero exit status") 887 888 return [bg_job.result for bg_job in bg_jobs] 889 890 891@deprecated 892def run_bg(command): 893 """Function deprecated. Please use BgJob class instead.""" 894 bg_job = BgJob(command) 895 return bg_job.sp, bg_job.result 896 897 898def join_bg_jobs(bg_jobs, timeout=None): 899 """Joins the bg_jobs with the current thread. 900 901 Returns the same list of bg_jobs objects that was passed in. 902 """ 903 ret, timeout_error = 0, False 904 for bg_job in bg_jobs: 905 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO()) 906 907 try: 908 # We are holding ends to stdin, stdout pipes 909 # hence we need to be sure to close those fds no mater what 910 start_time = time.time() 911 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout) 912 913 for bg_job in bg_jobs: 914 # Process stdout and stderr 915 bg_job.process_output(stdout=True,final_read=True) 916 bg_job.process_output(stdout=False,final_read=True) 917 finally: 918 # close our ends of the pipes to the sp no matter what 919 for bg_job in bg_jobs: 920 bg_job.cleanup() 921 922 if timeout_error: 923 # TODO: This needs to be fixed to better represent what happens when 924 # running in parallel. However this is backwards compatable, so it will 925 # do for the time being. 926 raise error.CmdTimeoutError( 927 bg_jobs[0].command, bg_jobs[0].result, 928 "Command(s) did not complete within %d seconds" % timeout) 929 930 931 return bg_jobs 932 933 934def _wait_for_commands(bg_jobs, start_time, timeout): 935 # This returns True if it must return due to a timeout, otherwise False. 936 937 # To check for processes which terminate without producing any output 938 # a 1 second timeout is used in select. 939 SELECT_TIMEOUT = 1 940 941 read_list = [] 942 write_list = [] 943 reverse_dict = {} 944 945 for bg_job in bg_jobs: 946 read_list.append(bg_job.sp.stdout) 947 read_list.append(bg_job.sp.stderr) 948 reverse_dict[bg_job.sp.stdout] = (bg_job, True) 949 reverse_dict[bg_job.sp.stderr] = (bg_job, False) 950 if bg_job.string_stdin is not None: 951 write_list.append(bg_job.sp.stdin) 952 reverse_dict[bg_job.sp.stdin] = bg_job 953 954 if timeout: 955 stop_time = start_time + timeout 956 time_left = stop_time - time.time() 957 else: 958 time_left = None # so that select never times out 959 960 while not timeout or time_left > 0: 961 # select will return when we may write to stdin or when there is 962 # stdout/stderr output we can read (including when it is 963 # EOF, that is the process has terminated). 964 read_ready, write_ready, _ = select.select(read_list, write_list, [], 965 SELECT_TIMEOUT) 966 967 # os.read() has to be used instead of 968 # subproc.stdout.read() which will otherwise block 969 for file_obj in read_ready: 970 bg_job, is_stdout = reverse_dict[file_obj] 971 bg_job.process_output(is_stdout) 972 973 for file_obj in write_ready: 974 # we can write PIPE_BUF bytes without blocking 975 # POSIX requires PIPE_BUF is >= 512 976 bg_job = reverse_dict[file_obj] 977 file_obj.write(bg_job.string_stdin[:512]) 978 bg_job.string_stdin = bg_job.string_stdin[512:] 979 # no more input data, close stdin, remove it from the select set 980 if not bg_job.string_stdin: 981 file_obj.close() 982 write_list.remove(file_obj) 983 del reverse_dict[file_obj] 984 985 all_jobs_finished = True 986 for bg_job in bg_jobs: 987 if bg_job.result.exit_status is not None: 988 continue 989 990 bg_job.result.exit_status = bg_job.sp.poll() 991 if bg_job.result.exit_status is not None: 992 # process exited, remove its stdout/stdin from the select set 993 bg_job.result.duration = time.time() - start_time 994 read_list.remove(bg_job.sp.stdout) 995 read_list.remove(bg_job.sp.stderr) 996 del reverse_dict[bg_job.sp.stdout] 997 del reverse_dict[bg_job.sp.stderr] 998 else: 999 all_jobs_finished = False 1000 1001 if all_jobs_finished: 1002 return False 1003 1004 if timeout: 1005 time_left = stop_time - time.time() 1006 1007 # Kill all processes which did not complete prior to timeout 1008 for bg_job in bg_jobs: 1009 if bg_job.result.exit_status is not None: 1010 continue 1011 1012 logging.warning('run process timeout (%s) fired on: %s', timeout, 1013 bg_job.command) 1014 if nuke_subprocess(bg_job.sp) is None: 1015 # If process could not be SIGKILL'd, log kernel stack. 1016 logging.warning(read_file('/proc/%d/stack' % bg_job.sp.pid)) 1017 bg_job.result.exit_status = bg_job.sp.poll() 1018 bg_job.result.duration = time.time() - start_time 1019 1020 return True 1021 1022 1023def pid_is_alive(pid): 1024 """ 1025 True if process pid exists and is not yet stuck in Zombie state. 1026 Zombies are impossible to move between cgroups, etc. 1027 pid can be integer, or text of integer. 1028 """ 1029 path = '/proc/%s/stat' % pid 1030 1031 try: 1032 stat = read_one_line(path) 1033 except IOError: 1034 if not os.path.exists(path): 1035 # file went away 1036 return False 1037 raise 1038 1039 return stat.split()[2] != 'Z' 1040 1041 1042def signal_pid(pid, sig): 1043 """ 1044 Sends a signal to a process id. Returns True if the process terminated 1045 successfully, False otherwise. 1046 """ 1047 try: 1048 os.kill(pid, sig) 1049 except OSError: 1050 # The process may have died before we could kill it. 1051 pass 1052 1053 for i in range(5): 1054 if not pid_is_alive(pid): 1055 return True 1056 time.sleep(1) 1057 1058 # The process is still alive 1059 return False 1060 1061 1062def nuke_subprocess(subproc): 1063 # check if the subprocess is still alive, first 1064 if subproc.poll() is not None: 1065 return subproc.poll() 1066 1067 # the process has not terminated within timeout, 1068 # kill it via an escalating series of signals. 1069 signal_queue = [signal.SIGTERM, signal.SIGKILL] 1070 for sig in signal_queue: 1071 signal_pid(subproc.pid, sig) 1072 if subproc.poll() is not None: 1073 return subproc.poll() 1074 1075 1076def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)): 1077 # the process has not terminated within timeout, 1078 # kill it via an escalating series of signals. 1079 pid_path = '/proc/%d/' 1080 if not os.path.exists(pid_path % pid): 1081 # Assume that if the pid does not exist in proc it is already dead. 1082 logging.error('No listing in /proc for pid:%d.', pid) 1083 raise error.AutoservPidAlreadyDeadError('Could not kill nonexistant ' 1084 'pid: %s.', pid) 1085 for sig in signal_queue: 1086 if signal_pid(pid, sig): 1087 return 1088 1089 # no signal successfully terminated the process 1090 raise error.AutoservRunError('Could not kill %d for process name: %s' % ( 1091 pid, get_process_name(pid)), None) 1092 1093 1094def system(command, timeout=None, ignore_status=False): 1095 """ 1096 Run a command 1097 1098 @param timeout: timeout in seconds 1099 @param ignore_status: if ignore_status=False, throw an exception if the 1100 command's exit code is non-zero 1101 if ignore_stauts=True, return the exit code. 1102 1103 @return exit status of command 1104 (note, this will always be zero unless ignore_status=True) 1105 """ 1106 return run(command, timeout=timeout, ignore_status=ignore_status, 1107 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status 1108 1109 1110def system_parallel(commands, timeout=None, ignore_status=False): 1111 """This function returns a list of exit statuses for the respective 1112 list of commands.""" 1113 return [bg_jobs.exit_status for bg_jobs in 1114 run_parallel(commands, timeout=timeout, ignore_status=ignore_status, 1115 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)] 1116 1117 1118def system_output(command, timeout=None, ignore_status=False, 1119 retain_output=False, args=()): 1120 """ 1121 Run a command and return the stdout output. 1122 1123 @param command: command string to execute. 1124 @param timeout: time limit in seconds before attempting to kill the 1125 running process. The function will take a few seconds longer 1126 than 'timeout' to complete if it has to kill the process. 1127 @param ignore_status: do not raise an exception, no matter what the exit 1128 code of the command is. 1129 @param retain_output: set to True to make stdout/stderr of the command 1130 output to be also sent to the logging system 1131 @param args: sequence of strings of arguments to be given to the command 1132 inside " quotes after they have been escaped for that; each 1133 element in the sequence will be given as a separate command 1134 argument 1135 1136 @return a string with the stdout output of the command. 1137 """ 1138 if retain_output: 1139 out = run(command, timeout=timeout, ignore_status=ignore_status, 1140 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS, 1141 args=args).stdout 1142 else: 1143 out = run(command, timeout=timeout, ignore_status=ignore_status, 1144 args=args).stdout 1145 if out[-1:] == '\n': 1146 out = out[:-1] 1147 return out 1148 1149 1150def system_output_parallel(commands, timeout=None, ignore_status=False, 1151 retain_output=False): 1152 if retain_output: 1153 out = [bg_job.stdout for bg_job 1154 in run_parallel(commands, timeout=timeout, 1155 ignore_status=ignore_status, 1156 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)] 1157 else: 1158 out = [bg_job.stdout for bg_job in run_parallel(commands, 1159 timeout=timeout, ignore_status=ignore_status)] 1160 for x in out: 1161 if out[-1:] == '\n': out = out[:-1] 1162 return out 1163 1164 1165def strip_unicode(input): 1166 if type(input) == list: 1167 return [strip_unicode(i) for i in input] 1168 elif type(input) == dict: 1169 output = {} 1170 for key in input.keys(): 1171 output[str(key)] = strip_unicode(input[key]) 1172 return output 1173 elif type(input) == unicode: 1174 return str(input) 1175 else: 1176 return input 1177 1178 1179def get_cpu_percentage(function, *args, **dargs): 1180 """Returns a tuple containing the CPU% and return value from function call. 1181 1182 This function calculates the usage time by taking the difference of 1183 the user and system times both before and after the function call. 1184 """ 1185 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN) 1186 self_pre = resource.getrusage(resource.RUSAGE_SELF) 1187 start = time.time() 1188 to_return = function(*args, **dargs) 1189 elapsed = time.time() - start 1190 self_post = resource.getrusage(resource.RUSAGE_SELF) 1191 child_post = resource.getrusage(resource.RUSAGE_CHILDREN) 1192 1193 # Calculate CPU Percentage 1194 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]] 1195 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]] 1196 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed 1197 1198 return cpu_percent, to_return 1199 1200 1201class SystemLoad(object): 1202 """ 1203 Get system and/or process values and return average value of load. 1204 """ 1205 def __init__(self, pids, advanced=False, time_step=0.1, cpu_cont=False, 1206 use_log=False): 1207 """ 1208 @param pids: List of pids to be monitored. If pid = 0 whole system will 1209 be monitored. pid == 0 means whole system. 1210 @param advanced: monitor add value for system irq count and softirq 1211 for process minor and maior page fault 1212 @param time_step: Time step for continuous monitoring. 1213 @param cpu_cont: If True monitor CPU load continuously. 1214 @param use_log: If true every monitoring is logged for dump. 1215 """ 1216 self.pids = [] 1217 self.stats = {} 1218 for pid in pids: 1219 if pid == 0: 1220 cpu = FileFieldMonitor("/proc/stat", 1221 [("cpu", 0), # User Time 1222 ("cpu", 2), # System Time 1223 ("intr", 0), # IRQ Count 1224 ("softirq", 0)], # Soft IRQ Count 1225 True, 1226 cpu_cont, 1227 use_log, 1228 " +", 1229 time_step) 1230 mem = FileFieldMonitor("/proc/meminfo", 1231 [("MemTotal:", 0), # Mem Total 1232 ("MemFree:", 0), # Mem Free 1233 ("Buffers:", 0), # Buffers 1234 ("Cached:", 0)], # Cached 1235 False, 1236 True, 1237 use_log, 1238 " +", 1239 time_step) 1240 self.stats[pid] = ["TOTAL", cpu, mem] 1241 self.pids.append(pid) 1242 else: 1243 name = "" 1244 if (type(pid) is int): 1245 self.pids.append(pid) 1246 name = get_process_name(pid) 1247 else: 1248 self.pids.append(pid[0]) 1249 name = pid[1] 1250 1251 cpu = FileFieldMonitor("/proc/%d/stat" % 1252 self.pids[-1], 1253 [("", 13), # User Time 1254 ("", 14), # System Time 1255 ("", 9), # Minority Page Fault 1256 ("", 11)], # Majority Page Fault 1257 True, 1258 cpu_cont, 1259 use_log, 1260 " +", 1261 time_step) 1262 mem = FileFieldMonitor("/proc/%d/status" % 1263 self.pids[-1], 1264 [("VmSize:", 0), # Virtual Memory Size 1265 ("VmRSS:", 0), # Resident Set Size 1266 ("VmPeak:", 0), # Peak VM Size 1267 ("VmSwap:", 0)], # VM in Swap 1268 False, 1269 True, 1270 use_log, 1271 " +", 1272 time_step) 1273 self.stats[self.pids[-1]] = [name, cpu, mem] 1274 1275 self.advanced = advanced 1276 1277 1278 def __str__(self): 1279 """ 1280 Define format how to print 1281 """ 1282 out = "" 1283 for pid in self.pids: 1284 for stat in self.stats[pid][1:]: 1285 out += str(stat.get_status()) + "\n" 1286 return out 1287 1288 1289 def start(self, pids=[]): 1290 """ 1291 Start monitoring of the process system usage. 1292 @param pids: List of PIDs you intend to control. Use pids=[] to control 1293 all defined PIDs. 1294 """ 1295 if pids == []: 1296 pids = self.pids 1297 1298 for pid in pids: 1299 for stat in self.stats[pid][1:]: 1300 stat.start() 1301 1302 1303 def stop(self, pids=[]): 1304 """ 1305 Stop monitoring of the process system usage. 1306 @param pids: List of PIDs you intend to control. Use pids=[] to control 1307 all defined PIDs. 1308 """ 1309 if pids == []: 1310 pids = self.pids 1311 1312 for pid in pids: 1313 for stat in self.stats[pid][1:]: 1314 stat.stop() 1315 1316 1317 def dump(self, pids=[]): 1318 """ 1319 Get the status of monitoring. 1320 @param pids: List of PIDs you intend to control. Use pids=[] to control 1321 all defined PIDs. 1322 @return: 1323 tuple([cpu load], [memory load]): 1324 ([(PID1, (PID1_cpu_meas)), (PID2, (PID2_cpu_meas)), ...], 1325 [(PID1, (PID1_mem_meas)), (PID2, (PID2_mem_meas)), ...]) 1326 1327 PID1_cpu_meas: 1328 average_values[], test_time, cont_meas_values[[]], time_step 1329 PID1_mem_meas: 1330 average_values[], test_time, cont_meas_values[[]], time_step 1331 where average_values[] are the measured values (mem_free,swap,...) 1332 which are described in SystemLoad.__init__()-FileFieldMonitor. 1333 cont_meas_values[[]] is a list of average_values in the sampling 1334 times. 1335 """ 1336 if pids == []: 1337 pids = self.pids 1338 1339 cpus = [] 1340 memory = [] 1341 for pid in pids: 1342 stat = (pid, self.stats[pid][1].get_status()) 1343 cpus.append(stat) 1344 for pid in pids: 1345 stat = (pid, self.stats[pid][2].get_status()) 1346 memory.append(stat) 1347 1348 return (cpus, memory) 1349 1350 1351 def get_cpu_status_string(self, pids=[]): 1352 """ 1353 Convert status to string array. 1354 @param pids: List of PIDs you intend to control. Use pids=[] to control 1355 all defined PIDs. 1356 @return: String format to table. 1357 """ 1358 if pids == []: 1359 pids = self.pids 1360 1361 headers = ["NAME", 1362 ("%7s") % "PID", 1363 ("%5s") % "USER", 1364 ("%5s") % "SYS", 1365 ("%5s") % "SUM"] 1366 if self.advanced: 1367 headers.extend(["MINFLT/IRQC", 1368 "MAJFLT/SOFTIRQ"]) 1369 headers.append(("%11s") % "TIME") 1370 textstatus = [] 1371 for pid in pids: 1372 stat = self.stats[pid][1].get_status() 1373 time = stat[1] 1374 stat = stat[0] 1375 textstatus.append(["%s" % self.stats[pid][0], 1376 "%7s" % pid, 1377 "%4.0f%%" % (stat[0] / time), 1378 "%4.0f%%" % (stat[1] / time), 1379 "%4.0f%%" % ((stat[0] + stat[1]) / time), 1380 "%10.3fs" % time]) 1381 if self.advanced: 1382 textstatus[-1].insert(-1, "%11d" % stat[2]) 1383 textstatus[-1].insert(-1, "%14d" % stat[3]) 1384 1385 return matrix_to_string(textstatus, tuple(headers)) 1386 1387 1388 def get_mem_status_string(self, pids=[]): 1389 """ 1390 Convert status to string array. 1391 @param pids: List of PIDs you intend to control. Use pids=[] to control 1392 all defined PIDs. 1393 @return: String format to table. 1394 """ 1395 if pids == []: 1396 pids = self.pids 1397 1398 headers = ["NAME", 1399 ("%7s") % "PID", 1400 ("%8s") % "TOTAL/VMSIZE", 1401 ("%8s") % "FREE/VMRSS", 1402 ("%8s") % "BUFFERS/VMPEAK", 1403 ("%8s") % "CACHED/VMSWAP", 1404 ("%11s") % "TIME"] 1405 textstatus = [] 1406 for pid in pids: 1407 stat = self.stats[pid][2].get_status() 1408 time = stat[1] 1409 stat = stat[0] 1410 textstatus.append(["%s" % self.stats[pid][0], 1411 "%7s" % pid, 1412 "%10dMB" % (stat[0] / 1024), 1413 "%8dMB" % (stat[1] / 1024), 1414 "%12dMB" % (stat[2] / 1024), 1415 "%11dMB" % (stat[3] / 1024), 1416 "%10.3fs" % time]) 1417 1418 return matrix_to_string(textstatus, tuple(headers)) 1419 1420 1421def get_arch(run_function=run): 1422 """ 1423 Get the hardware architecture of the machine. 1424 If specified, run_function should return a CmdResult object and throw a 1425 CmdError exception. 1426 If run_function is anything other than utils.run(), it is used to 1427 execute the commands. By default (when set to utils.run()) this will 1428 just examine os.uname()[4]. 1429 """ 1430 1431 # Short circuit from the common case. 1432 if run_function == run: 1433 return re.sub(r'i\d86$', 'i386', os.uname()[4]) 1434 1435 # Otherwise, use the run_function in case it hits a remote machine. 1436 arch = run_function('/bin/uname -m').stdout.rstrip() 1437 if re.match(r'i\d86$', arch): 1438 arch = 'i386' 1439 return arch 1440 1441def get_arch_userspace(run_function=run): 1442 """ 1443 Get the architecture by userspace (possibly different from kernel). 1444 """ 1445 archs = { 1446 'arm': 'ELF 32-bit.*, ARM,', 1447 'i386': 'ELF 32-bit.*, Intel 80386,', 1448 'x86_64': 'ELF 64-bit.*, x86-64,', 1449 } 1450 filestr = run_function('file -b /bin/true').stdout.rstrip() 1451 for a, regex in archs.iteritems(): 1452 if re.match(regex, filestr): 1453 return a 1454 1455 return get_arch() 1456 1457 1458def get_num_logical_cpus_per_socket(run_function=run): 1459 """ 1460 Get the number of cores (including hyperthreading) per cpu. 1461 run_function is used to execute the commands. It defaults to 1462 utils.run() but a custom method (if provided) should be of the 1463 same schema as utils.run. It should return a CmdResult object and 1464 throw a CmdError exception. 1465 """ 1466 siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip() 1467 num_siblings = map(int, 1468 re.findall(r'^siblings\s*:\s*(\d+)\s*$', 1469 siblings, re.M)) 1470 if len(num_siblings) == 0: 1471 raise error.TestError('Unable to find siblings info in /proc/cpuinfo') 1472 if min(num_siblings) != max(num_siblings): 1473 raise error.TestError('Number of siblings differ %r' % 1474 num_siblings) 1475 return num_siblings[0] 1476 1477 1478def merge_trees(src, dest): 1479 """ 1480 Merges a source directory tree at 'src' into a destination tree at 1481 'dest'. If a path is a file in both trees than the file in the source 1482 tree is APPENDED to the one in the destination tree. If a path is 1483 a directory in both trees then the directories are recursively merged 1484 with this function. In any other case, the function will skip the 1485 paths that cannot be merged (instead of failing). 1486 """ 1487 if not os.path.exists(src): 1488 return # exists only in dest 1489 elif not os.path.exists(dest): 1490 if os.path.isfile(src): 1491 shutil.copy2(src, dest) # file only in src 1492 else: 1493 shutil.copytree(src, dest, symlinks=True) # dir only in src 1494 return 1495 elif os.path.isfile(src) and os.path.isfile(dest): 1496 # src & dest are files in both trees, append src to dest 1497 destfile = open(dest, "a") 1498 try: 1499 srcfile = open(src) 1500 try: 1501 destfile.write(srcfile.read()) 1502 finally: 1503 srcfile.close() 1504 finally: 1505 destfile.close() 1506 elif os.path.isdir(src) and os.path.isdir(dest): 1507 # src & dest are directories in both trees, so recursively merge 1508 for name in os.listdir(src): 1509 merge_trees(os.path.join(src, name), os.path.join(dest, name)) 1510 else: 1511 # src & dest both exist, but are incompatible 1512 return 1513 1514 1515class CmdResult(object): 1516 """ 1517 Command execution result. 1518 1519 command: String containing the command line itself 1520 exit_status: Integer exit code of the process 1521 stdout: String containing stdout of the process 1522 stderr: String containing stderr of the process 1523 duration: Elapsed wall clock time running the process 1524 """ 1525 1526 1527 def __init__(self, command="", stdout="", stderr="", 1528 exit_status=None, duration=0): 1529 self.command = command 1530 self.exit_status = exit_status 1531 self.stdout = stdout 1532 self.stderr = stderr 1533 self.duration = duration 1534 1535 1536 def __repr__(self): 1537 wrapper = textwrap.TextWrapper(width = 78, 1538 initial_indent="\n ", 1539 subsequent_indent=" ") 1540 1541 stdout = self.stdout.rstrip() 1542 if stdout: 1543 stdout = "\nstdout:\n%s" % stdout 1544 1545 stderr = self.stderr.rstrip() 1546 if stderr: 1547 stderr = "\nstderr:\n%s" % stderr 1548 1549 return ("* Command: %s\n" 1550 "Exit status: %s\n" 1551 "Duration: %s\n" 1552 "%s" 1553 "%s" 1554 % (wrapper.fill(str(self.command)), self.exit_status, 1555 self.duration, stdout, stderr)) 1556 1557 1558class run_randomly: 1559 def __init__(self, run_sequentially=False): 1560 # Run sequentially is for debugging control files 1561 self.test_list = [] 1562 self.run_sequentially = run_sequentially 1563 1564 1565 def add(self, *args, **dargs): 1566 test = (args, dargs) 1567 self.test_list.append(test) 1568 1569 1570 def run(self, fn): 1571 while self.test_list: 1572 test_index = random.randint(0, len(self.test_list)-1) 1573 if self.run_sequentially: 1574 test_index = 0 1575 (args, dargs) = self.test_list.pop(test_index) 1576 fn(*args, **dargs) 1577 1578 1579def import_site_module(path, module, dummy=None, modulefile=None): 1580 """ 1581 Try to import the site specific module if it exists. 1582 1583 @param path full filename of the source file calling this (ie __file__) 1584 @param module full module name 1585 @param dummy dummy value to return in case there is no symbol to import 1586 @param modulefile module filename 1587 1588 @return site specific module or dummy 1589 1590 @raises ImportError if the site file exists but imports fails 1591 """ 1592 short_module = module[module.rfind(".") + 1:] 1593 1594 if not modulefile: 1595 modulefile = short_module + ".py" 1596 1597 if os.path.exists(os.path.join(os.path.dirname(path), modulefile)): 1598 return __import__(module, {}, {}, [short_module]) 1599 return dummy 1600 1601 1602def import_site_symbol(path, module, name, dummy=None, modulefile=None): 1603 """ 1604 Try to import site specific symbol from site specific file if it exists 1605 1606 @param path full filename of the source file calling this (ie __file__) 1607 @param module full module name 1608 @param name symbol name to be imported from the site file 1609 @param dummy dummy value to return in case there is no symbol to import 1610 @param modulefile module filename 1611 1612 @return site specific symbol or dummy 1613 1614 @raises ImportError if the site file exists but imports fails 1615 """ 1616 module = import_site_module(path, module, modulefile=modulefile) 1617 if not module: 1618 return dummy 1619 1620 # special unique value to tell us if the symbol can't be imported 1621 cant_import = object() 1622 1623 obj = getattr(module, name, cant_import) 1624 if obj is cant_import: 1625 logging.debug("unable to import site symbol '%s', using non-site " 1626 "implementation", name) 1627 return dummy 1628 1629 return obj 1630 1631 1632def import_site_class(path, module, classname, baseclass, modulefile=None): 1633 """ 1634 Try to import site specific class from site specific file if it exists 1635 1636 Args: 1637 path: full filename of the source file calling this (ie __file__) 1638 module: full module name 1639 classname: class name to be loaded from site file 1640 baseclass: base class object to return when no site file present or 1641 to mixin when site class exists but is not inherited from baseclass 1642 modulefile: module filename 1643 1644 Returns: baseclass if site specific class does not exist, the site specific 1645 class if it exists and is inherited from baseclass or a mixin of the 1646 site specific class and baseclass when the site specific class exists 1647 and is not inherited from baseclass 1648 1649 Raises: ImportError if the site file exists but imports fails 1650 """ 1651 1652 res = import_site_symbol(path, module, classname, None, modulefile) 1653 if res: 1654 if not issubclass(res, baseclass): 1655 # if not a subclass of baseclass then mix in baseclass with the 1656 # site specific class object and return the result 1657 res = type(classname, (res, baseclass), {}) 1658 else: 1659 res = baseclass 1660 1661 return res 1662 1663 1664def import_site_function(path, module, funcname, dummy, modulefile=None): 1665 """ 1666 Try to import site specific function from site specific file if it exists 1667 1668 Args: 1669 path: full filename of the source file calling this (ie __file__) 1670 module: full module name 1671 funcname: function name to be imported from site file 1672 dummy: dummy function to return in case there is no function to import 1673 modulefile: module filename 1674 1675 Returns: site specific function object or dummy 1676 1677 Raises: ImportError if the site file exists but imports fails 1678 """ 1679 1680 return import_site_symbol(path, module, funcname, dummy, modulefile) 1681 1682 1683def _get_pid_path(program_name): 1684 my_path = os.path.dirname(__file__) 1685 return os.path.abspath(os.path.join(my_path, "..", "..", 1686 "%s.pid" % program_name)) 1687 1688 1689def write_pid(program_name): 1690 """ 1691 Try to drop <program_name>.pid in the main autotest directory. 1692 1693 Args: 1694 program_name: prefix for file name 1695 """ 1696 pidfile = open(_get_pid_path(program_name), "w") 1697 try: 1698 pidfile.write("%s\n" % os.getpid()) 1699 finally: 1700 pidfile.close() 1701 1702 1703def delete_pid_file_if_exists(program_name): 1704 """ 1705 Tries to remove <program_name>.pid from the main autotest directory. 1706 """ 1707 pidfile_path = _get_pid_path(program_name) 1708 1709 try: 1710 os.remove(pidfile_path) 1711 except OSError: 1712 if not os.path.exists(pidfile_path): 1713 return 1714 raise 1715 1716 1717def get_pid_from_file(program_name): 1718 """ 1719 Reads the pid from <program_name>.pid in the autotest directory. 1720 1721 @param program_name the name of the program 1722 @return the pid if the file exists, None otherwise. 1723 """ 1724 pidfile_path = _get_pid_path(program_name) 1725 if not os.path.exists(pidfile_path): 1726 return None 1727 1728 pidfile = open(_get_pid_path(program_name), 'r') 1729 1730 try: 1731 try: 1732 pid = int(pidfile.readline()) 1733 except IOError: 1734 if not os.path.exists(pidfile_path): 1735 return None 1736 raise 1737 finally: 1738 pidfile.close() 1739 1740 return pid 1741 1742 1743def get_process_name(pid): 1744 """ 1745 Get process name from PID. 1746 @param pid: PID of process. 1747 @return: Process name if PID stat file exists or 'Dead PID' if it does not. 1748 """ 1749 pid_stat_path = "/proc/%d/stat" 1750 if not os.path.exists(pid_stat_path % pid): 1751 return "Dead Pid" 1752 return get_field(read_file(pid_stat_path % pid), 1)[1:-1] 1753 1754 1755def program_is_alive(program_name): 1756 """ 1757 Checks if the process is alive and not in Zombie state. 1758 1759 @param program_name the name of the program 1760 @return True if still alive, False otherwise 1761 """ 1762 pid = get_pid_from_file(program_name) 1763 if pid is None: 1764 return False 1765 return pid_is_alive(pid) 1766 1767 1768def signal_program(program_name, sig=signal.SIGTERM): 1769 """ 1770 Sends a signal to the process listed in <program_name>.pid 1771 1772 @param program_name the name of the program 1773 @param sig signal to send 1774 """ 1775 pid = get_pid_from_file(program_name) 1776 if pid: 1777 signal_pid(pid, sig) 1778 1779 1780def get_relative_path(path, reference): 1781 """Given 2 absolute paths "path" and "reference", compute the path of 1782 "path" as relative to the directory "reference". 1783 1784 @param path the absolute path to convert to a relative path 1785 @param reference an absolute directory path to which the relative 1786 path will be computed 1787 """ 1788 # normalize the paths (remove double slashes, etc) 1789 assert(os.path.isabs(path)) 1790 assert(os.path.isabs(reference)) 1791 1792 path = os.path.normpath(path) 1793 reference = os.path.normpath(reference) 1794 1795 # we could use os.path.split() but it splits from the end 1796 path_list = path.split(os.path.sep)[1:] 1797 ref_list = reference.split(os.path.sep)[1:] 1798 1799 # find the longest leading common path 1800 for i in xrange(min(len(path_list), len(ref_list))): 1801 if path_list[i] != ref_list[i]: 1802 # decrement i so when exiting this loop either by no match or by 1803 # end of range we are one step behind 1804 i -= 1 1805 break 1806 i += 1 1807 # drop the common part of the paths, not interested in that anymore 1808 del path_list[:i] 1809 1810 # for each uncommon component in the reference prepend a ".." 1811 path_list[:0] = ['..'] * (len(ref_list) - i) 1812 1813 return os.path.join(*path_list) 1814 1815 1816def sh_escape(command): 1817 """ 1818 Escape special characters from a command so that it can be passed 1819 as a double quoted (" ") string in a (ba)sh command. 1820 1821 Args: 1822 command: the command string to escape. 1823 1824 Returns: 1825 The escaped command string. The required englobing double 1826 quotes are NOT added and so should be added at some point by 1827 the caller. 1828 1829 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html 1830 """ 1831 command = command.replace("\\", "\\\\") 1832 command = command.replace("$", r'\$') 1833 command = command.replace('"', r'\"') 1834 command = command.replace('`', r'\`') 1835 return command 1836 1837 1838def configure(extra=None, configure='./configure'): 1839 """ 1840 Run configure passing in the correct host, build, and target options. 1841 1842 @param extra: extra command line arguments to pass to configure 1843 @param configure: which configure script to use 1844 """ 1845 args = [] 1846 if 'CHOST' in os.environ: 1847 args.append('--host=' + os.environ['CHOST']) 1848 if 'CBUILD' in os.environ: 1849 args.append('--build=' + os.environ['CBUILD']) 1850 if 'CTARGET' in os.environ: 1851 args.append('--target=' + os.environ['CTARGET']) 1852 if extra: 1853 args.append(extra) 1854 1855 system('%s %s' % (configure, ' '.join(args))) 1856 1857 1858def make(extra='', make='make', timeout=None, ignore_status=False): 1859 """ 1860 Run make, adding MAKEOPTS to the list of options. 1861 1862 @param extra: extra command line arguments to pass to make. 1863 """ 1864 cmd = '%s %s %s' % (make, os.environ.get('MAKEOPTS', ''), extra) 1865 return system(cmd, timeout=timeout, ignore_status=ignore_status) 1866 1867 1868def compare_versions(ver1, ver2): 1869 """Version number comparison between ver1 and ver2 strings. 1870 1871 >>> compare_tuple("1", "2") 1872 -1 1873 >>> compare_tuple("foo-1.1", "foo-1.2") 1874 -1 1875 >>> compare_tuple("1.2", "1.2a") 1876 -1 1877 >>> compare_tuple("1.2b", "1.2a") 1878 1 1879 >>> compare_tuple("1.3.5.3a", "1.3.5.3b") 1880 -1 1881 1882 Args: 1883 ver1: version string 1884 ver2: version string 1885 1886 Returns: 1887 int: 1 if ver1 > ver2 1888 0 if ver1 == ver2 1889 -1 if ver1 < ver2 1890 """ 1891 ax = re.split('[.-]', ver1) 1892 ay = re.split('[.-]', ver2) 1893 while len(ax) > 0 and len(ay) > 0: 1894 cx = ax.pop(0) 1895 cy = ay.pop(0) 1896 maxlen = max(len(cx), len(cy)) 1897 c = cmp(cx.zfill(maxlen), cy.zfill(maxlen)) 1898 if c != 0: 1899 return c 1900 return cmp(len(ax), len(ay)) 1901 1902 1903def args_to_dict(args): 1904 """Convert autoserv extra arguments in the form of key=val or key:val to a 1905 dictionary. Each argument key is converted to lowercase dictionary key. 1906 1907 Args: 1908 args - list of autoserv extra arguments. 1909 1910 Returns: 1911 dictionary 1912 """ 1913 arg_re = re.compile(r'(\w+)[:=](.*)$') 1914 dict = {} 1915 for arg in args: 1916 match = arg_re.match(arg) 1917 if match: 1918 dict[match.group(1).lower()] = match.group(2) 1919 else: 1920 logging.warning("args_to_dict: argument '%s' doesn't match " 1921 "'%s' pattern. Ignored.", arg, arg_re.pattern) 1922 return dict 1923 1924 1925def get_unused_port(): 1926 """ 1927 Finds a semi-random available port. A race condition is still 1928 possible after the port number is returned, if another process 1929 happens to bind it. 1930 1931 Returns: 1932 A port number that is unused on both TCP and UDP. 1933 """ 1934 1935 def try_bind(port, socket_type, socket_proto): 1936 s = socket.socket(socket.AF_INET, socket_type, socket_proto) 1937 try: 1938 try: 1939 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 1940 s.bind(('', port)) 1941 return s.getsockname()[1] 1942 except socket.error: 1943 return None 1944 finally: 1945 s.close() 1946 1947 # On the 2.6 kernel, calling try_bind() on UDP socket returns the 1948 # same port over and over. So always try TCP first. 1949 while True: 1950 # Ask the OS for an unused port. 1951 port = try_bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP) 1952 # Check if this port is unused on the other protocol. 1953 if port and try_bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP): 1954 return port 1955 1956 1957def ask(question, auto=False): 1958 """ 1959 Raw input with a prompt that emulates logging. 1960 1961 @param question: Question to be asked 1962 @param auto: Whether to return "y" instead of asking the question 1963 """ 1964 if auto: 1965 logging.info("%s (y/n) y", question) 1966 return "y" 1967 return raw_input("%s INFO | %s (y/n) " % 1968 (time.strftime("%H:%M:%S", time.localtime()), question)) 1969 1970 1971def rdmsr(address, cpu=0): 1972 """ 1973 Reads an x86 MSR from the specified CPU, returns as long integer. 1974 """ 1975 with open('/dev/cpu/%s/msr' % cpu, 'r', 0) as fd: 1976 fd.seek(address) 1977 return struct.unpack('=Q', fd.read(8))[0] 1978 1979 1980def wait_for_value(func, 1981 expected_value=None, 1982 min_threshold=None, 1983 max_threshold=None, 1984 timeout_sec=10): 1985 """ 1986 Returns the value of func(). If |expected_value|, |min_threshold|, and 1987 |max_threshold| are not set, returns immediately. 1988 1989 If |expected_value| is set, polls the return value until |expected_value| is 1990 reached, and returns that value. 1991 1992 If either |max_threshold| or |min_threshold| is set, this function will 1993 will repeatedly call func() until the return value reaches or exceeds one of 1994 these thresholds. 1995 1996 Polling will stop after |timeout_sec| regardless of these thresholds. 1997 1998 @param func: function whose return value is to be waited on. 1999 @param expected_value: wait for func to return this value. 2000 @param min_threshold: wait for func value to reach or fall below this value. 2001 @param max_threshold: wait for func value to reach or rise above this value. 2002 @param timeout_sec: Number of seconds to wait before giving up and 2003 returning whatever value func() last returned. 2004 2005 Return value: 2006 The most recent return value of func(). 2007 """ 2008 value = None 2009 start_time_sec = time.time() 2010 while True: 2011 value = func() 2012 if (expected_value is None and \ 2013 min_threshold is None and \ 2014 max_threshold is None) or \ 2015 (expected_value is not None and value == expected_value) or \ 2016 (min_threshold is not None and value <= min_threshold) or \ 2017 (max_threshold is not None and value >= max_threshold): 2018 break 2019 2020 if time.time() - start_time_sec >= timeout_sec: 2021 break 2022 time.sleep(0.1) 2023 2024 return value 2025 2026 2027def call_xrandr(args_string=''): 2028 """ 2029 Calls xrandr with the args given by args_string. 2030 |args_string| is a single string containing all arguments. 2031 e.g. call_xrandr('--output LVDS1 --off') will invoke: 2032 'xrandr --output LVDS1 --off' 2033 2034 Return value: Output of xrandr 2035 """ 2036 2037 cmd = 'xrandr' 2038 xauth = '/home/chronos/.Xauthority' 2039 environment = 'DISPLAY=:0.0 XAUTHORITY=%s' % xauth 2040 return system_output('%s %s %s' % (environment, cmd, args_string)) 2041 2042 2043def get_xrandr_output_state(): 2044 """ 2045 Retrieves output status of connected display(s) using xrandr. 2046 2047 When xrandr report a display is "connected", it doesn't mean the 2048 display is active. For active display, it will have '*' after display mode. 2049 2050 Return value: dictionary of connected display states. 2051 key = output name 2052 value = True if the display is active; False otherwise. 2053 """ 2054 2055 output = call_xrandr().split('\n') 2056 xrandr_outputs = {} 2057 current_output_name = '' 2058 2059 # Parse output of xrandr, line by line. 2060 for line in output: 2061 if line.startswith('Screen'): 2062 continue 2063 # If the line contains "connected", it is a connected display, as 2064 # opposed to a disconnected output. 2065 if line.find(' connected') != -1: 2066 current_output_name = line.split()[0] 2067 # Temporarily mark it as inactive until we see a '*' afterward. 2068 xrandr_outputs[current_output_name] = False 2069 continue 2070 2071 # If "connected" was not found, this is a line that shows a display 2072 # mode, e.g: 1920x1080 50.0 60.0 24.0 2073 # Check if this has an asterisk indicating it's on. 2074 if line.find('*') != -1 and current_output_name: 2075 xrandr_outputs[current_output_name] = True 2076 # Reset the output name since this should not be set more than once. 2077 current_output_name = '' 2078 2079 return xrandr_outputs 2080 2081 2082def set_xrandr_output(output_name, enable): 2083 """ 2084 Sets the output given by |output_name| on or off. 2085 2086 Parameters: 2087 output_name name of output, e.g. 'HDMI1', 'LVDS1', 'DP1' 2088 enable True or False, indicating whether to turn on or off 2089 """ 2090 2091 call_xrandr('--output %s --%s' % (output_name, 'auto' if enable else 'off')) 2092 2093 2094def restart_job(name): 2095 """ 2096 Restarts an upstart job if it's running. 2097 If it's not running, start it. 2098 """ 2099 2100 if system_output('status %s' % name).find('start/running') != -1: 2101 system_output('restart %s' % name) 2102 else: 2103 system_output('start %s' % name) 2104 2105