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