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