| #!/usr/bin/python |
| # |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import optparse |
| import os, re |
| from autotest_lib.client.bin import utils, test |
| from autotest_lib.client.common_lib import error |
| |
| re_float = r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?" |
| |
| class kernel_fs_Punybench(test.test): |
| """Run a selected subset of the puny benchmarks |
| """ |
| version = 1 |
| Bin = '/usr/local/opt/punybench/bin/' |
| |
| |
| def initialize(self): |
| self.results = [] |
| self.job.drop_caches_between_iterations = True |
| |
| |
| def _run(self, cmd, args): |
| """Run a puny benchmark |
| |
| Prepends the path to the puny benchmark bin. |
| |
| @param cmd: command to be run |
| @param args: arguments for the command |
| """ |
| result = utils.system_output( |
| os.path.join(self.Bin, cmd) + ' ' + args) |
| logging.debug(result) |
| return result |
| |
| |
| @staticmethod |
| def _ecrypt_mount(dir, mnt): |
| """Mount the eCrypt File System |
| |
| @param dir: directory where encrypted file system is stored |
| @param mnt: mount point for encrypted file system |
| """ |
| options = ('-o' |
| ' key=passphrase:passphrase_passwd=secret' |
| ',ecryptfs_cipher=aes' |
| ',ecryptfs_key_bytes=32' |
| ',no_sig_cache' |
| ',ecryptfs_passthrough=no' |
| ',ecryptfs_enable_filename_crypto=no') |
| utils.system_output('mkdir -p %s %s' % (dir, mnt)) |
| utils.system_output('mount -t ecryptfs %s %s %s' % |
| (options, dir, mnt)) |
| |
| |
| @staticmethod |
| def _ecrypt_unmount(dir, mnt): |
| """Unmount the eCrypt File System and remove it and its mount point |
| |
| @param dir: directory where encrypted file system was stored |
| @param mnt: mount point for encrypted file system |
| """ |
| utils.system_output('umount ' + mnt) |
| utils.system_output('rm -R ' + dir) |
| utils.system_output('rm -R ' + mnt) |
| |
| |
| @staticmethod |
| def _find_max(tag, text): |
| """Find the max in a memcpy result. |
| |
| @param tag: name of sub-test to select from text. |
| @param text: output from memcpy test. |
| @return Best result from that sub-test. |
| |
| Example input text: |
| memcpy (Meg = 2**20) |
| 0. 4746.96 MiB/sec |
| 1. 4748.99 MiB/sec |
| 2. 4748.14 MiB/sec |
| 3. 4748.59 MiB/sec |
| simple (Meg = 2**20) |
| 0. 727.996 MiB/sec |
| 1. 728.031 MiB/sec |
| 2. 728.22 MiB/sec |
| 3. 728.049 MiB/sec |
| 32bit (Meg = 2**20) |
| 0. 2713.16 MiB/sec |
| 1. 2719.93 MiB/sec |
| 2. 2724.33 MiB/sec |
| 3. 2711.5 MiB/sec |
| """ |
| r1 = re.search(tag + ".*\n(\d.*sec\n)+", text) |
| r2 = re.findall(r"\d+\. (" + re_float + r") M.*\n", r1.group(0)) |
| return max(float(result) for result in r2) |
| |
| |
| def _memcpy(self): |
| """Measure memory to memory copy. |
| |
| The size has to be large enough that it doesn't fit |
| in the cache. We then take the best of serveral runs |
| so we have a guarenteed not to exceed number. |
| |
| Several different ways are used to move memory. |
| """ |
| size = 64 * 1024 * 1024 |
| loops = 4 |
| iterations = 10 |
| args = '-z %d -i %d -l %d' % (size, iterations, loops) |
| result = self._run('memcpy', args) |
| |
| for tag in ['memcpy', '32bit', '64bit']: |
| value = self._find_max(tag, result) |
| self.write_perf_keyval({tag + '_MiB_s': value}) |
| |
| |
| @staticmethod |
| def _get_mib_s(tag, text): |
| """Extract the MiB/s for tag from text |
| |
| @param tag: name of sub-test to select from text |
| @param text: extact MiB/s from this text |
| |
| Example input text: |
| SDRAM: |
| memcpy_trivial: (2097152 bytes copy) = 727.6 MiB/s / 729.9 MiB/s |
| memcpy : (2097152 bytes copy) = 4514.2 MiB/s / 4746.9 MiB/s |
| memcpy_trivial: (3145728 bytes copy) = 727.7 MiB/s / 729.5 MiB/s |
| memcpy : (3145728 bytes copy) = 4489.5 MiB/s / 4701.5 MiB/s |
| """ |
| r1 = re.search(tag + ".*\n.*\n.*", text) |
| r2 = re.search(r"[^\s]+ MiB/s$", r1.group(0)) |
| r3 = re.search(re_float, r2.group(0)) |
| return r3.group(0) |
| |
| |
| def _memcpy_test(self): |
| """Test the various caches and alignments |
| |
| WARNING: test will have to be changed if cache sizes change. |
| """ |
| result = self._run('memcpy_test', "") |
| self.write_perf_keyval({'L1cache_MiB_s': |
| self._get_mib_s('L1 cache', result)}) |
| self.write_perf_keyval({'L2cache_MiB_s': |
| self._get_mib_s('L2 cache', result)}) |
| self.write_perf_keyval({'SDRAM_MiB_s': |
| self._get_mib_s('SDRAM', result)}) |
| |
| |
| def _threadtree(self, prefix, dir): |
| """Create and manipulate directory trees. |
| |
| Threadtree creates a directory tree with files for each task. |
| It then copies that tree then deletes it. |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param dir: directory path to use for test |
| |
| Example results: |
| opens = 3641 |
| created = 2914 |
| dirs = 1456 |
| files = 1458 |
| deleted = 4372 |
| read = 1046306816 |
| written = 2095407104 |
| 51.7 2. timer avg= 57.9 stdv= 8.76 |
| """ |
| iterations = 4 |
| tasks = 2 |
| width = 3 |
| depth = 5 |
| args = ('-d %s -i %d -t %d -w %d -k %d' % |
| (dir, iterations, tasks, width, depth)) |
| result = self._run('threadtree', args) |
| r1 = re.search(r"timer avg= *([^\s]*).*$", result) |
| timer_avg = float(r1.group(1)) |
| p = tasks * pow(width, depth + 1) / timer_avg |
| self.write_perf_keyval({prefix + 'threadtree_ops': p}) |
| |
| |
| def _uread(self, prefix, file): |
| """Read a large file. |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param file: file path to use for test |
| |
| The size should be picked so the file will |
| not fit in memory. |
| |
| Example results: |
| size=8589934592 n=1 55.5 3. timer avg= 55.5 stdv=0.0693 147.6 MiB/s |
| size=8589934592 n=1 55.6 4. timer avg= 55.5 stdv=0.0817 147.5 MiB/s |
| """ |
| args = '-f %s' % file |
| result = self._run('uread', args) |
| r1 = re.search(r"[^\s]+ MiB/s.*$", result) |
| r2 = re.search(re_float, r1.group(0)) |
| mib_s = r2.group(0) |
| self.write_perf_keyval({prefix + 'uread_MiB_s': mib_s}) |
| |
| |
| def _ureadrand(self, prefix, file): |
| """Read randomly a large file |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param file: file path to use for test |
| |
| Example results (modified to fit in 80 columes): |
| size=8589934592 n=10000 4.7 3. timer avg= 4 stdv= 4.6 9.1 MiB/s 2326 IOPs/sec |
| size=8589934592 n=10000 4.9 4. timer avg= 4.2 stdv= 4.5 8.8 MiB/s 2262 IOPs/sec |
| """ |
| args = '-f %s' % file |
| result = self._run('ureadrand', args) |
| r1 = re.search(r"([^\s]+ IOPs/sec).*$", result) |
| r2 = re.search(re_float, r1.group(0)) |
| iops = r2.group(0) |
| self.write_perf_keyval({prefix + 'ureadrand_iops': iops}) |
| |
| |
| def _uwrite(self, prefix, file): |
| """Write a large file. |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param file: file path to use for test |
| |
| The size should be picked so the file will not fit in memory. |
| |
| Example results: |
| size=8589934592 n=1 55.5 3. timer avg= 55.5 stdv=0.0693 147.6 MiB/s |
| size=8589934592 n=1 55.6 4. timer avg= 55.5 stdv=0.0817 147.5 MiB/s |
| """ |
| args = '-f %s' % file |
| result = self._run('uwrite', args) |
| r1 = re.search(r"[^\s]+ MiB/s.*$", result) |
| r2 = re.search(re_float, r1.group(0)) |
| mib_s = r2.group(0) |
| self.write_perf_keyval({prefix + 'uwrite_MiB_s': mib_s}) |
| |
| |
| def _uwriterand(self, prefix, file, size): |
| """Write randomly a file |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param file: file path to use for test |
| @param size: size of file - large files are much slower than small files |
| |
| Example results (modified to fit in 80 columes): |
| size=16777216 n=1000 13.4 1. timer avg= 13.4 stdv= 0 0.29 MiB/s 74.8 IOPs/sec |
| size=16777216 n=1000 13.3 2. timer avg= 13.3 stdv=0.032 0.3 MiB/s 75.0 IOPs/sec |
| |
| """ |
| loops = 4 |
| iterations = 1000 |
| args = ('-f %s -z %d -i %d -l %d -b12' % |
| (file, size, iterations, loops)) |
| result = self._run('uwriterand', args) |
| r1 = re.search(r"([^\s]+ IOPs/sec).*$", result) |
| r2 = re.search(re_float, r1.group(0)) |
| iops = r2.group(0) |
| self.write_perf_keyval({prefix + 'uwriterand_iops': iops}) |
| |
| |
| def _uwritesync(self, prefix, file): |
| """Synchronously writes a file |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param file: file path to use for test |
| |
| Example results (modified to fit in 80 columes): |
| size=409600 n=100 4.58 3. timer avg= 4.41 stdv=0.195 0.0887 MiB/s 22.7 IOPs/sec |
| size=409600 n=100 4.84 4. timer avg= 4.52 stdv= 0.27 0.0885 MiB/s 22.15 IOPs/sec |
| """ |
| loops = 4 # minimum loops to average or see trends |
| num_blocks_to_write = 100 # Because sync writes are slow, |
| # don't do too many |
| args = ('-f %s -i %d -l %d -b12' % |
| (file, num_blocks_to_write, loops)) |
| result = self._run('uwritesync', args) |
| r1 = re.search(r"([^\s]+ IOPs/sec).*$", result) |
| r2 = re.search(re_float, r1.group(0)) |
| iops = r2.group(0) |
| self.write_perf_keyval({prefix + 'uwritesync_iops': iops}) |
| |
| |
| def _disk_tests(self, prefix, dir, file): |
| """Run this collection of disk tests |
| |
| @param prefix: prefix to use on name/value pair for identifying results |
| @param dir: directory path to use for tests |
| @param file: file path to use for tests |
| """ |
| self._uread(prefix, file) |
| self._ureadrand(prefix, file) |
| self._uwrite(prefix, file) |
| self._uwriterand(prefix + '_large_', file, 8 * 1024 * 1024 * 1024) |
| # This tests sometimes gives invalid results |
| # self._uwriterand(prefix + '_small_', file, 8 * 1024) |
| self._uwritesync(prefix, file) |
| self._threadtree(prefix, dir) |
| |
| |
| def _ecryptfs(self): |
| """Setup up to run disk tests on encrypted volume |
| """ |
| dir = '/usr/local/ecrypt_tst' |
| mnt = '/usr/local/ecrypt_mnt' |
| self._ecrypt_mount(dir, mnt) |
| self._disk_tests('ecryptfs_', mnt + '/_Dir', mnt + '/xyzzy') |
| self._ecrypt_unmount(dir, mnt) |
| |
| |
| def _parse_args(self, args): |
| """Parse input arguments to this autotest. |
| |
| Args: |
| @param args: List of arguments to parse. |
| @return |
| opts: Options, as per optparse. |
| args: Non-option arguments, as per optparse. |
| """ |
| parser = optparse.OptionParser() |
| parser.add_option('--disk', dest='want_disk_tests', |
| action='store_true', default=False, |
| help='Run disk tests.') |
| parser.add_option('--ecryptfs', dest='want_ecryptfs_tests', |
| action='store_true', default=False, |
| help='Run ecryptfs tests.') |
| parser.add_option('--mem', dest='want_mem_tests', |
| action='store_true', default=False, |
| help='Run memory tests.') |
| parser.add_option('--nop', dest='want_nop_tests', |
| action='store_true', default=False, |
| help='Do nothing.') |
| return parser.parse_args(args) |
| |
| |
| def run_once(self, args=[]): |
| """Run the PyAuto performance tests. |
| |
| @param args: Either space-separated arguments or a list of string |
| arguments. If this is a space separated string, we'll just |
| call split() on it to get a list. The list will be sent to |
| optparse for parsing. |
| """ |
| if isinstance(args, str): |
| args = args.split() |
| options, test_args = self._parse_args(args) |
| |
| if test_args: |
| raise error.TestFail("Unknown args: %s" % repr(test_args)) |
| |
| if not os.path.exists(self.Bin): |
| raise error.TestFail("%s does not exist" % self.Bin) |
| |
| try: |
| restart_swap = True |
| utils.system_output('swapoff /dev/zram0') |
| except: |
| restart_swap = False |
| utils.system_output('stop ui') |
| if options.want_nop_tests: |
| pass |
| if options.want_mem_tests: |
| self._memcpy_test() |
| self._memcpy() |
| if options.want_disk_tests: |
| self._disk_tests('ext4_', '/usr/local/_Dir', '/usr/local/xyzzy') |
| if options.want_ecryptfs_tests: |
| self._ecryptfs() |
| |
| if restart_swap: |
| utils.system_output('swapon /dev/zram0') |
| utils.system_output('start ui') |