├── README.md └── buildbloat.py /README.md: -------------------------------------------------------------------------------- 1 | buildbloat 2 | ========== 3 | 4 | Converts [ninja](http://martine.github.io/ninja/) build logs to 5 | [webtreemap](https://github.com/martine/webtreemap) json files. 6 | 7 | Run `ninja -t recompact` first ot make sure that no duplicate entries are 8 | in the build log, and use a ninja newer than 1.4 to make sure recompaction 9 | removes old, stale buildlog entries. 10 | 11 | Usage: 12 | 13 | buildbloat.py out/Release/.ninja_log > data.json 14 | -------------------------------------------------------------------------------- /buildbloat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | import os 4 | import sys 5 | 6 | """Converts a ninja build log to webtreemap format. 7 | 8 | Run `ninja -t recompact` first ot make sure that no duplicate entries are 9 | in the build log, and use a ninja newer than 1.4 to make sure recompaction 10 | removes old, stale buildlog entries. 11 | 12 | Usage: 13 | buildbloat.py out/Release/.ninja_log > data.json 14 | """ 15 | 16 | 17 | class Node(object): 18 | def __init__(self, size): 19 | self.children = {} 20 | self.size = size 21 | 22 | 23 | def Insert(data, path, duration): 24 | """Takes a directory path and a build duration, and inserts nodes for every 25 | path component into data, adding duration to all directory nodes along the 26 | path.""" 27 | if '/' not in path: 28 | if path in data.children: 29 | Insert(data, os.path.join(path, 'postbuild'), duration) 30 | return 31 | assert not path in data.children 32 | data.children[path] = Node(size=duration) 33 | data.size += duration 34 | return 35 | 36 | prefix, path = path.split('/', 1) 37 | if prefix not in data.children: 38 | data.children[prefix] = Node(size=0) 39 | data.size += duration 40 | Insert(data.children[prefix], path, duration) 41 | 42 | 43 | def FormatTime(t): 44 | """Converts a time into a human-readable format.""" 45 | if t < 60: 46 | return '%.1fs' % t 47 | if t < 60 * 60: 48 | return '%dm%.1fs' % (t / 60, t % 60) 49 | return '%dh%dm%.1fs' % (t / (60 * 60), t % (60 * 60) / 60, t % 60) 50 | 51 | 52 | def ToDicts(node, name): 53 | """Converts a Node tree to an object tree usable by webtreemap.""" 54 | d = { 55 | 'name': "'%s' %s" % (name, FormatTime(node.size)), 56 | 'data': { '$area': node.size } 57 | } 58 | if node.children: 59 | d['children'] = [ToDicts(v, k) for k, v in node.children.iteritems()] 60 | d['data']['$dominant_symbol'] = 'node' 61 | return d 62 | 63 | 64 | def main(args): 65 | data = Node(size=0) 66 | times = set() 67 | did_policy = False 68 | for line in open(args[0], 'r').readlines()[1:]: 69 | start, finish, _, output, _ = line.split('\t') 70 | duration = (int(finish) - int(start)) / 1000.0 71 | 72 | # Multiple outputs with exactly the same timestamps were very likely part 73 | # of a single multi-output edge. Count these only once. 74 | if (start, finish) in times: 75 | continue 76 | times.add((start, finish)) 77 | 78 | # Massage output paths a bit. 79 | if output.startswith('obj/') or output.startswith('gen/'): 80 | output = output[4:] 81 | if output.endswith('_unittest.o') or output.endswith('Test.o'): 82 | output = 'test/' + output 83 | elif output.endswith('.o'): 84 | output = 'source/' + output 85 | 86 | Insert(data, output, duration) 87 | 88 | obj = ToDicts(data, 'everything') 89 | print json.dumps(obj, indent=2) 90 | 91 | 92 | if __name__ == '__main__': 93 | main(sys.argv[1:]) 94 | --------------------------------------------------------------------------------