site_utils.py revision d5972948ef1e4c173eb3e2a37e47832d1a06f468
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.')
530    cmd = 'top -b -n1 -c'
531    output = utils.run(cmd)
532    logging.info(output)
533
534
535def wait_for_cool_machine():
536    """
537    A simple heuristic to wait for a machine to cool.
538    The code looks a bit 'magic', but we don't know ambient temperature
539    nor machine characteristics and still would like to return the caller
540    a machine that cooled down as much as reasonably possible.
541    """
542    temperature = get_current_temperature_max()
543    # We got here with a cold machine, return immediately. This should be the
544    # most common case.
545    if temperature < 50:
546        return True
547    logging.info('Got a hot machine of %dC. Sleeping 1 minute.', temperature)
548    # A modest wait should cool the machine.
549    time.sleep(60.0)
550    temperature = get_current_temperature_max()
551    # Atoms idle below 60 and everyone else should be even lower.
552    if temperature < 62:
553        return True
554    # This should be rare.
555    logging.info('Did not cool down (%dC). Sleeping 2 minutes.', temperature)
556    time.sleep(120.0)
557    temperature = get_current_temperature_max()
558    # A temperature over 65'C doesn't give us much headroom to the critical
559    # temperatures that start at 85'C (and PerfControl as of today will fail at
560    # critical - 10'C).
561    if temperature < 65:
562        return True
563    logging.warning('Did not cool down (%dC), giving up.', temperature)
564    log_process_activity()
565    return False
566
567
568# System paths for machine performance state.
569_CPUINFO = '/proc/cpuinfo'
570_DIRTY_WRITEBACK_CENTISECS = '/proc/sys/vm/dirty_writeback_centisecs'
571_KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
572_MEMINFO = '/proc/meminfo'
573_TEMP_SENSOR_RE = 'Reading temperature...([0-9]*)'
574
575
576def _get_line_from_file(path, line):
577    """
578    line can be an integer or
579    line can be a string that matches the beginning of the line
580    """
581    with open(path) as f:
582        if isinstance(line, int):
583            l = f.readline()
584            for _ in range(0, line):
585                l = f.readline()
586            return l
587        else:
588            for l in f:
589                if l.startswith(line):
590                    return l
591    return None
592
593
594def _get_match_from_file(path, line, prefix, postfix):
595    """
596    Matches line in path and returns string between first prefix and postfix.
597    """
598    match = _get_line_from_file(path, line)
599    # Strip everything from front of line including prefix.
600    if prefix:
601        match = re.split(prefix, match)[1]
602    # Strip everything from back of string including first occurence of postfix.
603    if postfix:
604        match = re.split(postfix, match)[0]
605    return match
606
607
608def _get_float_from_file(path, line, prefix, postfix):
609    match = _get_match_from_file(path, line, prefix, postfix)
610    return float(match)
611
612
613def _get_int_from_file(path, line, prefix, postfix):
614    match = _get_match_from_file(path, line, prefix, postfix)
615    return int(match)
616
617
618def _get_hex_from_file(path, line, prefix, postfix):
619    match = _get_match_from_file(path, line, prefix, postfix)
620    return int(match, 16)
621
622
623# The paths don't change. Avoid running find all the time.
624_hwmon_paths = None
625
626def _get_hwmon_paths(file_pattern):
627    """
628    Returns a list of paths to the temperature sensors.
629    """
630    # Some systems like daisy_spring only have the virtual hwmon.
631    # And other systems like rambi only have coretemp.0. See crbug.com/360249.
632    #    /sys/class/hwmon/hwmon*/
633    #    /sys/devices/virtual/hwmon/hwmon*/
634    #    /sys/devices/platform/coretemp.0/
635    if not _hwmon_paths:
636        cmd = 'find /sys/ -name "' + file_pattern + '"'
637        _hwon_paths = utils.run(cmd, verbose=False).stdout.splitlines()
638    return _hwon_paths
639
640
641def get_temperature_critical():
642    """
643    Returns temperature at which we will see some throttling in the system.
644    """
645    min_temperature = 1000.0
646    paths = _get_hwmon_paths('temp*_crit')
647    for path in paths:
648        temperature = _get_float_from_file(path, 0, None, None) * 0.001
649        # Today typical for Intel is 98'C to 105'C while ARM is 85'C. Clamp to
650        # the lowest known value.
651        if (min_temperature < 60.0) or min_temperature > 150.0:
652            logging.warning('Critical temperature of %.1fC was reset to 85.0C.',
653                            min_temperature)
654            min_temperature = 85.0
655
656        min_temperature = min(temperature, min_temperature)
657    return min_temperature
658
659
660def get_temperature_input_max():
661    """
662    Returns the maximum currently observed temperature.
663    """
664    max_temperature = -1000.0
665    paths = _get_hwmon_paths('temp*_input')
666    for path in paths:
667        temperature = _get_float_from_file(path, 0, None, None) * 0.001
668        max_temperature = max(temperature, max_temperature)
669    return max_temperature
670
671
672def get_thermal_zone_temperatures():
673    """
674    Returns the maximum currently observered temperature in thermal_zones.
675    """
676    temperatures = []
677    for path in glob.glob('/sys/class/thermal/thermal_zone*/temp'):
678        try:
679            temperatures.append(
680                _get_float_from_file(path, 0, None, None) * 0.001)
681        except IOError:
682            # Some devices (e.g. Veyron) may have reserved thermal zones that
683            # are not active. Trying to read the temperature value would cause a
684            # EINVAL IO error.
685            continue
686    return temperatures
687
688
689def get_ec_temperatures():
690    """
691    Uses ectool to return a list of all sensor temperatures in Celsius.
692    """
693    temperatures = []
694    try:
695        full_cmd = 'ectool temps all'
696        lines = utils.run(full_cmd, verbose=False).stdout.splitlines()
697        for line in lines:
698            temperature = int(line.split(': ')[1]) - 273
699            temperatures.append(temperature)
700    except Exception:
701        logging.warning('Unable to read temperature sensors using ectool.')
702    for temperature in temperatures:
703        # Sanity check for real world values.
704        assert ((temperature > 10.0) and
705                (temperature < 150.0)), ('Unreasonable temperature %.1fC.' %
706                                         temperature)
707
708    return temperatures
709
710
711def get_current_temperature_max():
712    """
713    Returns the highest reported board temperature (all sensors) in Celsius.
714    """
715    temperature = max([get_temperature_input_max()] +
716                      get_thermal_zone_temperatures() +
717                      get_ec_temperatures())
718    # Sanity check for real world values.
719    assert ((temperature > 10.0) and
720            (temperature < 150.0)), ('Unreasonable temperature %.1fC.' %
721                                     temperature)
722    return temperature
723
724
725def get_cpu_cache_size():
726    """
727    Returns the last level CPU cache size in kBytes.
728    """
729    cache_size = _get_int_from_file(_CPUINFO, 'cache size', ': ', ' KB')
730    # Sanity check.
731    assert cache_size >= 64, 'Unreasonably small cache.'
732    return cache_size
733
734
735def get_cpu_model_frequency():
736    """
737    Returns the model frequency from the CPU model name on Intel only. This
738    might be redundant with get_cpu_max_frequency. Unit is Hz.
739    """
740    frequency = _get_float_from_file(_CPUINFO, 'model name', ' @ ', 'GHz')
741    return 1.e9 * frequency
742
743
744def get_cpu_max_frequency():
745    """
746    Returns the largest of the max CPU core frequencies. The unit is Hz.
747    """
748    max_frequency = -1
749    paths = _get_cpufreq_paths('cpuinfo_max_freq')
750    for path in paths:
751        # Convert from kHz to Hz.
752        frequency = 1000 * _get_float_from_file(path, 0, None, None)
753        max_frequency = max(frequency, max_frequency)
754    # Sanity check.
755    assert max_frequency > 1e8, 'Unreasonably low CPU frequency.'
756    return max_frequency
757
758
759def get_cpu_min_frequency():
760    """
761    Returns the smallest of the minimum CPU core frequencies.
762    """
763    min_frequency = 1e20
764    paths = _get_cpufreq_paths('cpuinfo_min_freq')
765    for path in paths:
766        frequency = _get_float_from_file(path, 0, None, None)
767        min_frequency = min(frequency, min_frequency)
768    # Sanity check.
769    assert min_frequency > 1e8, 'Unreasonably low CPU frequency.'
770    return min_frequency
771
772
773def get_cpu_model():
774    """
775    Returns the CPU model.
776    Only works on Intel.
777    """
778    cpu_model = _get_int_from_file(_CPUINFO, 'model\t', ': ', None)
779    return cpu_model
780
781
782def get_cpu_family():
783    """
784    Returns the CPU family.
785    Only works on Intel.
786    """
787    cpu_family = _get_int_from_file(_CPUINFO, 'cpu family\t', ': ', None)
788    return cpu_family
789
790
791def get_board_property(key):
792    """
793    Get a specific property from /etc/lsb-release.
794
795    @param key: board property to return value for
796
797    @return the value or '' if not present
798    """
799    with open('/etc/lsb-release') as f:
800        pattern = '%s=(.*)' % key
801        pat = re.search(pattern, f.read())
802        if pat:
803            return pat.group(1)
804    return ''
805
806
807def get_board():
808    """
809    Get the ChromeOS release board name from /etc/lsb-release.
810    """
811    return get_board_property('BOARD')
812
813
814def get_board_type():
815    """
816    Get the ChromeOS board type from /etc/lsb-release.
817
818    @return device type.
819    """
820    return get_board_property('DEVICETYPE')
821
822
823def get_board_with_frequency_and_memory():
824    """
825    Returns a board name modified with CPU frequency and memory size to
826    differentiate between different board variants. For instance
827    link -> link_1.8GHz_4GB.
828    """
829    board_name = get_board()
830    if is_virtual_machine():
831        board = '%s_VM' % board_name
832    else:
833        # Rounded to nearest GB and GHz.
834        memory = int(round(get_mem_total() / 1024.0))
835        # Convert frequency to GHz with 1 digit accuracy after the decimal point.
836        frequency = int(round(get_cpu_max_frequency() * 1e-8)) * 0.1
837        board = '%s_%1.1fGHz_%dGB' % (board_name, frequency, memory)
838    return board
839
840
841def get_mem_total():
842    """
843    Returns the total memory available in the system in MBytes.
844    """
845    mem_total = _get_float_from_file(_MEMINFO, 'MemTotal:', 'MemTotal:', ' kB')
846    # Sanity check, all Chromebooks have at least 1GB of memory.
847    assert mem_total > 256 * 1024, 'Unreasonable amount of memory.'
848    return mem_total / 1024
849
850
851def get_mem_free():
852    """
853    Returns the currently free memory in the system in MBytes.
854    """
855    mem_free = _get_float_from_file(_MEMINFO, 'MemFree:', 'MemFree:', ' kB')
856    return mem_free / 1024
857
858
859def get_kernel_max():
860    """
861    Returns content of kernel_max.
862    """
863    kernel_max = _get_int_from_file(_KERNEL_MAX, 0, None, None)
864    # Sanity check.
865    assert ((kernel_max > 0) and (kernel_max < 257)), 'Unreasonable kernel_max.'
866    return kernel_max
867
868
869def set_high_performance_mode():
870    """
871    Sets the kernel governor mode to the highest setting.
872    Returns previous governor state.
873    """
874    original_governors = get_scaling_governor_states()
875    set_scaling_governors('performance')
876    return original_governors
877
878
879def set_scaling_governors(value):
880    """
881    Sets all scaling governor to string value.
882    Sample values: 'performance', 'interactive', 'ondemand', 'powersave'.
883    """
884    paths = _get_cpufreq_paths('scaling_governor')
885    for path in paths:
886        cmd = 'echo %s > %s' % (value, path)
887        logging.info('Writing scaling governor mode \'%s\' -> %s', value, path)
888        # On Tegra CPUs can be dynamically enabled/disabled. Ignore failures.
889        utils.system(cmd, ignore_status=True)
890
891
892def _get_cpufreq_paths(filename):
893    """
894    Returns a list of paths to the governors.
895    """
896    cmd = 'ls /sys/devices/system/cpu/cpu*/cpufreq/' + filename
897    paths = utils.run(cmd, verbose=False).stdout.splitlines()
898    return paths
899
900
901def get_scaling_governor_states():
902    """
903    Returns a list of (performance governor path, current state) tuples.
904    """
905    paths = _get_cpufreq_paths('scaling_governor')
906    path_value_list = []
907    for path in paths:
908        value = _get_line_from_file(path, 0)
909        path_value_list.append((path, value))
910    return path_value_list
911
912
913def restore_scaling_governor_states(path_value_list):
914    """
915    Restores governor states. Inverse operation to get_scaling_governor_states.
916    """
917    for (path, value) in path_value_list:
918        cmd = 'echo %s > %s' % (value.rstrip('\n'), path)
919        # On Tegra CPUs can be dynamically enabled/disabled. Ignore failures.
920        utils.system(cmd, ignore_status=True)
921
922
923def get_dirty_writeback_centisecs():
924    """
925    Reads /proc/sys/vm/dirty_writeback_centisecs.
926    """
927    time = _get_int_from_file(_DIRTY_WRITEBACK_CENTISECS, 0, None, None)
928    return time
929
930
931def set_dirty_writeback_centisecs(time=60000):
932    """
933    In hundredths of a second, this is how often pdflush wakes up to write data
934    to disk. The default wakes up the two (or more) active threads every five
935    seconds. The ChromeOS default is 10 minutes.
936
937    We use this to set as low as 1 second to flush error messages in system
938    logs earlier to disk.
939    """
940    # Flush buffers first to make this function synchronous.
941    utils.system('sync')
942    if time >= 0:
943        cmd = 'echo %d > %s' % (time, _DIRTY_WRITEBACK_CENTISECS)
944        utils.system(cmd)
945
946
947def wflinfo_cmd():
948    """
949    Return a wflinfo command appropriate to the current graphics platform/api.
950    """
951    return 'wflinfo -p %s -a %s' % (graphics_platform(), graphics_api())
952
953
954def get_gpu_family():
955    """Return the GPU family name"""
956    global pciid_to_intel_architecture
957
958    cpuarch = base_utils.get_cpu_soc_family()
959    if cpuarch == 'exynos5' or cpuarch == 'rockchip':
960        cmd = wflinfo_cmd()
961        wflinfo = utils.system_output(cmd,
962                                      retain_output=True,
963                                      ignore_status=False)
964        version = re.findall(r'OpenGL renderer string: '
965                             r'Mali-T([0-9]+)', wflinfo)
966        if version:
967            return 'mali-t%s' % version[0]
968        return 'mali-unrecognized'
969    if cpuarch == 'tegra':
970        return 'tegra'
971    if os.path.exists('/sys/bus/platform/drivers/pvrsrvkm'):
972        return 'rogue'
973
974    pci_path = '/sys/bus/pci/devices/0000:00:02.0/device'
975
976    if not os.path.exists(pci_path):
977        raise error.TestError('PCI device 0000:00:02.0 not found')
978
979    device_id = utils.read_one_line(pci_path).lower()
980
981    # Only load Intel PCI ID file once and only if necessary.
982    if not pciid_to_intel_architecture:
983        with open(_INTEL_PCI_IDS_FILE_PATH, 'r') as in_f:
984            pciid_to_intel_architecture = json.load(in_f)
985
986    return pciid_to_intel_architecture[device_id]
987
988# TODO(ihf): Consider using /etc/lsb-release DEVICETYPE != CHROMEBOOK/CHROMEBASE
989# for sanity check, but usage seems a bit inconsistent. See
990# src/third_party/chromiumos-overlay/eclass/appid.eclass
991_BOARDS_WITHOUT_MONITOR = [
992    'anglar', 'mccloud', 'monroe', 'ninja', 'rikku', 'guado', 'jecht', 'tidus',
993    'veyron_brian', 'beltino', 'panther', 'stumpy', 'panther', 'tricky', 'zako',
994    'veyron_rialto'
995]
996
997
998def has_no_monitor():
999    """Return whether a machine doesn't have a built-in monitor"""
1000    board_name = get_board()
1001    if board_name in _BOARDS_WITHOUT_MONITOR:
1002        return True
1003
1004    return False
1005
1006
1007def get_fixed_dst_drive():
1008    """
1009    Return device name for internal disk.
1010    Example: return /dev/sda for falco booted from usb
1011    """
1012    cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
1013                    '. /usr/share/misc/chromeos-common.sh;',
1014                    'load_base_vars;',
1015                    'get_fixed_dst_drive'])
1016    return utils.system_output(cmd)
1017
1018
1019def get_root_device():
1020    """
1021    Return root device.
1022    Will return correct disk device even system boot from /dev/dm-0
1023    Example: return /dev/sdb for falco booted from usb
1024    """
1025    return utils.system_output('rootdev -s -d')
1026
1027
1028def get_root_partition():
1029    """
1030    Return current root partition
1031    Example: return /dev/sdb3 for falco booted from usb
1032    """
1033    return utils.system_output('rootdev -s')
1034
1035
1036def get_free_root_partition(root_part=None):
1037    """
1038    Return currently unused root partion
1039    Example: return /dev/sdb5 for falco booted from usb
1040
1041    @param root_part: cuurent root partition
1042    """
1043    spare_root_map = {'3': '5', '5': '3'}
1044    if not root_part:
1045        root_part = get_root_partition()
1046    return root_part[:-1] + spare_root_map[root_part[-1]]
1047
1048
1049def is_booted_from_internal_disk():
1050    """Return True if boot from internal disk. False, otherwise."""
1051    return get_root_device() == get_fixed_dst_drive()
1052
1053
1054def get_ui_use_flags():
1055    """Parses the USE flags as listed in /etc/ui_use_flags.txt.
1056
1057    @return: A list of flag strings found in the ui use flags file.
1058    """
1059    flags = []
1060    for flag in utils.read_file(_UI_USE_FLAGS_FILE_PATH).splitlines():
1061        # Removes everything after the '#'.
1062        flag_before_comment = flag.split('#')[0].strip()
1063        if len(flag_before_comment) != 0:
1064            flags.append(flag_before_comment)
1065
1066    return flags
1067
1068
1069def is_freon():
1070    """Returns False if the system uses X, True otherwise."""
1071    return 'X' not in get_ui_use_flags()
1072
1073
1074def graphics_platform():
1075    """
1076    Return a string identifying the graphics platform,
1077    e.g. 'glx' or 'x11_egl' or 'gbm'
1078    """
1079    use_flags = get_ui_use_flags()
1080    if 'X' not in use_flags:
1081        return 'null'
1082    elif 'opengles' in use_flags:
1083        return 'x11_egl'
1084    return 'glx'
1085
1086
1087def graphics_api():
1088    """Return a string identifying the graphics api, e.g. gl or gles2."""
1089    use_flags = get_ui_use_flags()
1090    if 'opengles' in use_flags:
1091        return 'gles2'
1092    return 'gl'
1093
1094
1095def assert_has_X_server():
1096    """Using X is soon to be deprecated. Print warning or raise error."""
1097    if is_freon():
1098        # TODO(ihf): Think about if we could support X for testing for a while.
1099        raise error.TestFail('freon: can\'t use X server.')
1100    logging.warning('freon: Using the X server will be deprecated soon.')
1101
1102
1103def is_vm():
1104    """Check if the process is running in a virtual machine.
1105
1106    @return: True if the process is running in a virtual machine, otherwise
1107             return False.
1108    """
1109    try:
1110        virt = utils.run('sudo -n virt-what').stdout.strip()
1111        logging.debug('virt-what output: %s', virt)
1112        return bool(virt)
1113    except error.CmdError:
1114        logging.warn('Package virt-what is not installed, default to assume '
1115                     'it is not a virtual machine.')
1116        return False
1117