| 1 | """
 | 
| 2 | uftrace_allocs.py - Python 3 plugin for uftrace
 | 
| 3 | 
 | 
| 4 | Count allocations and show sizes.
 | 
| 5 | 
 | 
| 6 | Annoying thing about uftrace: it swallows ImportError and other errors!
 | 
| 7 | 
 | 
| 8 | TODO:
 | 
| 9 |   Attribute allocations and sizes to Str, List, Dict, Token, etc.
 | 
| 10 |   How do we do that?  We need the call graph relationship
 | 
| 11 | 
 | 
| 12 | Structures to catch:
 | 
| 13 | 
 | 
| 14 | NewStr(12) {
 | 
| 15 |   MarkSweepHeap::Allocate(25)
 | 
| 16 | }
 | 
| 17 | 
 | 
| 18 | Alloc() {
 | 
| 19 |   MarkSweepHeap::Allocate(24);
 | 
| 20 |   syntax_asdl::Token::Token();
 | 
| 21 | }
 | 
| 22 | 
 | 
| 23 | Alloc() {
 | 
| 24 |   MarkSweepHeap::Allocate(24)
 | 
| 25 |   List::List()
 | 
| 26 |   # But what type is it?  We don't know
 | 
| 27 | }
 | 
| 28 | 
 | 
| 29 | // Some stuff missing here
 | 
| 30 | Alloc() {
 | 
| 31 |   MarkSweepHeap::Allocate(32);
 | 
| 32 |   Alloc() {
 | 
| 33 |     MarkSweepHeap::Allocate(24);
 | 
| 34 |     List::List();
 | 
| 35 |   }
 | 
| 36 | }
 | 
| 37 | """
 | 
| 38 | from __future__ import print_function
 | 
| 39 | 
 | 
| 40 | import os
 | 
| 41 | import sys
 | 
| 42 | 
 | 
| 43 | 
 | 
| 44 | def log(msg, *args):
 | 
| 45 |     if args:
 | 
| 46 |         msg = msg % args
 | 
| 47 |     print(msg, file=sys.stderr)
 | 
| 48 | 
 | 
| 49 | 
 | 
| 50 | num_allocs = 0
 | 
| 51 | num_lists = 0
 | 
| 52 | 
 | 
| 53 | gOutDir = None
 | 
| 54 | 
 | 
| 55 | 
 | 
| 56 | class Stats(object):
 | 
| 57 | 
 | 
| 58 |     def __init__(self, out_dir):
 | 
| 59 |         p = os.path.join(out_dir, 'all-untyped.tsv')
 | 
| 60 |         self.untyped = open(p, 'w')
 | 
| 61 |         header = ['obj_len']
 | 
| 62 |         print('\t'.join(header), file=self.untyped)
 | 
| 63 | 
 | 
| 64 |         p = os.path.join(out_dir, 'typed.tsv')
 | 
| 65 |         self.typed = open(p, 'w')
 | 
| 66 |         header = ['func_name']
 | 
| 67 |         print('\t'.join(header), file=self.typed)
 | 
| 68 | 
 | 
| 69 |         p = os.path.join(out_dir, 'strings.tsv')
 | 
| 70 |         self.strings = open(p, 'w')
 | 
| 71 |         header = ['func_name', 'str_len']
 | 
| 72 |         print('\t'.join(header), file=self.strings)
 | 
| 73 | 
 | 
| 74 |         # Note: we could extract Slab type
 | 
| 75 |         p = os.path.join(out_dir, 'slabs.tsv')
 | 
| 76 |         self.slabs = open(p, 'w')
 | 
| 77 |         header = ['func_name', 'slab_len']
 | 
| 78 |         print('\t'.join(header), file=self.slabs)
 | 
| 79 | 
 | 
| 80 |         # For the actual number of items
 | 
| 81 |         p = os.path.join(out_dir, 'reserve.tsv')
 | 
| 82 |         self.reserve = open(p, 'w')
 | 
| 83 |         header = ['func_name', 'num_items']
 | 
| 84 |         print('\t'.join(header), file=self.reserve)
 | 
| 85 | 
 | 
| 86 |     def EmitUntyped(self, obj_len):
 | 
| 87 |         print('%d' % (obj_len), file=self.untyped)
 | 
| 88 | 
 | 
| 89 |     def EmitTyped(self, func):
 | 
| 90 |         print('%s' % (func), file=self.typed)
 | 
| 91 | 
 | 
| 92 |     def EmitString(self, func, str_len):
 | 
| 93 |         print('%s\t%d' % (func, str_len), file=self.strings)
 | 
| 94 | 
 | 
| 95 |     def EmitSlab(self, func, slab_len):
 | 
| 96 |         print('%s\t%d' % (func, slab_len), file=self.slabs)
 | 
| 97 | 
 | 
| 98 |     def EmitReserve(self, func, num_items):
 | 
| 99 |         print('%s\t%d' % (func, num_items), file=self.reserve)
 | 
| 100 | 
 | 
| 101 |     def Close(self):
 | 
| 102 |         self.untyped.close()
 | 
| 103 |         self.typed.close()
 | 
| 104 |         self.strings.close()
 | 
| 105 |         self.slabs.close()
 | 
| 106 |         self.reserve.close()
 | 
| 107 | 
 | 
| 108 | 
 | 
| 109 | gStats = None
 | 
| 110 | 
 | 
| 111 | 
 | 
| 112 | def uftrace_begin(ctx):
 | 
| 113 |     """Script begin"""
 | 
| 114 | 
 | 
| 115 |     #print(ctx)
 | 
| 116 |     args = ctx['cmds']
 | 
| 117 |     #log('args %r', args)
 | 
| 118 |     out_dir = args[0]
 | 
| 119 | 
 | 
| 120 |     global gStats
 | 
| 121 |     gStats = Stats(out_dir)
 | 
| 122 | 
 | 
| 123 | 
 | 
| 124 | def uftrace_entry(ctx):
 | 
| 125 |     """Function entry"""
 | 
| 126 |     global num_allocs
 | 
| 127 | 
 | 
| 128 |     func_name = ctx["name"]
 | 
| 129 | 
 | 
| 130 |     #print(ctx)
 | 
| 131 |     #log('f %r', func_name)
 | 
| 132 | 
 | 
| 133 |     if func_name.startswith('MarkSweepHeap::Allocate'):
 | 
| 134 |         #log("MSW !!")
 | 
| 135 |         num_bytes = ctx['args'][0]
 | 
| 136 |         #log("MSW %r %s", num_bytes, type(num_bytes))
 | 
| 137 |         gStats.EmitUntyped(num_bytes)
 | 
| 138 |         num_allocs += 1
 | 
| 139 |         return
 | 
| 140 | 
 | 
| 141 |     if 'Alloc<' in func_name:
 | 
| 142 |         # TODO: We don't have the size
 | 
| 143 |         gStats.EmitTyped(func_name)
 | 
| 144 |         return
 | 
| 145 | 
 | 
| 146 |     if func_name.startswith('NewStr') or func_name.startswith(
 | 
| 147 |             'OverAllocatedStr'):
 | 
| 148 |         #log("Str")
 | 
| 149 |         str_len = ctx['args'][0]
 | 
| 150 |         #log("Str %d", str_len)
 | 
| 151 |         gStats.EmitString(func_name, str_len)
 | 
| 152 |         return
 | 
| 153 | 
 | 
| 154 |     if 'NewSlab<' in func_name:
 | 
| 155 |         #log('SLAB %r', func_name)
 | 
| 156 |         slab_len = ctx['args'][0]
 | 
| 157 |         #log('len %d', slab_len)
 | 
| 158 |         gStats.EmitSlab(func_name, slab_len)
 | 
| 159 |         return
 | 
| 160 | 
 | 
| 161 |     if '::reserve(' in func_name:
 | 
| 162 |         num_items = ctx['args'][0]
 | 
| 163 |         gStats.EmitReserve(func_name, num_items)
 | 
| 164 |         return
 | 
| 165 | 
 | 
| 166 | 
 | 
| 167 | def uftrace_exit(ctx):
 | 
| 168 |     """Function exit"""
 | 
| 169 |     pass
 | 
| 170 | 
 | 
| 171 | 
 | 
| 172 | def uftrace_end():
 | 
| 173 |     log('num MarkSweepHeap::Allocate() = %d', num_allocs)
 | 
| 174 | 
 | 
| 175 |     gStats.Close()
 | 
| 176 | 
 | 
| 177 |     #print('zz', file=sys.stderr)
 | 
| 178 | 
 | 
| 179 | 
 | 
| 180 | #print('hi')
 |