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