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