1#!/usr/bin/python
2#
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import logging
8import optparse
9import os, re
10from autotest_lib.client.bin import utils, test
11from autotest_lib.client.common_lib import error
12
13re_float = r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?"
14
15class kernel_fs_Punybench(test.test):
16    """Run a selected subset of the puny benchmarks
17    """
18    version = 1
19    Bin = '/usr/local/opt/punybench/bin/'
20
21
22    def initialize(self):
23        self.results = []
24        self.job.drop_caches_between_iterations = True
25
26
27    def _run(self, cmd, args):
28        """Run a puny benchmark
29
30        Prepends the path to the puny benchmark bin.
31
32        @param cmd: command to be run
33        @param args: arguments for the command
34        """
35        result = utils.system_output(
36            os.path.join(self.Bin, cmd) + ' ' + args)
37        logging.debug(result)
38        return result
39
40
41    @staticmethod
42    def _ecrypt_mount(dir, mnt):
43        """Mount the eCrypt File System
44
45        @param dir: directory where encrypted file system is stored
46        @param mnt: mount point for encrypted file system
47        """
48        options = ('-o'
49                   ' key=passphrase:passphrase_passwd=secret'
50                   ',ecryptfs_cipher=aes'
51                   ',ecryptfs_key_bytes=32'
52                   ',no_sig_cache'
53                   ',ecryptfs_passthrough=no'
54                   ',ecryptfs_enable_filename_crypto=no')
55        utils.system_output('mkdir -p %s %s' % (dir, mnt))
56        utils.system_output('mount -t ecryptfs %s %s %s' %
57                           (options, dir, mnt))
58
59
60    @staticmethod
61    def _ecrypt_unmount(dir, mnt):
62        """Unmount the eCrypt File System and remove it and its mount point
63
64        @param dir: directory where encrypted file system was stored
65        @param mnt: mount point for encrypted file system
66        """
67        utils.system_output('umount ' + mnt)
68        utils.system_output('rm -R ' + dir)
69        utils.system_output('rm -R ' + mnt)
70
71
72    @staticmethod
73    def _find_max(tag, text):
74        """Find the max in a memcpy result.
75
76        @param tag: name of sub-test to select from text.
77        @param text: output from memcpy test.
78        @return Best result from that sub-test.
79
80        Example input text:
81          memcpy (Meg = 2**20)
82          0. 4746.96 MiB/sec
83          1. 4748.99 MiB/sec
84          2. 4748.14 MiB/sec
85          3. 4748.59 MiB/sec
86          simple (Meg = 2**20)
87          0. 727.996 MiB/sec
88          1. 728.031 MiB/sec
89          2. 728.22 MiB/sec
90          3. 728.049 MiB/sec
91          32bit (Meg = 2**20)
92          0. 2713.16 MiB/sec
93          1. 2719.93 MiB/sec
94          2. 2724.33 MiB/sec
95          3. 2711.5 MiB/sec
96        """
97        r1 = re.search(tag + ".*\n(\d.*sec\n)+", text)
98        r2 = re.findall(r"\d+\. (" + re_float + r") M.*\n", r1.group(0))
99        return max(float(result) for result in r2)
100
101
102    def _memcpy(self):
103        """Measure memory to memory copy.
104
105        The size has to be large enough that it doesn't fit
106        in the cache. We then take the best of serveral runs
107        so we have a guarenteed not to exceed number.
108
109        Several different ways are used to move memory.
110        """
111        size = 64 * 1024 * 1024
112        loops = 4
113        iterations = 10
114        args  = '-z %d -i %d -l %d' % (size, iterations, loops)
115        result = self._run('memcpy', args)
116
117        for tag in ['memcpy', '32bit', '64bit']:
118            value = self._find_max(tag, result)
119            self.write_perf_keyval({tag + '_MiB_s': value})
120
121
122    @staticmethod
123    def _get_mib_s(tag, text):
124        """Extract the MiB/s for tag from text
125
126        @param tag: name of sub-test to select from text
127        @param text: extact MiB/s from this text
128
129        Example input text:
130          SDRAM:
131          memcpy_trivial:  (2097152 bytes copy) =  727.6 MiB/s /  729.9 MiB/s
132          memcpy        :  (2097152 bytes copy) = 4514.2 MiB/s / 4746.9 MiB/s
133          memcpy_trivial:  (3145728 bytes copy) =  727.7 MiB/s /  729.5 MiB/s
134          memcpy        :  (3145728 bytes copy) = 4489.5 MiB/s / 4701.5 MiB/s
135        """
136        r1 = re.search(tag + ".*\n.*\n.*", text)
137        r2 = re.search(r"[^\s]+ MiB/s$", r1.group(0))
138        r3 = re.search(re_float, r2.group(0))
139        return r3.group(0)
140
141
142    def _memcpy_test(self):
143        """Test the various caches and alignments
144
145        WARNING: test will have to be changed if cache sizes change.
146        """
147        result = self._run('memcpy_test', "")
148        self.write_perf_keyval({'L1cache_MiB_s':
149                               self._get_mib_s('L1 cache', result)})
150        self.write_perf_keyval({'L2cache_MiB_s':
151                               self._get_mib_s('L2 cache', result)})
152        self.write_perf_keyval({'SDRAM_MiB_s':
153                               self._get_mib_s('SDRAM', result)})
154
155
156    def _threadtree(self, prefix, dir):
157        """Create and manipulate directory trees.
158
159        Threadtree creates a directory tree with files for each task.
160        It then copies that tree then deletes it.
161
162        @param prefix: prefix to use on name/value pair for identifying results
163        @param dir: directory path to use for test
164
165        Example results:
166          opens   =       3641
167          created =       2914
168          dirs    =       1456
169          files   =       1458
170          deleted =       4372
171          read    = 1046306816
172          written = 2095407104
173           51.7   2. timer avg= 57.9 stdv= 8.76
174        """
175        iterations = 4
176        tasks = 2
177        width = 3
178        depth = 5
179        args = ('-d %s -i %d -t %d -w %d -k %d' %
180               (dir, iterations, tasks, width, depth))
181        result = self._run('threadtree', args)
182        r1 = re.search(r"timer avg= *([^\s]*).*$", result)
183        timer_avg = float(r1.group(1))
184        p = tasks * pow(width, depth + 1) / timer_avg
185        self.write_perf_keyval({prefix + 'threadtree_ops': p})
186
187
188    def _uread(self, prefix, file):
189        """Read a large file.
190
191        @param prefix: prefix to use on name/value pair for identifying results
192        @param file: file path to use for test
193
194        The size should be picked so the file will
195        not fit in memory.
196
197        Example results:
198          size=8589934592 n=1 55.5 3. timer avg= 55.5 stdv=0.0693 147.6 MiB/s
199          size=8589934592 n=1 55.6 4. timer avg= 55.5 stdv=0.0817 147.5 MiB/s
200        """
201        args = '-f %s' % file
202        result = self._run('uread', args)
203        r1 = re.search(r"[^\s]+ MiB/s.*$", result)
204        r2 = re.search(re_float, r1.group(0))
205        mib_s = r2.group(0)
206        self.write_perf_keyval({prefix + 'uread_MiB_s': mib_s})
207
208
209    def _ureadrand(self, prefix, file):
210        """Read randomly a large file
211
212        @param prefix: prefix to use on name/value pair for identifying results
213        @param file: file path to use for test
214
215        Example results (modified to fit in 80 columes):
216size=8589934592 n=10000 4.7 3. timer avg= 4 stdv= 4.6 9.1 MiB/s 2326 IOPs/sec
217size=8589934592 n=10000 4.9 4. timer avg= 4.2 stdv= 4.5 8.8 MiB/s 2262 IOPs/sec
218        """
219        args = '-f %s' % file
220        result = self._run('ureadrand', args)
221        r1 = re.search(r"([^\s]+ IOPs/sec).*$", result)
222        r2 = re.search(re_float, r1.group(0))
223        iops = r2.group(0)
224        self.write_perf_keyval({prefix + 'ureadrand_iops': iops})
225
226
227    def _uwrite(self, prefix, file):
228        """Write a large file.
229
230        @param prefix: prefix to use on name/value pair for identifying results
231        @param file: file path to use for test
232
233        The size should be picked so the file will not fit in memory.
234
235        Example results:
236          size=8589934592 n=1 55.5 3. timer avg= 55.5 stdv=0.0693 147.6 MiB/s
237          size=8589934592 n=1 55.6 4. timer avg= 55.5 stdv=0.0817 147.5 MiB/s
238        """
239        args = '-f %s' % file
240        result = self._run('uwrite', args)
241        r1 = re.search(r"[^\s]+ MiB/s.*$", result)
242        r2 = re.search(re_float, r1.group(0))
243        mib_s = r2.group(0)
244        self.write_perf_keyval({prefix + 'uwrite_MiB_s': mib_s})
245
246
247    def _uwriterand(self, prefix, file, size):
248        """Write randomly a file
249
250        @param prefix: prefix to use on name/value pair for identifying results
251        @param file: file path to use for test
252        @param size: size of file - large files are much slower than small files
253
254        Example results (modified to fit in 80 columes):
255size=16777216 n=1000 13.4 1. timer avg= 13.4 stdv= 0 0.29 MiB/s 74.8 IOPs/sec
256size=16777216 n=1000 13.3 2. timer avg= 13.3 stdv=0.032 0.3 MiB/s 75.0 IOPs/sec
257
258        """
259        loops = 4
260        iterations = 1000
261        args = ('-f %s -z %d -i %d -l %d -b12' %
262                (file, size, iterations, loops))
263        result = self._run('uwriterand', args)
264        r1 = re.search(r"([^\s]+ IOPs/sec).*$", result)
265        r2 = re.search(re_float, r1.group(0))
266        iops = r2.group(0)
267        self.write_perf_keyval({prefix + 'uwriterand_iops': iops})
268
269
270    def _uwritesync(self, prefix, file):
271        """Synchronously writes a file
272
273        @param prefix: prefix to use on name/value pair for identifying results
274        @param file: file path to use for test
275
276        Example results (modified to fit in 80 columes):
277size=409600 n=100 4.58 3. timer avg= 4.41 stdv=0.195 0.0887 MiB/s 22.7 IOPs/sec
278size=409600 n=100 4.84 4. timer avg= 4.52 stdv= 0.27 0.0885 MiB/s 22.15 IOPs/sec
279        """
280        loops = 4  # minimum loops to average or see trends
281        num_blocks_to_write = 100  # Because sync writes are slow,
282                                   # don't do too many
283        args = ('-f %s -i %d -l %d -b12' %
284                (file, num_blocks_to_write, loops))
285        result = self._run('uwritesync', args)
286        r1 = re.search(r"([^\s]+ IOPs/sec).*$", result)
287        r2 = re.search(re_float, r1.group(0))
288        iops = r2.group(0)
289        self.write_perf_keyval({prefix + 'uwritesync_iops': iops})
290
291
292    def _disk_tests(self, prefix,  dir, file):
293        """Run this collection of disk tests
294
295        @param prefix: prefix to use on name/value pair for identifying results
296        @param dir: directory path to use for tests
297        @param file: file path to use for tests
298        """
299        self._uread(prefix, file)
300        self._ureadrand(prefix, file)
301        self._uwrite(prefix, file)
302        self._uwriterand(prefix + '_large_', file, 8 * 1024 * 1024 * 1024)
303        # This tests sometimes gives invalid results
304        # self._uwriterand(prefix + '_small_', file, 8 * 1024)
305        self._uwritesync(prefix, file)
306        self._threadtree(prefix, dir)
307
308
309    def _ecryptfs(self):
310        """Setup up to run disk tests on encrypted volume
311        """
312        dir = '/usr/local/ecrypt_tst'
313        mnt = '/usr/local/ecrypt_mnt'
314        self._ecrypt_mount(dir, mnt)
315        self._disk_tests('ecryptfs_', mnt + '/_Dir', mnt + '/xyzzy')
316        self._ecrypt_unmount(dir, mnt)
317
318
319    def _parse_args(self, args):
320        """Parse input arguments to this autotest.
321
322        Args:
323        @param args: List of arguments to parse.
324        @return
325          opts: Options, as per optparse.
326          args: Non-option arguments, as per optparse.
327        """
328        parser = optparse.OptionParser()
329        parser.add_option('--disk', dest='want_disk_tests',
330                          action='store_true', default=False,
331                          help='Run disk tests.')
332        parser.add_option('--ecryptfs', dest='want_ecryptfs_tests',
333                          action='store_true', default=False,
334                          help='Run ecryptfs tests.')
335        parser.add_option('--mem', dest='want_mem_tests',
336                          action='store_true', default=False,
337                          help='Run memory tests.')
338        parser.add_option('--nop', dest='want_nop_tests',
339                          action='store_true', default=False,
340                          help='Do nothing.')
341        return parser.parse_args(args)
342
343
344    def run_once(self, args=[]):
345        """Run the PyAuto performance tests.
346
347        @param args: Either space-separated arguments or a list of string
348              arguments.  If this is a space separated string, we'll just
349              call split() on it to get a list.  The list will be sent to
350              optparse for parsing.
351        """
352        if isinstance(args, str):
353            args = args.split()
354        options, test_args = self._parse_args(args)
355
356        if test_args:
357            raise error.TestFail("Unknown args: %s" % repr(test_args))
358
359        if not os.path.exists(self.Bin):
360            raise error.TestFail("%s does not exist" % self.Bin)
361
362        try:
363            restart_swap = True
364            utils.system_output('swapoff /dev/zram0')
365        except:
366            restart_swap = False
367        utils.system_output('stop ui')
368        if options.want_nop_tests:
369            pass
370        if options.want_mem_tests:
371            self._memcpy_test()
372            self._memcpy()
373        if options.want_disk_tests:
374            self._disk_tests('ext4_', '/usr/local/_Dir', '/usr/local/xyzzy')
375        if options.want_ecryptfs_tests:
376            self._ecryptfs()
377
378        if restart_swap:
379            utils.system_output('swapon /dev/zram0')
380        utils.system_output('start ui')
381