diff options
Diffstat (limited to 'memory/replace/dmd/block_analyzer.py')
-rw-r--r-- | memory/replace/dmd/block_analyzer.py | 261 |
1 files changed, 0 insertions, 261 deletions
diff --git a/memory/replace/dmd/block_analyzer.py b/memory/replace/dmd/block_analyzer.py deleted file mode 100644 index cc0da1e11..000000000 --- a/memory/replace/dmd/block_analyzer.py +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/python - -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# From a scan mode DMD log, extract some information about a -# particular block, such as its allocation stack or which other blocks -# contain pointers to it. This can be useful when investigating leaks -# caused by unknown references to refcounted objects. - -import json -import gzip -import sys -import argparse -import re - - -# The DMD output version this script handles. -outputVersion = 5 - -# If --ignore-alloc-fns is specified, stack frames containing functions that -# match these strings will be removed from the *start* of stack traces. (Once -# we hit a non-matching frame, any subsequent frames won't be removed even if -# they do match.) -allocatorFns = [ - 'malloc (', - 'replace_malloc', - 'replace_calloc', - 'replace_realloc', - 'replace_memalign', - 'replace_posix_memalign', - 'malloc_zone_malloc', - 'moz_xmalloc', - 'moz_xcalloc', - 'moz_xrealloc', - 'operator new(', - 'operator new[](', - 'g_malloc', - 'g_slice_alloc', - 'callocCanGC', - 'reallocCanGC', - 'vpx_malloc', - 'vpx_calloc', - 'vpx_realloc', - 'vpx_memalign', - 'js_malloc', - 'js_calloc', - 'js_realloc', - 'pod_malloc', - 'pod_calloc', - 'pod_realloc', - 'nsTArrayInfallibleAllocator::Malloc', - # This one necessary to fully filter some sequences of allocation functions - # that happen in practice. Note that ??? entries that follow non-allocation - # functions won't be stripped, as explained above. - '???', -] - -#### - -# Command line arguments - -def range_1_24(string): - value = int(string) - if value < 1 or value > 24: - msg = '{:s} is not in the range 1..24'.format(string) - raise argparse.ArgumentTypeError(msg) - return value - -parser = argparse.ArgumentParser(description='Analyze the heap graph to find out things about an object. \ -By default this prints out information about blocks that point to the given block.') - -parser.add_argument('dmd_log_file_name', - help='clamped DMD log file name') - -parser.add_argument('block', - help='address of the block of interest') - -parser.add_argument('--info', dest='info', action='store_true', - default=False, - help='Print out information about the block.') - -parser.add_argument('-sfl', '--max-stack-frame-length', type=int, - default=150, - help='Maximum number of characters to print from each stack frame') - -parser.add_argument('-a', '--ignore-alloc-fns', action='store_true', - help='ignore allocation functions at the start of traces') - -parser.add_argument('-f', '--max-frames', type=range_1_24, - help='maximum number of frames to consider in each trace') - -parser.add_argument('-c', '--chain-reports', action='store_true', - help='if only one block is found to hold onto the object, report the next one, too') - - -#### - - -class BlockData: - def __init__(self, json_block): - self.addr = json_block['addr'] - - if 'contents' in json_block: - contents = json_block['contents'] - else: - contents = [] - self.contents = [] - for c in contents: - self.contents.append(int(c, 16)) - - self.req_size = json_block['req'] - - self.alloc_stack = json_block['alloc'] - - -def print_trace_segment(args, stacks, block): - (traceTable, frameTable) = stacks - - for l in traceTable[block.alloc_stack]: - # The 5: is to remove the bogus leading "#00: " from the stack frame. - print ' ', frameTable[l][5:args.max_stack_frame_length] - - -def show_referrers(args, blocks, stacks, block): - visited = set([]) - - anyFound = False - - while True: - referrers = {} - - for b, data in blocks.iteritems(): - which_edge = 0 - for e in data.contents: - if e == block: - # 8 is the number of bytes per word on a 64-bit system. - # XXX This means that this output will be wrong for logs from 32-bit systems! - referrers.setdefault(b, []).append(8 * which_edge) - anyFound = True - which_edge += 1 - - for r in referrers: - sys.stdout.write('0x{} size = {} bytes'.format(blocks[r].addr, blocks[r].req_size)) - plural = 's' if len(referrers[r]) > 1 else '' - sys.stdout.write(' at byte offset' + plural + ' ' + (', '.join(str(x) for x in referrers[r]))) - print - print_trace_segment(args, stacks, blocks[r]) - print - - if args.chain_reports: - if len(referrers) == 0: - sys.stdout.write('Found no more referrers.\n') - break - if len(referrers) > 1: - sys.stdout.write('Found too many referrers.\n') - break - - sys.stdout.write('Chaining to next referrer.\n\n') - for r in referrers: - block = r - if block in visited: - sys.stdout.write('Found a loop.\n') - break - visited.add(block) - else: - break - - if not anyFound: - print 'No referrers found.' - - -def show_block_info(args, blocks, stacks, block): - b = blocks[block] - sys.stdout.write('block: 0x{}\n'.format(b.addr)) - sys.stdout.write('requested size: {} bytes\n'.format(b.req_size)) - sys.stdout.write('\n') - sys.stdout.write('block contents: ') - for c in b.contents: - v = '0' if c == 0 else blocks[c].addr - sys.stdout.write('0x{} '.format(v)) - sys.stdout.write('\n\n') - sys.stdout.write('allocation stack:\n') - print_trace_segment(args, stacks, b) - return - - -def cleanupTraceTable(args, frameTable, traceTable): - # Remove allocation functions at the start of traces. - if args.ignore_alloc_fns: - # Build a regexp that matches every function in allocatorFns. - escapedAllocatorFns = map(re.escape, allocatorFns) - fn_re = re.compile('|'.join(escapedAllocatorFns)) - - # Remove allocator fns from each stack trace. - for traceKey, frameKeys in traceTable.items(): - numSkippedFrames = 0 - for frameKey in frameKeys: - frameDesc = frameTable[frameKey] - if re.search(fn_re, frameDesc): - numSkippedFrames += 1 - else: - break - if numSkippedFrames > 0: - traceTable[traceKey] = frameKeys[numSkippedFrames:] - - # Trim the number of frames. - for traceKey, frameKeys in traceTable.items(): - if len(frameKeys) > args.max_frames: - traceTable[traceKey] = frameKeys[:args.max_frames] - - -def loadGraph(options): - # Handle gzipped input if necessary. - isZipped = options.dmd_log_file_name.endswith('.gz') - opener = gzip.open if isZipped else open - - with opener(options.dmd_log_file_name, 'rb') as f: - j = json.load(f) - - if j['version'] != outputVersion: - raise Exception("'version' property isn't '{:d}'".format(outputVersion)) - - invocation = j['invocation'] - - block_list = j['blockList'] - blocks = {} - - for json_block in block_list: - blocks[int(json_block['addr'], 16)] = BlockData(json_block) - - traceTable = j['traceTable'] - frameTable = j['frameTable'] - - cleanupTraceTable(options, frameTable, traceTable) - - return (blocks, (traceTable, frameTable)) - - -def analyzeLogs(): - options = parser.parse_args() - - (blocks, stacks) = loadGraph(options) - - block = int(options.block, 16) - - if not block in blocks: - print 'Object', block, 'not found in traces.' - print 'It could still be the target of some nodes.' - return - - if options.info: - show_block_info(options, blocks, stacks, block) - return - - show_referrers(options, blocks, stacks, block) - - -if __name__ == "__main__": - analyzeLogs() |