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