blob: d85984b277e5a5ee71bfcb855133ad8335576c02 [file] [log] [blame]
Allen Li2c32d6b2017-02-03 15:28:10 -08001# 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
mblighaece77e2009-01-21 19:06:52 +00005"""
6Convenience functions for use by tests or whomever.
mblighaece77e2009-01-21 19:06:52 +00007"""
Allen Li2c32d6b2017-02-03 15:28:10 -08008
9# pylint: disable=missing-docstring
10
Joseph Hwange3cb1b02018-10-26 17:50:05 +080011import base64
Kuo-Hsin Yang9cd699c2018-03-23 12:13:27 +080012import collections
Allen Li2c32d6b2017-02-03 15:28:10 -080013import commands
Po-Hsien Wange141b8e2019-12-23 13:52:38 -080014import errno
Allen Li2c32d6b2017-02-03 15:28:10 -080015import fnmatch
16import glob
17import json
18import logging
19import math
20import multiprocessing
mblighcc038642009-02-05 20:15:52 +000021import os
Allen Li2c32d6b2017-02-03 15:28:10 -080022import pickle
23import platform
24import re
25import shutil
26import signal
Puthikorn Voravootivatc9f7f5f2018-10-23 18:24:34 -070027import string
Allen Li2c32d6b2017-02-03 15:28:10 -080028import tempfile
29import time
30import uuid
31
32from autotest_lib.client.common_lib import error
33from autotest_lib.client.common_lib import magic
34from autotest_lib.client.common_lib import utils
Greg Edelston1823cc02019-12-17 16:07:57 -070035from autotest_lib.client.common_lib.cros import cros_config
mblighaece77e2009-01-21 19:06:52 +000036
37from autotest_lib.client.common_lib.utils import *
Allen Li2c32d6b2017-02-03 15:28:10 -080038
39
40def 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
52def 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
61def cat_file_to_cmd(file, command, ignore_status=0, return_output=False):
62 """
63 equivalent to 'cat file | command' but knows to use
64 zcat or bzcat if appropriate
65 """
66 if not os.path.isfile(file):
67 raise NameError('invalid file %s to cat to command %s'
68 % (file, command))
69
70 if return_output:
71 run_cmd = utils.system_output
72 else:
73 run_cmd = utils.system
74
75 if magic.guess_type(file) == 'application/x-bzip2':
76 cat = 'bzcat'
77 elif magic.guess_type(file) == 'application/x-gzip':
78 cat = 'zcat'
79 else:
80 cat = 'cat'
81 return run_cmd('%s %s | %s' % (cat, file, command),
82 ignore_status=ignore_status)
83
84
85def extract_tarball_to_dir(tarball, dir):
86 """
87 Extract a tarball to a specified directory name instead of whatever
88 the top level of a tarball is - useful for versioned directory names, etc
89 """
90 if os.path.exists(dir):
91 if os.path.isdir(dir):
92 shutil.rmtree(dir)
93 else:
94 os.remove(dir)
95 pwd = os.getcwd()
96 os.chdir(os.path.dirname(os.path.abspath(dir)))
97 newdir = extract_tarball(tarball)
98 os.rename(newdir, dir)
99 os.chdir(pwd)
100
101
102def extract_tarball(tarball):
103 """Returns the directory extracted by the tarball."""
104 extracted = cat_file_to_cmd(tarball, 'tar xvf - 2>/dev/null',
105 return_output=True).splitlines()
106
107 dir = None
108
109 for line in extracted:
110 if line.startswith('./'):
111 line = line[2:]
112 if not line or line == '.':
113 continue
114 topdir = line.split('/')[0]
115 if os.path.isdir(topdir):
116 if dir:
Nick Crewsb681ac92019-10-17 14:26:11 -0600117 assert(dir == topdir), 'tarball must be a a single directory'
Allen Li2c32d6b2017-02-03 15:28:10 -0800118 else:
119 dir = topdir
120 if dir:
121 return dir
122 else:
123 raise NameError('extracting tarball produced no dir')
124
125
Allen Li2c32d6b2017-02-03 15:28:10 -0800126def force_copy(src, dest):
127 """Replace dest with a new copy of src, even if it exists"""
128 if os.path.isfile(dest):
129 os.remove(dest)
130 if os.path.isdir(dest):
131 dest = os.path.join(dest, os.path.basename(src))
132 shutil.copyfile(src, dest)
133 return dest
134
135
136def 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
141def 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
149def 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
159def 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
184def get_cc():
185 try:
186 return os.environ['CC']
187 except KeyError:
188 return 'gcc'
189
190
191def 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
205def 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
219def 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
228def 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
247def get_cpu_arch():
248 """Work out which CPU architecture we're running on"""
Douglas Anderson7978d022018-10-02 14:38:49 -0700249
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 Li2c32d6b2017-02-03 15:28:10 -0800259 return 'arm'
Douglas Anderson7978d022018-10-02 14:38:49 -0700260
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 Li2c32d6b2017-02-03 15:28:10 -0800266
267
268def 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 Anderson7978d022018-10-02 14:38:49 -0700277 compatible = f.read().split(chr(0))
Allen Li2c32d6b2017-02-03 15:28:10 -0800278 f.close()
Douglas Anderson7978d022018-10-02 14:38:49 -0700279 if list_grep(compatible, '^rockchip,'):
Allen Li2c32d6b2017-02-03 15:28:10 -0800280 return 'rockchip'
Douglas Anderson7978d022018-10-02 14:38:49 -0700281 elif list_grep(compatible, '^mediatek,'):
Allen Li2c32d6b2017-02-03 15:28:10 -0800282 return 'mediatek'
Douglas Anderson7978d022018-10-02 14:38:49 -0700283 elif list_grep(compatible, '^qcom,'):
284 return 'qualcomm'
Allen Li2c32d6b2017-02-03 15:28:10 -0800285 return None
286
287
288def 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
306def 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
319INTEL_UARCH_TABLE = {
Harry Panc9897aa2017-07-31 20:17:52 +0800320 '06_4C': 'Airmont',
Allen Li2c32d6b2017-02-03 15:28:10 -0800321 '06_1C': 'Atom',
322 '06_26': 'Atom',
Harry Panc9897aa2017-07-31 20:17:52 +0800323 '06_27': 'Atom',
324 '06_35': 'Atom',
Allen Li2c32d6b2017-02-03 15:28:10 -0800325 '06_36': 'Atom',
Allen Li2c32d6b2017-02-03 15:28:10 -0800326 '06_3D': 'Broadwell',
Harry Panc9897aa2017-07-31 20:17:52 +0800327 '06_47': 'Broadwell',
Harry Pan45cb0e02017-08-22 22:19:59 +0800328 '06_4F': 'Broadwell',
329 '06_56': 'Broadwell',
Harry Pan93d3cc72019-10-14 13:44:38 +0800330 '06_A5': 'Comet Lake',
331 '06_A6': 'Comet Lake',
Allen Li2c32d6b2017-02-03 15:28:10 -0800332 '06_0D': 'Dothan',
Harry Panc9897aa2017-07-31 20:17:52 +0800333 '06_5C': 'Goldmont',
Harry Pan0555ba02018-06-08 17:05:38 +0800334 '06_7A': 'Goldmont',
Allen Li2c32d6b2017-02-03 15:28:10 -0800335 '06_3C': 'Haswell',
Allen Li2c32d6b2017-02-03 15:28:10 -0800336 '06_45': 'Haswell',
337 '06_46': 'Haswell',
Harry Panc9897aa2017-07-31 20:17:52 +0800338 '06_3F': 'Haswell-E',
Harry Pan93d3cc72019-10-14 13:44:38 +0800339 '06_7D': 'Ice Lake',
340 '06_7E': 'Ice Lake',
Harry Panc9897aa2017-07-31 20:17:52 +0800341 '06_3A': 'Ivy Bridge',
342 '06_3E': 'Ivy Bridge-E',
343 '06_8E': 'Kaby Lake',
344 '06_9E': 'Kaby Lake',
Allen Li2c32d6b2017-02-03 15:28:10 -0800345 '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 Li2c32d6b2017-02-03 15:28:10 -0800353 '0F_03': 'Prescott',
354 '0F_04': 'Prescott',
355 '0F_06': 'Presler',
Harry Panc9897aa2017-07-31 20:17:52 +0800356 '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 Pan93d3cc72019-10-14 13:44:38 +0800366 '06_8C': 'Tiger Lake',
367 '06_8D': 'Tiger Lake',
Allen Li2c32d6b2017-02-03 15:28:10 -0800368 '06_25': 'Westmere',
369 '06_2C': 'Westmere',
370 '06_2F': 'Westmere',
371}
372
373
374def 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 Voravootivat086e88e2018-05-10 17:17:58 -0700393INTEL_SILVERMONT_BCLK_TABLE = [83333, 100000, 133333, 116667, 80000];
394
395
396def 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 Li2c32d6b2017-02-03 15:28:10 -0800409def 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
414def 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
422def 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
434def 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
445def get_cpu_family():
446 cpuinfo = get_cpuinfo()[0]
447 return int(cpuinfo['cpu_family'])
448
449
450def 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
459def 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
469def 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
474def memtotal():
475 return read_from_meminfo('MemTotal')
476
477
478def freememtotal():
479 return read_from_meminfo('MemFree')
480
481def usable_memtotal():
482 # Reserved 5% for OS use
483 return int(read_from_meminfo('MemFree') * 0.95)
484
Ben Chengca353aa2017-11-28 17:44:10 +0800485def swaptotal():
486 return read_from_meminfo('SwapTotal')
Allen Li2c32d6b2017-02-03 15:28:10 -0800487
488def 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 Yang9cd699c2018-03-23 12:13:27 +0800519_MEMINFO_RE = re.compile('^(\w+)(\(\w+\))?:\s+(\d+)')
520
521
522def 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 Li2c32d6b2017-02-03 15:28:10 -0800545def 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
559def 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
570def _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
580def 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 Erlandsson58776682017-11-06 11:32:49 +0100585def get_num_allocated_file_handles():
586 """
Kristoffer Erlandssona6928852017-11-09 10:04:31 +0100587 Returns the number of currently allocated file handles.
Kristoffer Erlandsson58776682017-11-06 11:32:49 +0100588
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 Li2c32d6b2017-02-03 15:28:10 -0800598def print_to_tty(string):
599 """Output string straight to the tty"""
600 open('/dev/tty', 'w').write(string + '\n')
601
602
603def 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
617def 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
625def prepend_path(newpath, oldpath):
626 """prepend newpath to oldpath"""
627 if (oldpath):
628 return newpath + ':' + oldpath
629 else:
630 return newpath
631
632
633def 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
646def 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
670def 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
681def 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
689def 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
702def 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
718def 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
725def 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
734def 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
748def 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
754def node_size():
755 nodes = max(len(numa_nodes()), 1)
756 return ((memtotal() * 1024) / nodes)
757
758
759def pickle_load(filename):
760 return pickle.load(open(filename, 'r'))
761
762
763# Return the kernel version and build timestamp.
764def running_os_release():
765 return os.uname()[2:4]
766
767
768def running_os_ident():
769 (version, timestamp) = running_os_release()
770 return version + '::' + timestamp
771
772
773def running_os_full_version():
774 (version, timestamp) = running_os_release()
775 return version
776
777
778# much like find . -name 'pattern'
779def 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
786def 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
792def 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
799def get_disks():
800 df_output = utils.system_output('df')
801 return _DISK_PARTITION_3_RE.findall(df_output)
802
803
804def 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
821def 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
830def 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 Grignou876e6912017-06-21 12:17:36 -0700843_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 Li2c32d6b2017-02-03 15:28:10 -0800846
847
848def 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
886def 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 Savery9bb7c322018-10-18 18:35:35 -0700899def 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 Li2c32d6b2017-02-03 15:28:10 -0800908def 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
917def 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 Grignou427c7b22017-10-26 17:39:29 -0700935def 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 Li2c32d6b2017-02-03 15:28:10 -0800949
950def 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 Savery9bb7c322018-10-18 18:35:35 -0700966def 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 Li2c32d6b2017-02-03 15:28:10 -0800980
981def 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 Lundmark1484a4a2019-01-09 16:36:35 +01001002_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
1006def 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 Li2c32d6b2017-02-03 15:28:10 -08001030def 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
1042def 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
1063def 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
1072def 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
1077def 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
1082def get_num_huge_pages():
1083 raw_hugepages = utils.system_output('/sbin/sysctl vm.nr_hugepages')
1084 return int(raw_hugepages.split()[2])
1085
1086
1087def set_num_huge_pages(num):
1088 utils.system('/sbin/sysctl vm.nr_hugepages=%d' % num)
1089
1090
1091def 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
1105def 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
1112def 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 Crews56a24352019-10-28 12:44:14 -06001122def 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 Li2c32d6b2017-02-03 15:28:10 -08001150
1151def 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
1172def 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
1179def 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
1186def standby():
1187 """
1188 Power-on suspend (S1)
1189 """
1190 set_power_state('standby')
1191
1192
1193def suspend_to_ram():
1194 """
1195 Suspend the system to RAM (S3)
1196 """
1197 set_power_state('mem')
1198
1199
1200def suspend_to_disk():
1201 """
1202 Suspend the system to disk (S4)
1203 """
1204 set_power_state('disk')
1205
1206
Po-Hsien Wangc02992f2017-08-10 11:18:46 -07001207_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 Li2c32d6b2017-02-03 15:28:10 -08001212_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
1220pciid_to_amd_architecture = {}
1221pciid_to_intel_architecture = {}
1222
1223class 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
1255def 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
1288def 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
1303def 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 Bhandarkare3adb0a2017-08-10 14:15:57 -07001307 port. if it is 0, look at DevToolsActivePort for the ephemeral port.
Allen Li2c32d6b2017-02-03 15:28:10 -08001308 """
1309 _, command = get_oldest_by_name('chrome')
1310 matches = re.search('--remote-debugging-port=([0-9]+)', command)
Achuith Bhandarkare3adb0a2017-08-10 14:15:57 -07001311 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 Li2c32d6b2017-02-03 15:28:10 -08001318
1319
1320def 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
1355def 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
1379def 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 Li5ed7e632017-02-03 16:31:33 -08001388 utils.TimeoutError: if processes still exist after timeout_sec.
Allen Li2c32d6b2017-02-03 15:28:10 -08001389 """
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
1404def is_virtual_machine():
Mike Frysingera4cbbe62018-07-29 02:33:16 -04001405 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 Li2c32d6b2017-02-03 15:28:10 -08001416
1417
1418def 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
1440def 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
1465def 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
1511def 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
1533def 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
1554def 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
1564def is_mountpoint(path):
1565 return path in [m['dest'] for m in mounts()]
1566
1567
1568def 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
1576def random_username():
1577 return str(uuid.uuid4()) + '@example.com'
1578
1579
1580def 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
1595def 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
1625def 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
1649def get_cpu_usage():
1650 """Returns machine's CPU usage.
1651
1652 This function uses /proc/stat to identify CPU usage.
1653 Returns:
Kristoffer Erlandsson82b9f322017-11-07 12:20:38 +01001654 A dictionary with values for all columns in /proc/stat
Allen Li2c32d6b2017-02-03 15:28:10 -08001655 Sample dictionary:
1656 {
1657 'user': 254544,
1658 'nice': 9,
1659 'system': 254768,
1660 'idle': 2859878,
Kristoffer Erlandsson82b9f322017-11-07 12:20:38 +01001661 'iowait': 1,
1662 'irq': 2,
1663 'softirq': 3,
1664 'steal': 4,
1665 'guest': 5,
1666 'guest_nice': 6
Allen Li2c32d6b2017-02-03 15:28:10 -08001667 }
Kristoffer Erlandsson82b9f322017-11-07 12:20:38 +01001668 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 Li2c32d6b2017-02-03 15:28:10 -08001670 """
Kristoffer Erlandsson82b9f322017-11-07 12:20:38 +01001671 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 Li2c32d6b2017-02-03 15:28:10 -08001682
1683def 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 Erlandsson82b9f322017-11-07 12:20:38 +01001688
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 Li2c32d6b2017-02-03 15:28:10 -08001692 """
Kristoffer Erlandsson82b9f322017-11-07 12:20:38 +01001693 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. Friedelb2b28b32017-12-19 16:34:25 -08001700 # 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 Li2c32d6b2017-02-03 15:28:10 -08001705 return ((float(time_active_end) - time_active_start) /
1706 (total_time_end - total_time_start))
1707
1708
1709def is_pgo_mode():
1710 return 'USE_PGO' in os.environ
1711
1712
1713def 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. Friedelb2b28b32017-12-19 16:34:25 -08001733 fraction_active_time = compute_active_cpu_time(cpu_usage_start,
1734 cpu_usage_end)
Allen Li2c32d6b2017-02-03 15:28:10 -08001735 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
1751def 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
1764def 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 Davenport4fc222a2019-04-30 17:35:57 -06001774 if temperature < 45:
Allen Li2c32d6b2017-02-03 15:28:10 -08001775 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 Davenporta4a3fc42019-04-05 15:26:00 -06001797def 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 Anderson49331e72020-01-08 14:36:06 -08001803 temperature = get_current_temperature_max()
Drew Davenporta4a3fc42019-04-05 15:26:00 -06001804 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 Li2c32d6b2017-02-03 15:28:10 -08001812# 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 Erlandsson1ad7db02017-11-01 11:28:44 +01001819def _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 Li2c32d6b2017-02-03 15:28:10 -08001827
1828def _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 Erlandsson1ad7db02017-11-01 11:28:44 +01001833 with _open_file(path) as f:
Allen Li2c32d6b2017-02-03 15:28:10 -08001834 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
1846def _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
1860def _get_float_from_file(path, line, prefix, postfix):
1861 match = _get_match_from_file(path, line, prefix, postfix)
1862 return float(match)
1863
1864
1865def _get_int_from_file(path, line, prefix, postfix):
1866 match = _get_match_from_file(path, line, prefix, postfix)
1867 return int(match)
1868
1869
1870def _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 Andersonef9e0a52020-01-08 14:46:03 -08001875def 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 Anderson5318cf12020-01-08 14:26:24 -08001895# The paths don't change. Avoid running find all the time.
1896_hwmon_paths = {}
1897
Po-Hsien Wange141b8e2019-12-23 13:52:38 -08001898def _get_hwmon_datas(file_pattern):
1899 """Returns a list of reading from hwmon."""
Allen Li2c32d6b2017-02-03 15:28:10 -08001900 # 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 Anderson5318cf12020-01-08 14:26:24 -08001905 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 Wange141b8e2019-12-23 13:52:38 -08001910 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 Li2c32d6b2017-02-03 15:28:10 -08001917
1918
Douglas Andersonef9e0a52020-01-08 14:46:03 -08001919def _get_hwmon_temperatures():
Allen Li2c32d6b2017-02-03 15:28:10 -08001920 """
Douglas Andersonef9e0a52020-01-08 14:46:03 -08001921 Returns the currently observed temperatures from hwmon
Allen Li2c32d6b2017-02-03 15:28:10 -08001922 """
Douglas Andersonef9e0a52020-01-08 14:46:03 -08001923 return list(_get_hwmon_datas('temp*_input'))
Allen Li2c32d6b2017-02-03 15:28:10 -08001924
1925
Douglas Andersonef9e0a52020-01-08 14:46:03 -08001926def _get_thermal_zone_temperatures():
Allen Li2c32d6b2017-02-03 15:28:10 -08001927 """
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
1943def get_ec_temperatures():
1944 """
1945 Uses ectool to return a list of all sensor temperatures in Celsius.
Nicolas Boichateebd1222017-11-10 10:21:42 -08001946
1947 Output from ectool is either '0: 300' or '0: 300 K' (newer ectool
1948 includes the unit).
Allen Li2c32d6b2017-02-03 15:28:10 -08001949 """
1950 temperatures = []
1951 try:
1952 full_cmd = 'ectool temps all'
1953 lines = utils.run(full_cmd, verbose=False).stdout.splitlines()
Nicolas Boichateebd1222017-11-10 10:21:42 -08001954 pattern = re.compile('.*: (\d+)')
Allen Li2c32d6b2017-02-03 15:28:10 -08001955 for line in lines:
Nicolas Boichateebd1222017-11-10 10:21:42 -08001956 matched = pattern.match(line)
1957 temperature = int(matched.group(1)) - 273
Allen Li2c32d6b2017-02-03 15:28:10 -08001958 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
1970def get_current_temperature_max():
1971 """
1972 Returns the highest reported board temperature (all sensors) in Celsius.
1973 """
Douglas Andersonef9e0a52020-01-08 14:46:03 -08001974 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 Li2c32d6b2017-02-03 15:28:10 -08001981 # 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
1988def 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
1998def 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
2007def 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 Khouderchah19e0ab32018-07-06 17:03:21 -07002012 paths = utils._get_cpufreq_paths('cpuinfo_max_freq')
Brian Norris951f3572019-01-16 10:13:03 -08002013 if not paths:
2014 raise ValueError('Could not find max freq; is cpufreq supported?')
Allen Li2c32d6b2017-02-03 15:28:10 -08002015 for path in paths:
Brian Norris951f3572019-01-16 10:13:03 -08002016 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 Li2c32d6b2017-02-03 15:28:10 -08002022 max_frequency = max(frequency, max_frequency)
2023 # Sanity check.
Brian Norris951f3572019-01-16 10:13:03 -08002024 assert max_frequency > 1e8, ('Unreasonably low CPU frequency: %.1f' %
2025 max_frequency)
Allen Li2c32d6b2017-02-03 15:28:10 -08002026 return max_frequency
2027
2028
Allen Li2c32d6b2017-02-03 15:28:10 -08002029def 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 Hughese3bf5d62019-02-27 14:08:57 -08002038def get_cpu_family_intel():
Allen Li2c32d6b2017-02-03 15:28:10 -08002039 """
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
2047def 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
2063def get_board():
2064 """
2065 Get the ChromeOS release board name from /etc/lsb-release.
2066 """
2067 return get_board_property('BOARD')
2068
2069
2070def 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 Suleyd5e648f2019-05-22 20:06:24 -07002079def 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 Broch2017ffc2018-07-10 08:49:26 -07002088def 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 Edelston1823cc02019-12-17 16:07:57 -07002099 platform = cros_config.call_cros_config_get_output('/ name', utils.run)
Greg Edelston85d14692019-11-26 22:35:32 +00002100 if platform == '':
2101 platform = get_board()
2102 return platform
Todd Broch2017ffc2018-07-10 08:49:26 -07002103
2104
En-Shuo Hsu17814eb2019-09-24 10:55:26 +08002105def get_sku():
2106 """
2107 Get the SKU number.
2108
2109 @returns SKU number
2110 """
Greg Edelston1823cc02019-12-17 16:07:57 -07002111 return cros_config.call_cros_config_get_output('/identity sku-id',
2112 utils.run)
En-Shuo Hsu17814eb2019-09-24 10:55:26 +08002113
2114
Puthikorn Voravootivat0f6d2302017-11-27 16:31:00 -08002115def get_ec_version():
2116 """Get the ec version as strings.
2117
2118 @returns a string representing this host's ec version.
2119 """
Puthikorn Voravootivat65ad7672018-01-30 14:00:01 -08002120 command = 'mosys ec info -s fw_version'
Puthikorn Voravootivat720640d2018-02-16 17:32:38 -08002121 result = utils.run(command, ignore_status=True)
2122 if result.exit_status != 0:
2123 return ''
2124 return result.stdout.strip()
Puthikorn Voravootivat0f6d2302017-11-27 16:31:00 -08002125
2126
2127def 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
2135def get_hardware_revision():
2136 """Get the hardware revision as strings.
2137
2138 @returns a string representing this host's hardware revision.
2139 """
Puthikorn Voravootivat65ad7672018-01-30 14:00:01 -08002140 command = 'mosys platform version'
Puthikorn Voravootivat720640d2018-02-16 17:32:38 -08002141 result = utils.run(command, ignore_status=True)
2142 if result.exit_status != 0:
2143 return ''
2144 return result.stdout.strip()
Puthikorn Voravootivat0f6d2302017-11-27 16:31:00 -08002145
2146
2147def 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 Voravootivata83e82c2018-02-15 17:49:45 -08002155def 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
2186def 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 Li2c32d6b2017-02-03 15:28:10 -08002201def 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 Voravootivata83e82c2018-02-15 17:49:45 -08002211 memory = get_mem_total_gb()
Allen Li2c32d6b2017-02-03 15:28:10 -08002212 # 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
2219def 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 Voravootivata83e82c2018-02-15 17:49:45 -08002229def 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 Li2c32d6b2017-02-03 15:28:10 -08002236def 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 Erlandssonb84da2b2017-11-01 21:29:24 +01002243def 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 Li2c32d6b2017-02-03 15:28:10 -08002259
2260def 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 Li2c32d6b2017-02-03 15:28:10 -08002270def 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
2278def 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
2294def 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
2301def has_mali():
2302 """ @return: True if system has a Mali GPU enabled."""
2303 return os.path.exists('/dev/mali0')
2304
2305def 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 Anderson7978d022018-10-02 14:38:49 -07002323 if socfamily == 'qualcomm':
2324 return 'qualcomm'
Allen Li2c32d6b2017-02-03 15:28:10 -08002325 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 Norrisbb745382017-06-30 11:49:27 -07002357 'beltino', 'panther', 'stumpy', 'panther', 'tricky', 'zako', 'veyron_rialto'
Allen Li2c32d6b2017-02-03 15:28:10 -08002358]
2359
2360
2361def 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
2370def 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
2382def 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 Savery3a1185b2019-10-15 12:00:08 -07002391def 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 Li2c32d6b2017-02-03 15:28:10 -08002408def 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
2416def 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
2429def 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
2442def 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
2454def 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
2459def 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 Li2c32d6b2017-02-03 15:28:10 -08002474def graphics_platform():
2475 """
2476 Return a string identifying the graphics platform,
2477 e.g. 'glx' or 'x11_egl' or 'gbm'
2478 """
Po-Hsien Wangdab8e182017-05-03 10:25:41 -07002479 return 'null'
Allen Li2c32d6b2017-02-03 15:28:10 -08002480
2481
2482def 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 Li2c32d6b2017-02-03 15:28:10 -08002490def 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
2503def 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
2516def 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 Keshetb3950442018-01-24 15:32:03 -08002532 return utils.run(cmd, verbose=False).stdout
Puthikorn Voravootivatc9f7f5f2018-10-23 18:24:34 -07002533
2534
2535def 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 Hwange3cb1b02018-10-26 17:50:05 +08002543
2544
2545def 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
2609def 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
2673def 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)