Backed out changeset 94c5d968e7e8 (bug 1014343)
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 24 Oct 2014 12:32:05 +0200
changeset 238533 1e4b92de7bc5505606aa1b5c65c17366864aaaa7
parent 238532 9c552295345fb2e204f63ef5c86fc1acfd5ac1b0
child 238534 de805196bbc444667c983ff0180ff99d411a8a5b
push id660
push userraliiev@mozilla.com
push dateWed, 18 Feb 2015 20:30:48 +0000
treeherdermozilla-release@49e493494178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1014343
milestone36.0a1
backs out94c5d968e7e802a423c2634ed15bea2dcff2d6ef
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
Backed out changeset 94c5d968e7e8 (bug 1014343)
memory/replace/dmd/dmd.py
memory/replace/dmd/test/script-diff-basic-expected.txt
memory/replace/dmd/test/script-diff-options-expected.txt
memory/replace/dmd/test/script-diff1.json
memory/replace/dmd/test/script-diff2.json
memory/replace/dmd/test/test_dmd.js
memory/replace/dmd/test/xpcshell.ini
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -53,105 +53,43 @@ allocatorFns = [
     'pod_realloc',
     # 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.
     '???',
 ]
 
 class Record(object):
-    '''A record is an aggregation of heap blocks that have identical stack
-    traces. It can also be used to represent the difference between two
-    records.'''
-
     def __init__(self):
         self.numBlocks = 0
         self.reqSize = 0
         self.slopSize = 0
         self.usableSize = 0
         self.isSampled = False
-        self.allocatedAtDesc = None
-        self.reportedAtDescs = []
         self.usableSizes = collections.defaultdict(int)
 
-    def isZero(self, args):
-        return self.numBlocks == 0 and \
-               self.reqSize == 0 and \
-               self.slopSize == 0 and \
-               self.usableSize == 0 and \
-               (not args.show_all_block_sizes or len(self.usableSizes) == 0)
-
-    def negate(self):
-        self.numBlocks = -self.numBlocks
-        self.reqSize = -self.reqSize
-        self.slopSize = -self.slopSize
-        self.usableSize = -self.usableSize
-
-        negatedUsableSizes = collections.defaultdict(int)
-        for (usableSize, isSampled), count in self.usableSizes.items():
-            negatedUsableSizes[(-usableSize, isSampled)] = count
-        self.usableSizes = negatedUsableSizes
-
-    def subtract(self, r):
-        # We should only be calling this on records with matching stack traces.
-        # Check this.
-        assert self.allocatedAtDesc == r.allocatedAtDesc
-        assert self.reportedAtDescs == r.reportedAtDescs
-
-        self.numBlocks -= r.numBlocks
-        self.reqSize -= r.reqSize
-        self.slopSize -= r.slopSize
-        self.usableSize -= r.usableSize
-        self.isSampled = self.isSampled or r.isSampled
-
-        usableSizes1 = self.usableSizes
-        usableSizes2 = r.usableSizes
-        usableSizes3 = collections.defaultdict(int)
-        for usableSize, isSampled in usableSizes1:
-            counts1 = usableSizes1[usableSize, isSampled]
-            if (usableSize, isSampled) in usableSizes2:
-                counts2 = usableSizes2[usableSize, isSampled]
-                del usableSizes2[usableSize, isSampled]
-                counts3 = counts1 - counts2
-                if counts3 != 0:
-                    if counts3 < 0:
-                        usableSize = -usableSize
-                        counts3 = -counts3
-                    usableSizes3[usableSize, isSampled] = counts3
-            else:
-                usableSizes3[usableSize, isSampled] = counts1
-
-        for usableSize, isSampled in usableSizes2:
-            usableSizes3[-usableSize, isSampled] = \
-                usableSizes2[usableSize, isSampled]
-
-        self.usableSizes = usableSizes3
-
     @staticmethod
     def cmpByIsSampled(r1, r2):
         # Treat sampled as smaller than non-sampled.
         return cmp(r2.isSampled, r1.isSampled)
 
     @staticmethod
     def cmpByUsableSize(r1, r2):
         # Sort by usable size, then req size, then by isSampled.
-        return cmp(abs(r1.usableSize), abs(r2.usableSize)) or \
-               Record.cmpByReqSize(r1, r2)
+        return cmp(r1.usableSize, r2.usableSize) or Record.cmpByReqSize(r1, r2)
 
     @staticmethod
     def cmpByReqSize(r1, r2):
         # Sort by req size, then by isSampled.
-        return cmp(abs(r1.reqSize), abs(r2.reqSize)) or \
-               Record.cmpByIsSampled(r1, r2)
+        return cmp(r1.reqSize, r2.reqSize) or Record.cmpByIsSampled(r1, r2)
 
     @staticmethod
     def cmpBySlopSize(r1, r2):
         # Sort by slop size, then by isSampled.
-        return cmp(abs(r1.slopSize), abs(r2.slopSize)) or \
-               Record.cmpByIsSampled(r1, r2)
+        return cmp(r1.slopSize, r2.slopSize) or Record.cmpByIsSampled(r1, r2)
 
 
 sortByChoices = {
     'usable': Record.cmpByUsableSize,   # the default
     'req':    Record.cmpByReqSize,
     'slop':   Record.cmpBySlopSize,
 }
 
@@ -162,19 +100,17 @@ def parseCommandLine():
         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
 
     description = '''
 Analyze heap data produced by DMD.
-If one file is specified, analyze it; if two files are specified, analyze the
-difference.
-Input files can be gzipped.
+If no files are specified, read from stdin; input can be gzipped.
 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. If specified, the BREAKPAD_SYMBOLS_PATH environment
 variable is used to find breakpad symbols for stack fixing.
 '''
     p = argparse.ArgumentParser(description=description)
 
@@ -199,21 +135,17 @@ variable is used to find breakpad symbol
                    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('--filter-stacks-for-testing', action='store_true',
                    help='filter stack traces; only useful for testing purposes')
 
-    p.add_argument('input_file',
-                   help='a file produced by DMD')
-
-    p.add_argument('input_file2', nargs='?',
-                   help='a file produced by DMD; if present, it is diff\'d with input_file')
+    p.add_argument('input_file')
 
     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(inputFilename, isZipped, opener):
     # This append() call is needed to make the import statements work when this
@@ -258,26 +190,28 @@ def fixStackTraces(inputFilename, isZipp
             for line in inputFile:
                 tmpFile.write(fix(line))
 
         tmpFile.close()
 
         shutil.move(tmpFilename, inputFilename)
 
 
-def getDigestFromFile(args, inputFile):
+def main():
+    args = parseCommandLine()
+
     # Handle gzipped input if necessary.
-    isZipped = inputFile.endswith('.gz')
+    isZipped = args.input_file.endswith('.gz')
     opener = gzip.open if isZipped else open
 
     # Fix stack traces unless otherwise instructed.
     if not args.no_fix_stacks:
-        fixStackTraces(inputFile, isZipped, opener)
+        fixStackTraces(args.input_file, isZipped, opener)
 
-    with opener(inputFile, 'rb') as f:
+    with opener(args.input_file, 'rb') as f:
         j = json.load(f)
 
     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']
@@ -306,41 +240,16 @@ def getDigestFromFile(args, inputFile):
             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 buildTraceDescription(traceTable, frameTable, traceKey):
-        frameKeys = traceTable[traceKey]
-        fmt = '    #{:02d}{:}'
-
-        if args.filter_stacks_for_testing:
-            # When running SmokeDMD.cpp, every stack trace should contain at
-            # least one frame that contains 'DMD.cpp', from either |DMD.cpp| or
-            # |SmokeDMD.cpp|. (Or 'dmd.cpp' on Windows.) If we see such a
-            # frame, we replace the entire stack trace with a single,
-            # predictable frame. There is too much variation in the stack
-            # traces across different machines and platforms to do more precise
-            # matching, but this level of matching will result in failure if
-            # stack fixing fails completely.
-            for frameKey in frameKeys:
-                frameDesc = frameTable[frameKey]
-                if 'DMD.cpp' in frameDesc or 'dmd.cpp' in frameDesc:
-                    return [fmt.format(1, ': ... DMD.cpp ...')]
-
-        # The frame number is always '#00' (see DMD.h for why), so we have to
-        # replace that with the correct frame number.
-        desc = []
-        for n, frameKey in enumerate(traceTable[traceKey], start=1):
-            desc.append(fmt.format(n, frameTable[frameKey][3:]))
-        return desc
-
     # Aggregate blocks into records. All sufficiently similar blocks go into a
     # single record.
 
     if args.ignore_reports:
         liveRecords = collections.defaultdict(Record)
     else:
         unreportedRecords    = collections.defaultdict(Record)
         onceReportedRecords  = collections.defaultdict(Record)
@@ -350,43 +259,34 @@ def getDigestFromFile(args, inputFile):
     heapBlocks = 0
 
     for block in blockList:
         # For each block we compute a |recordKey|, and all blocks with the same
         # |recordKey| are aggregated into a single record. The |recordKey| is
         # derived from the block's 'alloc' and 'reps' (if present) stack
         # traces.
         #
-        # We use frame descriptions (e.g. "#00: foo (X.cpp:99)") when comparing
-        # traces for equality. We can't use trace keys or frame keys because
-        # they're not comparable across different DMD runs (which is relevant
-        # when doing diffs).
-        #
-        # Using frame descriptions also fits in with the stack trimming done
-        # for --max-frames, which requires that stack traces with common
-        # beginnings but different endings to be considered equivalent. E.g. if
-        # we have distinct traces T1:[A:D1,B:D2,C:D3] and T2:[X:D1,Y:D2,Z:D4]
-        # and we trim the final frame of each they should be considered
-        # equivalent because the untrimmed frame descriptions (D1 and D2)
-        # match.
-        def makeRecordKeyPart(traceKey):
-            return str(map(lambda frameKey: frameTable[frameKey],
-                           traceTable[traceKey]))
-
-        allocatedAtTraceKey = block['alloc']
+        # Each stack trace has a key in the JSON file. But we don't use that
+        # key to construct |recordKey|; instead we use the frame keys.
+        # This is because the stack trimming done for --max-frames can cause
+        # stack traces with distinct trace keys to end up with the same frame
+        # keys, and these should be considered equivalent. E.g. if we have
+        # distinct traces T1:[A,B,C] and T2:[A,B,D] and we trim the final frame
+        # of each they should be considered equivalent.
+        allocatedAt = block['alloc']
         if args.ignore_reports:
-            recordKey = makeRecordKeyPart(allocatedAtTraceKey)
+            recordKey = str(traceTable[allocatedAt])
             records = liveRecords
         else:
-            recordKey = makeRecordKeyPart(allocatedAtTraceKey)
+            recordKey = str(traceTable[allocatedAt])
             if 'reps' in block:
-                reportedAtTraceKeys = block['reps']
-                for reportedAtTraceKey in reportedAtTraceKeys:
-                    recordKey += makeRecordKeyPart(reportedAtTraceKey)
-                if len(reportedAtTraceKeys) == 1:
+                reportedAts = block['reps']
+                for reportedAt in reportedAts:
+                    recordKey += str(traceTable[reportedAt])
+                if len(reportedAts) == 1:
                     records = onceReportedRecords
                 else:
                     records = twiceReportedRecords
             else:
                 records = unreportedRecords
 
         record = records[recordKey]
 
@@ -407,102 +307,25 @@ def getDigestFromFile(args, inputFile):
         heapUsableSize += usableSize
         heapBlocks += 1
 
         record.numBlocks  += 1
         record.reqSize    += reqSize
         record.slopSize   += slopSize
         record.usableSize += usableSize
         record.isSampled   = record.isSampled or isSampled
-        if record.allocatedAtDesc == None:
-            record.allocatedAtDesc = \
-                buildTraceDescription(traceTable, frameTable,
-                                      allocatedAtTraceKey)
-
+        record.allocatedAt = block['alloc']
         if args.ignore_reports:
             pass
         else:
-            if 'reps' in block and record.reportedAtDescs == []:
-                f = lambda k: buildTraceDescription(traceTable, frameTable, k)
-                record.reportedAtDescs = map(f, reportedAtTraceKeys)
+            if 'reps' in block:
+                record.reportedAts = block['reps']
         record.usableSizes[(usableSize, isSampled)] += 1
 
-    # All the processed data for a single DMD file is called a "digest".
-    digest = {}
-    digest['dmdEnvVar'] = dmdEnvVar
-    digest['sampleBelowSize'] = sampleBelowSize
-    digest['heapUsableSize'] = heapUsableSize
-    digest['heapBlocks'] = heapBlocks
-    digest['heapIsSampled'] = heapIsSampled
-    if args.ignore_reports:
-        digest['liveRecords'] = liveRecords
-    else:
-        digest['unreportedRecords'] = unreportedRecords
-        digest['onceReportedRecords'] = onceReportedRecords
-        digest['twiceReportedRecords'] = twiceReportedRecords
-    return digest
-
-
-def diffRecords(args, records1, records2):
-    records3 = {}
-
-    # Process records1.
-    for k in records1:
-        r1 = records1[k]
-        if k in records2:
-            # This record is present in both records1 and records2.
-            r2 = records2[k]
-            del records2[k]
-            r2.subtract(r1)
-            if not r2.isZero(args):
-                records3[k] = r2
-        else:
-            # This record is present only in records1.
-            r1.negate()
-            records3[k] = r1
-
-    for k in records2:
-        # This record is present only in records2.
-        records3[k] = records2[k]
-
-    return records3
-
-
-def diffDigests(args, d1, d2):
-    d3 = {}
-    d3['dmdEnvVar'] = (d1['dmdEnvVar'], d2['dmdEnvVar'])
-    d3['sampleBelowSize'] = (d1['sampleBelowSize'], d2['sampleBelowSize'])
-    d3['heapUsableSize'] = d2['heapUsableSize'] - d1['heapUsableSize']
-    d3['heapBlocks']     = d2['heapBlocks']     - d1['heapBlocks']
-    d3['heapIsSampled']  = d2['heapIsSampled'] or d1['heapIsSampled']
-    if args.ignore_reports:
-        d3['liveRecords'] = diffRecords(args, d1['liveRecords'],
-                                              d2['liveRecords'])
-    else:
-        d3['unreportedRecords']    = diffRecords(args, d1['unreportedRecords'],
-                                                       d2['unreportedRecords'])
-        d3['onceReportedRecords']  = diffRecords(args, d1['onceReportedRecords'],
-                                                       d2['onceReportedRecords'])
-        d3['twiceReportedRecords'] = diffRecords(args, d1['twiceReportedRecords'],
-                                                       d2['twiceReportedRecords'])
-    return d3
-
-
-def printDigest(args, digest):
-    dmdEnvVar       = digest['dmdEnvVar']
-    sampleBelowSize = digest['sampleBelowSize']
-    heapUsableSize  = digest['heapUsableSize']
-    heapIsSampled   = digest['heapIsSampled']
-    heapBlocks      = digest['heapBlocks']
-    if args.ignore_reports:
-        liveRecords = digest['liveRecords']
-    else:
-        unreportedRecords    = digest['unreportedRecords']
-        onceReportedRecords  = digest['onceReportedRecords']
-        twiceReportedRecords = digest['twiceReportedRecords']
+    # Print records.
 
     separator = '#' + '-' * 65 + '\n'
 
     def number(n, isSampled):
         '''Format a number, with comma as a separator and a '~' prefix if it's
         sampled.'''
         return '{:}{:,d}'.format('~' if isSampled else '', n)
 
@@ -511,19 +334,39 @@ def printDigest(args, digest):
 
     def plural(n):
         return '' if n == 1 else 's'
 
     # Prints to stdout, or to file if -o/--output was specified.
     def out(*arguments, **kwargs):
         print(*arguments, file=args.output, **kwargs)
 
-    def printStack(traceDesc):
-        for frameDesc in traceDesc:
-            out(frameDesc)
+    def printStack(traceTable, frameTable, traceKey):
+        frameKeys = traceTable[traceKey]
+        fmt = '    #{:02d}{:}'
+
+        if args.filter_stacks_for_testing:
+            # When running SmokeDMD.cpp, every stack trace should contain at
+            # least one frame that contains 'DMD.cpp', from either |DMD.cpp| or
+            # |SmokeDMD.cpp|. (Or 'dmd.cpp' on Windows.) If we see such a
+            # frame, we replace the entire stack trace with a single,
+            # predictable frame. There is too much variation in the stack
+            # traces across different machines and platforms to do more precise
+            # matching, but this level of matching will result in failure if
+            # stack fixing fails completely.
+            for frameKey in frameKeys:
+                frameDesc = frameTable[frameKey]
+                if 'DMD.cpp' in frameDesc or 'dmd.cpp' in frameDesc:
+                    out(fmt.format(1, ': ... DMD.cpp ...'))
+                    return
+
+        # The frame number is always '#00' (see DMD.h for why), so we have to
+        # replace that with the correct frame number.
+        for n, frameKey in enumerate(traceTable[traceKey], start=1):
+            out(fmt.format(n, frameTable[frameKey][3:]))
 
     def printRecords(recordKind, records, heapUsableSize):
         RecordKind = recordKind.capitalize()
         out(separator)
         numRecords = len(records)
         cmpRecords = sortByChoices[args.sort_by]
         sortedRecords = sorted(records.values(), cmp=cmpRecords, reverse=True)
         kindBlocks = 0
@@ -566,79 +409,66 @@ def printDigest(args, digest):
                 pass
             else:
                 out('  {:4.2f}% of {:} ({:4.2f}% cumulative)'.
                     format(perc(record.usableSize, kindUsableSize),
                            recordKind,
                            perc(kindCumulativeUsableSize, kindUsableSize)))
 
             if args.show_all_block_sizes:
-                abscmp = lambda ((usableSize1, _1a), _1b), \
-                                ((usableSize2, _2a), _2b): \
-                                cmp(abs(usableSize1), abs(usableSize2))
-                usableSizes = sorted(record.usableSizes.items(), cmp=abscmp,
-                                     reverse=True)
+                usableSizes = sorted(record.usableSizes.items(), reverse=True)
 
                 out('  Individual block sizes: ', end='')
-                if len(usableSizes) == 0:
-                    out('(no change)', end='')
-                else:
-                    isFirst = True
-                    for (usableSize, isSampled), count in usableSizes:
-                        if not isFirst:
-                            out('; ', end='')
-                        out('{:}'.format(number(usableSize, isSampled)), end='')
-                        if count > 1:
-                            out(' x {:,d}'.format(count), end='')
-                        isFirst = False
+                isFirst = True
+                for (usableSize, isSampled), count in usableSizes:
+                    if not isFirst:
+                        out('; ', end='')
+                    out('{:}'.format(number(usableSize, isSampled)), end='')
+                    if count > 1:
+                        out(' x {:,d}'.format(count), end='')
+                    isFirst = False
                 out()
 
             out('  Allocated at {')
-            printStack(record.allocatedAtDesc)
+            printStack(traceTable, frameTable, record.allocatedAt)
             out('  }')
             if args.ignore_reports:
                 pass
             else:
-                for n, reportedAtDesc in enumerate(record.reportedAtDescs):
-                    again = 'again ' if n > 0 else ''
-                    out('  Reported {:}at {{'.format(again))
-                    printStack(reportedAtDesc)
-                    out('  }')
+                if hasattr(record, 'reportedAts'):
+                    for n, reportedAt in enumerate(record.reportedAts):
+                        again = 'again ' if n > 0 else ''
+                        out('  Reported {:}at {{'.format(again))
+                        printStack(traceTable, frameTable, reportedAt)
+                        out('  }')
             out('}\n')
 
         return (kindUsableSize, kindBlocks)
 
 
-    def printInvocation(n, dmdEnvVar, sampleBelowSize):
-        out('Invocation{:} {{'.format(n))
-        out('  $DMD = \'' + dmdEnvVar + '\'')
-        out('  Sample-below size = ' + str(sampleBelowSize))
-        out('}\n')
-
-    # Print invocation(s).
+    # Print header.
     out(separator)
-    if type(dmdEnvVar) is not tuple:
-        printInvocation('', dmdEnvVar, sampleBelowSize)
-    else:
-        printInvocation(' 1', dmdEnvVar[0], sampleBelowSize[0])
-        printInvocation(' 2', dmdEnvVar[1], sampleBelowSize[1])
+    out('Invocation {')
+    out('  $DMD = \'' + dmdEnvVar + '\'')
+    out('  Sample-below size = ' + str(sampleBelowSize))
+    out('}\n')
 
     # Print records.
     if args.ignore_reports:
         liveUsableSize, liveBlocks = \
             printRecords('live', liveRecords, heapUsableSize)
     else:
         twiceReportedUsableSize, twiceReportedBlocks = \
             printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
 
         unreportedUsableSize, unreportedBlocks = \
-            printRecords('unreported', unreportedRecords, heapUsableSize)
+            printRecords('unreported',     unreportedRecords, heapUsableSize)
 
         onceReportedUsableSize, onceReportedBlocks = \
-            printRecords('once-reported', onceReportedRecords, heapUsableSize)
+            printRecords('once-reported',  onceReportedRecords, heapUsableSize)
 
     # Print summary.
     out(separator)
     out('Summary {')
     if args.ignore_reports:
         out('  Total: {:} bytes in {:} blocks'.
             format(number(liveUsableSize, heapIsSampled),
                    number(liveBlocks, heapIsSampled)))
@@ -666,20 +496,10 @@ def printDigest(args, digest):
             format('Twice-reported:',
                    number(twiceReportedUsableSize, heapIsSampled),
                    perc(twiceReportedUsableSize, heapUsableSize),
                    number(twiceReportedBlocks, heapIsSampled),
                    perc(twiceReportedBlocks, heapBlocks)))
     out('}\n')
 
 
-def main():
-    args = parseCommandLine()
-    digest = getDigestFromFile(args, args.input_file)
-    if args.input_file2:
-        digest2 = getDigestFromFile(args, args.input_file2)
-        digest = diffDigests(args, digest, digest2)
-    printDigest(args, digest)
-
-
 if __name__ == '__main__':
     main()
-
deleted file mode 100644
--- a/memory/replace/dmd/test/script-diff-basic-expected.txt
+++ /dev/null
@@ -1,109 +0,0 @@
-#-----------------------------------------------------------------
-
-Invocation 1 {
-  $DMD = '--sample-below=127'
-  Sample-below size = 127
-}
-
-Invocation 2 {
-  $DMD = '--sample-below=63'
-  Sample-below size = 63
-}
-
-#-----------------------------------------------------------------
-
-Twice-reported {
-  ~-1 blocks in heap block record 1 of 1
-  ~-1,088 bytes (~-1,064 requested / ~-24 slop)
-  15.46% of the heap (15.46% cumulative)
-  100.00% of twice-reported (100.00% cumulative)
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-  Reported at {
-    #01: R1 (R1.cpp:99)
-  }
-  Reported again at {
-    #01: R2 (R2.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Unreported {
-  4 blocks in heap block record 1 of 4
-  16,384 bytes (16,384 requested / 0 slop)
-  -232.76% of the heap (-232.76% cumulative)
-  371.01% of unreported (371.01% cumulative)
-  Allocated at {
-    #01: E (E.cpp:99)
-  }
-}
-
-Unreported {
-  ~7 blocks in heap block record 2 of 4
-  ~-11,968 bytes (~-12,016 requested / ~48 slop)
-  170.02% of the heap (-62.74% cumulative)
-  -271.01% of unreported (100.00% cumulative)
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-}
-
-Unreported {
-  0 blocks in heap block record 3 of 4
-  0 bytes (-384 requested / 384 slop)
-  -0.00% of the heap (-62.74% cumulative)
-  0.00% of unreported (100.00% cumulative)
-  Allocated at {
-    #01: C (C.cpp:99)
-  }
-}
-
-Unreported {
-  -2 blocks in heap block record 4 of 4
-  0 bytes (0 requested / 0 slop)
-  -0.00% of the heap (-62.74% cumulative)
-  0.00% of unreported (100.00% cumulative)
-  Allocated at {
-    #01: B (B.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Once-reported {
-  -3 blocks in heap block record 1 of 2
-  -10,240 bytes (-10,192 requested / -48 slop)
-  145.48% of the heap (145.48% cumulative)
-  98.77% of once-reported (98.77% cumulative)
-  Allocated at {
-    #01: D (D.cpp:99)
-  }
-  Reported at {
-    #01: R1 (R1.cpp:99)
-  }
-}
-
-Once-reported {
-  ~-1 blocks in heap block record 2 of 2
-  ~-127 bytes (~-151 requested / ~24 slop)
-  1.80% of the heap (147.28% cumulative)
-  1.23% of once-reported (100.00% cumulative)
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-  Reported at {
-    #01: R1 (R1.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total:               ~-7,039 bytes (100.00%) in      ~4 blocks (100.00%)
-  Unreported:           ~4,416 bytes (-62.74%) in      ~9 blocks (225.00%)
-  Once-reported:      ~-10,367 bytes (147.28%) in     ~-4 blocks (-100.00%)
-  Twice-reported:      ~-1,088 bytes ( 15.46%) in     ~-1 blocks (-25.00%)
-}
-
deleted file mode 100644
--- a/memory/replace/dmd/test/script-diff-options-expected.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-#-----------------------------------------------------------------
-
-Invocation 1 {
-  $DMD = '--sample-below=127'
-  Sample-below size = 127
-}
-
-Invocation 2 {
-  $DMD = '--sample-below=63'
-  Sample-below size = 63
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  4 blocks in heap block record 1 of 6
-  16,384 bytes (16,384 requested / 0 slop)
-  -232.76% of the heap (-232.76% cumulative)
-  Individual block sizes: 4,096 x 4
-  Allocated at {
-    #01: E (E.cpp:99)
-  }
-}
-
-Live {
-  ~5 blocks in heap block record 2 of 6
-  ~-13,183 bytes (~-13,231 requested / ~48 slop)
-  187.29% of the heap (-45.48% cumulative)
-  Individual block sizes: -15,360; 2,048; -1,024; 512 x 2; 128; ~-127 x 3; 64 x 4; ~63 x 2
-  Allocated at {
-    #01: F (F.cpp:99)
-  }
-}
-
-Live {
-  -3 blocks in heap block record 3 of 6
-  -10,240 bytes (-10,192 requested / -48 slop)
-  145.48% of the heap (100.00% cumulative)
-  Individual block sizes: -4,096 x 2; -2,048
-  Allocated at {
-    #01: D (D.cpp:99)
-  }
-}
-
-Live {
-  0 blocks in heap block record 4 of 6
-  0 bytes (-384 requested / 384 slop)
-  -0.00% of the heap (100.00% cumulative)
-  Individual block sizes: (no change)
-  Allocated at {
-    #01: C (C.cpp:99)
-  }
-}
-
-Live {
-  0 blocks in heap block record 5 of 6
-  0 bytes (0 requested / 0 slop)
-  -0.00% of the heap (100.00% cumulative)
-  Individual block sizes: 20,480; -16,384; -8,192; 4,096
-  Allocated at {
-    #01: G (G.cpp:99)
-  }
-}
-
-Live {
-  -2 blocks in heap block record 6 of 6
-  0 bytes (0 requested / 0 slop)
-  -0.00% of the heap (100.00% cumulative)
-  Individual block sizes: 8,192 x 2; -4,096 x 4
-  Allocated at {
-    #01: B (B.cpp:99)
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: ~-7,039 bytes in ~4 blocks
-}
-
deleted file mode 100644
--- a/memory/replace/dmd/test/script-diff1.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "version": 1,
- "invocation": {
-  "dmdEnvVar": "--sample-below=127",
-  "sampleBelowSize": 127
- },
- "blockList": [
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
-  {"req": 4096, "alloc": "B"},
-
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-  {"req": 4096, "alloc": "C"},
-
-  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
-  {"req": 4096,             "alloc": "D", "reps": ["R1"]},
-  {"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
-
-  {"req": 15360,            "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {                         "alloc": "F"},
-  {"req": 1024,             "alloc": "F", "reps": ["R1"]},
-  {                         "alloc": "F", "reps": ["R1"]},
-  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
-  {                         "alloc": "F", "reps": ["R1", "R2"]},
-
-  {"req": 4096,            "alloc": "G"},
-  {"req": 8192,            "alloc": "G"},
-  {"req": 16384,           "alloc": "G"}
- ],
- "traceTable": {
-  "A": ["AA"],
-  "B": ["BB"],
-  "C": ["CC"],
-  "D": ["DD"],
-  "E": ["EE"],
-  "F": ["FF"],
-  "G": ["GG"],
-  "R1": ["RR1"],
-  "R2": ["RR2"]
- },
- "frameTable": {
-  "AA": "#00: A (A.cpp:99)",
-  "BB": "#00: B (B.cpp:99)",
-  "CC": "#00: C (C.cpp:99)",
-  "DD": "#00: D (D.cpp:99)",
-  "EE": "#00: E (E.cpp:99)",
-  "FF": "#00: F (F.cpp:99)",
-  "GG": "#00: G (G.cpp:99)",
-  "RR1": "#00: R1 (R1.cpp:99)",
-  "RR2": "#00: R2 (R2.cpp:99)"
- }
-}
deleted file mode 100644
--- a/memory/replace/dmd/test/script-diff2.json
+++ /dev/null
@@ -1,66 +0,0 @@
-{
- "version": 1,
- "invocation": {
-  "dmdEnvVar": "--sample-below=63",
-  "sampleBelowSize": 63
- },
- "blockList": [
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-  {"req": 4096, "alloc": "A"},
-
-  {"req": 8192, "alloc": "B"},
-  {"req": 8192, "alloc": "B"},
-
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-  {"req": 4000, "slop": 96, "alloc": "C"},
-
-  {"req": 4096, "alloc": "E"},
-  {"req": 4096, "alloc": "E"},
-  {"req": 4096, "alloc": "E"},
-  {"req": 4096, "alloc": "E"},
-
-  {"req": 2000, "slop": 48, "alloc": "F"},
-  {"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1"]},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 512,              "alloc": "F"},
-  {"req": 128,              "alloc": "F"},
-  {                         "alloc": "F", "reps": ["R1", "R2"]},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {"req": 64,               "alloc": "F"},
-  {                         "alloc": "F"},
-
-  {"req": 4096,            "alloc": "G"},
-  {"req": 4096,            "alloc": "G"},
-  {"req": 20480,           "alloc": "G"}
- ],
- "traceTable": {
-  "A": ["AA"],
-  "B": ["BB"],
-  "C": ["CC"],
-  "D": ["DD"],
-  "E": ["EE"],
-  "F": ["FF"],
-  "G": ["GG"],
-  "R1": ["RR1"],
-  "R2": ["RR2"]
- },
- "frameTable": {
-  "AA": "#00: A (A.cpp:99)",
-  "BB": "#00: B (B.cpp:99)",
-  "CC": "#00: C (C.cpp:99)",
-  "DD": "#00: D (D.cpp:99)",
-  "EE": "#00: E (E.cpp:99)",
-  "FF": "#00: F (F.cpp:99)",
-  "GG": "#00: G (G.cpp:99)",
-  "RR1": "#00: R1 (R1.cpp:99)",
-  "RR2": "#00: R2 (R2.cpp:99)"
- }
-}
--- a/memory/replace/dmd/test/test_dmd.js
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -60,29 +60,31 @@ function readFile(aFile) {
 function runProcess(aExeFile, aArgs) {
   let process = Cc["@mozilla.org/process/util;1"]
                   .createInstance(Components.interfaces.nsIProcess);
   process.init(aExeFile);
   process.run(/* blocking = */true, aArgs, aArgs.length);
   return process.exitValue;
 }
 
-function test(aPrefix, aArgs) {
+function test(aJsonFile, aPrefix, aOptions) {
   // DMD writes the JSON files to CurWorkD, so we do likewise here with
   // |actualFile| for consistency. It is removed once we've finished.
   let expectedFile = FileUtils.getFile("CurWorkD", [aPrefix + "-expected.txt"]);
   let actualFile   = FileUtils.getFile("CurWorkD", [aPrefix + "-actual.txt"]);
 
   // Run dmd.py on the JSON file, producing |actualFile|.
 
   let args = [
     gDmdScriptFile.path,
     "--filter-stacks-for-testing",
     "-o", actualFile.path
-  ].concat(aArgs);
+  ];
+  args = args.concat(aOptions);
+  args.push(aJsonFile.path);
 
   runProcess(new FileUtils.File(gPythonName), args);
 
   // Compare |expectedFile| with |actualFile|. We produce nice diffs with
   // /usr/bin/diff on systems that have it (Mac and Linux). Otherwise (Windows)
   // we do a string compare of the file contents and then print them both if
   // they don't match.
 
@@ -109,17 +111,17 @@ function test(aPrefix, aArgs) {
   }
 
   ok(success, aPrefix);
 
   actualFile.remove(true);
 }
 
 function run_test() {
-  let jsonFile, jsonFile2;
+  let jsonFile;
 
   // These tests do full end-to-end testing of DMD, i.e. both the C++ code that
   // generates the JSON output, and the script that post-processes that output.
   //
   // Run these synchronously, because test() updates the full*.json files
   // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
   // asynchronously.
 
@@ -127,64 +129,46 @@ function run_test() {
   gEnv.set(gEnv.get("DMD_PRELOAD_VAR"), gEnv.get("DMD_PRELOAD_VALUE"));
 
   runProcess(gDmdTestFile, []);
 
   let fullTestNames = ["empty", "unsampled1", "unsampled2", "sampled"];
   for (let i = 0; i < fullTestNames.length; i++) {
       let name = fullTestNames[i];
       jsonFile = FileUtils.getFile("CurWorkD", ["full-" + name + ".json"]);
-      test("full-heap-" + name, ["--ignore-reports", jsonFile.path])
-      test("full-reports-" + name, [jsonFile.path])
+      test(jsonFile, "full-heap-" + name, ["--ignore-reports"])
+      test(jsonFile, "full-reports-" + name, [])
       jsonFile.remove(true);
   }
 
   // These tests only test the post-processing script. They use hand-written
   // JSON files as input. Ideally the JSON files would contain comments
   // explaining how they work, but JSON doesn't allow comments, so I've put
   // explanations here.
 
   // This just tests that stack traces of various lengths are truncated
   // appropriately. The number of records in the output is different for each
   // of the tested values.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-max-frames.json"]);
-  test("script-max-frames-8",
-       ["--ignore-reports", "--max-frames=8", jsonFile.path]);
-  test("script-max-frames-3",
-       ["--ignore-reports", "--max-frames=3", "--no-fix-stacks",
-        jsonFile.path]);
-  test("script-max-frames-1",
-       ["--ignore-reports", "--max-frames=1", jsonFile.path]);
+  test(jsonFile, "script-max-frames-8", ["-r", "--max-frames=8"]);
+  test(jsonFile, "script-max-frames-3", ["-r", "--max-frames=3",
+                                         "--no-fix-stacks"]);
+  test(jsonFile, "script-max-frames-1", ["-r", "--max-frames=1"]);
 
-  // This file has three records that are shown in a different order for each
+  // This test has three records that are shown in a different order for each
   // of the different sort values. It also tests the handling of gzipped JSON
   // files.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-sort-by.json.gz"]);
-  test("script-sort-by-usable",
-       ["--ignore-reports", "--sort-by=usable", jsonFile.path]);
-  test("script-sort-by-req",
-       ["--ignore-reports", "--sort-by=req", "--no-fix-stacks", jsonFile.path]);
-  test("script-sort-by-slop",
-       ["--ignore-reports", "--sort-by=slop", jsonFile.path]);
+  test(jsonFile, "script-sort-by-usable", ["-r", "--sort-by=usable"]);
+  test(jsonFile, "script-sort-by-req",    ["-r", "--sort-by=req",
+                                           "--no-fix-stacks"]);
+  test(jsonFile, "script-sort-by-slop",   ["-r", "--sort-by=slop"]);
 
-  // This file has several real stack traces taken from Firefox execution, each
+  // This test has several real stack traces taken from Firefox execution, each
   // of which tests a different allocator function (or functions).
   jsonFile = FileUtils.getFile("CurWorkD", ["script-ignore-alloc-fns.json"]);
-  test("script-ignore-alloc-fns",
-       ["--ignore-reports", "--ignore-alloc-fns", jsonFile.path]);
+  test(jsonFile, "script-ignore-alloc-fns", ["-r", "--ignore-alloc-fns"]);
 
-  // This file has numerous allocations of different sizes, some repeated, some
+  // This test has numerous allocations of different sizes, some repeated, some
   // sampled, that all end up in the same record.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-show-all-block-sizes.json"]);
-  test("script-show-all-block-sizes",
-       ["--ignore-reports", "--show-all-block-sizes", jsonFile.path]);
-
-  // This tests diffs. The first invocation has no options, the second has
-  // several.
-  jsonFile  = FileUtils.getFile("CurWorkD", ["script-diff1.json"]);
-  jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff2.json"]);
-  test("script-diff-basic",
-       [jsonFile.path, jsonFile2.path]);
-  test("script-diff-options",
-       ["--ignore-reports", "--show-all-block-sizes",
-        jsonFile.path, jsonFile2.path]);
+  test(jsonFile, "script-show-all-block-sizes", ["-r", "--show-all-block-sizes"]);
 }
-
--- a/memory/replace/dmd/test/xpcshell.ini
+++ b/memory/replace/dmd/test/xpcshell.ini
@@ -15,19 +15,15 @@ support-files =
   script-sort-by.json.gz
   script-sort-by-usable-expected.txt
   script-sort-by-req-expected.txt
   script-sort-by-slop-expected.txt
   script-ignore-alloc-fns.json
   script-ignore-alloc-fns-expected.txt
   script-show-all-block-sizes.json
   script-show-all-block-sizes-expected.txt
-  script-diff1.json
-  script-diff2.json
-  script-diff-basic-expected.txt
-  script-diff-options-expected.txt
 
 # Bug 1077230 explains why this test is disabled on Mac 10.6.
 # Bug 1076446 comment 20 explains why this test is only enabled on Windows 5.1
 # (WinXP) and 6.1 (Win7), but not 6.2 (Win8).
 [test_dmd.js]
 dmd = true
 run-if = os == 'linux' || os == 'mac' && os_version != '10.6' || os == 'win' && (os_version == '5.1' || os_version == '6.1')