Created
March 4, 2014 11:15
-
-
Save alessandrod/9344589 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from argparse import ArgumentParser | |
import re | |
import os | |
PARSER_STATE_NONE = 0 | |
PARSER_STATE_FILES = 1 | |
PARSER_STATE_SYMBOLS = 2 | |
def format_size(num): | |
for x in ['bytes','KB','MB','GB']: | |
if num < 1024.0 and num > -1024.0: | |
return "%3.2f%s" % (num, x) | |
num /= 1024.0 | |
return "%3.1f%s" % (num, 'TB') | |
class VoidParser(object): | |
state = PARSER_STATE_NONE | |
def __init__(self, parser): | |
pass | |
def match(self, line): | |
return True | |
def parse(self, line): | |
pass | |
class File(object): | |
def __init__(self, filename): | |
self.filename = filename | |
self.symbols = [] | |
self.size = None | |
def add_symbol(self, symbol): | |
self.symbols.append(symbol) | |
self.size = None | |
def __str__(self): | |
return '<object file "{filename}">'.format(filename=self.filename) | |
def __repr__(self): | |
return self.__str__() | |
def get_size(self): | |
if self.size is not None: | |
return self.size | |
self.size = 0 | |
for symbol in self.symbols: | |
self.size += symbol.size | |
return self.size | |
class FileParser(object): | |
state = PARSER_STATE_FILES | |
def __init__(self, parser): | |
self.parser = parser | |
self.files = {} | |
def match(self, line): | |
return line == '# Object files:' | |
def parse(self, line): | |
index_end = line.find(']') | |
index = int(line[1:index_end].strip()) | |
filename = line[index_end + 1:].strip() | |
assert index not in self.files | |
self.files[index] = File(filename) | |
class Symbol(object): | |
def __init__(self, address, size, name): | |
self.address = address | |
self.size = size | |
self.name = name | |
def __str__(self): | |
return '<symbol {name}>'.format(name=self.name) | |
def __repr__(self): | |
return self.__str__() | |
class SymbolParser(object): | |
state = PARSER_STATE_SYMBOLS | |
def __init__(self, parser): | |
self.parser = parser | |
self.files = {} | |
self.pattern = re.compile('(?P<address>0x[0-9A-Z]+)\s+(?P<size>0x[0-9A-Z]+)\s+\[\s*(?P<index>\d+)\]\s+(?P<symbol>\S+)') | |
def match(self, line): | |
return line == '# Symbols:' | |
def parse(self, line): | |
match = self.pattern.match(line) | |
if match is None: | |
return | |
address = match.group('address') | |
size = int(match.group('size'), 16) | |
index = int(match.group('index')) | |
name = match.group('symbol') | |
symbol = Symbol(address, size, name) | |
self.parser.get_file(index).add_symbol(symbol) | |
class Parser(object): | |
def __init__(self, filename): | |
self.filename = filename | |
self.parse_state = PARSER_STATE_NONE | |
self.section_parsers = [FileParser(self), SymbolParser(self), | |
VoidParser(self)] | |
self.section_parsers_map = {parser.state: parser for parser in | |
self.section_parsers} | |
def parse(self): | |
with open(self.filename) as map_file: | |
for line in map_file: | |
line = line.strip() | |
if not line: | |
continue | |
self.parse_line(line) | |
def parse_section(self, line): | |
for parser in self.section_parsers: | |
if parser.match(line): | |
self.parse_state = parser.state | |
break | |
def parse_line(self, line): | |
if line[0] == '#' and ':' in line: | |
self.parse_section(line) | |
return | |
self.section_parsers_map[self.parse_state].parse(line) | |
def get_file(self, index): | |
return self.section_parsers_map[PARSER_STATE_FILES].files[index] | |
def get_files(self): | |
return self.section_parsers_map[PARSER_STATE_FILES].files.values() | |
class FileSizeReporter(object): | |
def __init__(self): | |
self.prefixes = [0, {}, 0] | |
def add_object_file(self, object_file): | |
prefix = self.prefixes | |
name = object_file.filename | |
components = name.split('/') | |
for depth, component in enumerate(components): | |
prefix[0] += object_file.get_size() | |
prefix = prefix[1].setdefault(component, [0, {}, depth]) | |
prefix[0] += object_file.get_size() | |
def get_sizes(self, prefix=None, max_depth=None): | |
prefixes = list(self.prefixes) | |
if prefix is not None: | |
components = prefix.split('/') | |
while components: | |
prefixes = prefixes[1][components.pop(0)] | |
starting_depth = prefix.count('/') | |
dirs = [[prefix, prefixes]] | |
results = [] | |
while dirs: | |
prefix, (size, subdirs, depth) = dirs.pop(0) | |
depth -= starting_depth | |
within_depth = max_depth is None or depth < max_depth | |
if subdirs and within_depth: | |
for subdir in subdirs: | |
dirs.append((os.path.join(prefix, subdir), | |
subdirs[subdir])) | |
if within_depth: | |
results.append((prefix, size)) | |
return results | |
def main(): | |
cmd = ArgumentParser() | |
cmd.add_argument('--prefix') | |
cmd.add_argument('--depth', default=None, type=int) | |
cmd.add_argument('filename') | |
args = cmd.parse_args() | |
parser = Parser(args.filename) | |
parser.parse() | |
reporter = FileSizeReporter() | |
for object_file in parser.get_files(): | |
reporter.add_object_file(object_file) | |
for name, size in sorted(reporter.get_sizes(args.prefix, args.depth), | |
key=lambda r: r[0]): | |
print name, format_size(size) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment