зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1014343 - Add diff support to dmd.py. r=mccr8.
--HG-- extra : rebase_source : d91abce6ec9a3b81419f997ed5dfc1b23c89cc7c
This commit is contained in:
Родитель
f999a06e8c
Коммит
c0a7f6141b
|
@ -58,14 +58,73 @@ allocatorFns = [
|
|||
]
|
||||
|
||||
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.
|
||||
|
@ -74,17 +133,20 @@ class Record(object):
|
|||
@staticmethod
|
||||
def cmpByUsableSize(r1, r2):
|
||||
# Sort by usable size, then req size, then by isSampled.
|
||||
return cmp(r1.usableSize, r2.usableSize) or Record.cmpByReqSize(r1, r2)
|
||||
return cmp(abs(r1.usableSize), abs(r2.usableSize)) or \
|
||||
Record.cmpByReqSize(r1, r2)
|
||||
|
||||
@staticmethod
|
||||
def cmpByReqSize(r1, r2):
|
||||
# Sort by req size, then by isSampled.
|
||||
return cmp(r1.reqSize, r2.reqSize) or Record.cmpByIsSampled(r1, r2)
|
||||
return cmp(abs(r1.reqSize), abs(r2.reqSize)) or \
|
||||
Record.cmpByIsSampled(r1, r2)
|
||||
|
||||
@staticmethod
|
||||
def cmpBySlopSize(r1, r2):
|
||||
# Sort by slop size, then by isSampled.
|
||||
return cmp(r1.slopSize, r2.slopSize) or Record.cmpByIsSampled(r1, r2)
|
||||
return cmp(abs(r1.slopSize), abs(r2.slopSize)) or \
|
||||
Record.cmpByIsSampled(r1, r2)
|
||||
|
||||
|
||||
sortByChoices = {
|
||||
|
@ -105,7 +167,9 @@ def parseCommandLine():
|
|||
|
||||
description = '''
|
||||
Analyze heap data produced by DMD.
|
||||
If no files are specified, read from stdin; input can be gzipped.
|
||||
If one file is specified, analyze it; if two files are specified, analyze the
|
||||
difference.
|
||||
Input files 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
|
||||
|
@ -140,7 +204,11 @@ variable is used to find breakpad symbols for stack fixing.
|
|||
p.add_argument('--filter-stacks-for-testing', action='store_true',
|
||||
help='filter stack traces; only useful for testing purposes')
|
||||
|
||||
p.add_argument('input_file')
|
||||
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')
|
||||
|
||||
return p.parse_args(sys.argv[1:])
|
||||
|
||||
|
@ -195,18 +263,16 @@ def fixStackTraces(inputFilename, isZipped, opener):
|
|||
shutil.move(tmpFilename, inputFilename)
|
||||
|
||||
|
||||
def main():
|
||||
args = parseCommandLine()
|
||||
|
||||
def getDigestFromFile(args, inputFile):
|
||||
# Handle gzipped input if necessary.
|
||||
isZipped = args.input_file.endswith('.gz')
|
||||
isZipped = inputFile.endswith('.gz')
|
||||
opener = gzip.open if isZipped else open
|
||||
|
||||
# Fix stack traces unless otherwise instructed.
|
||||
if not args.no_fix_stacks:
|
||||
fixStackTraces(args.input_file, isZipped, opener)
|
||||
fixStackTraces(inputFile, isZipped, opener)
|
||||
|
||||
with opener(args.input_file, 'rb') as f:
|
||||
with opener(inputFile, 'rb') as f:
|
||||
j = json.load(f)
|
||||
|
||||
if j['version'] != outputVersion:
|
||||
|
@ -245,6 +311,31 @@ def main():
|
|||
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.
|
||||
|
||||
|
@ -264,24 +355,33 @@ def main():
|
|||
# derived from the block's 'alloc' and 'reps' (if present) stack
|
||||
# traces.
|
||||
#
|
||||
# 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']
|
||||
# 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']
|
||||
if args.ignore_reports:
|
||||
recordKey = str(traceTable[allocatedAt])
|
||||
recordKey = makeRecordKeyPart(allocatedAtTraceKey)
|
||||
records = liveRecords
|
||||
else:
|
||||
recordKey = str(traceTable[allocatedAt])
|
||||
recordKey = makeRecordKeyPart(allocatedAtTraceKey)
|
||||
if 'reps' in block:
|
||||
reportedAts = block['reps']
|
||||
for reportedAt in reportedAts:
|
||||
recordKey += str(traceTable[reportedAt])
|
||||
if len(reportedAts) == 1:
|
||||
reportedAtTraceKeys = block['reps']
|
||||
for reportedAtTraceKey in reportedAtTraceKeys:
|
||||
recordKey += makeRecordKeyPart(reportedAtTraceKey)
|
||||
if len(reportedAtTraceKeys) == 1:
|
||||
records = onceReportedRecords
|
||||
else:
|
||||
records = twiceReportedRecords
|
||||
|
@ -312,15 +412,92 @@ def main():
|
|||
record.slopSize += slopSize
|
||||
record.usableSize += usableSize
|
||||
record.isSampled = record.isSampled or isSampled
|
||||
record.allocatedAt = block['alloc']
|
||||
if record.allocatedAtDesc == None:
|
||||
record.allocatedAtDesc = \
|
||||
buildTraceDescription(traceTable, frameTable,
|
||||
allocatedAtTraceKey)
|
||||
|
||||
if args.ignore_reports:
|
||||
pass
|
||||
else:
|
||||
if 'reps' in block:
|
||||
record.reportedAts = block['reps']
|
||||
if 'reps' in block and record.reportedAtDescs == []:
|
||||
f = lambda k: buildTraceDescription(traceTable, frameTable, k)
|
||||
record.reportedAtDescs = map(f, reportedAtTraceKeys)
|
||||
record.usableSizes[(usableSize, isSampled)] += 1
|
||||
|
||||
# Print records.
|
||||
# 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']
|
||||
|
||||
separator = '#' + '-' * 65 + '\n'
|
||||
|
||||
|
@ -339,29 +516,9 @@ def main():
|
|||
def out(*arguments, **kwargs):
|
||||
print(*arguments, file=args.output, **kwargs)
|
||||
|
||||
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 printStack(traceDesc):
|
||||
for frameDesc in traceDesc:
|
||||
out(frameDesc)
|
||||
|
||||
def printRecords(recordKind, records, heapUsableSize):
|
||||
RecordKind = recordKind.capitalize()
|
||||
|
@ -414,42 +571,55 @@ def main():
|
|||
perc(kindCumulativeUsableSize, kindUsableSize)))
|
||||
|
||||
if args.show_all_block_sizes:
|
||||
usableSizes = sorted(record.usableSizes.items(), reverse=True)
|
||||
abscmp = lambda ((usableSize1, _1a), _1b), \
|
||||
((usableSize2, _2a), _2b): \
|
||||
cmp(abs(usableSize1), abs(usableSize2))
|
||||
usableSizes = sorted(record.usableSizes.items(), cmp=abscmp,
|
||||
reverse=True)
|
||||
|
||||
out(' Individual block sizes: ', end='')
|
||||
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
|
||||
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
|
||||
out()
|
||||
|
||||
out(' Allocated at {')
|
||||
printStack(traceTable, frameTable, record.allocatedAt)
|
||||
printStack(record.allocatedAtDesc)
|
||||
out(' }')
|
||||
if args.ignore_reports:
|
||||
pass
|
||||
else:
|
||||
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(' }')
|
||||
for n, reportedAtDesc in enumerate(record.reportedAtDescs):
|
||||
again = 'again ' if n > 0 else ''
|
||||
out(' Reported {:}at {{'.format(again))
|
||||
printStack(reportedAtDesc)
|
||||
out(' }')
|
||||
out('}\n')
|
||||
|
||||
return (kindUsableSize, kindBlocks)
|
||||
|
||||
|
||||
# Print header.
|
||||
def printInvocation(n, dmdEnvVar, sampleBelowSize):
|
||||
out('Invocation{:} {{'.format(n))
|
||||
out(' $DMD = \'' + dmdEnvVar + '\'')
|
||||
out(' Sample-below size = ' + str(sampleBelowSize))
|
||||
out('}\n')
|
||||
|
||||
# Print invocation(s).
|
||||
out(separator)
|
||||
out('Invocation {')
|
||||
out(' $DMD = \'' + dmdEnvVar + '\'')
|
||||
out(' Sample-below size = ' + str(sampleBelowSize))
|
||||
out('}\n')
|
||||
if type(dmdEnvVar) is not tuple:
|
||||
printInvocation('', dmdEnvVar, sampleBelowSize)
|
||||
else:
|
||||
printInvocation(' 1', dmdEnvVar[0], sampleBelowSize[0])
|
||||
printInvocation(' 2', dmdEnvVar[1], sampleBelowSize[1])
|
||||
|
||||
# Print records.
|
||||
if args.ignore_reports:
|
||||
|
@ -460,10 +630,10 @@ def main():
|
|||
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)
|
||||
|
@ -501,5 +671,15 @@ def main():
|
|||
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()
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
#-----------------------------------------------------------------
|
||||
|
||||
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%)
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#-----------------------------------------------------------------
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"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)"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"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)"
|
||||
}
|
||||
}
|
|
@ -65,7 +65,7 @@ function runProcess(aExeFile, aArgs) {
|
|||
return process.exitValue;
|
||||
}
|
||||
|
||||
function test(aJsonFile, aPrefix, aOptions) {
|
||||
function test(aPrefix, aArgs) {
|
||||
// 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"]);
|
||||
|
@ -77,9 +77,7 @@ function test(aJsonFile, aPrefix, aOptions) {
|
|||
gDmdScriptFile.path,
|
||||
"--filter-stacks-for-testing",
|
||||
"-o", actualFile.path
|
||||
];
|
||||
args = args.concat(aOptions);
|
||||
args.push(aJsonFile.path);
|
||||
].concat(aArgs);
|
||||
|
||||
runProcess(new FileUtils.File(gPythonName), args);
|
||||
|
||||
|
@ -116,7 +114,7 @@ function test(aJsonFile, aPrefix, aOptions) {
|
|||
}
|
||||
|
||||
function run_test() {
|
||||
let jsonFile;
|
||||
let jsonFile, jsonFile2;
|
||||
|
||||
// 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.
|
||||
|
@ -134,8 +132,8 @@ function run_test() {
|
|||
for (let i = 0; i < fullTestNames.length; i++) {
|
||||
let name = fullTestNames[i];
|
||||
jsonFile = FileUtils.getFile("CurWorkD", ["full-" + name + ".json"]);
|
||||
test(jsonFile, "full-heap-" + name, ["--ignore-reports"])
|
||||
test(jsonFile, "full-reports-" + name, [])
|
||||
test("full-heap-" + name, ["--ignore-reports", jsonFile.path])
|
||||
test("full-reports-" + name, [jsonFile.path])
|
||||
jsonFile.remove(true);
|
||||
}
|
||||
|
||||
|
@ -148,27 +146,45 @@ function run_test() {
|
|||
// 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(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"]);
|
||||
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]);
|
||||
|
||||
// This test has three records that are shown in a different order for each
|
||||
// This file 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(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"]);
|
||||
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]);
|
||||
|
||||
// This test has several real stack traces taken from Firefox execution, each
|
||||
// This file 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(jsonFile, "script-ignore-alloc-fns", ["-r", "--ignore-alloc-fns"]);
|
||||
test("script-ignore-alloc-fns",
|
||||
["--ignore-reports", "--ignore-alloc-fns", jsonFile.path]);
|
||||
|
||||
// This test has numerous allocations of different sizes, some repeated, some
|
||||
// This file 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(jsonFile, "script-show-all-block-sizes", ["-r", "--show-all-block-sizes"]);
|
||||
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]);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ support-files =
|
|||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче