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