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