summaryrefslogtreecommitdiff
path: root/memory/replace/dmd/block_analyzer.py
diff options
context:
space:
mode:
Diffstat (limited to 'memory/replace/dmd/block_analyzer.py')
-rw-r--r--memory/replace/dmd/block_analyzer.py261
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 cc0da1e117..0000000000
--- 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()