2 DO NOT import this file directly - import client/bin/utils.py,
5 Convenience functions for use by tests or whomever.
7 Note that this file is mixed in by utils.py - note very carefully the
8 precedence order defined there
10 import os
, shutil
, sys
, signal
, commands
, pickle
, glob
, statvfs
11 import math
, re
, string
, fnmatch
, logging
12 from autotest_lib
.client
.common_lib
import error
, utils
, magic
15 def grep(pattern
, file):
17 This is mainly to fix the return code inversion from grep
18 Also handles compressed files.
20 returns 1 if the pattern is present in the file, 0 if not.
22 command
= 'grep "%s" > /dev/null' % pattern
23 ret
= cat_file_to_cmd(file, command
, ignore_status
=True)
27 def difflist(list1
, list2
):
28 """returns items in list2 that are not in list1"""
36 def cat_file_to_cmd(file, command
, ignore_status
=0, return_output
=False):
38 equivalent to 'cat file | command' but knows to use
39 zcat or bzcat if appropriate
41 if not os
.path
.isfile(file):
42 raise NameError('invalid file %s to cat to command %s'
46 run_cmd
= utils
.system_output
48 run_cmd
= utils
.system
50 if magic
.guess_type(file) == 'application/x-bzip2':
52 elif magic
.guess_type(file) == 'application/x-gzip':
56 return run_cmd('%s %s | %s' % (cat
, file, command
),
57 ignore_status
=ignore_status
)
60 def extract_tarball_to_dir(tarball
, dir):
62 Extract a tarball to a specified directory name instead of whatever
63 the top level of a tarball is - useful for versioned directory names, etc
65 if os
.path
.exists(dir):
66 if os
.path
.isdir(dir):
71 os
.chdir(os
.path
.dirname(os
.path
.abspath(dir)))
72 newdir
= extract_tarball(tarball
)
73 os
.rename(newdir
, dir)
77 def extract_tarball(tarball
):
78 """Returns the directory extracted by the tarball."""
79 extracted
= cat_file_to_cmd(tarball
, 'tar xvf - 2>/dev/null',
80 return_output
=True).splitlines()
84 for line
in extracted
:
85 line
= re
.sub(r
'^./', '', line
)
86 if not line
or line
== '.':
88 topdir
= line
.split('/')[0]
89 if os
.path
.isdir(topdir
):
97 raise NameError('extracting tarball produced no dir')
100 def hash_file(filename
, size
=None, method
="md5"):
102 Calculate the hash of filename.
103 If size is not None, limit to first size bytes.
104 Throw exception if something is wrong with filename.
105 Can be also implemented with bash one-liner (assuming size%1024==0):
106 dd if=filename bs=1024 count=size/1024 | sha1sum -
108 @param filename: Path of the file that will have its hash calculated.
109 @param method: Method used to calculate the hash. Supported methods:
112 @returns: Hash of the file, if something goes wrong, return None.
115 fsize
= os
.path
.getsize(filename
)
117 if not size
or size
> fsize
:
119 f
= open(filename
, 'rb')
122 hash = utils
.hash(method
)
124 logging
.error("Unknown hash type %s, returning None" % method
)
129 data
= f
.read(chunksize
)
131 logging
.debug("Nothing left to read but size=%d" % size
)
136 return hash.hexdigest()
139 def unmap_url_cache(cachedir
, url
, expected_hash
, method
="md5"):
141 Downloads a file from a URL to a cache directory. If the file is already
142 at the expected position and has the expected hash, let's not download it
145 @param cachedir: Directory that might hold a copy of the file we want to
147 @param url: URL for the file we want to download.
148 @param expected_hash: Hash string that we expect the file downloaded to
150 @param method: Method used to calculate the hash string (md5, sha1).
152 # Let's convert cachedir to a canonical path, if it's not already
153 cachedir
= os
.path
.realpath(cachedir
)
154 if not os
.path
.isdir(cachedir
):
156 os
.makedirs(cachedir
)
158 raise ValueError('Could not create cache directory %s' % cachedir
)
159 file_from_url
= os
.path
.basename(url
)
160 file_local_path
= os
.path
.join(cachedir
, file_from_url
)
164 while not file_hash
== expected_hash
:
165 if os
.path
.isfile(file_local_path
):
166 file_hash
= hash_file(file_local_path
, method
)
167 if file_hash
== expected_hash
:
168 # File is already at the expected position and ready to go
171 # Let's download the package again, it's corrupted...
172 logging
.error("Seems that file %s is corrupted, trying to "
173 "download it again" % file_from_url
)
177 # File is not there, let's download it
179 if failure_counter
> 1:
180 raise EnvironmentError("Consistently failed to download the "
181 "package %s. Aborting further download "
182 "attempts. This might mean either the "
183 "network connection has problems or the "
184 "expected hash string that was determined "
185 "for this file is wrong" % file_from_url
)
186 file_path
= utils
.unmap_url(cachedir
, src
, cachedir
)
191 def force_copy(src
, dest
):
192 """Replace dest with a new copy of src, even if it exists"""
193 if os
.path
.isfile(dest
):
195 if os
.path
.isdir(dest
):
196 dest
= os
.path
.join(dest
, os
.path
.basename(src
))
197 shutil
.copyfile(src
, dest
)
201 def force_link(src
, dest
):
202 """Link src to dest, overwriting it if it exists"""
203 return utils
.system("ln -sf %s %s" % (src
, dest
))
206 def file_contains_pattern(file, pattern
):
207 """Return true if file contains the specified egrep pattern"""
208 if not os
.path
.isfile(file):
209 raise NameError('file %s does not exist' % file)
210 return not utils
.system('egrep -q "' + pattern
+ '" ' + file, ignore_status
=True)
213 def list_grep(list, pattern
):
214 """True if any item in list matches the specified pattern."""
215 compiled
= re
.compile(pattern
)
217 match
= compiled
.search(line
)
224 """Try to guess what's the os vendor
226 if os
.path
.isfile('/etc/SuSE-release'):
231 if not os
.path
.isfile(issue
):
234 if file_contains_pattern(issue
, 'Red Hat'):
236 elif file_contains_pattern(issue
, 'Fedora'):
238 elif file_contains_pattern(issue
, 'SUSE'):
240 elif file_contains_pattern(issue
, 'Ubuntu'):
242 elif file_contains_pattern(issue
, 'Debian'):
250 return os
.environ
['CC']
256 """Return the full path to vmlinux
258 Ahem. This is crap. Pray harder. Bad Martin.
260 vmlinux
= '/boot/vmlinux-%s' % utils
.system_output('uname -r')
261 if os
.path
.isfile(vmlinux
):
263 vmlinux
= '/lib/modules/%s/build/vmlinux' % utils
.system_output('uname -r')
264 if os
.path
.isfile(vmlinux
):
270 """Return the full path to System.map
272 Ahem. This is crap. Pray harder. Bad Martin.
274 map = '/boot/System.map-%s' % utils
.system_output('uname -r')
275 if os
.path
.isfile(map):
277 map = '/lib/modules/%s/build/System.map' % utils
.system_output('uname -r')
278 if os
.path
.isfile(map):
283 def get_modules_dir():
284 """Return the modules dir for the running kernel version"""
285 kernel_version
= utils
.system_output('uname -r')
286 return '/lib/modules/%s/kernel' % kernel_version
290 """Work out which CPU architecture we're running on"""
291 f
= open('/proc/cpuinfo', 'r')
292 cpuinfo
= f
.readlines()
294 if list_grep(cpuinfo
, '^cpu.*(RS64|POWER3|Broadband Engine)'):
296 elif list_grep(cpuinfo
, '^cpu.*POWER4'):
298 elif list_grep(cpuinfo
, '^cpu.*POWER5'):
300 elif list_grep(cpuinfo
, '^cpu.*POWER6'):
302 elif list_grep(cpuinfo
, '^cpu.*POWER7'):
304 elif list_grep(cpuinfo
, '^cpu.*PPC970'):
306 elif list_grep(cpuinfo
, 'ARM'):
308 elif list_grep(cpuinfo
, '^flags.*:.* lm .*'):
314 def get_current_kernel_arch():
315 """Get the machine architecture, now just a wrap of 'uname -m'."""
316 return os
.popen('uname -m').read().rstrip()
319 def get_file_arch(filename
):
320 # -L means follow symlinks
321 file_data
= utils
.system_output('file -L ' + filename
)
322 if file_data
.count('80386'):
328 """number of CPUs in the local machine according to /proc/cpuinfo"""
329 f
= file('/proc/cpuinfo', 'r')
331 for line
in f
.readlines():
332 if line
.lower().startswith('processor'):
337 # Returns total memory in kb
338 def read_from_meminfo(key
):
339 meminfo
= utils
.system_output('grep %s /proc/meminfo' % key
)
340 return int(re
.search(r
'\d+', meminfo
).group(0))
344 return read_from_meminfo('MemTotal')
348 return read_from_meminfo('MemFree')
351 def rounded_memtotal():
352 # Get total of all physical mem, in kbytes
353 usable_kbytes
= memtotal()
354 # usable_kbytes is system's usable DRAM in kbytes,
355 # as reported by memtotal() from device /proc/meminfo memtotal
356 # after Linux deducts 1.5% to 5.1% for system table overhead
357 # Undo the unknown actual deduction by rounding up
358 # to next small multiple of a big power-of-two
359 # eg 12GB - 5.1% gets rounded back up to 12GB
360 mindeduct
= 0.015 # 1.5 percent
361 maxdeduct
= 0.055 # 5.5 percent
362 # deduction range 1.5% .. 5.5% supports physical mem sizes
363 # 6GB .. 12GB in steps of .5GB
364 # 12GB .. 24GB in steps of 1 GB
365 # 24GB .. 48GB in steps of 2 GB ...
366 # Finer granularity in physical mem sizes would require
367 # tighter spread between min and max possible deductions
369 # increase mem size by at least min deduction, without rounding
370 min_kbytes
= int(usable_kbytes
/ (1.0 - mindeduct
))
371 # increase mem size further by 2**n rounding, by 0..roundKb or more
372 round_kbytes
= int(usable_kbytes
/ (1.0 - maxdeduct
)) - min_kbytes
373 # find least binary roundup 2**n that covers worst-cast roundKb
374 mod2n
= 1 << int(math
.ceil(math
.log(round_kbytes
, 2)))
375 # have round_kbytes <= mod2n < round_kbytes*2
376 # round min_kbytes up to next multiple of mod2n
377 phys_kbytes
= min_kbytes
+ mod2n
- 1
378 phys_kbytes
= phys_kbytes
- (phys_kbytes
% mod2n
) # clear low bits
382 def sysctl(key
, value
=None):
383 """Generic implementation of sysctl, to read and write.
385 @param key: A location under /proc/sys
386 @param value: If not None, a value to write into the sysctl.
388 @return The single-line sysctl value as a string.
390 path
= '/proc/sys/%s' % key
391 if value
is not None:
392 utils
.write_one_line(path
, str(value
))
393 return utils
.read_one_line(path
)
396 def sysctl_kernel(key
, value
=None):
397 """(Very) partial implementation of sysctl, for kernel params"""
398 if value
is not None:
400 utils
.write_one_line('/proc/sys/kernel/%s' % key
, str(value
))
403 out
= utils
.read_one_line('/proc/sys/kernel/%s' % key
)
404 return int(re
.search(r
'\d+', out
).group(0))
407 def _convert_exit_status(sts
):
408 if os
.WIFSIGNALED(sts
):
409 return -os
.WTERMSIG(sts
)
410 elif os
.WIFEXITED(sts
):
411 return os
.WEXITSTATUS(sts
)
414 raise RuntimeError("Unknown exit status %d!" % sts
)
417 def where_art_thy_filehandles():
418 """Dump the current list of filehandles"""
419 os
.system("ls -l /proc/%d/fd >> /dev/tty" % os
.getpid())
422 def print_to_tty(string
):
423 """Output string straight to the tty"""
424 open('/dev/tty', 'w').write(string
+ '\n')
427 def dump_object(object):
428 """Dump an object's attributes and methods
432 for item
in object.__dict
__.iteritems():
441 def environ(env_key
):
442 """return the requested environment variable, or '' if unset"""
443 if (os
.environ
.has_key(env_key
)):
444 return os
.environ
[env_key
]
449 def prepend_path(newpath
, oldpath
):
450 """prepend newpath to oldpath"""
452 return newpath
+ ':' + oldpath
457 def append_path(oldpath
, newpath
):
458 """append newpath to oldpath"""
460 return oldpath
+ ':' + newpath
465 def avgtime_print(dir):
466 """ Calculate some benchmarking statistics.
467 Input is a directory containing a file called 'time'.
468 File contains one-per-line results of /usr/bin/time.
469 Output is average Elapsed, User, and System time in seconds,
470 and average CPU percentage.
472 f
= open(dir + "/time")
473 user
= system
= elapsed
= cpu
= count
= 0
474 r
= re
.compile('([\d\.]*)user ([\d\.]*)system (\d*):([\d\.]*)elapsed (\d*)%CPU')
475 for line
in f
.readlines():
478 user
+= float(s
.group(1))
479 system
+= float(s
.group(2))
480 elapsed
+= (float(s
.group(3)) * 60) + float(s
.group(4))
481 cpu
+= float(s
.group(5))
484 raise ValueError("badly formatted times")
487 return "Elapsed: %0.2fs User: %0.2fs System: %0.2fs CPU: %0.0f%%" % \
488 (elapsed
/count
, user
/count
, system
/count
, cpu
/count
)
491 def running_config():
493 Return path of config file of the currently running kernel
495 version
= utils
.system_output('uname -r')
496 for config
in ('/proc/config.gz', \
497 '/boot/config-%s' % version
,
498 '/lib/modules/%s/build/.config' % version
):
499 if os
.path
.isfile(config
):
504 def check_for_kernel_feature(feature
):
505 config
= running_config()
508 raise TypeError("Can't find kernel config file")
510 if magic
.guess_type(config
) == 'application/x-gzip':
514 grep
+= ' ^CONFIG_%s= %s' % (feature
, config
)
516 if not utils
.system_output(grep
, ignore_status
=True):
517 raise ValueError("Kernel doesn't have a %s feature" % (feature
))
520 def cpu_online_map():
522 Check out the available cpu online map
525 for line
in open('/proc/cpuinfo', 'r').readlines():
526 if line
.startswith('processor'):
527 cpus
.append(line
.split()[2]) # grab cpu number
531 def check_glibc_ver(ver
):
532 glibc_ver
= commands
.getoutput('ldd --version').splitlines()[0]
533 glibc_ver
= re
.search(r
'(\d+\.\d+(\.\d+)?)', glibc_ver
).group()
534 if utils
.compare_versions(glibc_ver
, ver
) == -1:
535 raise error
.TestError("Glibc too old (%s). Glibc >= %s is needed." %
538 def check_kernel_ver(ver
):
539 kernel_ver
= utils
.system_output('uname -r')
540 kv_tmp
= re
.split(r
'[-]', kernel_ver
)[0:3]
541 # In compare_versions, if v1 < v2, return value == -1
542 if utils
.compare_versions(kv_tmp
[0], ver
) == -1:
543 raise error
.TestError("Kernel too old (%s). Kernel > %s is needed." %
547 def human_format(number
):
548 # Convert number to kilo / mega / giga format.
551 kilo
= float(number
) / 1024.0
553 return "%.2fk" % kilo
562 node_paths
= glob
.glob('/sys/devices/system/node/node*')
563 nodes
= [int(re
.sub(r
'.*node(\d+)', r
'\1', x
)) for x
in node_paths
]
564 return (sorted(nodes
))
568 nodes
= max(len(numa_nodes()), 1)
569 return ((memtotal() * 1024) / nodes
)
572 def to_seconds(time_string
):
573 """Converts a string in M+:SS.SS format to S+.SS"""
574 elts
= time_string
.split(':')
577 return str(int(elts
[0]) * 60 + float(elts
[1]))
580 def extract_all_time_results(results_string
):
581 """Extract user, system, and elapsed times into a list of tuples"""
582 pattern
= re
.compile(r
"(.*?)user (.*?)system (.*?)elapsed")
584 for result
in pattern
.findall(results_string
):
585 results
.append(tuple([to_seconds(elt
) for elt
in result
]))
589 def pickle_load(filename
):
590 return pickle
.load(open(filename
, 'r'))
593 # Return the kernel version and build timestamp.
594 def running_os_release():
595 return os
.uname()[2:4]
598 def running_os_ident():
599 (version
, timestamp
) = running_os_release()
600 return version
+ '::' + timestamp
603 def running_os_full_version():
604 (version
, timestamp
) = running_os_release()
608 # much like find . -name 'pattern'
609 def locate(pattern
, root
=os
.getcwd()):
610 for path
, dirs
, files
in os
.walk(root
):
612 if fnmatch
.fnmatch(f
, pattern
):
613 yield os
.path
.abspath(os
.path
.join(path
, f
))
617 """Return the disk free space, in bytes"""
619 return s
.f_bavail
* s
.f_bsize
622 def disk_block_size(path
):
623 """Return the disk block size, in bytes"""
624 return os
.statvfs(path
).f_bsize
627 def get_cpu_family():
628 procinfo
= utils
.system_output('cat /proc/cpuinfo')
629 CPU_FAMILY_RE
= re
.compile(r
'^cpu family\s+:\s+(\S+)', re
.M
)
630 matches
= CPU_FAMILY_RE
.findall(procinfo
)
632 return int(matches
[0])
634 raise error
.TestError('Could not get valid cpu family data')
638 df_output
= utils
.system_output('df')
639 disk_re
= re
.compile(r
'^(/dev/hd[a-z]+)3', re
.M
)
640 return disk_re
.findall(df_output
)
643 def load_module(module_name
):
644 # Checks if a module has already been loaded
645 if module_is_loaded(module_name
):
648 utils
.system('/sbin/modprobe ' + module_name
)
652 def unload_module(module_name
):
654 Removes a module. Handles dependencies. If even then it's not possible
655 to remove one of the modules, it will trhow an error.CmdError exception.
657 @param module_name: Name of the module we want to remove.
659 l_raw
= utils
.system_output("/sbin/lsmod").splitlines()
660 lsmod
= [x
for x
in l_raw
if x
.split()[0] == module_name
]
662 line_parts
= lsmod
[0].split()
663 if len(line_parts
) == 4:
664 submodules
= line_parts
[3].split(",")
665 for submodule
in submodules
:
666 unload_module(submodule
)
667 utils
.system("/sbin/modprobe -r %s" % module_name
)
668 logging
.info("Module %s unloaded" % module_name
)
670 logging
.info("Module %s is already unloaded" % module_name
)
673 def module_is_loaded(module_name
):
674 module_name
= module_name
.replace('-', '_')
675 modules
= utils
.system_output('/sbin/lsmod').splitlines()
676 for module
in modules
:
677 if module
.startswith(module_name
) and module
[len(module_name
)] == ' ':
682 def get_loaded_modules():
683 lsmod_output
= utils
.system_output('/sbin/lsmod').splitlines()[1:]
684 return [line
.split(None, 1)[0] for line
in lsmod_output
]
687 def get_huge_page_size():
688 output
= utils
.system_output('grep Hugepagesize /proc/meminfo')
689 return int(output
.split()[1]) # Assumes units always in kB. :(
692 def get_num_huge_pages():
693 raw_hugepages
= utils
.system_output('/sbin/sysctl vm.nr_hugepages')
694 return int(raw_hugepages
.split()[2])
697 def set_num_huge_pages(num
):
698 utils
.system('/sbin/sysctl vm.nr_hugepages=%d' % num
)
701 def get_cpu_vendor():
702 cpuinfo
= open('/proc/cpuinfo').read()
703 vendors
= re
.findall(r
'(?m)^vendor_id\s*:\s*(\S+)\s*$', cpuinfo
)
704 for i
in xrange(1, len(vendors
)):
705 if vendors
[i
] != vendors
[0]:
706 raise error
.TestError('multiple cpu vendors found: ' + str(vendors
))
712 This routine returns a list of cpu devices found under
713 /sys/devices/system/cpu.
715 cmd
= 'find /sys/devices/system/cpu/ -maxdepth 1 -type d -name cpu*'
716 return utils
.system_output(cmd
).splitlines()
719 def ping_default_gateway():
720 """Ping the default gateway."""
722 network
= open('/etc/sysconfig/network')
723 m
= re
.search('GATEWAY=(\S+)', network
.read())
727 cmd
= 'ping %s -c 5 > /dev/null' % gw
728 return utils
.system(cmd
, ignore_status
=True)
730 raise error
.TestError('Unable to find default gateway')
734 """Writes back all dirty pages to disk and clears all the caches."""
736 # We ignore failures here as this will fail on 2.6.11 kernels.
737 utils
.system("echo 3 > /proc/sys/vm/drop_caches", ignore_status
=True)
740 def process_is_alive(name_pattern
):
742 'pgrep name' misses all python processes and also long process names.
743 'pgrep -f name' gets all shell commands with name in args.
744 So look only for command whose initial pathname ends with name.
745 Name itself is an egrep pattern, so it can use | etc for variations.
747 return utils
.system("pgrep -f '^([^ /]*/)*(%s)([ ]|$)'" % name_pattern
,
748 ignore_status
=True) == 0
751 def get_hwclock_seconds(utc
=True):
753 Return the hardware clock in seconds as a floating point value.
754 Use Coordinated Universal Time if utc is True, local time otherwise.
755 Raise a ValueError if unable to read the hardware clock.
757 cmd
= '/sbin/hwclock --debug'
760 hwclock_output
= utils
.system_output(cmd
, ignore_status
=True)
761 match
= re
.search(r
'= ([0-9]+) seconds since .+ (-?[0-9.]+) seconds$',
762 hwclock_output
, re
.DOTALL
)
764 seconds
= int(match
.group(1)) + float(match
.group(2))
765 logging
.debug('hwclock seconds = %f' % seconds
)
768 raise ValueError('Unable to read the hardware clock -- ' +
772 def set_wake_alarm(alarm_time
):
774 Set the hardware RTC-based wake alarm to 'alarm_time'.
776 utils
.write_one_line('/sys/class/rtc/rtc0/wakealarm', str(alarm_time
))
779 def set_power_state(state
):
781 Set the system power state to 'state'.
783 utils
.write_one_line('/sys/power/state', state
)
788 Power-on suspend (S1)
790 set_power_state('standby')
793 def suspend_to_ram():
795 Suspend the system to RAM (S3)
797 set_power_state('mem')
800 def suspend_to_disk():
802 Suspend the system to disk (S4)
804 set_power_state('disk')