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