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 233440 2b73787681bddb7b30b4b6c4c484278f10f582ab
parent 233439 d73853c26a1a571c1970295d71f38d075d9458ec
child 233441 51509077b97ccd5b193f0d0504d64d10e22b9702
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs1074008
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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']