Home | History | Annotate | Download | only in bench
      1 #!/usr/bin/env python
      2 
      3 # Copyright (C) 2015 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the 'License');
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an 'AS IS' BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Generates storage benchmark from captured strace output.
     19 
     20 Currently assumes that all mmap'ed regions are resource accesses, and emulates as pread().
     21 
     22 Usage:
     23 $ adb shell strace -p `pid zygote` -o /data/local/tmp/trace -f -ff -y -ttt -e trace=file,desc,munmap
     24 $ adb pull /data/local/tmp/trace*
     25 $ python benchgen.py trace.*
     26 
     27 """
     28 
     29 import re, sys, collections, traceback, argparse
     30 
     31 from operator import itemgetter
     32 from collections import defaultdict
     33 
     34 class Event:
     35     def __init__(self, thread, time, call, args, ret):
     36         self.thread = thread
     37         self.time = time
     38         self.call = call
     39         self.args = args
     40         self.ret = ret
     41 
     42     def __repr__(self):
     43         return "%s(%s)=%s" % (self.call, repr(self.args), self.ret)
     44 
     45 
     46 class File:
     47     def __init__(self, name, ident):
     48         self.name = name
     49         self.ident = ident
     50         self.size = 0
     51 
     52     def __repr__(self):
     53         return self.name
     54 
     55 
     56 events = []
     57 files = {}
     58 
     59 def find_file(name):
     60     name = name.strip('<>"')
     61     if name not in files:
     62         files[name] = File(name, len(files))
     63     return files[name]
     64 
     65 def extract_file(e, arg):
     66     if "<" in arg:
     67         fd, path = arg.split("<")
     68         path = path.strip(">")
     69         handle = "t%sf%s" % (e.thread, fd)
     70         return (fd, find_file(path), handle)
     71     else:
     72         return (None, None, None)
     73 
     74 def parse_args(s):
     75     args = []
     76     arg = ""
     77     esc = False
     78     quot = False
     79     for c in s:
     80         if esc:
     81             esc = False
     82             arg += c
     83             continue
     84 
     85         if c == '"':
     86             if quot:
     87                 quot = False
     88                 continue
     89             else:
     90                 quot = True
     91                 continue
     92 
     93         if c == '\\':
     94             esc = True
     95             continue
     96 
     97         if c == ',' and not quot:
     98             args.append(arg.strip())
     99             arg = ""
    100         else:
    101             arg += c
    102 
    103     args.append(arg.strip())
    104     return args
    105 
    106 
    107 bufsize = 1048576
    108 interesting = ["mmap2","read","write","pread64","pwrite64","fsync","fdatasync","openat","close","lseek","_llseek"]
    109 
    110 re_event = re.compile(r"^([\d\.]+) (.+?)\((.+?)\) = (.+?)$")
    111 re_arg = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''')
    112 for fn in sys.argv[1:]:
    113     with open(fn) as f:
    114         thread = int(fn.split(".")[-1])
    115         for line in f:
    116             line = re_event.match(line)
    117             if not line: continue
    118 
    119             time, call, args, ret = line.groups()
    120             if call not in interesting: continue
    121             if "/data/" not in args: continue
    122 
    123             time = float(time)
    124             args = parse_args(args)
    125             events.append(Event(thread, time, call, args, ret))
    126 
    127 
    128 with open("BenchmarkGen.h", 'w') as bench:
    129     print >>bench, """/*
    130  * Copyright (C) 2015 The Android Open Source Project
    131  *
    132  * Licensed under the Apache License, Version 2.0 (the "License");
    133  * you may not use this file except in compliance with the License.
    134  * You may obtain a copy of the License at
    135  *
    136  *      http://www.apache.org/licenses/LICENSE-2.0
    137  *
    138  * Unless required by applicable law or agreed to in writing, software
    139  * distributed under the License is distributed on an "AS IS" BASIS,
    140  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    141  * See the License for the specific language governing permissions and
    142  * limitations under the License.
    143  */
    144 
    145 
    146 /******************************************************************
    147  * THIS CODE WAS GENERATED BY benchgen.py, DO NOT MODIFY DIRECTLY *
    148  ******************************************************************/
    149 
    150 
    151 #include <android-base/logging.h>
    152 
    153 #include <stdlib.h>
    154 #include <sys/types.h>
    155 #include <sys/stat.h>
    156 #include <sys/sendfile.h>
    157 #include <fcntl.h>
    158 
    159 #include <algorithm>
    160 #include <functional>
    161 #include <string>
    162 
    163 #include <Utils.h>
    164 
    165 namespace android {
    166 namespace vold {
    167 
    168 static status_t BenchmarkRun(std::function<bool(int)> checkpoint) {
    169 
    170 """
    171 
    172     print >>bench, "char* buf = (char*) malloc(%d);" % (bufsize)
    173 
    174     nread = 0
    175     nwrite = 0
    176     nsync = 0
    177     events = sorted(events, key=lambda e: e.time)
    178     active = set()
    179     defined = set()
    180     i = 0
    181     total = len(events)
    182     for e in events:
    183         i += 1
    184         if i % 256 == 0:
    185             print >>bench, "if (!checkpoint(%d)) return -1;" % (50 + ((i * 50) / total))
    186 
    187         if e.call == "openat":
    188             fd, f, handle = extract_file(e, e.ret)
    189             if f:
    190                 active.add(handle)
    191                 if handle not in defined:
    192                     print >>bench, "int",
    193                     defined.add(handle)
    194                 create_mode = ''
    195                 if 'O_CREAT' in e.args[2]:
    196                     assert len(e.args) > 3, 'File creation lacks a mode?'
    197                     create_mode = ', ' + e.args[3]
    198                 print >>bench, '%s = TEMP_FAILURE_RETRY(open("file%s", %s%s));' \
    199                         % (handle, f.ident, e.args[2], create_mode)
    200 
    201         elif e.call == "close":
    202             fd, f, handle = extract_file(e, e.args[0])
    203             if handle in active:
    204                 active.remove(handle)
    205                 print >>bench, 'close(%s);' % (handle)
    206 
    207         elif e.call == "lseek":
    208             fd, f, handle = extract_file(e, e.args[0])
    209             if handle in active:
    210                 print >>bench, 'TEMP_FAILURE_RETRY(lseek(%s, %s, %s));' % (handle, e.args[1], e.args[2])
    211 
    212         elif e.call == "_llseek":
    213             fd, f, handle = extract_file(e, e.args[0])
    214             if handle in active:
    215                 print >>bench, 'TEMP_FAILURE_RETRY(lseek(%s, %s, %s));' % (handle, e.args[1], e.args[3])
    216 
    217         elif e.call == "read":
    218             fd, f, handle = extract_file(e, e.args[0])
    219             if handle in active:
    220                 # TODO: track actual file size instead of guessing
    221                 count = min(int(e.args[2]), bufsize)
    222                 f.size += count
    223                 print >>bench, 'TEMP_FAILURE_RETRY(read(%s, buf, %d));' % (handle, count)
    224                 nread += 1
    225 
    226         elif e.call == "write":
    227             fd, f, handle = extract_file(e, e.args[0])
    228             if handle in active:
    229                 # TODO: track actual file size instead of guessing
    230                 count = min(int(e.args[2]), bufsize)
    231                 f.size += count
    232                 print >>bench, 'TEMP_FAILURE_RETRY(write(%s, buf, %d));' % (handle, count)
    233                 nwrite += 1
    234 
    235         elif e.call == "pread64":
    236             fd, f, handle = extract_file(e, e.args[0])
    237             if handle in active:
    238                 f.size = max(f.size, int(e.args[2]) + int(e.args[3]))
    239                 count = min(int(e.args[2]), bufsize)
    240                 print >>bench, 'TEMP_FAILURE_RETRY(pread(%s, buf, %d, %s));' % (handle, count, e.args[3])
    241                 nread += 1
    242 
    243         elif e.call == "pwrite64":
    244             fd, f, handle = extract_file(e, e.args[0])
    245             if handle in active:
    246                 f.size = max(f.size, int(e.args[2]) + int(e.args[3]))
    247                 count = min(int(e.args[2]), bufsize)
    248                 print >>bench, 'TEMP_FAILURE_RETRY(pwrite(%s, buf, %d, %s));' % (handle, count, e.args[3])
    249                 nwrite += 1
    250 
    251         elif e.call == "fsync":
    252             fd, f, handle = extract_file(e, e.args[0])
    253             if handle in active:
    254                 print >>bench, 'TEMP_FAILURE_RETRY(fsync(%s));' % (handle)
    255                 nsync += 1
    256 
    257         elif e.call == "fdatasync":
    258             fd, f, handle = extract_file(e, e.args[0])
    259             if handle in active:
    260                 print >>bench, 'TEMP_FAILURE_RETRY(fdatasync(%s));' % (handle)
    261                 nsync += 1
    262 
    263         elif e.call == "mmap2":
    264             fd, f, handle = extract_file(e, e.args[4])
    265             if handle in active:
    266                 count = min(int(e.args[1]), bufsize)
    267                 offset = int(e.args[5], 0)
    268                 f.size = max(f.size, count + offset)
    269                 print >>bench, 'TEMP_FAILURE_RETRY(pread(%s, buf, %s, %s)); // mmap2' % (handle, count, offset)
    270                 nread += 1
    271 
    272     for handle in active:
    273         print >>bench, 'close(%s);' % (handle)
    274 
    275     print >>bench, """
    276 free(buf);
    277 return 0;
    278 }
    279 
    280 static status_t CreateFile(const char* name, int len) {
    281     int chunk = std::min(len, 65536);
    282     int out = -1;
    283     std::string buf;
    284 
    285     if (android::vold::ReadRandomBytes(chunk, buf) != OK) {
    286         LOG(ERROR) << "Failed to read random data";
    287         return -EIO;
    288     }
    289     if ((out = TEMP_FAILURE_RETRY(open(name, O_WRONLY|O_CREAT|O_TRUNC, 0644))) < 0) {
    290         PLOG(ERROR) << "Failed to open " << name;
    291         return -errno;
    292     }
    293 
    294     while (len > 0) {
    295         int n = write(out, buf.c_str(), std::min(len, chunk));
    296         if (n < 0) {
    297             PLOG(ERROR) << "Failed to write";
    298             close(out);
    299             return -errno;
    300         }
    301         len -= n;
    302     }
    303 
    304     close(out);
    305     return OK;
    306 }
    307 
    308 static status_t BenchmarkCreate(std::function<bool(int)> checkpoint) {
    309 status_t res = 0;
    310 res |= CreateFile("stub", 0);
    311 """
    312     i = 0
    313     total = len(files.values())
    314     for f in files.values():
    315         i += 1
    316         if i % 12 == 0:
    317             print >>bench, "if (!checkpoint(%d)) return -1;" % ((i * 50) / total)
    318 
    319         print >>bench, 'res |= CreateFile("file%s", %d);' % (f.ident, f.size)
    320 
    321     print >>bench, """
    322 return res;
    323 }
    324 
    325 static status_t BenchmarkDestroy() {
    326 status_t res = 0;
    327 res |= unlink("stub");
    328 """
    329     for f in files.values():
    330         print >>bench, 'res |= unlink("file%s");' % (f.ident)
    331 
    332     print >>bench, """
    333 return res;
    334 }
    335 
    336 static std::string BenchmarkIdent() {"""
    337     print >>bench, """return "r%d:w%d:s%d";""" % (nread, nwrite, nsync)
    338     print >>bench, """}
    339 
    340 }  // namespace vold
    341 }  // namespace android
    342 """
    343 
    344 
    345 size = sum([ f.size for f in files.values() ])
    346 print "Found", len(files), "data files accessed, total size", (size/1024), "kB"
    347 
    348 types = defaultdict(int)
    349 for e in events:
    350     types[e.call] += 1
    351 
    352 print "Found syscalls:"
    353 for t, n in types.iteritems():
    354     print str(n).rjust(8), t
    355 
    356 print
    357