• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7Convenience functions for use by tests or whomever.
8"""
9
10# pylint: disable=missing-docstring
11
12from __future__ import absolute_import
13from __future__ import division
14from __future__ import print_function
15
16import base64
17import chardet
18import collections
19import errno
20import glob
21import json
22import logging
23import math
24import multiprocessing
25import os
26import platform
27import re
28import shutil
29import signal
30import string
31import subprocess
32import sys
33import tempfile
34import time
35import uuid
36
37from autotest_lib.client.common_lib import error
38from autotest_lib.client.common_lib import magic
39from autotest_lib.client.common_lib import utils
40from autotest_lib.client.common_lib.cros import cros_config
41
42from autotest_lib.client.common_lib.utils import *
43import six
44from six.moves import map
45from six.moves import range
46from six.moves import zip
47
48
49def grep(pattern, file):
50    """
51    This is mainly to fix the return code inversion from grep
52    Also handles compressed files.
53
54    returns 1 if the pattern is present in the file, 0 if not.
55    """
56    command = 'grep "%s" > /dev/null' % pattern
57    ret = cat_file_to_cmd(file, command, ignore_status=True)
58    return not ret
59
60
61def cat_file_to_cmd(file, command, ignore_status=0, return_output=False):
62    """
63    equivalent to 'cat file | command' but knows to use
64    zcat or bzcat if appropriate
65    """
66    if not os.path.isfile(file):
67        raise NameError('invalid file %s to cat to command %s'
68                % (file, command))
69
70    if return_output:
71        run_cmd = utils.system_output
72    else:
73        run_cmd = utils.system
74
75    if magic.guess_type(file) == 'application/x-bzip2':
76        cat = 'bzcat'
77    elif magic.guess_type(file) == 'application/x-gzip':
78        cat = 'zcat'
79    else:
80        cat = 'cat'
81    return run_cmd('%s %s | %s' % (cat, file, command),
82                   ignore_status=ignore_status)
83
84
85def extract_tarball_to_dir(tarball, dir):
86    """
87    Extract a tarball to a specified directory name instead of whatever
88    the top level of a tarball is - useful for versioned directory names, etc
89    """
90    if os.path.exists(dir):
91        if os.path.isdir(dir):
92            shutil.rmtree(dir)
93        else:
94            os.remove(dir)
95    pwd = os.getcwd()
96    os.chdir(os.path.dirname(os.path.abspath(dir)))
97    newdir = extract_tarball(tarball)
98    os.rename(newdir, dir)
99    os.chdir(pwd)
100
101
102def extract_tarball(tarball):
103    """Returns the directory extracted by the tarball."""
104    extracted = cat_file_to_cmd(tarball, 'tar xvf - 2>/dev/null',
105                                    return_output=True).splitlines()
106
107    dir = None
108
109    for line in extracted:
110        if line.startswith('./'):
111            line = line[2:]
112        if not line or line == '.':
113            continue
114        topdir = line.split('/')[0]
115        if os.path.isdir(topdir):
116            if dir:
117                assert(dir == topdir), 'tarball must be a a single directory'
118            else:
119                dir = topdir
120    if dir:
121        return dir
122    else:
123        raise NameError('extracting tarball produced no dir')
124
125
126def force_copy(src, dest):
127    """Replace dest with a new copy of src, even if it exists"""
128    if os.path.isfile(dest):
129        os.remove(dest)
130    if os.path.isdir(dest):
131        dest = os.path.join(dest, os.path.basename(src))
132    shutil.copyfile(src, dest)
133    return dest
134
135
136def file_contains_pattern(file, pattern):
137    """Return true if file contains the specified egrep pattern"""
138    if not os.path.isfile(file):
139        raise NameError('file %s does not exist' % file)
140    return not utils.system('egrep -q "' + pattern + '" ' + file,
141                            ignore_status=True)
142
143
144def list_grep(list, pattern):
145    """True if any item in list matches the specified pattern."""
146    compiled = re.compile(pattern)
147    for line in list:
148        match = compiled.search(line)
149        if (match):
150            return 1
151    return 0
152
153
154def get_os_vendor():
155    """Try to guess what's the os vendor
156    """
157    if os.path.isfile('/etc/SuSE-release'):
158        return 'SUSE'
159
160    issue = '/etc/issue'
161
162    if not os.path.isfile(issue):
163        return 'Unknown'
164
165    if file_contains_pattern(issue, 'Red Hat'):
166        return 'Red Hat'
167    elif file_contains_pattern(issue, 'Fedora'):
168        return 'Fedora Core'
169    elif file_contains_pattern(issue, 'SUSE'):
170        return 'SUSE'
171    elif file_contains_pattern(issue, 'Ubuntu'):
172        return 'Ubuntu'
173    elif file_contains_pattern(issue, 'Debian'):
174        return 'Debian'
175    else:
176        return 'Unknown'
177
178
179def get_cc():
180    try:
181        return os.environ['CC']
182    except KeyError:
183        return 'gcc'
184
185
186def get_vmlinux():
187    """Return the full path to vmlinux
188
189    Ahem. This is crap. Pray harder. Bad Martin.
190    """
191    vmlinux = '/boot/vmlinux-%s' % utils.system_output('uname -r')
192    if os.path.isfile(vmlinux):
193        return vmlinux
194    vmlinux = '/lib/modules/%s/build/vmlinux' % utils.system_output('uname -r')
195    if os.path.isfile(vmlinux):
196        return vmlinux
197    return None
198
199
200def get_systemmap():
201    """Return the full path to System.map
202
203    Ahem. This is crap. Pray harder. Bad Martin.
204    """
205    map = '/boot/System.map-%s' % utils.system_output('uname -r')
206    if os.path.isfile(map):
207        return map
208    map = '/lib/modules/%s/build/System.map' % utils.system_output('uname -r')
209    if os.path.isfile(map):
210        return map
211    return None
212
213
214def get_modules_dir():
215    """Return the modules dir for the running kernel version"""
216    kernel_version = utils.system_output('uname -r')
217    return '/lib/modules/%s/kernel' % kernel_version
218
219
220_CPUINFO_RE = re.compile(r'^(?P<key>[^\t]*)\t*: ?(?P<value>.*)$')
221
222
223def get_cpuinfo():
224    """Read /proc/cpuinfo and convert to a list of dicts."""
225    cpuinfo = []
226    with open('/proc/cpuinfo', 'r') as f:
227        cpu = {}
228        for line in f:
229            line = line.strip()
230            if not line:
231                cpuinfo.append(cpu)
232                cpu = {}
233                continue
234            match = _CPUINFO_RE.match(line)
235            cpu[match.group('key')] = match.group('value')
236        if cpu:
237            # cpuinfo usually ends in a blank line, so this shouldn't happen.
238            cpuinfo.append(cpu)
239    return cpuinfo
240
241
242def get_cpu_arch():
243    """Work out which CPU architecture we're running on"""
244
245    # Using 'uname -m' should be a very portable way to do this since the
246    # format is pretty standard.
247    machine_name = utils.system_output('uname -m').strip()
248
249    # Apparently ARM64 and ARM have both historically returned the string 'arm'
250    # here so continue the tradition.  Use startswith() because:
251    # - On most of our arm devices we'll actually see the string armv7l.
252    # - In theory the machine name could include a suffix for endianness.
253    if machine_name.startswith('aarch64') or machine_name.startswith('arm'):
254        return 'arm'
255
256    # Historically we _have_ treated x86_64 and i386 separately.
257    if machine_name in ('x86_64', 'i386'):
258        return machine_name
259
260    raise error.TestError('unsupported machine type %s' % machine_name)
261
262
263def get_arm_soc_family_from_devicetree():
264    """
265    Work out which ARM SoC we're running on based on the 'compatible' property
266    of the base node of devicetree, if it exists.
267    """
268    devicetree_compatible = '/sys/firmware/devicetree/base/compatible'
269    if not os.path.isfile(devicetree_compatible):
270        return None
271    f = open(devicetree_compatible, 'r')
272    compatible = f.read().split(chr(0))
273    f.close()
274    if list_grep(compatible, '^rockchip,'):
275        return 'rockchip'
276    elif list_grep(compatible, '^mediatek,'):
277        return 'mediatek'
278    elif list_grep(compatible, '^qcom,'):
279        return 'qualcomm'
280    return None
281
282
283def get_arm_soc_family():
284    """Work out which ARM SoC we're running on"""
285    family = get_arm_soc_family_from_devicetree()
286    if family is not None:
287        return family
288
289    f = open('/proc/cpuinfo', 'r')
290    cpuinfo = f.readlines()
291    f.close()
292    if list_grep(cpuinfo, 'EXYNOS5'):
293        return 'exynos5'
294    elif list_grep(cpuinfo, 'Tegra'):
295        return 'tegra'
296    elif list_grep(cpuinfo, 'Rockchip'):
297        return 'rockchip'
298    return 'arm'
299
300
301def get_cpu_soc_family():
302    """Like get_cpu_arch, but for ARM, returns the SoC family name"""
303    f = open('/proc/cpuinfo', 'r')
304    cpuinfo = f.readlines()
305    f.close()
306    family = get_cpu_arch()
307    if family == 'arm':
308        family = get_arm_soc_family()
309    if list_grep(cpuinfo, '^vendor_id.*:.*AMD'):
310        family = 'amd'
311    return family
312
313
314# When adding entries here, also add them at the right spot in the
315# INTEL_*_ORDER lists below.
316INTEL_UARCH_TABLE = {
317    '06_9A': 'Alder Lake',
318    '06_4C': 'Airmont',
319    '06_1C': 'Atom',
320    '06_26': 'Atom',
321    '06_27': 'Atom',
322    '06_35': 'Atom',
323    '06_36': 'Atom',
324    '06_3D': 'Broadwell',
325    '06_47': 'Broadwell',
326    '06_4F': 'Broadwell',
327    '06_56': 'Broadwell',
328    '06_A5': 'Comet Lake',
329    '06_A6': 'Comet Lake',
330    '06_0D': 'Dothan',
331    '06_5C': 'Goldmont',
332    '06_7A': 'Goldmont',
333    '06_3C': 'Haswell',
334    '06_45': 'Haswell',
335    '06_46': 'Haswell',
336    '06_3F': 'Haswell-E',
337    '06_7D': 'Ice Lake',
338    '06_7E': 'Ice Lake',
339    '06_3A': 'Ivy Bridge',
340    '06_3E': 'Ivy Bridge-E',
341    '06_8E': 'Kaby Lake',
342    '06_9E': 'Kaby Lake',
343    '06_0F': 'Merom',
344    '06_16': 'Merom',
345    '06_17': 'Nehalem',
346    '06_1A': 'Nehalem',
347    '06_1D': 'Nehalem',
348    '06_1E': 'Nehalem',
349    '06_1F': 'Nehalem',
350    '06_2E': 'Nehalem',
351    '0F_03': 'Prescott',
352    '0F_04': 'Prescott',
353    '0F_06': 'Presler',
354    '06_2A': 'Sandy Bridge',
355    '06_2D': 'Sandy Bridge',
356    '06_37': 'Silvermont',
357    '06_4A': 'Silvermont',
358    '06_4D': 'Silvermont',
359    '06_5A': 'Silvermont',
360    '06_5D': 'Silvermont',
361    '06_4E': 'Skylake',
362    '06_5E': 'Skylake',
363    '06_55': 'Skylake',
364    '06_8C': 'Tiger Lake',
365    '06_8D': 'Tiger Lake',
366    '06_86': 'Tremont',
367    '06_96': 'Tremont',
368    '06_9C': 'Tremont',
369    '06_25': 'Westmere',
370    '06_2C': 'Westmere',
371    '06_2F': 'Westmere',
372}
373
374INTEL_ATOM_ORDER = ['Silvermont', 'Airmont', 'Goldmont', 'Tremont', 'Gracemont']
375
376INTEL_BIGCORE_ORDER = [
377        'Prescott', 'Presler', 'Dothan', 'Merom', 'Nehalem', 'Westmere',
378        'Sandy Bridge', 'Ivy Bridge', 'Ivy Bridge-E', 'Haswell', 'Haswell-E',
379        'Broadwell', 'Skylake', 'Kaby Lake', 'Coffee Lake', 'Whiskey Lake',
380        'Cannon Lake', 'Comet Lake', 'Ice Lake', 'Tiger Lake', 'Alder Lake'
381]
382
383
384def get_intel_cpu_uarch(numeric=False):
385    """Return the Intel microarchitecture we're running on, or None.
386
387    Returns None if this is not an Intel CPU. Returns the family and model as
388    underscore-separated hex (per Intel manual convention) if the uarch is not
389    known, or if numeric is True.
390    """
391    if not get_current_kernel_arch().startswith('x86'):
392        return None
393    cpuinfo = get_cpuinfo()[0]
394    if cpuinfo['vendor_id'] != 'GenuineIntel':
395        return None
396    family_model = '%02X_%02X' % (int(cpuinfo['cpu family']),
397                                  int(cpuinfo['model']))
398    if numeric:
399        return family_model
400    return INTEL_UARCH_TABLE.get(family_model, family_model)
401
402
403def is_intel_uarch_older_than(reference):
404    """Returns True if the DUT's is older than reference, False otherwise.
405
406    Raises a test error exception if the uarch is unknown to make developers
407    add entries to the tables above.
408    """
409
410    uarch = get_intel_cpu_uarch()
411    if uarch is None:
412        raise error.TestError("Doing Intel test for non-Intel hardware.")
413
414    if "_" in uarch:
415        raise error.TestError("Intel uarch unknown. Add to tables.")
416
417    if reference not in INTEL_BIGCORE_ORDER and reference not in INTEL_ATOM_ORDER:
418        raise error.TestError("Testing for unknown reference Intel uarch.")
419
420    result = False
421
422    if reference in INTEL_BIGCORE_ORDER:
423        for v in INTEL_BIGCORE_ORDER:
424            if v == reference:
425                break
426            if v == uarch:
427                result = True
428
429    elif reference in INTEL_ATOM_ORDER:
430        for v in INTEL_ATOM_ORDER:
431            if v == reference:
432                break
433            if v == uarch:
434                result = True
435
436    return result
437
438
439INTEL_SILVERMONT_BCLK_TABLE = [83333, 100000, 133333, 116667, 80000]
440
441
442def get_intel_bclk_khz():
443    """Return Intel CPU base clock.
444
445    This only worked with SandyBridge (released in 2011) or newer. Older CPU has
446    133 MHz bclk. See turbostat code for implementation that also works with
447    older CPU. https://git.io/vpyKT
448    """
449    if get_intel_cpu_uarch() == 'Silvermont':
450        MSR_FSB_FREQ = 0xcd
451        return INTEL_SILVERMONT_BCLK_TABLE[utils.rdmsr(MSR_FSB_FREQ) & 0xf]
452    return 100000
453
454
455def get_energy_usage():
456    """On Intel chips that support it, return the energy usage."""
457    if get_intel_cpu_uarch() == None:
458        return 0
459
460    with open('/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj') as fd:
461        return fd.readline()
462
463
464def get_current_kernel_arch():
465    """Get the machine architecture, now just a wrap of 'uname -m'."""
466    return os.popen('uname -m').read().rstrip()
467
468
469def count_cpus():
470    """number of CPUs in the local machine according to /proc/cpuinfo"""
471    try:
472        return multiprocessing.cpu_count()
473    except Exception:
474        logging.exception('can not get cpu count from'
475                          ' multiprocessing.cpu_count()')
476    cpuinfo = get_cpuinfo()
477    # Returns at least one cpu. Check comment #1 in crosbug.com/p/9582.
478    return len(cpuinfo) or 1
479
480
481def cpu_online_map():
482    """
483    Check out the available cpu online map
484    """
485    cpuinfo = get_cpuinfo()
486    cpus = []
487    for cpu in cpuinfo:
488        cpus.append(cpu['processor'])  # grab cpu number
489    return cpus
490
491
492# Returns total memory in kb
493def read_from_meminfo(key):
494    meminfo = utils.system_output('grep %s /proc/meminfo' % key)
495    return int(re.search(r'\d+', meminfo).group(0))
496
497
498def memtotal():
499    return read_from_meminfo('MemTotal')
500
501
502def freememtotal():
503    return read_from_meminfo('MemFree')
504
505def usable_memtotal():
506    # Reserved 5% for OS use
507    return int(read_from_meminfo('MemFree') * 0.95)
508
509def swaptotal():
510    return read_from_meminfo('SwapTotal')
511
512def rounded_memtotal():
513    # Get total of all physical mem, in kbytes
514    usable_kbytes = memtotal()
515    # usable_kbytes is system's usable DRAM in kbytes,
516    #   as reported by memtotal() from device /proc/meminfo memtotal
517    #   after Linux deducts 1.5% to 5.1% for system table overhead
518    # Undo the unknown actual deduction by rounding up
519    #   to next small multiple of a big power-of-two
520    #   eg  12GB - 5.1% gets rounded back up to 12GB
521    mindeduct = 0.015  # 1.5 percent
522    maxdeduct = 0.055  # 5.5 percent
523    # deduction range 1.5% .. 5.5% supports physical mem sizes
524    #    6GB .. 12GB in steps of .5GB
525    #   12GB .. 24GB in steps of 1 GB
526    #   24GB .. 48GB in steps of 2 GB ...
527    # Finer granularity in physical mem sizes would require
528    #   tighter spread between min and max possible deductions
529
530    # increase mem size by at least min deduction, without rounding
531    min_kbytes = int(usable_kbytes / (1.0 - mindeduct))
532    # increase mem size further by 2**n rounding, by 0..roundKb or more
533    round_kbytes = int(usable_kbytes / (1.0 - maxdeduct)) - min_kbytes
534    # find least binary roundup 2**n that covers worst-cast roundKb
535    mod2n = 1 << int(math.ceil(math.log(round_kbytes, 2)))
536    # have round_kbytes <= mod2n < round_kbytes*2
537    # round min_kbytes up to next multiple of mod2n
538    phys_kbytes = min_kbytes + mod2n - 1
539    phys_kbytes = phys_kbytes - (phys_kbytes % mod2n)  # clear low bits
540    return phys_kbytes
541
542
543_MEMINFO_RE = re.compile('^(\w+)(\(\w+\))?:\s+(\d+)')
544
545
546def get_meminfo():
547    """Returns a namedtuple of pairs from /proc/meminfo.
548
549    Example /proc/meminfo snippets:
550        MemTotal:        2048000 kB
551        Active(anon):     409600 kB
552    Example usage:
553        meminfo = utils.get_meminfo()
554        print meminfo.Active_anon
555    """
556    info = {}
557    with _open_file('/proc/meminfo') as f:
558        for line in f:
559            m = _MEMINFO_RE.match(line)
560            if m:
561                if m.group(2):
562                    name = m.group(1) + '_' + m.group(2)[1:-1]
563                else:
564                    name = m.group(1)
565                info[name] = int(m.group(3))
566    return collections.namedtuple('MemInfo', list(info.keys()))(**info)
567
568
569def sysctl(key, value=None):
570    """Generic implementation of sysctl, to read and write.
571
572    @param key: A location under /proc/sys
573    @param value: If not None, a value to write into the sysctl.
574
575    @return The single-line sysctl value as a string.
576    """
577    path = '/proc/sys/%s' % key
578    if value is not None:
579        utils.write_one_line(path, str(value))
580    return utils.read_one_line(path)
581
582
583def sysctl_kernel(key, value=None):
584    """(Very) partial implementation of sysctl, for kernel params"""
585    if value is not None:
586        # write
587        utils.write_one_line('/proc/sys/kernel/%s' % key, str(value))
588    else:
589        # read
590        out = utils.read_one_line('/proc/sys/kernel/%s' % key)
591        return int(re.search(r'\d+', out).group(0))
592
593
594def get_num_allocated_file_handles():
595    """
596    Returns the number of currently allocated file handles.
597
598    Gets this information by parsing /proc/sys/fs/file-nr.
599    See https://www.kernel.org/doc/Documentation/sysctl/fs.txt
600    for details on this file.
601    """
602    with _open_file('/proc/sys/fs/file-nr') as f:
603        line = f.readline()
604    allocated_handles = int(line.split()[0])
605    return allocated_handles
606
607
608def dump_object(object):
609    """Dump an object's attributes and methods
610
611    kind of like dir()
612    """
613    for item in six.iteritems(object.__dict__):
614        print(item)
615        try:
616            (key, value) = item
617            dump_object(value)
618        except:
619            continue
620
621
622def environ(env_key):
623    """return the requested environment variable, or '' if unset"""
624    if (env_key in os.environ):
625        return os.environ[env_key]
626    else:
627        return ''
628
629
630def prepend_path(newpath, oldpath):
631    """prepend newpath to oldpath"""
632    if (oldpath):
633        return newpath + ':' + oldpath
634    else:
635        return newpath
636
637
638def append_path(oldpath, newpath):
639    """append newpath to oldpath"""
640    if (oldpath):
641        return oldpath + ':' + newpath
642    else:
643        return newpath
644
645
646_TIME_OUTPUT_RE = re.compile(
647        r'([\d\.]*)user ([\d\.]*)system '
648        r'(\d*):([\d\.]*)elapsed (\d*)%CPU')
649
650
651def to_seconds(time_string):
652    """Converts a string in M+:SS.SS format to S+.SS"""
653    elts = time_string.split(':')
654    if len(elts) == 1:
655        return time_string
656    return str(int(elts[0]) * 60 + float(elts[1]))
657
658
659_TIME_OUTPUT_RE_2 = re.compile(r'(.*?)user (.*?)system (.*?)elapsed')
660
661
662def running_config():
663    """
664    Return path of config file of the currently running kernel
665    """
666    version = utils.system_output('uname -r')
667    for config in ('/proc/config.gz', \
668                   '/boot/config-%s' % version,
669                   '/lib/modules/%s/build/.config' % version):
670        if os.path.isfile(config):
671            return config
672    return None
673
674
675def check_for_kernel_feature(feature):
676    config = running_config()
677
678    if not config:
679        raise TypeError("Can't find kernel config file")
680
681    if magic.guess_type(config) == 'application/x-gzip':
682        grep = 'zgrep'
683    else:
684        grep = 'grep'
685    grep += ' ^CONFIG_%s= %s' % (feature, config)
686
687    if not utils.system_output(grep, ignore_status=True):
688        raise ValueError("Kernel doesn't have a %s feature" % (feature))
689
690
691def check_glibc_ver(ver):
692    try:
693        glibc_ver = subprocess.check_output("ldd --version", shell=True)
694    except subprocess.CalledProcessError:
695        # To mimic previous behavior, if the command errors set the result to
696        # an empty str
697        glibc_ver = ''
698    glibc_ver = glibc_ver.splitlines()[0].decode()
699    glibc_ver = re.search(r'(\d+\.\d+(\.\d+)?)', glibc_ver).group()
700    if utils.compare_versions(glibc_ver, ver) == -1:
701        raise error.TestError("Glibc too old (%s). Glibc >= %s is needed." %
702                              (glibc_ver, ver))
703
704def check_kernel_ver(ver):
705    kernel_ver = utils.system_output('uname -r')
706    kv_tmp = re.split(r'[-]', kernel_ver)[0:3]
707    # In compare_versions, if v1 < v2, return value == -1
708    if utils.compare_versions(kv_tmp[0], ver) == -1:
709        raise error.TestError("Kernel too old (%s). Kernel > %s is needed." %
710                              (kernel_ver, ver))
711
712
713def numa_nodes():
714    node_paths = glob.glob('/sys/devices/system/node/node*')
715    nodes = [int(re.sub(r'.*node(\d+)', r'\1', x)) for x in node_paths]
716    return (sorted(nodes))
717
718
719# Return the kernel version and build timestamp.
720def running_os_release():
721    return os.uname()[2:4]
722
723
724def running_os_ident():
725    (version, timestamp) = running_os_release()
726    return version + '::' + timestamp
727
728
729def freespace(path):
730    """Return the disk free space, in bytes"""
731    s = os.statvfs(path)
732    return s.f_bavail * s.f_bsize
733
734
735_DISK_PARTITION_3_RE = re.compile(r'^(/dev/hd[a-z]+)3', re.M)
736
737
738def get_disk_size(disk_name):
739    """
740    Return size of disk in byte. Return 0 in Error Case
741
742    @param disk_name: disk name to find size
743    """
744    device = os.path.basename(disk_name)
745    with open('/proc/partitions') as f:
746        lines = f.readlines()
747    for line in lines:
748        try:
749            _, _, blocks, name = re.split(r' +', line.strip())
750        except ValueError:
751            continue
752        if name == device:
753            return 1024 * int(blocks)
754    return 0
755
756
757def get_disk_size_gb(disk_name):
758    """
759    Return size of disk in GB (10^9). Return 0 in Error Case
760
761    @param disk_name: disk name to find size
762    """
763    return int(get_disk_size(disk_name) / (10.0 ** 9) + 0.5)
764
765
766def get_disk_model(disk_name):
767    """
768    Return model name for internal storage device
769
770    @param disk_name: disk name to find model
771    """
772    cmd1 = 'udevadm info --query=property --name=%s' % disk_name
773    cmd2 = 'grep -E "ID_(NAME|MODEL)="'
774    cmd3 = 'cut -f 2 -d"="'
775    cmd = ' | '.join([cmd1, cmd2, cmd3])
776    return utils.system_output(cmd)
777
778
779_DISK_DEV_RE = re.compile(r'/dev/sd[a-z]|'
780                          r'/dev/mmcblk[0-9]+|'
781                          r'/dev/nvme[0-9]+n[0-9]+')
782
783
784def get_disk_from_filename(filename):
785    """
786    Return the disk device the filename is on.
787    If the file is on tmpfs or other special file systems,
788    return None.
789
790    @param filename: name of file, full path.
791    """
792
793    if not os.path.exists(filename):
794        raise error.TestError('file %s missing' % filename)
795
796    if filename[0] != '/':
797        raise error.TestError('This code works only with full path')
798
799    m = _DISK_DEV_RE.match(filename)
800    while not m:
801        if filename[0] != '/':
802            return None
803        if filename == '/dev/root':
804            cmd = 'rootdev -d -s'
805        elif filename.startswith('/dev/mapper'):
806            cmd = 'dmsetup table "%s"' % os.path.basename(filename)
807            dmsetup_output = utils.system_output(cmd).split(' ')
808            if dmsetup_output[2] == 'verity':
809                maj_min = dmsetup_output[4]
810            elif dmsetup_output[2] == 'crypt':
811                maj_min = dmsetup_output[6]
812            elif dmsetup_output[2] in ['thin', 'thin-pool', 'linear']:
813                maj_min = dmsetup_output[3]
814            cmd = 'realpath "/dev/block/%s"' % maj_min
815        elif filename.startswith('/dev/loop'):
816            cmd = 'losetup -O BACK-FILE "%s" | tail -1' % filename
817        else:
818            cmd = 'df "%s" | tail -1 | cut -f 1 -d" "' % filename
819        filename = utils.system_output(cmd)
820        m = _DISK_DEV_RE.match(filename)
821    return m.group(0)
822
823
824def get_disk_firmware_version(disk_name):
825    """
826    Return firmware version for internal storage device. (empty string for eMMC)
827
828    @param disk_name: disk name to find model
829    """
830    cmd1 = 'udevadm info --query=property --name=%s' % disk_name
831    cmd2 = 'grep -E "ID_REVISION="'
832    cmd3 = 'cut -f 2 -d"="'
833    cmd = ' | '.join([cmd1, cmd2, cmd3])
834    return utils.system_output(cmd)
835
836
837def is_disk_nvme(disk_name):
838    """
839    Return true if disk is a nvme device, return false otherwise
840
841    @param disk_name: disk name to check
842    """
843    return re.match('/dev/nvme[0-9]+n[0-9]+', disk_name)
844
845
846def is_disk_scsi(disk_name):
847    """
848    Return true if disk is a scsi device, return false otherwise
849
850    @param disk_name: disk name check
851    """
852    return re.match('/dev/sd[a-z]+', disk_name)
853
854
855def is_disk_harddisk(disk_name):
856    """
857    Return true if disk is a harddisk, return false otherwise
858
859    @param disk_name: disk name check
860    """
861    cmd1 = 'udevadm info --query=property --name=%s' % disk_name
862    cmd2 = 'grep -E "ID_ATA_ROTATION_RATE_RPM="'
863    cmd3 = 'cut -f 2 -d"="'
864    cmd = ' | '.join([cmd1, cmd2, cmd3])
865
866    rtt = utils.system_output(cmd)
867
868    # eMMC will not have this field; rtt == ''
869    # SSD will have zero rotation rate; rtt == '0'
870    # For harddisk rtt > 0
871    return rtt and int(rtt) > 0
872
873def concat_partition(disk_name, partition_number):
874    """
875    Return the name of a partition:
876    sda, 3 --> sda3
877    mmcblk0, 3 --> mmcblk0p3
878
879    @param disk_name: diskname string
880    @param partition_number: integer
881    """
882    if disk_name.endswith(tuple(str(i) for i in range(0, 10))):
883        sep = 'p'
884    else:
885        sep = ''
886    return disk_name + sep + str(partition_number)
887
888def verify_hdparm_feature(disk_name, feature):
889    """
890    Check for feature support for SCSI disk using hdparm
891
892    @param disk_name: target disk
893    @param feature: hdparm output string of the feature
894    """
895    cmd = 'hdparm -I %s | grep -q "%s"' % (disk_name, feature)
896    ret = utils.system(cmd, ignore_status=True)
897    if ret == 0:
898        return True
899    elif ret == 1:
900        return False
901    else:
902        raise error.TestFail('Error running command %s' % cmd)
903
904def get_nvme_id_ns_feature(disk_name, feature):
905    """
906    Return feature value for NVMe disk using nvme id-ns
907
908    @param disk_name: target disk
909    @param feature: output string of the feature
910    """
911    cmd = "nvme id-ns -n 1 %s | grep %s" % (disk_name, feature)
912    feat = utils.system_output(cmd, ignore_status=True)
913    if not feat:
914        return 'None'
915    start = feat.find(':')
916    value = feat[start+2:]
917    return value
918
919def get_storage_error_msg(disk_name, reason):
920    """
921    Get Error message for storage test which include disk model.
922    and also include the firmware version for the SCSI disk
923
924    @param disk_name: target disk
925    @param reason: Reason of the error.
926    """
927
928    msg = reason
929
930    model = get_disk_model(disk_name)
931    msg += ' Disk model: %s' % model
932
933    if is_disk_scsi(disk_name):
934        fw = get_disk_firmware_version(disk_name)
935        msg += ' firmware: %s' % fw
936
937    return msg
938
939
940_IOSTAT_FIELDS = ('transfers_per_s', 'read_kb_per_s', 'written_kb_per_s',
941                  'read_kb', 'written_kb')
942_IOSTAT_RE = re.compile('ALL' + len(_IOSTAT_FIELDS) * r'\s+([\d\.]+)')
943
944def get_storage_statistics(device=None):
945    """
946    Fetches statistics for a storage device.
947
948    Using iostat(1) it retrieves statistics for a device since last boot.  See
949    the man page for iostat(1) for details on the different fields.
950
951    @param device: Path to a block device. Defaults to the device where root
952            is mounted.
953
954    @returns a dict mapping each field to its statistic.
955
956    @raises ValueError: If the output from iostat(1) can not be parsed.
957    """
958    if device is None:
959        device = get_root_device()
960    cmd = 'iostat -d -k -g ALL -H %s' % device
961    output = utils.system_output(cmd, ignore_status=True)
962    match = _IOSTAT_RE.search(output)
963    if not match:
964        raise ValueError('Unable to get iostat for %s' % device)
965    return dict(list(zip(_IOSTAT_FIELDS, list(map(float, match.groups())))))
966
967
968def load_module(module_name, params=None):
969    # Checks if a module has already been loaded
970    if module_is_loaded(module_name):
971        return False
972
973    cmd = '/sbin/modprobe ' + module_name
974    if params:
975        cmd += ' ' + params
976    utils.system(cmd)
977    return True
978
979
980def unload_module(module_name):
981    """
982    Removes a module. Handles dependencies. If even then it's not possible
983    to remove one of the modules, it will trhow an error.CmdError exception.
984
985    @param module_name: Name of the module we want to remove.
986    """
987    module_name = module_name.replace('-', '_')
988    l_raw = utils.system_output("/bin/lsmod").splitlines()
989    lsmod = [x for x in l_raw if x.split()[0] == module_name]
990    if len(lsmod) > 0:
991        line_parts = lsmod[0].split()
992        if len(line_parts) == 4:
993            submodules = line_parts[3].split(",")
994            for submodule in submodules:
995                unload_module(submodule)
996        utils.system("/sbin/modprobe -r %s" % module_name)
997        logging.info("Module %s unloaded", module_name)
998    else:
999        logging.info("Module %s is already unloaded", module_name)
1000
1001
1002def module_is_loaded(module_name):
1003    module_name = module_name.replace('-', '_')
1004    modules = utils.system_output('/bin/lsmod').splitlines()
1005    for module in modules:
1006        if module.startswith(module_name) and module[len(module_name)] == ' ':
1007            return True
1008    return False
1009
1010
1011def ping_default_gateway():
1012    """Ping the default gateway."""
1013
1014    network = open('/etc/sysconfig/network')
1015    m = re.search('GATEWAY=(\S+)', network.read())
1016
1017    if m:
1018        gw = m.group(1)
1019        cmd = 'ping %s -c 5 > /dev/null' % gw
1020        return utils.system(cmd, ignore_status=True)
1021
1022    raise error.TestError('Unable to find default gateway')
1023
1024
1025def drop_caches():
1026    """Writes back all dirty pages to disk and clears all the caches."""
1027    utils.system("sync")
1028    # We ignore failures here as this will fail on 2.6.11 kernels.
1029    utils.system("echo 3 > /proc/sys/vm/drop_caches", ignore_status=True)
1030
1031
1032def set_hwclock(time='system',
1033                utc=True,
1034                rtc=None,
1035                noadjfile=False,
1036                ignore_status=False):
1037    """Uses the hwclock command to set time of an RTC.
1038
1039    @param time: Either 'system', meaning use the system time, or a string
1040                 to be passed to the --date argument of hwclock.
1041    @param utc: Boolean of whether to use UTC or localtime.
1042    @param rtc: String to be passed to the --rtc arg of hwclock.
1043    @param noadjfile: Boolean of whether to use --noadjfile flag with hwclock.
1044    @param ignore_status: Boolean of whether to ignore exit code of hwclock.
1045    """
1046    cmd = '/sbin/hwclock'
1047    if time == 'system':
1048        cmd += ' --systohc'
1049    else:
1050        cmd += ' --set --date "{}"'.format(time)
1051    if utc:
1052        cmd += ' --utc'
1053    else:
1054        cmd += ' --localtime'
1055    if rtc is not None:
1056        cmd += ' --rtc={}'.format(rtc)
1057    if noadjfile:
1058        cmd += ' --noadjfile'
1059    return utils.system(cmd, ignore_status=ignore_status)
1060
1061def set_wake_alarm(alarm_time):
1062    """
1063    Set the hardware RTC-based wake alarm to 'alarm_time'.
1064    """
1065    utils.write_one_line('/sys/class/rtc/rtc0/wakealarm', str(alarm_time))
1066
1067
1068_AUTOTEST_CLIENT_PATH = os.path.join(os.path.dirname(__file__), '..')
1069_AMD_PCI_IDS_FILE_PATH = os.path.join(_AUTOTEST_CLIENT_PATH,
1070                                      'bin/amd_pci_ids.json')
1071_INTEL_PCI_IDS_FILE_PATH = os.path.join(_AUTOTEST_CLIENT_PATH,
1072                                        'bin/intel_pci_ids.json')
1073_UI_USE_FLAGS_FILE_PATH = '/etc/ui_use_flags.txt'
1074
1075# Command to check if a package is installed. If the package is not installed
1076# the command shall fail.
1077_CHECK_PACKAGE_INSTALLED_COMMAND =(
1078        "dpkg-query -W -f='${Status}\n' %s | head -n1 | awk '{print $3;}' | "
1079        "grep -q '^installed$'")
1080
1081pciid_to_amd_architecture = {}
1082pciid_to_intel_architecture = {}
1083
1084class Crossystem(object):
1085    """A wrapper for the crossystem utility."""
1086
1087    def __init__(self, client):
1088        self.cros_system_data = {}
1089        self._client = client
1090
1091    def init(self):
1092        self.cros_system_data = {}
1093        (_, fname) = tempfile.mkstemp()
1094        f = open(fname, 'w')
1095        self._client.run('crossystem', stdout_tee=f)
1096        f.close()
1097        text = utils.read_file(fname)
1098        for line in text.splitlines():
1099            assignment_string = line.split('#')[0]
1100            if not assignment_string.count('='):
1101                continue
1102            (name, value) = assignment_string.split('=', 1)
1103            self.cros_system_data[name.strip()] = value.strip()
1104        os.remove(fname)
1105
1106    def __getattr__(self, name):
1107        """
1108        Retrieve a crosssystem attribute.
1109
1110        The call crossystemobject.name() will return the crossystem reported
1111        string.
1112        """
1113        return lambda: self.cros_system_data[name]
1114
1115
1116def get_oldest_pid_by_name(name):
1117    """
1118    Return the oldest pid of a process whose name perfectly matches |name|.
1119
1120    name is an egrep expression, which will be matched against the entire name
1121    of processes on the system.  For example:
1122
1123      get_oldest_pid_by_name('chrome')
1124
1125    on a system running
1126      8600 ?        00:00:04 chrome
1127      8601 ?        00:00:00 chrome
1128      8602 ?        00:00:00 chrome-sandbox
1129
1130    would return 8600, as that's the oldest process that matches.
1131    chrome-sandbox would not be matched.
1132
1133    Arguments:
1134      name: egrep expression to match.  Will be anchored at the beginning and
1135            end of the match string.
1136
1137    Returns:
1138      pid as an integer, or None if one cannot be found.
1139
1140    Raises:
1141      ValueError if pgrep returns something odd.
1142    """
1143    str_pid = utils.system_output('pgrep -o ^%s$' % name,
1144                                  ignore_status=True).rstrip()
1145    if str_pid:
1146        return int(str_pid)
1147
1148
1149def get_oldest_by_name(name):
1150    """Return pid and command line of oldest process whose name matches |name|.
1151
1152    @param name: egrep expression to match desired process name.
1153    @return: A tuple of (pid, command_line) of the oldest process whose name
1154             matches |name|.
1155
1156    """
1157    pid = get_oldest_pid_by_name(name)
1158    if pid:
1159        command_line = utils.system_output('ps -p %i -o command=' % pid,
1160                                           ignore_status=True).rstrip()
1161        return (pid, command_line)
1162
1163
1164def get_chrome_remote_debugging_port():
1165    """Returns remote debugging port for Chrome.
1166
1167    Parse chrome process's command line argument to get the remote debugging
1168    port. if it is 0, look at DevToolsActivePort for the ephemeral port.
1169    """
1170    _, command = get_oldest_by_name('chrome')
1171    matches = re.search('--remote-debugging-port=([0-9]+)', command)
1172    if not matches:
1173        return 0
1174    port = int(matches.group(1))
1175    if port:
1176        return port
1177    with open('/home/chronos/DevToolsActivePort') as f:
1178        return int(f.readline().rstrip())
1179
1180
1181def get_process_list(name, command_line=None):
1182    """
1183    Return the list of pid for matching process |name command_line|.
1184
1185    on a system running
1186      31475 ?    0:06 /opt/google/chrome/chrome --allow-webui-compositing -
1187      31478 ?    0:00 /opt/google/chrome/chrome-sandbox /opt/google/chrome/
1188      31485 ?    0:00 /opt/google/chrome/chrome --type=zygote --log-level=1
1189      31532 ?    1:05 /opt/google/chrome/chrome --type=renderer
1190
1191    get_process_list('chrome')
1192    would return ['31475', '31485', '31532']
1193
1194    get_process_list('chrome', '--type=renderer')
1195    would return ['31532']
1196
1197    Arguments:
1198      name: process name to search for. If command_line is provided, name is
1199            matched against full command line. If command_line is not provided,
1200            name is only matched against the process name.
1201      command line: when command line is passed, the full process command line
1202                    is used for matching.
1203
1204    Returns:
1205      list of PIDs of the matching processes.
1206
1207    """
1208    # TODO(rohitbm) crbug.com/268861
1209    flag = '-x' if not command_line else '-f'
1210    name = '\'%s.*%s\'' % (name, command_line) if command_line else name
1211    str_pid = utils.system_output('pgrep %s %s' % (flag, name),
1212                                  ignore_status=True).rstrip()
1213    return str_pid.split()
1214
1215
1216def nuke_process_by_name(name, with_prejudice=False):
1217    """Tell the oldest process specified by name to exit.
1218
1219    Arguments:
1220      name: process name specifier, as understood by pgrep.
1221      with_prejudice: if True, don't allow for graceful exit.
1222
1223    Raises:
1224      error.AutoservPidAlreadyDeadError: no existing process matches name.
1225    """
1226    try:
1227        pid = get_oldest_pid_by_name(name)
1228    except Exception as e:
1229        logging.error(e)
1230        return
1231    if pid is None:
1232        raise error.AutoservPidAlreadyDeadError('No process matching %s.' %
1233                                                name)
1234    if with_prejudice:
1235        utils.nuke_pid(pid, [signal.SIGKILL])
1236    else:
1237        utils.nuke_pid(pid)
1238
1239
1240def is_virtual_machine():
1241    if 'QEMU' in platform.processor():
1242        return True
1243
1244    try:
1245        with open('/sys/devices/virtual/dmi/id/sys_vendor') as f:
1246            if 'QEMU' in f.read():
1247                return True
1248    except IOError:
1249        pass
1250
1251    return False
1252
1253
1254def save_vm_state(checkpoint):
1255    """Saves the current state of the virtual machine.
1256
1257    This function is a NOOP if the test is not running under a virtual machine
1258    with the USB serial port redirected.
1259
1260    Arguments:
1261      checkpoint - Name used to identify this state
1262
1263    Returns:
1264      None
1265    """
1266    # The QEMU monitor has been redirected to the guest serial port located at
1267    # /dev/ttyUSB0. To save the state of the VM, we just send the 'savevm'
1268    # command to the serial port.
1269    if is_virtual_machine() and os.path.exists('/dev/ttyUSB0'):
1270        logging.info('Saving VM state "%s"', checkpoint)
1271        serial = open('/dev/ttyUSB0', 'w')
1272        serial.write('savevm %s\r\n' % checkpoint)
1273        logging.info('Done saving VM state "%s"', checkpoint)
1274
1275
1276def mounts():
1277    ret = []
1278    with open('/proc/mounts') as f:
1279        lines = f.readlines()
1280    for line in lines:
1281        m = re.match(
1282            r'(?P<src>\S+) (?P<dest>\S+) (?P<type>\S+) (?P<opts>\S+).*', line)
1283        if m:
1284            ret.append(m.groupdict())
1285    return ret
1286
1287
1288def is_mountpoint(path):
1289    return path in [m['dest'] for m in mounts()]
1290
1291
1292def require_mountpoint(path):
1293    """
1294    Raises an exception if path is not a mountpoint.
1295    """
1296    if not is_mountpoint(path):
1297        raise error.TestFail('Path not mounted: "%s"' % path)
1298
1299
1300def random_username():
1301    return str(uuid.uuid4()) + '@example.com'
1302
1303
1304def get_signin_credentials(filepath):
1305    """Returns user_id, password tuple from credentials file at filepath.
1306
1307    File must have one line of the format user_id:password
1308
1309    @param filepath: path of credentials file.
1310    @return user_id, password tuple.
1311    """
1312    user_id, password = None, None
1313    if os.path.isfile(filepath):
1314        with open(filepath) as f:
1315            user_id, password = f.read().rstrip().split(':')
1316    return user_id, password
1317
1318
1319def parse_cmd_output(command, run_method=utils.run):
1320    """Runs a command on a host object to retrieve host attributes.
1321
1322    The command should output to stdout in the format of:
1323    <key> = <value> # <optional_comment>
1324
1325
1326    @param command: Command to execute on the host.
1327    @param run_method: Function to use to execute the command. Defaults to
1328                       utils.run so that the command will be executed locally.
1329                       Can be replace with a host.run call so that it will
1330                       execute on a DUT or external machine. Method must accept
1331                       a command argument, stdout_tee and stderr_tee args and
1332                       return a result object with a string attribute stdout
1333                       which will be parsed.
1334
1335    @returns a dictionary mapping host attributes to their values.
1336    """
1337    result = {}
1338    # Suppresses stdout so that the files are not printed to the logs.
1339    cmd_result = run_method(command, stdout_tee=None, stderr_tee=None)
1340    for line in cmd_result.stdout.splitlines():
1341        # Lines are of the format "<key>     = <value>      # <comment>"
1342        key_value = re.match(r'^\s*(?P<key>[^ ]+)\s*=\s*(?P<value>[^ '
1343                             r']+)(?:\s*#.*)?$', line)
1344        if key_value:
1345            result[key_value.group('key')] = key_value.group('value')
1346    return result
1347
1348
1349def set_from_keyval_output(out, delimiter=' '):
1350    """Parse delimiter-separated key-val output into a set of tuples.
1351
1352    Output is expected to be multiline text output from a command.
1353    Stuffs the key-vals into tuples in a set to be later compared.
1354
1355    e.g.  deactivated 0
1356          disableForceClear 0
1357          ==>  set(('deactivated', '0'), ('disableForceClear', '0'))
1358
1359    @param out: multiple lines of space-separated key-val pairs.
1360    @param delimiter: character that separates key from val. Usually a
1361                      space but may be '=' or something else.
1362    @return set of key-val tuples.
1363    """
1364    results = set()
1365    kv_match_re = re.compile('([^ ]+)%s(.*)' % delimiter)
1366    for linecr in out.splitlines():
1367        match = kv_match_re.match(linecr.strip())
1368        if match:
1369            results.add((match.group(1), match.group(2)))
1370    return results
1371
1372
1373def get_cpu_usage():
1374    """Returns machine's CPU usage.
1375
1376    This function uses /proc/stat to identify CPU usage.
1377    Returns:
1378        A dictionary with values for all columns in /proc/stat
1379        Sample dictionary:
1380        {
1381            'user': 254544,
1382            'nice': 9,
1383            'system': 254768,
1384            'idle': 2859878,
1385            'iowait': 1,
1386            'irq': 2,
1387            'softirq': 3,
1388            'steal': 4,
1389            'guest': 5,
1390            'guest_nice': 6
1391        }
1392        If a column is missing or malformed in /proc/stat (typically on older
1393        systems), the value for that column is set to 0.
1394    """
1395    with _open_file('/proc/stat') as proc_stat:
1396        cpu_usage_str = proc_stat.readline().split()
1397    columns = ('user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq',
1398               'steal', 'guest', 'guest_nice')
1399    d = {}
1400    for index, col in enumerate(columns, 1):
1401        try:
1402            d[col] = int(cpu_usage_str[index])
1403        except:
1404            d[col] = 0
1405    return d
1406
1407def compute_active_cpu_time(cpu_usage_start, cpu_usage_end):
1408    """Computes the fraction of CPU time spent non-idling.
1409
1410    This function should be invoked using before/after values from calls to
1411    get_cpu_usage().
1412
1413    See https://stackoverflow.com/a/23376195 and
1414    https://unix.stackexchange.com/a/303224 for some more context how
1415    to calculate usage given two /proc/stat snapshots.
1416    """
1417    idle_cols = ('idle', 'iowait')  # All other cols are calculated as active.
1418    time_active_start = sum([x[1] for x in six.iteritems(cpu_usage_start)
1419                             if x[0] not in idle_cols])
1420    time_active_end = sum([x[1] for x in six.iteritems(cpu_usage_end)
1421                           if x[0] not in idle_cols])
1422    total_time_start = sum(cpu_usage_start.values())
1423    total_time_end = sum(cpu_usage_end.values())
1424    # Avoid bogus division which has been observed on Tegra.
1425    if total_time_end <= total_time_start:
1426        logging.warning('compute_active_cpu_time observed bogus data')
1427        # We pretend to be busy, this will force a longer wait for idle CPU.
1428        return 1.0
1429    return ((float(time_active_end) - time_active_start) /
1430            (total_time_end - total_time_start))
1431
1432
1433def is_pgo_mode():
1434    return 'USE_PGO' in os.environ
1435
1436
1437def wait_for_idle_cpu(timeout, utilization):
1438    """Waits for the CPU to become idle (< utilization).
1439
1440    Args:
1441        timeout: The longest time in seconds to wait before throwing an error.
1442        utilization: The CPU usage below which the system should be considered
1443                idle (between 0 and 1.0 independent of cores/hyperthreads).
1444    """
1445    time_passed = 0.0
1446    fraction_active_time = 1.0
1447    sleep_time = 1
1448    logging.info('Starting to wait up to %.1fs for idle CPU...', timeout)
1449    while fraction_active_time >= utilization:
1450        cpu_usage_start = get_cpu_usage()
1451        # Split timeout interval into not too many chunks to limit log spew.
1452        # Start at 1 second, increase exponentially
1453        time.sleep(sleep_time)
1454        time_passed += sleep_time
1455        sleep_time = min(16.0, 2.0 * sleep_time)
1456        cpu_usage_end = get_cpu_usage()
1457        fraction_active_time = compute_active_cpu_time(cpu_usage_start,
1458                                                       cpu_usage_end)
1459        logging.info('After waiting %.1fs CPU utilization is %.3f.',
1460                     time_passed, fraction_active_time)
1461        if time_passed > timeout:
1462            if fraction_active_time < utilization:
1463                break
1464            logging.warning('CPU did not become idle.')
1465            log_process_activity()
1466            # crosbug.com/37389
1467            if is_pgo_mode():
1468                logging.info('Still continuing because we are in PGO mode.')
1469                return True
1470
1471            return False
1472    logging.info('Wait for idle CPU took %.1fs (utilization = %.3f).',
1473                 time_passed, fraction_active_time)
1474    return True
1475
1476
1477def log_process_activity():
1478    """Logs the output of top.
1479
1480    Useful to debug performance tests and to find runaway processes.
1481    """
1482    logging.info('Logging current process activity using top and ps.')
1483    cmd = 'top -b -n1 -c'
1484    output = utils.run(cmd)
1485    logging.info(output)
1486    output = utils.run('ps axl')
1487    logging.info(output)
1488
1489
1490def wait_for_cool_machine():
1491    """
1492    A simple heuristic to wait for a machine to cool.
1493    The code looks a bit 'magic', but we don't know ambient temperature
1494    nor machine characteristics and still would like to return the caller
1495    a machine that cooled down as much as reasonably possible.
1496    """
1497    temperature = get_current_temperature_max()
1498    # We got here with a cold machine, return immediately. This should be the
1499    # most common case.
1500    if temperature < 45:
1501        return True
1502    logging.info('Got a hot machine of %dC. Sleeping 1 minute.', temperature)
1503    # A modest wait should cool the machine.
1504    time.sleep(60.0)
1505    temperature = get_current_temperature_max()
1506    # Atoms idle below 60 and everyone else should be even lower.
1507    if temperature < 62:
1508        return True
1509    # This should be rare.
1510    logging.info('Did not cool down (%dC). Sleeping 2 minutes.', temperature)
1511    time.sleep(120.0)
1512    temperature = get_current_temperature_max()
1513    # A temperature over 65'C doesn't give us much headroom to the critical
1514    # temperatures that start at 85'C (and PerfControl as of today will fail at
1515    # critical - 10'C).
1516    if temperature < 65:
1517        return True
1518    logging.warning('Did not cool down (%dC), giving up.', temperature)
1519    log_process_activity()
1520    return False
1521
1522
1523def report_temperature(test, keyname):
1524    """Report current max observed temperature with given keyname.
1525
1526    @param test: autotest_lib.client.bin.test.test instance
1527    @param keyname: key to be used when reporting perf value.
1528    """
1529    temperature = get_current_temperature_max()
1530    logging.info('%s = %f degree Celsius', keyname, temperature)
1531    test.output_perf_value(
1532        description=keyname,
1533        value=temperature,
1534        units='Celsius',
1535        higher_is_better=False)
1536
1537
1538# System paths for machine performance state.
1539_CPUINFO = '/proc/cpuinfo'
1540_DIRTY_WRITEBACK_CENTISECS = '/proc/sys/vm/dirty_writeback_centisecs'
1541_KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
1542_MEMINFO = '/proc/meminfo'
1543_TEMP_SENSOR_RE = 'Reading temperature...([0-9]*)'
1544
1545def _open_file(path):
1546    """
1547    Opens a file and returns the file object.
1548
1549    This method is intended to be mocked by tests.
1550    @return The open file object.
1551    """
1552    return open(path)
1553
1554def _get_line_from_file(path, line):
1555    """
1556    line can be an integer or
1557    line can be a string that matches the beginning of the line
1558    """
1559    with _open_file(path) as f:
1560        if isinstance(line, int):
1561            l = f.readline()
1562            for _ in range(0, line):
1563                l = f.readline()
1564            return l
1565        else:
1566            for l in f:
1567                if l.startswith(line):
1568                    return l
1569    return None
1570
1571
1572def _get_match_from_file(path, line, prefix, postfix):
1573    """
1574    Matches line in path and returns string between first prefix and postfix.
1575    """
1576    match = _get_line_from_file(path, line)
1577    # Strip everything from front of line including prefix.
1578    if prefix:
1579        match = re.split(prefix, match)[1]
1580    # Strip everything from back of string including first occurence of postfix.
1581    if postfix:
1582        match = re.split(postfix, match)[0]
1583    return match
1584
1585
1586def _get_float_from_file(path, line, prefix, postfix):
1587    match = _get_match_from_file(path, line, prefix, postfix)
1588    return float(match)
1589
1590
1591def _get_int_from_file(path, line, prefix, postfix):
1592    match = _get_match_from_file(path, line, prefix, postfix)
1593    return int(match)
1594
1595
1596def _get_hex_from_file(path, line, prefix, postfix):
1597    match = _get_match_from_file(path, line, prefix, postfix)
1598    return int(match, 16)
1599
1600
1601def is_system_thermally_throttled():
1602    """
1603    Returns whether the system appears to be thermally throttled.
1604    """
1605    for path in glob.glob('/sys/class/thermal/cooling_device*/type'):
1606        with _open_file(path) as f:
1607            cdev_type = f.read().strip()
1608
1609        if not (cdev_type == 'Processor' or
1610                cdev_type.startswith('thermal-devfreq') or
1611                cdev_type.startswith('thermal-cpufreq')):
1612            continue
1613
1614        cur_state_path = os.path.join(os.path.dirname(path), 'cur_state')
1615        if _get_int_from_file(cur_state_path, 0, None, None) > 0:
1616            return True
1617
1618    return False
1619
1620
1621# The paths don't change. Avoid running find all the time.
1622_hwmon_paths = {}
1623
1624def _get_hwmon_datas(file_pattern):
1625    """Returns a list of reading from hwmon."""
1626    # Some systems like daisy_spring only have the virtual hwmon.
1627    # And other systems like rambi only have coretemp.0. See crbug.com/360249.
1628    #    /sys/class/hwmon/hwmon*/
1629    #    /sys/devices/virtual/hwmon/hwmon*/
1630    #    /sys/devices/platform/coretemp.0/
1631    if file_pattern not in _hwmon_paths:
1632        cmd = 'find /sys/class /sys/devices -name "' + file_pattern + '"'
1633        _hwmon_paths[file_pattern] = \
1634            utils.run(cmd, verbose=False).stdout.splitlines()
1635    for _hwmon_path in _hwmon_paths[file_pattern]:
1636        try:
1637            yield _get_float_from_file(_hwmon_path, 0, None, None) * 0.001
1638        except IOError as err:
1639            # Files under /sys may get truncated and result in ENODATA.
1640            # Ignore those.
1641            if err.errno is not errno.ENODATA:
1642                raise
1643
1644
1645def _get_hwmon_temperatures():
1646    """
1647    Returns the currently observed temperatures from hwmon
1648    """
1649    return list(_get_hwmon_datas('temp*_input'))
1650
1651
1652def _get_thermal_zone_temperatures():
1653    """
1654    Returns the maximum currently observered temperature in thermal_zones.
1655    """
1656    temperatures = []
1657    for path in glob.glob('/sys/class/thermal/thermal_zone*/temp'):
1658        try:
1659            temperatures.append(
1660                _get_float_from_file(path, 0, None, None) * 0.001)
1661        except IOError:
1662            # Some devices (e.g. Veyron) may have reserved thermal zones that
1663            # are not active. Trying to read the temperature value would cause a
1664            # EINVAL IO error.
1665            continue
1666    return temperatures
1667
1668
1669def get_ec_temperatures():
1670    """
1671    Uses ectool to return a list of all sensor temperatures in Celsius.
1672
1673    Output from ectool is either '0: 300' or '0: 300 K' (newer ectool
1674    includes the unit).
1675    """
1676    temperatures = []
1677    try:
1678        full_cmd = 'ectool temps all'
1679        lines = utils.run(full_cmd, verbose=False).stdout.splitlines()
1680        pattern = re.compile('.*: (\d+)')
1681        for line in lines:
1682            matched = pattern.match(line)
1683            temperature = int(matched.group(1)) - 273
1684            temperatures.append(temperature)
1685    except Exception as e:
1686        logging.warning('Unable to read temperature sensors using ectool %s.',
1687                        e)
1688    # Check for real world values.
1689    if not all(10.0 <= temperature <= 150.0 for temperature in temperatures):
1690        logging.warning('Unreasonable EC temperatures: %s.', temperatures)
1691    return temperatures
1692
1693
1694def get_current_temperature_max():
1695    """
1696    Returns the highest reported board temperature (all sensors) in Celsius.
1697    """
1698    all_temps = (_get_hwmon_temperatures() +
1699                 _get_thermal_zone_temperatures() +
1700                 get_ec_temperatures())
1701    if all_temps:
1702        temperature = max(all_temps)
1703    else:
1704        temperature = -1
1705    # Check for real world values.
1706    assert ((temperature > 10.0) and
1707            (temperature < 150.0)), ('Unreasonable temperature %.1fC.' %
1708                                     temperature)
1709    return temperature
1710
1711
1712def get_cpu_max_frequency():
1713    """
1714    Returns the largest of the max CPU core frequencies. The unit is Hz.
1715    """
1716    max_frequency = -1
1717    paths = utils._get_cpufreq_paths('cpuinfo_max_freq')
1718    if not paths:
1719        raise ValueError('Could not find max freq; is cpufreq supported?')
1720    for path in paths:
1721        try:
1722            # Convert from kHz to Hz.
1723            frequency = 1000 * _get_float_from_file(path, 0, None, None)
1724        # CPUs may come and go. A missing entry or two aren't critical.
1725        except IOError:
1726            continue
1727        max_frequency = max(frequency, max_frequency)
1728    # Confidence check.
1729    assert max_frequency > 1e8, ('Unreasonably low CPU frequency: %.1f' %
1730            max_frequency)
1731    return max_frequency
1732
1733
1734def get_board_property(key):
1735    """
1736    Get a specific property from /etc/lsb-release.
1737
1738    @param key: board property to return value for
1739
1740    @return the value or '' if not present
1741    """
1742    with open('/etc/lsb-release') as f:
1743        pattern = '%s=(.*)' % key
1744        pat = re.search(pattern, f.read())
1745        if pat:
1746            return pat.group(1)
1747    return ''
1748
1749
1750def get_board():
1751    """
1752    Get the ChromeOS release board name from /etc/lsb-release.
1753    """
1754    return get_board_property('BOARD')
1755
1756
1757def get_board_type():
1758    """
1759    Get the ChromeOS board type from /etc/lsb-release.
1760
1761    @return device type.
1762    """
1763    return get_board_property('DEVICETYPE')
1764
1765
1766def get_chromeos_version():
1767    """
1768    Get the ChromeOS build version from /etc/lsb-release.
1769
1770    @return chromeos release version.
1771    """
1772    return get_board_property('CHROMEOS_RELEASE_VERSION')
1773
1774
1775def get_android_version():
1776    """
1777    Get the Android SDK version from /etc/lsb-release.
1778
1779    @return android sdk version.
1780    """
1781    return get_board_property('CHROMEOS_ARC_ANDROID_SDK_VERSION')
1782
1783
1784def is_arcvm():
1785    try:
1786        return int(get_android_version()) >= 30
1787    except:
1788        return False
1789
1790
1791def get_platform():
1792    """
1793    Get the ChromeOS platform name.
1794
1795    For unibuild this should be equal to model name.  For non-unibuild
1796    it will either be board name or empty string.  In the case of
1797    empty string return board name to match equivalent logic in
1798    server/hosts/cros_host.py
1799
1800    @returns platform name
1801    """
1802    platform = cros_config.call_cros_config_get_output('/ name', utils.run)
1803    if platform == '':
1804        platform = get_board()
1805    return platform
1806
1807
1808def get_sku():
1809    """
1810    Get the SKU number.
1811
1812    @returns SKU number
1813    """
1814    return cros_config.call_cros_config_get_output('/identity sku-id',
1815                                                   utils.run)
1816
1817
1818def get_ec_version():
1819    """Get the ec version as strings.
1820
1821    @returns a string representing this host's ec version.
1822    """
1823    command = 'mosys ec info -s fw_version'
1824    result = utils.run(command, ignore_status=True)
1825    if result.exit_status != 0:
1826        return ''
1827    return result.stdout.strip()
1828
1829
1830def get_firmware_version():
1831    """Get the firmware version as strings.
1832
1833    @returns a string representing this host's firmware version.
1834    """
1835    return utils.run('crossystem fwid').stdout.strip()
1836
1837
1838def get_hardware_id():
1839    """Get hardware id as strings.
1840
1841    @returns a string representing this host's hardware id.
1842    """
1843    return utils.run('crossystem hwid').stdout.strip()
1844
1845
1846def get_hardware_revision():
1847    """Get the hardware revision as strings.
1848
1849    @returns a string representing this host's hardware revision.
1850    """
1851    command = 'mosys platform version'
1852    result = utils.run(command, ignore_status=True)
1853    if result.exit_status != 0:
1854        return ''
1855    return result.stdout.strip()
1856
1857
1858def get_kernel_version():
1859    """Get the kernel version as strings.
1860
1861    @returns a string representing this host's kernel version.
1862    """
1863    return utils.run('uname -r').stdout.strip()
1864
1865
1866def get_cpu_name():
1867    """Get the cpu name as strings.
1868
1869    @returns a string representing this host's cpu name.
1870    """
1871
1872    # Try get cpu name from device tree first
1873    if os.path.exists("/proc/device-tree/compatible"):
1874        command = "sed -e 's/\\x0/\\n/g' /proc/device-tree/compatible | tail -1"
1875        return utils.run(command).stdout.strip().replace(',', ' ')
1876
1877
1878    # Get cpu name from uname -p
1879    command = "uname -p"
1880    ret = utils.run(command).stdout.strip()
1881
1882    # 'uname -p' return variant of unknown or amd64 or x86_64 or i686
1883    # Try get cpu name from /proc/cpuinfo instead
1884    if re.match("unknown|amd64|[ix][0-9]?86(_64)?", ret, re.IGNORECASE):
1885        command = "grep model.name /proc/cpuinfo | cut -f 2 -d: | head -1"
1886        ret = utils.run(command).stdout.strip()
1887
1888    # Remove bloat from CPU name, for example
1889    # 'Intel(R) Core(TM) i5-7Y57 CPU @ 1.20GHz'       -> 'Intel Core i5-7Y57'
1890    # 'Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz'     -> 'Intel Xeon E5-2690 v4'
1891    # 'AMD A10-7850K APU with Radeon(TM) R7 Graphics' -> 'AMD A10-7850K'
1892    # 'AMD GX-212JC SOC with Radeon(TM) R2E Graphics' -> 'AMD GX-212JC'
1893    trim_re = " (@|processor|apu|soc|radeon).*|\(.*?\)| cpu"
1894    return re.sub(trim_re, '', ret, flags=re.IGNORECASE)
1895
1896
1897def get_screen_resolution():
1898    """Get the screen(s) resolution as strings.
1899    In case of more than 1 monitor, return resolution for each monitor separate
1900    with plus sign.
1901
1902    @returns a string representing this host's screen(s) resolution.
1903    """
1904    command = 'for f in /sys/class/drm/*/*/modes; do head -1 $f; done'
1905    ret = utils.run(command, ignore_status=True)
1906    # We might have Chromebox without a screen
1907    if ret.exit_status != 0:
1908        return ''
1909    return ret.stdout.strip().replace('\n', '+')
1910
1911
1912def get_board_with_frequency_and_memory():
1913    """
1914    Returns a board name modified with CPU frequency and memory size to
1915    differentiate between different board variants. For instance
1916    link -> link_1.8GHz_4GB.
1917    """
1918    board_name = get_board()
1919    if is_virtual_machine():
1920        board = '%s_VM' % board_name
1921    else:
1922        memory = get_mem_total_gb()
1923        # Convert frequency to GHz with 1 digit accuracy after the
1924        # decimal point.
1925        frequency = int(round(get_cpu_max_frequency() * 1e-8)) * 0.1
1926        board = '%s_%1.1fGHz_%dGB' % (board_name, frequency, memory)
1927    return board
1928
1929
1930def get_mem_total():
1931    """
1932    Returns the total memory available in the system in MBytes.
1933    """
1934    mem_total = _get_float_from_file(_MEMINFO, 'MemTotal:', 'MemTotal:', ' kB')
1935    # Confidence check, all Chromebooks have at least 1GB of memory.
1936    assert mem_total > 256 * 1024, 'Unreasonable amount of memory.'
1937    return int(mem_total / 1024)
1938
1939
1940def get_mem_total_gb():
1941    """
1942    Returns the total memory available in the system in GBytes.
1943    """
1944    return int(round(get_mem_total() / 1024.0))
1945
1946
1947def get_mem_free():
1948    """
1949    Returns the currently free memory in the system in MBytes.
1950    """
1951    mem_free = _get_float_from_file(_MEMINFO, 'MemFree:', 'MemFree:', ' kB')
1952    return int(mem_free / 1024)
1953
1954def get_mem_free_plus_buffers_and_cached():
1955    """
1956    Returns the free memory in MBytes, counting buffers and cached as free.
1957
1958    This is most often the most interesting number since buffers and cached
1959    memory can be reclaimed on demand. Note however, that there are cases
1960    where this as misleading as well, for example used tmpfs space
1961    count as Cached but can not be reclaimed on demand.
1962    See https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt.
1963    """
1964    free_mb = get_mem_free()
1965    cached_mb = (_get_float_from_file(
1966        _MEMINFO, 'Cached:', 'Cached:', ' kB') / 1024)
1967    buffers_mb = (_get_float_from_file(
1968        _MEMINFO, 'Buffers:', 'Buffers:', ' kB') / 1024)
1969    return free_mb + buffers_mb + cached_mb
1970
1971
1972def get_dirty_writeback_centisecs():
1973    """
1974    Reads /proc/sys/vm/dirty_writeback_centisecs.
1975    """
1976    time = _get_int_from_file(_DIRTY_WRITEBACK_CENTISECS, 0, None, None)
1977    return time
1978
1979
1980def set_dirty_writeback_centisecs(time=60000):
1981    """
1982    In hundredths of a second, this is how often pdflush wakes up to write data
1983    to disk. The default wakes up the two (or more) active threads every five
1984    seconds. The ChromeOS default is 10 minutes.
1985
1986    We use this to set as low as 1 second to flush error messages in system
1987    logs earlier to disk.
1988    """
1989    # Flush buffers first to make this function synchronous.
1990    utils.system('sync')
1991    if time >= 0:
1992        cmd = 'echo %d > %s' % (time, _DIRTY_WRITEBACK_CENTISECS)
1993        utils.system(cmd)
1994
1995
1996def wflinfo_cmd():
1997    """
1998    Returns a wflinfo command appropriate to the current graphics platform/api.
1999    """
2000    return 'wflinfo -p %s -a %s' % (graphics_platform(), graphics_api())
2001
2002
2003def has_mali():
2004    """ @return: True if system has a Mali GPU enabled."""
2005    return os.path.exists('/dev/mali0')
2006
2007def get_gpu_family():
2008    """Returns the GPU family name."""
2009    global pciid_to_amd_architecture
2010    global pciid_to_intel_architecture
2011
2012    socfamily = get_cpu_soc_family()
2013    if socfamily == 'exynos5' or socfamily == 'rockchip' or has_mali():
2014        cmd = wflinfo_cmd()
2015        wflinfo = utils.system_output(cmd,
2016                                      retain_output=True,
2017                                      ignore_status=False)
2018        m = re.findall(r'OpenGL renderer string: (Mali-\w+)', wflinfo)
2019        if m:
2020            return m[0].lower()
2021        return 'mali-unrecognized'
2022    if socfamily == 'tegra':
2023        return 'tegra'
2024    if socfamily == 'qualcomm':
2025        return 'qualcomm'
2026    if os.path.exists('/sys/kernel/debug/pvr'):
2027        return 'rogue'
2028
2029    pci_vga_device = utils.run("lspci | grep VGA").stdout.rstrip('\n')
2030    bus_device_function = pci_vga_device.partition(' ')[0]
2031    pci_path = '/sys/bus/pci/devices/0000:' + bus_device_function + '/device'
2032
2033    if not os.path.exists(pci_path):
2034        raise error.TestError('PCI device 0000:' + bus_device_function + ' not found')
2035
2036    device_id = utils.read_one_line(pci_path).lower()
2037
2038    if "Advanced Micro Devices" in pci_vga_device:
2039        if not pciid_to_amd_architecture:
2040            with open(_AMD_PCI_IDS_FILE_PATH, 'r') as in_f:
2041                pciid_to_amd_architecture = json.load(in_f)
2042
2043        return pciid_to_amd_architecture[device_id]
2044
2045    if "Intel Corporation" in pci_vga_device:
2046        # Only load Intel PCI ID file once and only if necessary.
2047        if not pciid_to_intel_architecture:
2048            with open(_INTEL_PCI_IDS_FILE_PATH, 'r') as in_f:
2049                pciid_to_intel_architecture = json.load(in_f)
2050
2051        return pciid_to_intel_architecture[device_id]
2052
2053# TODO(ihf): Consider using /etc/lsb-release DEVICETYPE != CHROMEBOOK/CHROMEBASE
2054# for confidence check, but usage seems a bit inconsistent. See
2055# src/third_party/chromiumos-overlay/eclass/appid.eclass
2056_BOARDS_WITHOUT_MONITOR = [
2057    'anglar', 'mccloud', 'monroe', 'ninja', 'rikku', 'guado', 'jecht', 'tidus',
2058    'beltino', 'panther', 'stumpy', 'panther', 'tricky', 'zako', 'veyron_rialto'
2059]
2060
2061
2062def has_no_monitor():
2063    """Returns whether a machine doesn't have a built-in monitor."""
2064    board_name = get_board()
2065    if board_name in _BOARDS_WITHOUT_MONITOR:
2066        return True
2067
2068    return False
2069
2070
2071def get_fixed_dst_drive():
2072    """
2073    Return device name for internal disk.
2074    Example: return /dev/sda for falco booted from usb
2075    """
2076    cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2077                    '. /usr/share/misc/chromeos-common.sh;',
2078                    'load_base_vars;',
2079                    'get_fixed_dst_drive'])
2080    return utils.system_output(cmd)
2081
2082
2083def get_root_device():
2084    """
2085    Return root device.
2086    Will return correct disk device even system boot from /dev/dm-0
2087    Example: return /dev/sdb for falco booted from usb
2088    """
2089    return utils.system_output('rootdev -s -d')
2090
2091
2092def get_other_device():
2093    """
2094    Return the non root devices.
2095    Will return a list of other block devices, that are not the root device.
2096    """
2097
2098    cmd = 'lsblk -dpn -o NAME | grep -v -E "(loop|zram|boot|rpmb)"'
2099    devs = utils.system_output(cmd).splitlines()
2100
2101    for dev in devs[:]:
2102        if not re.match(r'/dev/(sd[a-z]|mmcblk[0-9]+|nvme[0-9]+)p?[0-9]*', dev):
2103            devs.remove(dev)
2104        if dev == get_root_device():
2105            devs.remove(dev)
2106    return devs
2107
2108
2109def get_root_partition():
2110    """
2111    Return current root partition
2112    Example: return /dev/sdb3 for falco booted from usb
2113    """
2114    return utils.system_output('rootdev -s')
2115
2116
2117def get_free_root_partition(root_part=None):
2118    """
2119    Return currently unused root partion
2120    Example: return /dev/sdb5 for falco booted from usb
2121
2122    @param root_part: cuurent root partition
2123    """
2124    spare_root_map = {'3': '5', '5': '3'}
2125    if not root_part:
2126        root_part = get_root_partition()
2127    return root_part[:-1] + spare_root_map[root_part[-1]]
2128
2129
2130def get_kernel_partition(root_part=None):
2131    """
2132    Return current kernel partition
2133    Example: return /dev/sda2 for falco booted from usb
2134
2135    @param root_part: current root partition
2136    """
2137    if not root_part:
2138        root_part = get_root_partition()
2139    current_kernel_map = {'3': '2', '5': '4'}
2140    return root_part[:-1] + current_kernel_map[root_part[-1]]
2141
2142
2143def get_free_kernel_partition(root_part=None):
2144    """
2145    return currently unused kernel partition
2146    Example: return /dev/sda4 for falco booted from usb
2147
2148    @param root_part: current root partition
2149    """
2150    kernel_part = get_kernel_partition(root_part)
2151    spare_kernel_map = {'2': '4', '4': '2'}
2152    return kernel_part[:-1] + spare_kernel_map[kernel_part[-1]]
2153
2154
2155def is_booted_from_internal_disk():
2156    """Return True if boot from internal disk. False, otherwise."""
2157    return get_root_device() == get_fixed_dst_drive()
2158
2159
2160def get_ui_use_flags():
2161    """Parses the USE flags as listed in /etc/ui_use_flags.txt.
2162
2163    @return: A list of flag strings found in the ui use flags file.
2164    """
2165    flags = []
2166    for flag in utils.read_file(_UI_USE_FLAGS_FILE_PATH).splitlines():
2167        # Removes everything after the '#'.
2168        flag_before_comment = flag.split('#')[0].strip()
2169        if len(flag_before_comment) != 0:
2170            flags.append(flag_before_comment)
2171
2172    return flags
2173
2174
2175def graphics_platform():
2176    """
2177    Return a string identifying the graphics platform,
2178    e.g. 'glx' or 'x11_egl' or 'gbm'
2179    """
2180    return 'null'
2181
2182
2183def graphics_api():
2184    """Return a string identifying the graphics api, e.g. gl or gles2."""
2185    use_flags = get_ui_use_flags()
2186    if 'opengles' in use_flags:
2187        return 'gles2'
2188    return 'gl'
2189
2190
2191def is_package_installed(package):
2192    """Check if a package is installed already.
2193
2194    @return: True if the package is already installed, otherwise return False.
2195    """
2196    try:
2197        utils.run(_CHECK_PACKAGE_INSTALLED_COMMAND % package)
2198        return True
2199    except error.CmdError:
2200        logging.warning('Package %s is not installed.', package)
2201        return False
2202
2203
2204def is_python_package_installed(package):
2205    """Check if a Python package is installed already.
2206
2207    @return: True if the package is already installed, otherwise return False.
2208    """
2209    try:
2210        __import__(package)
2211        return True
2212    except ImportError:
2213        logging.warning('Python package %s is not installed.', package)
2214        return False
2215
2216
2217def run_sql_cmd(server, user, password, command, database=''):
2218    """Run the given sql command against the specified database.
2219
2220    @param server: Hostname or IP address of the MySQL server.
2221    @param user: User name to log in the MySQL server.
2222    @param password: Password to log in the MySQL server.
2223    @param command: SQL command to run.
2224    @param database: Name of the database to run the command. Default to empty
2225                     for command that does not require specifying database.
2226
2227    @return: The stdout of the command line.
2228    """
2229    cmd = ('mysql -u%s -p%s --host %s %s -e "%s"' %
2230           (user, password, server, database, command))
2231    # Set verbose to False so the command line won't be logged, as it includes
2232    # database credential.
2233    return utils.run(cmd, verbose=False).stdout
2234
2235
2236def strip_non_printable(s):
2237    """Strip non printable characters from string.
2238
2239    @param s: Input string
2240
2241    @return: The input string with only printable characters.
2242    """
2243    return ''.join(x for x in s if x in string.printable)
2244
2245
2246def recursive_func(obj, func, types, sequence_types=(list, tuple, set),
2247                   dict_types=(dict,), fix_num_key=False):
2248    """Apply func to obj recursively.
2249
2250    This function traverses recursively through any sequence-like and
2251    dict-like elements in obj.
2252
2253    @param obj: the object to apply the function func recursively.
2254    @param func: the function to invoke.
2255    @param types: the target types in the object to apply func.
2256    @param sequence_types: the sequence types in python.
2257    @param dict_types: the dict types in python.
2258    @param fix_num_key: to indicate if the key of a dict should be
2259            converted from str type to a number, int or float, type.
2260            It is a culprit of json that it always treats the key of
2261            a dict as string.
2262            Refer to https://docs.python.org/2/library/json.html
2263            for more information.
2264
2265    @return: the result object after applying the func recursively.
2266    """
2267    def ancestors(obj, types):
2268        """Any ancestor of the object class is a subclass of the types?
2269
2270        @param obj: the object to apply the function func.
2271        @param types: the target types of the object.
2272
2273        @return: True if any ancestor class of the obj is found in types;
2274                 False otherwise.
2275        """
2276        return any([issubclass(anc, types) for anc in type(obj).__mro__])
2277
2278    if isinstance(obj, sequence_types) or ancestors(obj, sequence_types):
2279        result_lst = [recursive_func(elm, func, types, fix_num_key=fix_num_key)
2280                      for elm in obj]
2281        # Convert the result list to the object's original sequence type.
2282        return type(obj)(result_lst)
2283    elif isinstance(obj, dict_types) or ancestors(obj, dict_types):
2284        result_lst = [
2285                (recursive_func(key, func, types, fix_num_key=fix_num_key),
2286                 recursive_func(value, func, types, fix_num_key=fix_num_key))
2287                for (key, value) in obj.items()]
2288        # Convert the result list to the object's original dict type.
2289        return type(obj)(result_lst)
2290    # Here are the basic types.
2291    elif isinstance(obj, types) or ancestors(obj, types):
2292        if fix_num_key:
2293            # Check if this is a int or float
2294            try:
2295                result_obj = int(obj)
2296                return result_obj
2297            except ValueError:
2298                try:
2299                    result_obj = float(obj)
2300                    return result_obj
2301                except ValueError:
2302                    pass
2303        try:
2304            result_obj = func(obj)
2305            return result_obj
2306        except UnicodeEncodeError:
2307            pass
2308    else:
2309        return obj
2310
2311
2312def is_python2():
2313    """True if it is interpreted by Python 2."""
2314    return sys.version_info.major == 2
2315
2316
2317def base64_recursive_encode(obj):
2318    """Apply base64 encode recursively into the obj structure.
2319
2320    Python 2 case:
2321        Most of the string-like types could be traced to basestring and bytearray
2322        as follows:
2323            str: basestring
2324            bytes: basestring
2325            dbus.String: basestring
2326            dbus.Signature: basestring
2327            dbus.ByteArray: basestring
2328
2329        Note that all the above types except dbus.String could be traced back to
2330        str. In order to cover dbus.String, basestring is used as the ancestor
2331        class for string-like types.
2332
2333    Python 3 case:
2334        Perform base64 encode on bytes element only.
2335
2336    The other type that needs encoding with base64 in a structure includes
2337        bytearray: bytearray
2338
2339    The sequence types include (list, tuple, set). The dbus.Array is also
2340    covered as
2341        dbus.Array: list
2342
2343    The base dictionary type is dict. The dbus.Dictionary is also covered as
2344        dbus.Dictionary: dict
2345
2346    An example code and output look like
2347    in Python 2:
2348        obj = {'a': 10, 'b': 'hello',
2349               'c': [100, 200, bytearray(b'\xf0\xf1\xf2\xf3\xf4')],
2350               'd': {784: bytearray(b'@\x14\x01P'),
2351                     78.0: bytearray(b'\x10\x05\x0b\x10\xb2\x1b\x00')}}
2352        encode_obj = base64_recursive_encode(obj)
2353        decode_obj = base64_recursive_decode(encode_obj)
2354
2355        encode_obj:  {'YQ==': 10,
2356                      'Yw==': [100, 200, '8PHy8/Q='],
2357                      'Yg==': 'aGVsbG8='
2358                      'ZA==': {784: 'QBQBUA==', 78.0: 'EAULELIbAA=='}}
2359        decode_obj:  {'a': 10,
2360                      'c': [100, 200, '\xf0\xf1\xf2\xf3\xf4'],
2361                      'b': 'hello',
2362                      'd': {784: '@\x14\x01P',
2363                            78.0: '\x10\x05\x0b\x10\xb2\x1b\x00'}}
2364
2365    in Python 3:
2366        obj = {'a': 10, 'b': 'hello',
2367               'c': [100, 200, bytearray(b'\xf0\xf1\xf2\xf3\xf4')],
2368               'd': {784: bytearray(b'@\x14\x01P'),
2369                     78.0: bytearray(b'\x10\x05\x0b\x10\xb2\x1b\x00')}}
2370        encode_obj = base64_recursive_encode(obj)
2371        decode_obj = base64_recursive_decode(encode_obj)
2372
2373        encode_obj:  {'a': 10,
2374                      'c': [100, 200, '8PHy8/Q='],
2375                      'b': 'hello',
2376                      'ZA==': {784: 'QBQBUA==', 78.0: 'EAULELIbAA=='}}
2377        decode_obj:  {'a': 10,
2378                      'c': [100, 200, '\xf0\xf1\xf2\xf3\xf4'],
2379                      'b': 'hello',
2380                      'd': {784: '@\x14\x01P',
2381                            78.0: '\x10\x05\x0b\x10\xb2\x1b\x00'}}
2382
2383    @param obj: the object to apply base64 encoding recursively.
2384
2385    @return: the base64 encoded object.
2386    """
2387    if is_python2():
2388        encode_types = (six.string_types, bytearray)
2389    else:
2390        encode_types = (bytes, bytearray)
2391
2392    return recursive_func(obj, base64.standard_b64encode, encode_types)
2393
2394
2395def base64_recursive_decode(obj):
2396    """Apply base64 decode recursively into the obj structure.
2397
2398    @param obj: the object to apply base64 decoding recursively.
2399
2400    @return: the base64 decoded object.
2401    """
2402    if is_python2():
2403        decode_types = (six.string_types, )
2404    else:
2405        decode_types = (bytes, bytearray)
2406    return recursive_func(obj, base64.standard_b64decode, decode_types,
2407                          fix_num_key=True)
2408
2409
2410def bytes_to_str_recursive(obj):
2411    """Converts obj's bytes elements to str.
2412
2413    It focuses on elements in the input obj whose type is bytes or byearray.
2414    For the elements, it first guesses the encoding of the input bytes (or
2415    bytearray) and decode the bytes to str. For unknown encoding, try UTF-8.
2416    If it still fails, converts the element as "ERROR_DECODE_BYTES_TO_STR".
2417
2418    @param obj: an object.
2419
2420    @return: an object that converts the input object's bytes elements to
2421        strings.
2422    """
2423    # Python 2's bytes is equivalent to string. Do nothing.
2424    if is_python2():
2425        return obj
2426
2427    def bytes_to_str(bytes_obj):
2428        guessed_encoding = chardet.detect(bytes_obj).get('encoding')
2429        if not guessed_encoding:
2430            guessed_encoding = 'utf-8'
2431        try:
2432            return bytes_obj.decode(guessed_encoding, 'backslashreplace')
2433        except:
2434            logging.info("Failed to decode bytes %r to str with encoding %r",
2435                         bytes_obj, guessed_encoding)
2436            return 'ERROR_DECODE_BYTES_TO_STR'
2437
2438    return recursive_func(obj, bytes_to_str, (bytes, bytearray))
2439