site_utils.py revision fc9cb18c0884692808beb00be9c45e88f60c7c05
1#pylint: disable=C0111
2
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import glob, hashlib, logging, os, platform, re, signal, tempfile, time
8import uuid, urllib2
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib import utils
12from autotest_lib.client.bin import base_utils
13
14from contextlib import closing
15
16_UI_USE_FLAGS_FILE_PATH = '/etc/ui_use_flags.txt'
17
18class TimeoutError(error.TestError):
19    """Error raised when we time out when waiting on a condition."""
20    pass
21
22
23class Crossystem(object):
24    """A wrapper for the crossystem utility."""
25
26    def __init__(self, client):
27        self.cros_system_data = {}
28        self._client = client
29
30    def init(self):
31        self.cros_system_data = {}
32        (_, fname) = tempfile.mkstemp()
33        f = open(fname, 'w')
34        self._client.run('crossystem', stdout_tee=f)
35        f.close()
36        text = utils.read_file(fname)
37        for line in text.splitlines():
38            assignment_string = line.split('#')[0]
39            if not assignment_string.count('='):
40                continue
41            (name, value) = assignment_string.split('=', 1)
42            self.cros_system_data[name.strip()] = value.strip()
43        os.remove(fname)
44
45    def __getattr__(self, name):
46        """
47        Retrieve a crosssystem attribute.
48
49        The call crossystemobject.name() will return the crossystem reported
50        string.
51        """
52        return lambda : self.cros_system_data[name]
53
54
55def get_oldest_pid_by_name(name):
56    """
57    Return the oldest pid of a process whose name perfectly matches |name|.
58
59    name is an egrep expression, which will be matched against the entire name
60    of processes on the system.  For example:
61
62      get_oldest_pid_by_name('chrome')
63
64    on a system running
65      8600 ?        00:00:04 chrome
66      8601 ?        00:00:00 chrome
67      8602 ?        00:00:00 chrome-sandbox
68
69    would return 8600, as that's the oldest process that matches.
70    chrome-sandbox would not be matched.
71
72    Arguments:
73      name: egrep expression to match.  Will be anchored at the beginning and
74            end of the match string.
75
76    Returns:
77      pid as an integer, or None if one cannot be found.
78
79    Raises:
80      ValueError if pgrep returns something odd.
81    """
82    str_pid = utils.system_output(
83        'pgrep -o ^%s$' % name, ignore_status=True).rstrip()
84    if str_pid:
85        return int(str_pid)
86
87
88def get_oldest_by_name(name):
89    """Return pid and command line of oldest process whose name matches |name|.
90
91    @param name: egrep expression to match desired process name.
92    @return: A tuple of (pid, command_line) of the oldest process whose name
93             matches |name|.
94
95    """
96    pid = get_oldest_pid_by_name(name)
97    if pid:
98        command_line = utils.system_output('ps -p %i -o command=' % pid,
99                                           ignore_status=True).rstrip()
100        return (pid, command_line)
101
102
103def get_chrome_remote_debugging_port():
104    """Returns remote debugging port for Chrome.
105
106    Parse chrome process's command line argument to get the remote debugging
107    port.
108    """
109    pid, command = get_oldest_by_name('chrome')
110    matches = re.search('--remote-debugging-port=([0-9]+)', command)
111    if matches:
112        return int(matches.group(1))
113
114
115def get_process_list(name, command_line=None):
116    """
117    Return the list of pid for matching process |name command_line|.
118
119    on a system running
120      31475 ?    0:06 /opt/google/chrome/chrome --allow-webui-compositing -
121      31478 ?    0:00 /opt/google/chrome/chrome-sandbox /opt/google/chrome/
122      31485 ?    0:00 /opt/google/chrome/chrome --type=zygote --log-level=1
123      31532 ?    1:05 /opt/google/chrome/chrome --type=renderer
124
125    get_process_list('chrome')
126    would return ['31475', '31485', '31532']
127
128    get_process_list('chrome', '--type=renderer')
129    would return ['31532']
130
131    Arguments:
132      name: process name to search for. If command_line is provided, name is
133            matched against full command line. If command_line is not provided,
134            name is only matched against the process name.
135      command line: when command line is passed, the full process command line
136                    is used for matching.
137
138    Returns:
139      list of PIDs of the matching processes.
140
141    """
142    # TODO(rohitbm) crbug.com/268861
143    flag = '-x' if not command_line else '-f'
144    name = '\'%s.*%s\'' % (name, command_line) if command_line else name
145    str_pid = utils.system_output(
146            'pgrep %s %s' % (flag, name), ignore_status=True).rstrip()
147    return str_pid.split()
148
149
150def nuke_process_by_name(name, with_prejudice=False):
151    """Tell the oldest process specified by name to exit.
152
153    Arguments:
154      name: process name specifier, as understood by pgrep.
155      with_prejudice: if True, don't allow for graceful exit.
156
157    Raises:
158      error.AutoservPidAlreadyDeadError: no existing process matches name.
159    """
160    try:
161        pid = get_oldest_pid_by_name(name)
162    except Exception as e:
163        logging.error(e)
164        return
165    if pid is None:
166        raise error.AutoservPidAlreadyDeadError(
167            'No process matching %s.' % name)
168    if with_prejudice:
169        utils.nuke_pid(pid, [signal.SIGKILL])
170    else:
171        utils.nuke_pid(pid)
172
173
174def ensure_processes_are_dead_by_name(name, timeout_sec=10):
175    """Terminate all processes specified by name and ensure they're gone.
176
177    Arguments:
178      name: process name specifier, as understood by pgrep.
179      timeout_sec: maximum number of seconds to wait for processes to die.
180
181    Raises:
182      error.AutoservPidAlreadyDeadError: no existing process matches name.
183      site_utils.TimeoutError: if processes still exist after timeout_sec.
184    """
185    def list_and_kill_processes(name):
186        process_list = get_process_list(name)
187        try:
188            for pid in [int(str_pid) for str_pid in process_list]:
189                utils.nuke_pid(pid)
190        except error.AutoservPidAlreadyDeadError:
191            pass
192        return process_list
193
194    poll_for_condition(lambda: list_and_kill_processes(name) == [],
195                       timeout=timeout_sec)
196
197
198def poll_for_condition(
199    condition, exception=None, timeout=10, sleep_interval=0.1, desc=None):
200    """Poll until a condition becomes true.
201
202    Arguments:
203      condition: function taking no args and returning bool
204      exception: exception to throw if condition doesn't become true
205      timeout: maximum number of seconds to wait
206      sleep_interval: time to sleep between polls
207      desc: description of default TimeoutError used if 'exception' is None
208
209    Returns:
210      The true value that caused the poll loop to terminate.
211
212    Raises:
213      'exception' arg if supplied; site_utils.TimeoutError otherwise
214    """
215    start_time = time.time()
216    while True:
217        value = condition()
218        if value:
219            return value
220        if time.time() + sleep_interval - start_time > timeout:
221            if exception:
222                logging.error(exception)
223                raise exception
224
225            if desc:
226                desc = 'Timed out waiting for condition: %s' % desc
227            else:
228                desc = 'Timed out waiting for unnamed condition'
229            logging.error(desc)
230            raise TimeoutError, desc
231
232        time.sleep(sleep_interval)
233
234
235def save_vm_state(checkpoint):
236    """Saves the current state of the virtual machine.
237
238    This function is a NOOP if the test is not running under a virtual machine
239    with the USB serial port redirected.
240
241    Arguments:
242      checkpoint - Name used to identify this state
243
244    Returns:
245      None
246    """
247    # The QEMU monitor has been redirected to the guest serial port located at
248    # /dev/ttyUSB0. To save the state of the VM, we just send the 'savevm'
249    # command to the serial port.
250    proc = platform.processor()
251    if 'QEMU' in proc and os.path.exists('/dev/ttyUSB0'):
252        logging.info('Saving VM state "%s"', checkpoint)
253        serial = open('/dev/ttyUSB0', 'w')
254        serial.write("savevm %s\r\n" % checkpoint)
255        logging.info('Done saving VM state "%s"', checkpoint)
256
257
258def check_raw_dmesg(dmesg, message_level, whitelist):
259    """Checks dmesg for unexpected warnings.
260
261    This function parses dmesg for message with message_level <= message_level
262    which do not appear in the whitelist.
263
264    Arguments:
265      dmesg - string containing raw dmesg buffer
266      message_level - minimum message priority to check
267      whitelist - messages to ignore
268
269    Returns:
270      List of unexpected warnings
271    """
272    whitelist_re = re.compile(r'(%s)' % '|'.join(whitelist))
273    unexpected = []
274    for line in dmesg.splitlines():
275        if int(line[1]) <= message_level:
276            stripped_line = line.split('] ', 1)[1]
277            if whitelist_re.search(stripped_line):
278                continue
279            unexpected.append(stripped_line)
280    return unexpected
281
282def verify_mesg_set(mesg, regex, whitelist):
283    """Verifies that the exact set of messages are present in a text.
284
285    This function finds all strings in the text matching a certain regex, and
286    then verifies that all expected strings are present in the set, and no
287    unexpected strings are there.
288
289    Arguments:
290      mesg - the mutiline text to be scanned
291      regex - regular expression to match
292      whitelist - messages to find in the output, a list of strings
293          (potentially regexes) to look for in the filtered output. All these
294          strings must be there, and no other strings should be present in the
295          filtered output.
296
297    Returns:
298      string of inconsistent findings (i.e. an empty string on success).
299    """
300
301    rv = []
302
303    missing_strings = []
304    present_strings = []
305    for line in mesg.splitlines():
306        if not re.search(r'%s' % regex, line):
307            continue
308        present_strings.append(line.split('] ', 1)[1])
309
310    for string in whitelist:
311        for present_string in list(present_strings):
312            if re.search(r'^%s$' % string, present_string):
313                present_strings.remove(present_string)
314                break
315        else:
316            missing_strings.append(string)
317
318    if present_strings:
319        rv.append('unexpected strings:')
320        rv.extend(present_strings)
321    if missing_strings:
322        rv.append('missing strings:')
323        rv.extend(missing_strings)
324
325    return '\n'.join(rv)
326
327
328def target_is_pie():
329    """Returns whether the toolchain produces a PIE (position independent
330    executable) by default.
331
332    Arguments:
333      None
334
335    Returns:
336      True if the target toolchain produces a PIE by default.
337      False otherwise.
338    """
339
340
341    command = 'echo | ${CC} -E -dD -P - | grep -i pie'
342    result = utils.system_output(command, retain_output=True,
343                                 ignore_status=True)
344    if re.search('#define __PIE__', result):
345        return True
346    else:
347        return False
348
349def target_is_x86():
350    """Returns whether the toolchain produces an x86 object
351
352    Arguments:
353      None
354
355    Returns:
356      True if the target toolchain produces an x86 object
357      False otherwise.
358    """
359
360
361    command = 'echo | ${CC} -E -dD -P - | grep -i 86'
362    result = utils.system_output(command, retain_output=True,
363                                 ignore_status=True)
364    if re.search('__i386__', result) or re.search('__x86_64__', result):
365        return True
366    else:
367        return False
368
369def mounts():
370    ret = []
371    for line in file('/proc/mounts'):
372        m = re.match(r'(?P<src>\S+) (?P<dest>\S+) (?P<type>\S+) (?P<opts>\S+).*', line)
373        if m:
374            ret.append(m.groupdict())
375    return ret
376
377def is_mountpoint(path):
378    return path in [ m['dest'] for m in mounts() ]
379
380def require_mountpoint(path):
381    """
382    Raises an exception if path is not a mountpoint.
383    """
384    if not is_mountpoint(path):
385        raise error.TestFail('Path not mounted: "%s"' % path)
386
387def random_username():
388    return str(uuid.uuid4()) + '@example.com'
389
390
391def get_signin_credentials(filepath):
392    """Returns user_id, password tuple from credentials file at filepath.
393
394    File must have one line of the format user_id:password
395
396    @param filepath: path of credentials file.
397    @return user_id, password tuple.
398    """
399    user_id, password = None, None
400    if os.path.isfile(filepath):
401        with open(filepath) as f:
402            user_id, password = f.read().rstrip().split(':')
403    return user_id, password
404
405
406def parse_cmd_output(command, run_method=utils.run):
407    """Runs a command on a host object to retrieve host attributes.
408
409    The command should output to stdout in the format of:
410    <key> = <value> # <optional_comment>
411
412
413    @param command: Command to execute on the host.
414    @param run_method: Function to use to execute the command. Defaults to
415                       utils.run so that the command will be executed locally.
416                       Can be replace with a host.run call so that it will
417                       execute on a DUT or external machine. Method must accept
418                       a command argument, stdout_tee and stderr_tee args and
419                       return a result object with a string attribute stdout
420                       which will be parsed.
421
422    @returns a dictionary mapping host attributes to their values.
423    """
424    result = {}
425    # Suppresses stdout so that the files are not printed to the logs.
426    cmd_result = run_method(command, stdout_tee=None, stderr_tee=None)
427    for line in cmd_result.stdout.splitlines():
428        # Lines are of the format "<key>     = <value>      # <comment>"
429        key_value = re.match('^\s*(?P<key>[^ ]+)\s*=\s*(?P<value>[^ ]+)'
430                             '(?:\s*#.*)?$', line)
431        if key_value:
432            result[key_value.group('key')] = key_value.group('value')
433    return result
434
435
436def set_from_keyval_output(out, delimiter=' '):
437    """Parse delimiter-separated key-val output into a set of tuples.
438
439    Output is expected to be multiline text output from a command.
440    Stuffs the key-vals into tuples in a set to be later compared.
441
442    e.g.  deactivated 0
443          disableForceClear 0
444          ==>  set(('deactivated', '0'), ('disableForceClear', '0'))
445
446    @param out: multiple lines of space-separated key-val pairs.
447    @param delimiter: character that separates key from val. Usually a
448                      space but may be '=' or something else.
449    @return set of key-val tuples.
450    """
451    results = set()
452    kv_match_re = re.compile('([^ ]+)%s(.*)' % delimiter)
453    for linecr in out.splitlines():
454        match = kv_match_re.match(linecr.strip())
455        if match:
456            results.add((match.group(1), match.group(2)))
457    return results
458
459
460def get_cpu_usage():
461    """Returns machine's CPU usage.
462
463    This function uses /proc/stat to identify CPU usage.
464    Returns:
465        A dictionary with 'user', 'nice', 'system' and 'idle' values.
466        Sample dictionary:
467        {
468            'user': 254544,
469            'nice': 9,
470            'system': 254768,
471            'idle': 2859878,
472        }
473    """
474    proc_stat = open('/proc/stat')
475    cpu_usage_str = proc_stat.readline().split()
476    proc_stat.close()
477    return {
478        'user': int(cpu_usage_str[1]),
479        'nice': int(cpu_usage_str[2]),
480        'system': int(cpu_usage_str[3]),
481        'idle': int(cpu_usage_str[4])
482    }
483
484
485def compute_active_cpu_time(cpu_usage_start, cpu_usage_end):
486    """Computes the fraction of CPU time spent non-idling.
487
488    This function should be invoked using before/after values from calls to
489    get_cpu_usage().
490    """
491    time_active_end = (cpu_usage_end['user'] + cpu_usage_end['nice'] +
492                                                  cpu_usage_end['system'])
493    time_active_start = (cpu_usage_start['user'] + cpu_usage_start['nice'] +
494                                                      cpu_usage_start['system'])
495    total_time_end = (cpu_usage_end['user'] + cpu_usage_end['nice'] +
496                      cpu_usage_end['system'] + cpu_usage_end['idle'])
497    total_time_start = (cpu_usage_start['user'] + cpu_usage_start['nice'] +
498                        cpu_usage_start['system'] + cpu_usage_start['idle'])
499    return ((float(time_active_end) - time_active_start) /
500                    (total_time_end - total_time_start))
501
502
503def is_pgo_mode():
504    return 'USE_PGO' in os.environ
505
506
507def wait_for_idle_cpu(timeout, utilization):
508    """Waits for the CPU to become idle (< utilization).
509
510    Args:
511        timeout: The longest time in seconds to wait before throwing an error.
512        utilization: The CPU usage below which the system should be considered
513                idle (between 0 and 1.0 independent of cores/hyperthreads).
514    """
515    time_passed = 0.0
516    fraction_active_time = 1.0
517    sleep_time = 1
518    logging.info('Starting to wait up to %.1fs for idle CPU...', timeout)
519    while fraction_active_time >= utilization:
520        cpu_usage_start = get_cpu_usage()
521        # Split timeout interval into not too many chunks to limit log spew.
522        # Start at 1 second, increase exponentially
523        time.sleep(sleep_time)
524        time_passed += sleep_time
525        sleep_time = min(16.0, 2.0 * sleep_time)
526        cpu_usage_end = get_cpu_usage()
527        fraction_active_time = \
528                compute_active_cpu_time(cpu_usage_start, cpu_usage_end)
529        logging.info('After waiting %.1fs CPU utilization is %.3f.',
530                     time_passed, fraction_active_time)
531        if time_passed > timeout:
532            logging.warning('CPU did not become idle.')
533            log_process_activity()
534            # crosbug.com/37389
535            if is_pgo_mode():
536                logging.info('Still continuing because we are in PGO mode.')
537                return True
538
539            return False
540    logging.info('Wait for idle CPU took %.1fs (utilization = %.3f).',
541                              time_passed, fraction_active_time)
542    return True
543
544
545def log_process_activity():
546    """Logs the output of top.
547
548    Useful to debug performance tests and to find runaway processes.
549    """
550    logging.info('Logging current process activity using top.')
551    cmd = 'top -b -n1 -c'
552    output = utils.run(cmd)
553    logging.info(output)
554
555
556def wait_for_cool_machine():
557    """
558    A simple heuristic to wait for a machine to cool.
559    The code looks a bit 'magic', but we don't know ambient temperature
560    nor machine characteristics and still would like to return the caller
561    a machine that cooled down as much as reasonably possible.
562    """
563    temperature = get_current_temperature_max()
564    # We got here with a cold machine, return immediately. This should be the
565    # most common case.
566    if temperature < 50:
567        return True
568    logging.info('Got a hot machine of %dC. Sleeping 1 minute.', temperature)
569    # A modest wait should cool the machine.
570    time.sleep(60.0)
571    temperature = get_current_temperature_max()
572    # Atoms idle below 60 and everyone else should be even lower.
573    if temperature < 62:
574        return True
575    # This should be rare.
576    logging.info('Did not cool down (%dC). Sleeping 2 minutes.', temperature)
577    time.sleep(120.0)
578    temperature = get_current_temperature_max()
579    # A temperature over 65'C doesn't give us much headroom to the critical
580    # temperatures that start at 85'C (and PerfControl as of today will fail at
581    # critical - 10'C).
582    if temperature < 65:
583        return True
584    logging.warning('Did not cool down (%dC), giving up.', temperature)
585    log_process_activity()
586    return False
587
588
589# System paths for machine performance state.
590_CPUINFO = '/proc/cpuinfo'
591_DIRTY_WRITEBACK_CENTISECS = '/proc/sys/vm/dirty_writeback_centisecs'
592_KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
593_MEMINFO = '/proc/meminfo'
594_TEMP_SENSOR_RE = 'Reading temperature...([0-9]*)'
595
596
597def _get_line_from_file(path, line):
598    """
599    line can be an integer or
600    line can be a string that matches the beginning of the line
601    """
602    with open(path) as f:
603        if isinstance(line, int):
604            l = f.readline()
605            for _ in range(0, line):
606                l = f.readline()
607            return l
608        else:
609            for l in f:
610                if l.startswith(line):
611                    return l
612    return None
613
614
615def _get_match_from_file(path, line, prefix, postfix):
616    """
617    Matches line in path and returns string between first prefix and postfix.
618    """
619    match = _get_line_from_file(path, line)
620    # Strip everything from front of line including prefix.
621    if prefix:
622        match = re.split(prefix, match)[1]
623    # Strip everything from back of string including first occurence of postfix.
624    if postfix:
625        match = re.split(postfix, match)[0]
626    return match
627
628
629def _get_float_from_file(path, line, prefix, postfix):
630    match = _get_match_from_file(path, line, prefix, postfix)
631    return float(match)
632
633
634def _get_int_from_file(path, line, prefix, postfix):
635    match = _get_match_from_file(path, line, prefix, postfix)
636    return int(match)
637
638
639def _get_hex_from_file(path, line, prefix, postfix):
640    match = _get_match_from_file(path, line, prefix, postfix)
641    return int(match, 16)
642
643
644# The paths don't change. Avoid running find all the time.
645_hwmon_paths = None
646
647def _get_hwmon_paths(file_pattern):
648    """
649    Returns a list of paths to the temperature sensors.
650    """
651    # Some systems like daisy_spring only have the virtual hwmon.
652    # And other systems like rambi only have coretemp.0. See crbug.com/360249.
653    #    /sys/class/hwmon/hwmon*/
654    #    /sys/devices/virtual/hwmon/hwmon*/
655    #    /sys/devices/platform/coretemp.0/
656    if not _hwmon_paths:
657        cmd = 'find /sys/ -name "' + file_pattern + '"'
658        _hwon_paths = utils.run(cmd, verbose=False).stdout.splitlines()
659    return _hwon_paths
660
661
662def get_temperature_critical():
663    """
664    Returns temperature at which we will see some throttling in the system.
665    """
666    min_temperature = 1000.0
667    paths = _get_hwmon_paths('temp*_crit')
668    for path in paths:
669        temperature = _get_float_from_file(path, 0, None, None) * 0.001
670        # Today typical for Intel is 98'C to 105'C while ARM is 85'C. Clamp to
671        # the lowest known value.
672        if ((min_temperature < 60.0) or min_temperature > 150.0):
673            logging.warning('Critical temperature of %.1fC was reset to 85.0C.')
674            min_temperature = 85.0
675
676        min_temperature = min(temperature, min_temperature)
677    return min_temperature
678
679
680def get_temperature_input_max():
681    """
682    Returns the maximum currently observed temperature.
683    """
684    max_temperature = -1000.0
685    paths = _get_hwmon_paths('temp*_input')
686    for path in paths:
687        temperature = _get_float_from_file(path, 0, None, None) * 0.001
688        max_temperature = max(temperature, max_temperature)
689    return max_temperature
690
691
692def get_thermal_zone_temperatures():
693    """
694    Returns the maximum currently observered temperature in thermal_zones.
695    """
696    temperatures = []
697    for path in glob.glob('/sys/class/thermal/thermal_zone*/temp'):
698        temperatures.append(_get_float_from_file(path, 0, None, None) * 0.001)
699    return temperatures
700
701
702def get_ec_temperatures():
703    """
704    Uses ectool to return a list of all sensor temperatures in Celsius.
705    """
706    temperatures = []
707    # TODO(ihf): On all ARM boards I tested 'ectool temps all' returns 200K
708    # for all sensors. Remove this check once crbug.com/358342 is fixed.
709    if 'arm' in utils.get_arch():
710        return temperatures
711    try:
712        full_cmd = 'ectool temps all'
713        lines = utils.run(full_cmd, verbose=False).stdout.splitlines()
714        for line in lines:
715            temperature = int(line.split(': ')[1]) - 273
716            temperatures.append(temperature)
717    except Exception:
718        logging.warning('Unable to read temperature sensors using ectool.')
719    for temperature in temperatures:
720        # Sanity check for real world values.
721        assert ((temperature > 10.0) and
722                (temperature < 150.0)), ('Unreasonable temperature %.1fC.' %
723                                         temperature)
724
725    return temperatures
726
727
728def get_current_temperature_max():
729    """
730    Returns the highest reported board temperature (all sensors) in Celsius.
731    """
732    temperature = max(get_temperature_input_max(),
733                      max(get_thermal_zone_temperatures()),
734                      max(get_ec_temperatures()))
735    # Sanity check for real world values.
736    assert ((temperature > 10.0) and
737            (temperature < 150.0)), ('Unreasonable temperature %.1fC.' %
738                                     temperature)
739    return temperature
740
741
742def get_cpu_cache_size():
743    """
744    Returns the last level CPU cache size in kBytes.
745    """
746    cache_size = _get_int_from_file(_CPUINFO, 'cache size', ': ', ' KB')
747    # Sanity check.
748    assert cache_size >= 64, 'Unreasonably small cache.'
749    return cache_size
750
751
752def get_cpu_model_frequency():
753    """
754    Returns the model frequency from the CPU model name on Intel only. This
755    might be redundant with get_cpu_max_frequency. Unit is Hz.
756    """
757    frequency = _get_float_from_file(_CPUINFO, 'model name', ' @ ', 'GHz')
758    return 1.e9 * frequency
759
760
761def get_cpu_max_frequency():
762    """
763    Returns the largest of the max CPU core frequencies. The unit is Hz.
764    """
765    max_frequency = -1
766    paths = _get_cpufreq_paths('cpuinfo_max_freq')
767    for path in paths:
768        # Convert from kHz to Hz.
769        frequency = 1000 * _get_float_from_file(path, 0, None, None)
770        max_frequency = max(frequency, max_frequency)
771    # Sanity check.
772    assert max_frequency > 1e8, 'Unreasonably low CPU frequency.'
773    return max_frequency
774
775
776def get_cpu_min_frequency():
777    """
778    Returns the smallest of the minimum CPU core frequencies.
779    """
780    min_frequency = 1e20
781    paths = _get_cpufreq_paths('cpuinfo_min_freq')
782    for path in paths:
783        frequency = _get_float_from_file(path, 0, None, None)
784        min_frequency = min(frequency, min_frequency)
785    # Sanity check.
786    assert min_frequency > 1e8, 'Unreasonably low CPU frequency.'
787    return min_frequency
788
789
790def get_cpu_model():
791    """
792    Returns the CPU model.
793    Only works on Intel.
794    """
795    cpu_model = _get_int_from_file(_CPUINFO, 'model\t', ': ', None)
796    return cpu_model
797
798
799def get_cpu_family():
800    """
801    Returns the CPU family.
802    Only works on Intel.
803    """
804    cpu_family = _get_int_from_file(_CPUINFO, 'cpu family\t', ': ', None)
805    return cpu_family
806
807
808def get_board():
809    """
810    Get the ChromeOS release board name from /etc/lsb-release.
811    """
812    f = open('/etc/lsb-release')
813    try:
814        return re.search('BOARD=(.*)', f.read()).group(1)
815    finally:
816        f.close()
817
818
819def get_board_with_frequency_and_memory():
820    """
821    Returns a board name modified with CPU frequency and memory size to
822    differentiate between different board variants. For instance
823    link -> link_1.8GHz_4GB.
824    """
825    board_name = get_board()
826    # Rounded to nearest GB and GHz.
827    memory = int(round(get_mem_total() / 1024.0))
828    # Convert frequency to GHz with 1 digit accuracy after the decimal point.
829    frequency = int(round(get_cpu_max_frequency() * 1e-8)) * 0.1
830    board = "%s_%1.1fGHz_%dGB" % (board_name, frequency, memory)
831    return board
832
833
834def get_mem_total():
835    """
836    Returns the total memory available in the system in MBytes.
837    """
838    mem_total = _get_float_from_file(_MEMINFO, 'MemTotal:', 'MemTotal:', ' kB')
839    # Sanity check, all Chromebooks have at least 1GB of memory.
840    assert mem_total > 1024 * 1024, 'Unreasonable amount of memory.'
841    return mem_total / 1024
842
843
844def get_mem_free():
845    """
846    Returns the currently free memory in the system in MBytes.
847    """
848    mem_free = _get_float_from_file(_MEMINFO, 'MemFree:', 'MemFree:', ' kB')
849    return mem_free / 1024
850
851
852def get_kernel_max():
853    """
854    Returns content of kernel_max.
855    """
856    kernel_max = _get_int_from_file(_KERNEL_MAX, 0, None, None)
857    # Sanity check.
858    assert ((kernel_max > 0) and (kernel_max < 257)), 'Unreasonable kernel_max.'
859    return kernel_max
860
861
862def set_high_performance_mode():
863    """
864    Sets the kernel governor mode to the highest setting.
865    Returns previous governor state.
866    """
867    original_governors = get_scaling_governor_states()
868    set_scaling_governors('performance')
869    return original_governors
870
871
872def set_scaling_governors(value):
873    """
874    Sets all scaling governor to string value.
875    Sample values: 'performance', 'interactive', 'ondemand', 'powersave'.
876    """
877    paths = _get_cpufreq_paths('scaling_governor')
878    for path in paths:
879        cmd = 'echo %s > %s' % (value, path)
880        logging.info('Writing scaling governor mode \'%s\' -> %s', value, path)
881        utils.system(cmd)
882
883
884def _get_cpufreq_paths(filename):
885    """
886    Returns a list of paths to the governors.
887    """
888    cmd = 'ls /sys/devices/system/cpu/cpu*/cpufreq/' + filename
889    paths = utils.run(cmd, verbose=False).stdout.splitlines()
890    return paths
891
892
893def get_scaling_governor_states():
894    """
895    Returns a list of (performance governor path, current state) tuples.
896    """
897    paths = _get_cpufreq_paths('scaling_governor')
898    path_value_list = []
899    for path in paths:
900        value = _get_line_from_file(path, 0)
901        path_value_list.append((path, value))
902    return path_value_list
903
904
905def restore_scaling_governor_states(path_value_list):
906    """
907    Restores governor states. Inverse operation to get_scaling_governor_states.
908    """
909    for (path, value) in path_value_list:
910        cmd = 'echo %s > %s' % (value.rstrip('\n'), path)
911        utils.system(cmd)
912
913
914def get_dirty_writeback_centisecs():
915    """
916    Reads /proc/sys/vm/dirty_writeback_centisecs.
917    """
918    time = _get_int_from_file(_DIRTY_WRITEBACK_CENTISECS, 0, None, None)
919    return time
920
921
922def set_dirty_writeback_centisecs(time=60000):
923    """
924    In hundredths of a second, this is how often pdflush wakes up to write data
925    to disk. The default wakes up the two (or more) active threads every five
926    seconds. The ChromeOS default is 10 minutes.
927
928    We use this to set as low as 1 second to flush error messages in system
929    logs earlier to disk.
930    """
931    # Flush buffers first to make this function synchronous.
932    utils.system('sync')
933    if time >= 0:
934        cmd = 'echo %d > %s' % (time, _DIRTY_WRITEBACK_CENTISECS)
935        utils.system(cmd)
936
937
938def get_gpu_family():
939    """Return the GPU family name"""
940    cpuarch = base_utils.get_cpu_soc_family()
941    if cpuarch == 'exynos5' or cpuarch == 'rockchip':
942        return 'mali'
943    if cpuarch == 'tegra':
944        return 'tegra'
945    if os.path.exists('/sys/bus/platform/drivers/pvrsrvkm'):
946        return 'rogue'
947
948    pci_path = '/sys/bus/pci/devices/0000:00:02.0/device'
949
950    if not os.path.exists(pci_path):
951        raise error.TestError('PCI device 0000:00:02.0 not found')
952
953    device_id = int(utils.read_one_line(pci_path), 16)
954    intel_architecture = {
955        0xa011: 'pinetrail',
956        0x0106: 'sandybridge',
957        0x0116: 'sandybridge',
958        0x0126: 'sandybridge',
959        0x0156: 'ivybridge',
960        0x0166: 'ivybridge',
961        0x0a06: 'haswell',
962        0x0a16: 'haswell',
963        0x0f31: 'baytrail',
964        0x1606: 'broadwell',
965        0x1616: 'broadwell',
966        0x22b0: 'braswell',
967        0x22b1: 'braswell',
968        0x1916: 'skylake',
969        0x191e: 'skylake',
970    }
971
972    return intel_architecture[device_id]
973
974
975def has_no_monitor():
976    """Return whether a machine doesn't have a built-in monitor"""
977    board_name = get_board()
978    if (board_name == 'anglar' or board_name == 'mccloud' or
979        board_name == 'monroe' or board_name == 'stumpy' or
980        board_name == 'panther' or board_name == 'tricky' or
981        board_name == 'zako'):
982        return True
983
984    return False
985
986
987def get_fixed_dst_drive():
988    """
989    Return device name for internal disk.
990    Example: return /dev/sda for falco booted from usb
991    """
992    cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
993                    '. /usr/share/misc/chromeos-common.sh;',
994                    'load_base_vars;',
995                    'get_fixed_dst_drive'])
996    return utils.system_output(cmd)
997
998
999def get_root_device():
1000    """
1001    Return root device.
1002    Will return correct disk device even system boot from /dev/dm-0
1003    Example: return /dev/sdb for falco booted from usb
1004    """
1005    return utils.system_output('rootdev -s -d')
1006
1007
1008def get_root_partition():
1009    """
1010    Return current root partition
1011    Example: return /dev/sdb3 for falco booted from usb
1012    """
1013    return utils.system_output('rootdev -s')
1014
1015
1016def get_free_root_partition(root_part=None):
1017    """
1018    Return currently unused root partion
1019    Example: return /dev/sdb5 for falco booted from usb
1020
1021    @param root_part: cuurent root partition
1022    """
1023    spare_root_map = {'3': '5', '5': '3'}
1024    if not root_part:
1025        root_part = get_root_partition()
1026    return root_part[:-1] + spare_root_map[root_part[-1]]
1027
1028
1029def is_booted_from_internal_disk():
1030    """Return True if boot from internal disk. False, otherwise."""
1031    return get_root_device() == get_fixed_dst_drive()
1032
1033
1034def get_ui_use_flags():
1035    """Parses the USE flags as listed in /etc/ui_use_flags.txt.
1036
1037    @return: A list of flag strings found in the ui use flags file.
1038    """
1039    flags = []
1040    for flag in utils.read_file(_UI_USE_FLAGS_FILE_PATH).splitlines():
1041        # Removes everything after the '#'.
1042        flag_before_comment = flag.split('#')[0].strip()
1043        if len(flag_before_comment) != 0:
1044            flags.append(flag_before_comment)
1045
1046    return flags
1047
1048
1049def is_freon():
1050    """Returns False if the system uses X, True otherwise."""
1051    return 'X' not in get_ui_use_flags()
1052
1053
1054def graphics_platform():
1055    """
1056    Return a string identifying the graphics platform,
1057    e.g. 'glx' or 'x11_egl' or 'gbm'
1058    """
1059    use_flags = get_ui_use_flags()
1060    if 'X' not in use_flags:
1061        return 'null'
1062    elif 'opengles' in use_flags:
1063        return 'x11_egl'
1064    return 'glx'
1065
1066
1067def graphics_api():
1068    """Return a string identifying the graphics api, e.g. gl or gles2."""
1069    use_flags = get_ui_use_flags()
1070    if 'opengles' in use_flags:
1071        return 'gles2'
1072    return 'gl'
1073
1074
1075def assert_has_X_server():
1076    """Using X is soon to be deprecated. Print warning or raise error."""
1077    if is_freon():
1078        # TODO(ihf): Think about if we could support X for testing for a while.
1079        raise error.TestFail('freon: can\'t use X server.')
1080    logging.warning('freon: Using the X server will be deprecated soon.')
1081
1082
1083def is_vm():
1084    """Check if the process is running in a virtual machine.
1085
1086    @return: True if the process is running in a virtual machine, otherwise
1087             return False.
1088    """
1089    try:
1090        virt = utils.run('sudo -n virt-what').stdout.strip()
1091        logging.debug('virt-what output: %s', virt)
1092        return bool(virt)
1093    except error.CmdError:
1094        logging.warn('Package virt-what is not installed, default to assume '
1095                     'it is not a virtual machine.')
1096        return False
1097
1098
1099def download_file(file_to_download, local_path, checksum=None):
1100    """Downloads the file on DUT and verifies its checksum.
1101
1102    @param file_to_download: file to download.
1103    @param local_path: local download location on the DUT
1104    @param checksum: checksum to verify after downloading the file (optional)
1105
1106    @raises error.TestError if the file_to_download  url is not found or
1107                            if checksum is not matching.
1108    """
1109    logging.info('File to download from: %s' % file_to_download)
1110    logging.info('File to download to: %s' % local_path)
1111    logging.info('Checksum to verified: %s' % checksum)
1112    md5 = hashlib.md5()
1113    try:
1114        with closing(urllib2.urlopen(file_to_download)) as r, \
1115                open(local_path, 'w') as w:
1116            while True:
1117                content = r.read(4096)
1118                if not content: break
1119                md5.update(content)
1120                w.write(content)
1121        md5sum = md5.hexdigest()
1122    except urllib2.HTTPError as e:
1123        logging.debug(e.code)
1124        logging.debug(e.read())
1125        raise error.TestError('Error occured while downloading %s.'
1126                % file_to_download)
1127
1128    if checksum and md5sum != checksum:
1129        raise error.TestError('Unmatched md5 sum: %s' % md5sum)
1130