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 <string>
    161 
    162 #include <Utils.h>
    163 
    164 namespace android {
    165 namespace vold {
    166 
    167 static status_t BenchmarkRun() {
    168 """
    169 
    170     print >>bench, "char* buf = (char*) malloc(%d);" % (bufsize)
    171 
    172     nread = 0
    173     nwrite = 0
    174     nsync = 0
    175     events = sorted(events, key=lambda e: e.time)
    176     active = set()
    177     defined = set()
    178     for e in events:
    179         if e.call == "openat":
    180             fd, f, handle = extract_file(e, e.ret)
    181             if f:
    182                 active.add(handle)
    183                 if handle not in defined:
    184                     print >>bench, "int ",
    185                     defined.add(handle)
    186                 create_mode = ''
    187                 if 'O_CREAT' in e.args[2]:
    188                     assert len(e.args) > 3, 'File creation lacks a mode?'
    189                     create_mode = ', ' + e.args[3]
    190                 print >>bench, '%s = TEMP_FAILURE_RETRY(open("file%s", %s%s));' \
    191                         % (handle, f.ident, e.args[2], create_mode)
    192 
    193         elif e.call == "close":
    194             fd, f, handle = extract_file(e, e.args[0])
    195             if handle in active:
    196                 active.remove(handle)
    197                 print >>bench, 'close(%s);' % (handle)
    198 
    199         elif e.call == "lseek":
    200             fd, f, handle = extract_file(e, e.args[0])
    201             if handle in active:
    202                 print >>bench, 'TEMP_FAILURE_RETRY(lseek(%s, %s, %s));' % (handle, e.args[1], e.args[2])
    203 
    204         elif e.call == "_llseek":
    205             fd, f, handle = extract_file(e, e.args[0])
    206             if handle in active:
    207                 print >>bench, 'TEMP_FAILURE_RETRY(lseek(%s, %s, %s));' % (handle, e.args[1], e.args[3])
    208 
    209         elif e.call == "read":
    210             fd, f, handle = extract_file(e, e.args[0])
    211             if handle in active:
    212                 # TODO: track actual file size instead of guessing
    213                 count = min(int(e.args[2]), bufsize)
    214                 f.size += count
    215                 print >>bench, 'TEMP_FAILURE_RETRY(read(%s, buf, %d));' % (handle, count)
    216                 nread += 1
    217 
    218         elif e.call == "write":
    219             fd, f, handle = extract_file(e, e.args[0])
    220             if handle in active:
    221                 # TODO: track actual file size instead of guessing
    222                 count = min(int(e.args[2]), bufsize)
    223                 f.size += count
    224                 print >>bench, 'TEMP_FAILURE_RETRY(write(%s, buf, %d));' % (handle, count)
    225                 nwrite += 1
    226 
    227         elif e.call == "pread64":
    228             fd, f, handle = extract_file(e, e.args[0])
    229             if handle in active:
    230                 f.size = max(f.size, int(e.args[2]) + int(e.args[3]))
    231                 count = min(int(e.args[2]), bufsize)
    232                 print >>bench, 'TEMP_FAILURE_RETRY(pread(%s, buf, %d, %s));' % (handle, count, e.args[3])
    233                 nread += 1
    234 
    235         elif e.call == "pwrite64":
    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(pwrite(%s, buf, %d, %s));' % (handle, count, e.args[3])
    241                 nwrite += 1
    242 
    243         elif e.call == "fsync":
    244             fd, f, handle = extract_file(e, e.args[0])
    245             if handle in active:
    246                 print >>bench, 'TEMP_FAILURE_RETRY(fsync(%s));' % (handle)
    247                 nsync += 1
    248 
    249         elif e.call == "fdatasync":
    250             fd, f, handle = extract_file(e, e.args[0])
    251             if handle in active:
    252                 print >>bench, 'TEMP_FAILURE_RETRY(fdatasync(%s));' % (handle)
    253                 nsync += 1
    254 
    255         elif e.call == "mmap2":
    256             fd, f, handle = extract_file(e, e.args[4])
    257             if handle in active:
    258                 count = min(int(e.args[1]), bufsize)
    259                 offset = int(e.args[5], 0)
    260                 f.size = max(f.size, count + offset)
    261                 print >>bench, 'TEMP_FAILURE_RETRY(pread(%s, buf, %s, %s)); // mmap2' % (handle, count, offset)
    262                 nread += 1
    263 
    264     for handle in active:
    265         print >>bench, 'close(%s);' % (handle)
    266 
    267     print >>bench, """
    268 free(buf);
    269 return 0;
    270 }
    271 
    272 static status_t CreateFile(const char* name, int len) {
    273     int chunk = std::min(len, 65536);
    274     int out = -1;
    275     std::string buf;
    276 
    277     if (android::vold::ReadRandomBytes(chunk, buf) != OK) {
    278         LOG(ERROR) << "Failed to read random data";
    279         return -EIO;
    280     }
    281     if ((out = TEMP_FAILURE_RETRY(open(name, O_WRONLY|O_CREAT|O_TRUNC, 0644))) < 0) {
    282         PLOG(ERROR) << "Failed to open " << name;
    283         return -errno;
    284     }
    285 
    286     while (len > 0) {
    287         int n = write(out, buf.c_str(), std::min(len, chunk));
    288         if (n < 0) {
    289             PLOG(ERROR) << "Failed to write";
    290             close(out);
    291             return -errno;
    292         }
    293         len -= n;
    294     }
    295 
    296     close(out);
    297     return OK;
    298 }
    299 
    300 static status_t BenchmarkCreate() {
    301 status_t res = 0;
    302 res |= CreateFile("stub", 0);
    303 """
    304     for f in files.values():
    305         print >>bench, 'res |= CreateFile("file%s", %d);' % (f.ident, f.size)
    306 
    307     print >>bench, """
    308 return res;
    309 }
    310 
    311 static status_t BenchmarkDestroy() {
    312 status_t res = 0;
    313 res |= unlink("stub");
    314 """
    315     for f in files.values():
    316         print >>bench, 'res |= unlink("file%s");' % (f.ident)
    317 
    318     print >>bench, """
    319 return res;
    320 }
    321 
    322 static std::string BenchmarkIdent() {"""
    323     print >>bench, """return "r%d:w%d:s%d";""" % (nread, nwrite, nsync)
    324     print >>bench, """}
    325 
    326 }  // namespace vold
    327 }  // namespace android
    328 """
    329 
    330 
    331 size = sum([ f.size for f in files.values() ])
    332 print "Found", len(files), "data files accessed, total size", (size/1024), "kB"
    333 
    334 types = defaultdict(int)
    335 for e in events:
    336     types[e.call] += 1
    337 
    338 print "Found syscalls:"
    339 for t, n in types.iteritems():
    340     print str(n).rjust(8), t
    341 
    342 print
    343