virt.virt_test_utils: run_autotest - 'tar' needs relative paths to strip the leading '/'
[autotest-zwu.git] / client / bin / base_utils.py
blob5d7256667694769dbda61c525ac88f62d0d190b6
1 """
2 DO NOT import this file directly - import client/bin/utils.py,
3 which will mix this in
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
9 """
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):
16 """
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.
21 """
22 command = 'grep "%s" > /dev/null' % pattern
23 ret = cat_file_to_cmd(file, command, ignore_status=True)
24 return not ret
27 def difflist(list1, list2):
28 """returns items in list2 that are not in list1"""
29 diff = [];
30 for x in list2:
31 if x not in list1:
32 diff.append(x)
33 return diff
36 def cat_file_to_cmd(file, command, ignore_status=0, return_output=False):
37 """
38 equivalent to 'cat file | command' but knows to use
39 zcat or bzcat if appropriate
40 """
41 if not os.path.isfile(file):
42 raise NameError('invalid file %s to cat to command %s'
43 % (file, command))
45 if return_output:
46 run_cmd = utils.system_output
47 else:
48 run_cmd = utils.system
50 if magic.guess_type(file) == 'application/x-bzip2':
51 cat = 'bzcat'
52 elif magic.guess_type(file) == 'application/x-gzip':
53 cat = 'zcat'
54 else:
55 cat = 'cat'
56 return run_cmd('%s %s | %s' % (cat, file, command),
57 ignore_status=ignore_status)
60 def extract_tarball_to_dir(tarball, dir):
61 """
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
64 """
65 if os.path.exists(dir):
66 if os.path.isdir(dir):
67 shutil.rmtree(dir)
68 else:
69 os.remove(dir)
70 pwd = os.getcwd()
71 os.chdir(os.path.dirname(os.path.abspath(dir)))
72 newdir = extract_tarball(tarball)
73 os.rename(newdir, dir)
74 os.chdir(pwd)
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()
82 dir = None
84 for line in extracted:
85 line = re.sub(r'^./', '', line)
86 if not line or line == '.':
87 continue
88 topdir = line.split('/')[0]
89 if os.path.isdir(topdir):
90 if dir:
91 assert(dir == topdir)
92 else:
93 dir = topdir
94 if dir:
95 return dir
96 else:
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:
110 * md5
111 * sha1
112 @returns: Hash of the file, if something goes wrong, return None.
114 chunksize = 4096
115 fsize = os.path.getsize(filename)
117 if not size or size > fsize:
118 size = fsize
119 f = open(filename, 'rb')
121 try:
122 hash = utils.hash(method)
123 except ValueError:
124 logging.error("Unknown hash type %s, returning None" % method)
126 while size > 0:
127 if chunksize > size:
128 chunksize = size
129 data = f.read(chunksize)
130 if len(data) == 0:
131 logging.debug("Nothing left to read but size=%d" % size)
132 break
133 hash.update(data)
134 size -= len(data)
135 f.close()
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
143 again.
145 @param cachedir: Directory that might hold a copy of the file we want to
146 download.
147 @param url: URL for the file we want to download.
148 @param expected_hash: Hash string that we expect the file downloaded to
149 have.
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):
155 try:
156 os.makedirs(cachedir)
157 except:
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)
162 file_hash = None
163 failure_counter = 0
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
169 src = file_from_url
170 else:
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)
174 src = url
175 failure_counter += 1
176 else:
177 # File is not there, let's download it
178 src = url
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)
188 return file_path
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):
194 os.remove(dest)
195 if os.path.isdir(dest):
196 dest = os.path.join(dest, os.path.basename(src))
197 shutil.copyfile(src, dest)
198 return 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)
216 for line in list:
217 match = compiled.search(line)
218 if (match):
219 return 1
220 return 0
223 def get_os_vendor():
224 """Try to guess what's the os vendor
226 if os.path.isfile('/etc/SuSE-release'):
227 return 'SUSE'
229 issue = '/etc/issue'
231 if not os.path.isfile(issue):
232 return 'Unknown'
234 if file_contains_pattern(issue, 'Red Hat'):
235 return 'Red Hat'
236 elif file_contains_pattern(issue, 'Fedora'):
237 return 'Fedora Core'
238 elif file_contains_pattern(issue, 'SUSE'):
239 return 'SUSE'
240 elif file_contains_pattern(issue, 'Ubuntu'):
241 return 'Ubuntu'
242 elif file_contains_pattern(issue, 'Debian'):
243 return 'Debian'
244 else:
245 return 'Unknown'
248 def get_cc():
249 try:
250 return os.environ['CC']
251 except KeyError:
252 return 'gcc'
255 def get_vmlinux():
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):
262 return vmlinux
263 vmlinux = '/lib/modules/%s/build/vmlinux' % utils.system_output('uname -r')
264 if os.path.isfile(vmlinux):
265 return vmlinux
266 return None
269 def get_systemmap():
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):
276 return map
277 map = '/lib/modules/%s/build/System.map' % utils.system_output('uname -r')
278 if os.path.isfile(map):
279 return map
280 return None
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
289 def get_cpu_arch():
290 """Work out which CPU architecture we're running on"""
291 f = open('/proc/cpuinfo', 'r')
292 cpuinfo = f.readlines()
293 f.close()
294 if list_grep(cpuinfo, '^cpu.*(RS64|POWER3|Broadband Engine)'):
295 return 'power'
296 elif list_grep(cpuinfo, '^cpu.*POWER4'):
297 return 'power4'
298 elif list_grep(cpuinfo, '^cpu.*POWER5'):
299 return 'power5'
300 elif list_grep(cpuinfo, '^cpu.*POWER6'):
301 return 'power6'
302 elif list_grep(cpuinfo, '^cpu.*POWER7'):
303 return 'power7'
304 elif list_grep(cpuinfo, '^cpu.*PPC970'):
305 return 'power970'
306 elif list_grep(cpuinfo, 'ARM'):
307 return 'arm'
308 elif list_grep(cpuinfo, '^flags.*:.* lm .*'):
309 return 'x86_64'
310 else:
311 return 'i386'
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'):
323 return 'i386'
324 return None
327 def count_cpus():
328 """number of CPUs in the local machine according to /proc/cpuinfo"""
329 f = file('/proc/cpuinfo', 'r')
330 cpus = 0
331 for line in f.readlines():
332 if line.lower().startswith('processor'):
333 cpus += 1
334 return cpus
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))
343 def memtotal():
344 return read_from_meminfo('MemTotal')
347 def freememtotal():
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
379 return phys_kbytes
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:
399 # write
400 utils.write_one_line('/proc/sys/kernel/%s' % key, str(value))
401 else:
402 # read
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)
412 else:
413 # impossible?
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
430 kind of like dir()
432 for item in object.__dict__.iteritems():
433 print item
434 try:
435 (key,value) = item
436 dump_object(value)
437 except:
438 continue
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]
445 else:
446 return ''
449 def prepend_path(newpath, oldpath):
450 """prepend newpath to oldpath"""
451 if (oldpath):
452 return newpath + ':' + oldpath
453 else:
454 return newpath
457 def append_path(oldpath, newpath):
458 """append newpath to oldpath"""
459 if (oldpath):
460 return oldpath + ':' + newpath
461 else:
462 return 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():
476 try:
477 s = r.match(line);
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))
482 count += 1
483 except:
484 raise ValueError("badly formatted times")
486 f.close()
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):
500 return config
501 return None
504 def check_for_kernel_feature(feature):
505 config = running_config()
507 if not config:
508 raise TypeError("Can't find kernel config file")
510 if magic.guess_type(config) == 'application/x-gzip':
511 grep = 'zgrep'
512 else:
513 grep = 'grep'
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
524 cpus = []
525 for line in open('/proc/cpuinfo', 'r').readlines():
526 if line.startswith('processor'):
527 cpus.append(line.split()[2]) # grab cpu number
528 return cpus
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." %
536 (glibc_ver, ver))
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." %
544 (kernel_ver, ver))
547 def human_format(number):
548 # Convert number to kilo / mega / giga format.
549 if number < 1024:
550 return "%d" % number
551 kilo = float(number) / 1024.0
552 if kilo < 1024:
553 return "%.2fk" % kilo
554 meg = kilo / 1024.0
555 if meg < 1024:
556 return "%.2fM" % meg
557 gig = meg / 1024.0
558 return "%.2fG" % gig
561 def numa_nodes():
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))
567 def node_size():
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(':')
575 if len(elts) == 1:
576 return time_string
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")
583 results = []
584 for result in pattern.findall(results_string):
585 results.append(tuple([to_seconds(elt) for elt in result]))
586 return results
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()
605 return version
608 # much like find . -name 'pattern'
609 def locate(pattern, root=os.getcwd()):
610 for path, dirs, files in os.walk(root):
611 for f in files:
612 if fnmatch.fnmatch(f, pattern):
613 yield os.path.abspath(os.path.join(path, f))
616 def freespace(path):
617 """Return the disk free space, in bytes"""
618 s = os.statvfs(path)
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)
631 if matches:
632 return int(matches[0])
633 else:
634 raise error.TestError('Could not get valid cpu family data')
637 def get_disks():
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):
646 return False
648 utils.system('/sbin/modprobe ' + module_name)
649 return True
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]
661 if len(lsmod) > 0:
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)
669 else:
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)] == ' ':
678 return True
679 return False
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))
707 return vendors[0]
710 def probe_cpus():
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())
725 if m:
726 gw = m.group(1)
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')
733 def drop_caches():
734 """Writes back all dirty pages to disk and clears all the caches."""
735 utils.system("sync")
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'
758 if utc:
759 cmd += ' --utc'
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)
763 if match:
764 seconds = int(match.group(1)) + float(match.group(2))
765 logging.debug('hwclock seconds = %f' % seconds)
766 return seconds
768 raise ValueError('Unable to read the hardware clock -- ' +
769 hwclock_output)
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)
786 def standby():
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')