Bug 1074008 - Add a --fix-stacks option to dmd.py. r=glandium.
authorNicholas Nethercote <nnethercote@mozilla.com>
Sun, 28 Sep 2014 18:36:49 -0700
changeset 232120 2b73787681bddb7b30b4b6c4c484278f10f582ab
parent 232119 d73853c26a1a571c1970295d71f38d075d9458ec
child 232121 51509077b97ccd5b193f0d0504d64d10e22b9702
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
reviewersglandium
bugs1074008
milestone35.0a1
Bug 1074008 - Add a --fix-stacks option to dmd.py. r=glandium.
build/Makefile.in
memory/replace/dmd/check_test_output.py
memory/replace/dmd/dmd.py
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -93,25 +93,32 @@ GARBAGE_DIRS += $(_VALGRIND_DIR)
 		$(topsrcdir)/build/valgrind/i386-redhat-linux-gnu.sup \
 		$(topsrcdir)/build/valgrind/x86_64-redhat-linux-gnu.sup \
 		$(NULL)
 
 libs:: $(_VALGRIND_FILES)
 	$(INSTALL) $^ $(_VALGRIND_DIR)
 endif
 
-ifdef ENABLE_TESTS
+ifneq (,$(ENABLE_TESTS)$(MOZ_DMD))
 libs:: $(topsrcdir)/tools/rb/fix_stack_using_bpsyms.py
 	$(INSTALL) $< $(DIST)/bin
 
 ifeq ($(OS_ARCH),Darwin)
 libs:: $(topsrcdir)/tools/rb/fix_macosx_stack.py
 	$(INSTALL) $< $(DIST)/bin
 endif
 
 ifeq ($(OS_ARCH),Linux)
 libs:: $(topsrcdir)/tools/rb/fix_linux_stack.py
 	$(INSTALL) $< $(DIST)/bin
 endif
+endif # ENABLE_TESTS or MOZ_DMD
 
+ifdef ENABLE_TESTS
 GARBAGE += $(srcdir)/automationutils.pyc
+endif # ENABLE_TESTS
 
-endif # ENABLE_TESTS
+ifdef MOZ_DMD
+libs:: $(topsrcdir)/memory/replace/dmd/dmd.py
+	$(INSTALL) $< $(DIST)/bin
+endif
+
--- a/memory/replace/dmd/check_test_output.py
+++ b/memory/replace/dmd/check_test_output.py
@@ -50,17 +50,17 @@ def test(src_dir, kind, options, i):
         sys.exit(1)
 
     subprocess.call(fix, stdin=open(in_name, "r"),
                          stdout=open(fixed_name, "w"))
 
     # Convert from JSON
 
     convert = [os.path.join(src_dir, "memory", "replace", "dmd", "dmd.py")] + \
-               options + [fixed_name]
+               options + ['--no-fix-stacks', fixed_name]
     subprocess.call(convert, stdout=open(converted_name, "w"))
 
     # Filter output
 
     # In heap block records we filter out most stack frames.  The only thing
     # we leave behind is a "DMD.cpp" entry if we see one or more frames that
     # have DMD.cpp in them.  There is simply too much variation to do anything
     # better than that.
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -6,18 +6,22 @@
 
 '''This script analyzes a JSON file emitted by DMD.'''
 
 from __future__ import print_function, division
 
 import argparse
 import collections
 import json
+import os
+import platform
 import re
+import shutil
 import sys
+import tempfile
 
 # The DMD output version this script handles.
 outputVersion = 1
 
 # If --ignore-alloc-fns is specified, stack frames containing functions that
 # match these strings will be removed.
 allocatorFns = [
     'replace_malloc',
@@ -91,16 +95,19 @@ def parseCommandLine():
             msg = '{:s} is not in the range 1..24'.format(string)
             raise argparse.ArgumentTypeError(msg)
         return value
 
     description = '''
 Analyze heap data produced by DMD.
 If no files are specified, read from stdin.
 Write to stdout unless -o/--output is specified.
+Stack traces are fixed to show function names, filenames and line numbers
+unless --no-fix-stacks is specified; stack fixing modifies the original file
+and may take some time.
 '''
     p = argparse.ArgumentParser(description=description)
 
     p.add_argument('-o', '--output', type=argparse.FileType('w'),
                    help='output file; stdout if unspecified')
 
     p.add_argument('-f', '--max-frames', type=range_1_24,
                    help='maximum number of frames to consider in each trace')
@@ -114,24 +121,61 @@ Write to stdout unless -o/--output is sp
                    help='sort the records by a particular metric')
 
     p.add_argument('-a', '--ignore-alloc-fns', action='store_true',
                    help='ignore allocation functions at the start of traces')
 
     p.add_argument('-b', '--show-all-block-sizes', action='store_true',
                    help='show individual block sizes for each record')
 
+    p.add_argument('--no-fix-stacks', action='store_true',
+                   help='do not fix stacks')
+
     p.add_argument('input_file', type=argparse.FileType('r'))
 
     return p.parse_args(sys.argv[1:])
 
 
+# Fix stacks if necessary: first write the output to a tempfile, then replace
+# the original file with it.
+def fixStackTraces(args):
+    # This append() call is needed to make the import statements work when this
+    # script is installed as a symlink.
+    sys.path.append(os.path.dirname(__file__))
+
+    # XXX: should incorporate fix_stack_using_bpsyms.py here as well, like in
+    #      testing/mochitests/runtests.py
+    sysname = platform.system()
+    if sysname == 'Linux':
+        import fix_linux_stack as fixModule
+        fix = lambda line: fixModule.fixSymbols(line)
+    elif sysname == 'Darwin':
+        import fix_macosx_stack as fixModule
+        fix = lambda line: fixModule.fixSymbols(line)
+    else:
+        fix = None  # there is no fix script for Windows
+
+    if fix:
+        # Fix stacks, writing output to a temporary file, and then
+        # overwrite the original file.
+        with tempfile.NamedTemporaryFile(delete=False) as tmp:
+            for line in args.input_file:
+                tmp.write(fix(line))
+            shutil.move(tmp.name, args.input_file.name)
+
+        args.input_file = open(args.input_file.name)
+
+
 def main():
     args = parseCommandLine()
 
+    # Fix stack traces unless otherwise instructed.
+    if not args.no_fix_stacks:
+        fixStackTraces(args)
+
     j = json.load(args.input_file)
 
     if j['version'] != outputVersion:
         raise Exception("'version' property isn't '{:d}'".format(outputVersion))
 
     # Extract the main parts of the JSON object.
     invocation = j['invocation']
     dmdEnvVar = invocation['dmdEnvVar']