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