import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

2122
memory/replace/dmd/DMD.cpp Normal file

File diff suppressed because it is too large Load diff

310
memory/replace/dmd/DMD.h Normal file
View file

@ -0,0 +1,310 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef DMD_h___
#define DMD_h___
#include <string.h>
#include <stdarg.h>
#include "mozilla/DebugOnly.h"
#include "mozilla/Move.h"
#include "mozilla/Types.h"
#include "mozilla/UniquePtr.h"
#include "replace_malloc_bridge.h"
namespace mozilla {
class JSONWriteFunc;
namespace dmd {
struct Sizes
{
size_t mStackTracesUsed;
size_t mStackTracesUnused;
size_t mStackTraceTable;
size_t mLiveBlockTable;
size_t mDeadBlockTable;
Sizes() { Clear(); }
void Clear() { memset(this, 0, sizeof(Sizes)); }
};
// See further below for a description of each method. The DMDFuncs class
// should contain a virtual method for each of them (except IsRunning,
// which can be inferred from the DMDFuncs singleton existing).
struct DMDFuncs
{
virtual void Report(const void*);
virtual void ReportOnAlloc(const void*);
virtual void ClearReports();
virtual void Analyze(UniquePtr<JSONWriteFunc>);
virtual void SizeOf(Sizes*);
virtual void StatusMsg(const char*, va_list);
virtual void ResetEverything(const char*);
#ifndef REPLACE_MALLOC_IMPL
// We deliberately don't use ReplaceMalloc::GetDMDFuncs here, because if we
// did, the following would happen.
// - The code footprint of each call to Get() larger as GetDMDFuncs ends
// up inlined.
// - When no replace-malloc library is loaded, the number of instructions
// executed is equivalent, but don't necessarily fit in the same cache
// line.
// - When a non-DMD replace-malloc library is loaded, the overhead is
// higher because there is first a check for the replace malloc bridge
// and then for the DMDFuncs singleton.
// Initializing the DMDFuncs singleton on the first access makes the
// overhead even worse. Either Get() is inlined and massive, or it isn't
// and a simple value check becomes a function call.
static DMDFuncs* Get() { return sSingleton.Get(); }
private:
// Wrapper class keeping a pointer to the DMD functions. It is statically
// initialized because it needs to be set early enough.
// Debug builds also check that it's never accessed before the static
// initialization actually occured, which could be the case if some other
// static initializer ended up calling into DMD.
class Singleton
{
public:
Singleton()
: mValue(ReplaceMalloc::GetDMDFuncs())
#ifdef DEBUG
, mInitialized(true)
#endif
{}
DMDFuncs* Get()
{
MOZ_ASSERT(mInitialized);
return mValue;
}
private:
DMDFuncs* mValue;
#ifdef DEBUG
bool mInitialized;
#endif
};
// This singleton pointer must be defined on the program side. In Gecko,
// this is done in xpcom/base/nsMemoryInfoDumper.cpp.
static /* DMDFuncs:: */Singleton sSingleton;
#endif
};
#ifndef REPLACE_MALLOC_IMPL
// Mark a heap block as reported by a memory reporter.
inline void
Report(const void* aPtr)
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
funcs->Report(aPtr);
}
}
// Mark a heap block as reported immediately on allocation.
inline void
ReportOnAlloc(const void* aPtr)
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
funcs->ReportOnAlloc(aPtr);
}
}
// Clears existing reportedness data from any prior runs of the memory
// reporters. The following sequence should be used.
// - ClearReports()
// - run the memory reporters
// - Analyze()
// This sequence avoids spurious twice-reported warnings.
inline void
ClearReports()
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
funcs->ClearReports();
}
}
// Determines which heap blocks have been reported, and dumps JSON output
// (via |aWriter|) describing the heap.
//
// The following sample output contains comments that explain the format and
// design choices. The output files can be quite large, so a number of
// decisions were made to minimize size, such as using short property names and
// omitting properties whenever possible.
//
// {
// // The version number of the format, which will be incremented each time
// // backwards-incompatible changes are made. A mandatory integer.
// //
// // Version history:
// // - 1: Bug 1044709
// // - 2: Bug 1094552
// // - 3: Bug 1100851
// // - 4: Bug 1121830
// // - 5: Bug 1253512
// "version": 5,
//
// // Information about how DMD was invoked. A mandatory object.
// "invocation": {
// // The contents of the $DMD environment variable. A string, or |null| if
// // $DMD is undefined.
// "dmdEnvVar": "--mode=dark-matter",
//
// // The profiling mode. A mandatory string taking one of the following
// // values: "live", "dark-matter", "cumulative", "scan".
// "mode": "dark-matter",
// },
//
// // Details of all analyzed heap blocks. A mandatory array.
// "blockList": [
// // An example of a heap block.
// {
// // Requested size, in bytes. This is a mandatory integer.
// "req": 3584,
//
// // Requested slop size, in bytes. This is mandatory if it is non-zero,
// // but omitted otherwise.
// "slop": 512,
//
// // The stack trace at which the block was allocated. An optional
// // string that indexes into the "traceTable" object. If omitted, no
// // allocation stack trace was recorded for the block.
// "alloc": "A",
//
// // One or more stack traces at which this heap block was reported by a
// // memory reporter. An optional array that will only be present in
// // "dark-matter" mode. The elements are strings that index into
// // the "traceTable" object.
// "reps": ["B"]
//
// // The number of heap blocks with exactly the above properties. This
// // is mandatory if it is greater than one, but omitted otherwise.
// // (Blocks with identical properties don't have to be aggregated via
// // this property, but it can greatly reduce output file size.)
// "num": 5,
//
// // The address of the block. This is mandatory in "scan" mode, but
// // omitted otherwise.
// "addr": "4e4e4e4e",
//
// // The contents of the block, read one word at a time. This is
// // mandatory in "scan" mode for blocks at least one word long, but
// // omitted otherwise.
// "contents": ["0", "6", "7f7f7f7f", "0"]
// }
// ],
//
// // The stack traces referenced by elements of the "blockList" array. This
// // could be an array, but making it an object makes it easier to see
// // which stacks correspond to which references in the "blockList" array.
// "traceTable": {
// // Each property corresponds to a stack trace mentioned in the "blocks"
// // object. Each element is an index into the "frameTable" object.
// "A": ["D", "E"],
// "B": ["F", "G"]
// },
//
// // The stack frames referenced by the "traceTable" object. The
// // descriptions can be quite long, so they are stored separately from the
// // "traceTable" object so that each one only has to be written once.
// // This could also be an array, but again, making it an object makes it
// // easier to see which frames correspond to which references in the
// // "traceTable" object.
// "frameTable": {
// // Each property key is a frame key mentioned in the "traceTable" object.
// // Each property value is a string containing a frame description. Each
// // frame description must be in a format recognized by the stack-fixing
// // scripts (e.g. fix_linux_stack.py), which require a frame number at
// // the start. Because each stack frame description in this table can
// // be shared between multiple stack traces, we use a dummy value of
// // #00. The proper frame number can be reconstructed later by scripts
// // that output stack traces in a conventional non-shared format.
// "D": "#00: foo (Foo.cpp:123)",
// "E": "#00: bar (Bar.cpp:234)",
// "F": "#00: baz (Baz.cpp:345)",
// "G": "#00: quux (Quux.cpp:456)"
// }
// }
//
// Implementation note: normally, this function wouldn't be templated, but in
// that case, the function is compiled, which makes the destructor for the
// UniquePtr fire up, and that needs JSONWriteFunc to be fully defined. That,
// in turn, requires to include JSONWriter.h, which includes
// double-conversion.h, which ends up breaking various things built with
// -Werror for various reasons.
//
template <typename JSONWriteFunc>
inline void
Analyze(UniquePtr<JSONWriteFunc> aWriteFunc)
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
funcs->Analyze(Move(aWriteFunc));
}
}
// Gets the size of various data structures. Used to implement a memory
// reporter for DMD.
inline void
SizeOf(Sizes* aSizes)
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
funcs->SizeOf(aSizes);
}
}
// Prints a status message prefixed with "DMD[<pid>]". Use sparingly.
inline void
StatusMsg(const char* aFmt, ...)
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
va_list ap;
va_start(ap, aFmt);
funcs->StatusMsg(aFmt, ap);
va_end(ap);
}
}
// Indicates whether or not DMD is running.
inline bool
IsRunning()
{
return !!DMDFuncs::Get();
}
// Resets all DMD options and then sets new ones according to those specified
// in |aOptions|. Also clears all recorded data about allocations. Only used
// for testing purposes.
inline void
ResetEverything(const char* aOptions)
{
DMDFuncs* funcs = DMDFuncs::Get();
if (funcs) {
funcs->ResetEverything(aOptions);
}
}
#endif
} // namespace dmd
} // namespace mozilla
#endif /* DMD_h___ */

View file

@ -0,0 +1,2 @@
This is DMD. See https://wiki.mozilla.org/Performance/MemShrink/DMD for
details on how to use it.

View file

@ -0,0 +1,261 @@
#!/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()

890
memory/replace/dmd/dmd.py Normal file
View file

@ -0,0 +1,890 @@
#! /usr/bin/env 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/.
'''This script analyzes a JSON file emitted by DMD.'''
from __future__ import print_function, division
import argparse
import collections
import gzip
import json
import os
import platform
import re
import shutil
import sys
import tempfile
from bisect import bisect_right
# 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 = [
# Matches malloc, replace_malloc, moz_xmalloc, vpx_malloc, js_malloc, pod_malloc, malloc_zone_*, g_malloc.
'malloc',
# Matches calloc, replace_calloc, moz_xcalloc, vpx_calloc, js_calloc, pod_calloc, malloc_zone_calloc, pod_callocCanGC.
'calloc',
# Matches realloc, replace_realloc, moz_xrealloc, vpx_realloc, js_realloc, pod_realloc, pod_reallocCanGC.
'realloc',
# Matches memalign, posix_memalign, replace_memalign, replace_posix_memalign, moz_xmemalign, moz_xposix_memalign, vpx_memalign, malloc_zone_memalign.
'memalign',
'operator new(',
'operator new[](',
'g_slice_alloc',
# 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.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 \
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, count in self.usableSizes.items():
negatedUsableSizes[-usableSize] = 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
usableSizes1 = self.usableSizes
usableSizes2 = r.usableSizes
usableSizes3 = collections.defaultdict(int)
for usableSize in usableSizes1:
counts1 = usableSizes1[usableSize]
if usableSize in usableSizes2:
counts2 = usableSizes2[usableSize]
del usableSizes2[usableSize]
counts3 = counts1 - counts2
if counts3 != 0:
if counts3 < 0:
usableSize = -usableSize
counts3 = -counts3
usableSizes3[usableSize] = counts3
else:
usableSizes3[usableSize] = counts1
for usableSize in usableSizes2:
usableSizes3[-usableSize] = usableSizes2[usableSize]
self.usableSizes = usableSizes3
@staticmethod
def cmpByUsableSize(r1, r2):
# Sort by usable size, then by req size.
return cmp(abs(r1.usableSize), abs(r2.usableSize)) or \
Record.cmpByReqSize(r1, r2)
@staticmethod
def cmpByReqSize(r1, r2):
# Sort by req size.
return cmp(abs(r1.reqSize), abs(r2.reqSize))
@staticmethod
def cmpBySlopSize(r1, r2):
# Sort by slop size.
return cmp(abs(r1.slopSize), abs(r2.slopSize))
@staticmethod
def cmpByNumBlocks(r1, r2):
# Sort by block counts, then by usable size.
return cmp(abs(r1.numBlocks), abs(r2.numBlocks)) or \
Record.cmpByUsableSize(r1, r2)
sortByChoices = {
'usable': Record.cmpByUsableSize, # the default
'req': Record.cmpByReqSize,
'slop': Record.cmpBySlopSize,
'num-blocks': Record.cmpByNumBlocks,
}
def parseCommandLine():
# 24 is the maximum number of frames that DMD will produce.
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
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.
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)
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')
p.add_argument('-s', '--sort-by', choices=sortByChoices.keys(),
default='usable',
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('--no-fix-stacks', action='store_true',
help='do not fix stacks')
p.add_argument('--clamp-contents', action='store_true',
help='for a scan mode log, clamp addresses to the start of live blocks, or zero if not in one')
p.add_argument('--print-clamp-stats', action='store_true',
help='print information about the results of pointer clamping; mostly useful for debugging clamping')
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')
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
# script is installed as a symlink.
sys.path.append(os.path.dirname(__file__))
bpsyms = os.environ.get('BREAKPAD_SYMBOLS_PATH', None)
sysname = platform.system()
if bpsyms and os.path.exists(bpsyms):
import fix_stack_using_bpsyms as fixModule
fix = lambda line: fixModule.fixSymbols(line, bpsyms)
elif 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.
tmpFile = tempfile.NamedTemporaryFile(delete=False)
# If the input is gzipped, then the output (written initially to
# |tmpFile|) should be gzipped as well.
#
# And we want to set its pre-gzipped filename to '' rather than the
# name of the temporary file, so that programs like the Unix 'file'
# utility don't say that it was called 'tmp6ozTxE' (or something like
# that) before it was zipped. So that explains the |filename=''|
# parameter.
#
# But setting the filename like that clobbers |tmpFile.name|, so we
# must get that now in order to move |tmpFile| at the end.
tmpFilename = tmpFile.name
if isZipped:
tmpFile = gzip.GzipFile(filename='', fileobj=tmpFile)
with opener(inputFilename, 'rb') as inputFile:
for line in inputFile:
tmpFile.write(fix(line))
tmpFile.close()
shutil.move(tmpFilename, inputFilename)
def getDigestFromFile(args, inputFile):
# Handle gzipped input if necessary.
isZipped = inputFile.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)
if args.clamp_contents:
clampBlockList(args, inputFile, isZipped, opener)
with opener(inputFile, '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']
mode = invocation['mode']
blockList = j['blockList']
traceTable = j['traceTable']
frameTable = j['frameTable']
# Insert the necessary entries for unrecorded stack traces. Note that 'ut'
# and 'uf' will not overlap with any keys produced by DMD's
# ToIdStringConverter::Base32() function.
unrecordedTraceID = 'ut'
unrecordedFrameID = 'uf'
traceTable[unrecordedTraceID] = [unrecordedFrameID]
frameTable[unrecordedFrameID] = \
'#00: (no stack trace recorded due to --stacks=partial)'
# For the purposes of this script, 'scan' behaves like 'live'.
if mode == 'scan':
mode = 'live'
if not mode in ['live', 'dark-matter', 'cumulative']:
raise Exception("bad 'mode' property: '{:s}'".format(mode))
# 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 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 mode in ['live', 'cumulative']:
liveOrCumulativeRecords = collections.defaultdict(Record)
elif mode == 'dark-matter':
unreportedRecords = collections.defaultdict(Record)
onceReportedRecords = collections.defaultdict(Record)
twiceReportedRecords = collections.defaultdict(Record)
heapUsableSize = 0
heapBlocks = 0
recordKeyPartCache = {}
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.
#
# Having said all that, during a single invocation of dmd.py on a
# single DMD file, for a single frameKey value the record key will
# always be the same, and we might encounter it 1000s of times. So we
# cache prior results for speed.
def makeRecordKeyPart(traceKey):
if traceKey in recordKeyPartCache:
return recordKeyPartCache[traceKey]
recordKeyPart = str(map(lambda frameKey: frameTable[frameKey],
traceTable[traceKey]))
recordKeyPartCache[traceKey] = recordKeyPart
return recordKeyPart
allocatedAtTraceKey = block.get('alloc', unrecordedTraceID)
if mode in ['live', 'cumulative']:
recordKey = makeRecordKeyPart(allocatedAtTraceKey)
records = liveOrCumulativeRecords
elif mode == 'dark-matter':
recordKey = makeRecordKeyPart(allocatedAtTraceKey)
if 'reps' in block:
reportedAtTraceKeys = block['reps']
for reportedAtTraceKey in reportedAtTraceKeys:
recordKey += makeRecordKeyPart(reportedAtTraceKey)
if len(reportedAtTraceKeys) == 1:
records = onceReportedRecords
else:
records = twiceReportedRecords
else:
records = unreportedRecords
record = records[recordKey]
if 'req' not in block:
raise Exception("'req' property missing in block'")
reqSize = block['req']
slopSize = block.get('slop', 0)
if 'num' in block:
num = block['num']
else:
num = 1
usableSize = reqSize + slopSize
heapUsableSize += num * usableSize
heapBlocks += num
record.numBlocks += num
record.reqSize += num * reqSize
record.slopSize += num * slopSize
record.usableSize += num * usableSize
if record.allocatedAtDesc == None:
record.allocatedAtDesc = \
buildTraceDescription(traceTable, frameTable,
allocatedAtTraceKey)
if mode in ['live', 'cumulative']:
pass
elif mode == 'dark-matter':
if 'reps' in block and record.reportedAtDescs == []:
f = lambda k: buildTraceDescription(traceTable, frameTable, k)
record.reportedAtDescs = map(f, reportedAtTraceKeys)
record.usableSizes[usableSize] += num
# All the processed data for a single DMD file is called a "digest".
digest = {}
digest['dmdEnvVar'] = dmdEnvVar
digest['mode'] = mode
digest['heapUsableSize'] = heapUsableSize
digest['heapBlocks'] = heapBlocks
if mode in ['live', 'cumulative']:
digest['liveOrCumulativeRecords'] = liveOrCumulativeRecords
elif mode == 'dark-matter':
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):
if (d1['mode'] != d2['mode']):
raise Exception("the input files have different 'mode' properties")
d3 = {}
d3['dmdEnvVar'] = (d1['dmdEnvVar'], d2['dmdEnvVar'])
d3['mode'] = d1['mode']
d3['heapUsableSize'] = d2['heapUsableSize'] - d1['heapUsableSize']
d3['heapBlocks'] = d2['heapBlocks'] - d1['heapBlocks']
if d1['mode'] in ['live', 'cumulative']:
d3['liveOrCumulativeRecords'] = \
diffRecords(args, d1['liveOrCumulativeRecords'],
d2['liveOrCumulativeRecords'])
elif d1['mode'] == 'dark-matter':
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']
mode = digest['mode']
heapUsableSize = digest['heapUsableSize']
heapBlocks = digest['heapBlocks']
if mode in ['live', 'cumulative']:
liveOrCumulativeRecords = digest['liveOrCumulativeRecords']
elif mode == 'dark-matter':
unreportedRecords = digest['unreportedRecords']
onceReportedRecords = digest['onceReportedRecords']
twiceReportedRecords = digest['twiceReportedRecords']
separator = '#' + '-' * 65 + '\n'
def number(n):
'''Format a number with comma as a separator.'''
return '{:,d}'.format(n)
def perc(m, n):
return 0 if n == 0 else (100 * m / n)
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 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
kindUsableSize = 0
maxRecord = 1000
# First iteration: get totals, etc.
for record in sortedRecords:
kindBlocks += record.numBlocks
kindUsableSize += record.usableSize
# Second iteration: print.
if numRecords == 0:
out('# no {:} heap blocks\n'.format(recordKind))
kindCumulativeUsableSize = 0
for i, record in enumerate(sortedRecords, start=1):
# Stop printing at the |maxRecord|th record.
if i == maxRecord:
out('# {:}: stopping after {:,d} heap block records\n'.
format(RecordKind, i))
break
kindCumulativeUsableSize += record.usableSize
out(RecordKind + ' {')
out(' {:} block{:} in heap block record {:,d} of {:,d}'.
format(number(record.numBlocks),
plural(record.numBlocks), i, numRecords))
out(' {:} bytes ({:} requested / {:} slop)'.
format(number(record.usableSize),
number(record.reqSize),
number(record.slopSize)))
abscmp = lambda (usableSize1, _1), (usableSize2, _2): \
cmp(abs(usableSize1), abs(usableSize2))
usableSizes = sorted(record.usableSizes.items(), cmp=abscmp,
reverse=True)
hasSingleBlock = len(usableSizes) == 1 and usableSizes[0][1] == 1
if not hasSingleBlock:
out(' Individual block sizes: ', end='')
if len(usableSizes) == 0:
out('(no change)', end='')
else:
isFirst = True
for usableSize, count in usableSizes:
if not isFirst:
out('; ', end='')
out('{:}'.format(number(usableSize)), end='')
if count > 1:
out(' x {:,d}'.format(count), end='')
isFirst = False
out()
out(' {:4.2f}% of the heap ({:4.2f}% cumulative)'.
format(perc(record.usableSize, heapUsableSize),
perc(kindCumulativeUsableSize, heapUsableSize)))
if mode in ['live', 'cumulative']:
pass
elif mode == 'dark-matter':
out(' {:4.2f}% of {:} ({:4.2f}% cumulative)'.
format(perc(record.usableSize, kindUsableSize),
recordKind,
perc(kindCumulativeUsableSize, kindUsableSize)))
out(' Allocated at {')
printStack(record.allocatedAtDesc)
out(' }')
if mode in ['live', 'cumulative']:
pass
elif mode == 'dark-matter':
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)
def printInvocation(n, dmdEnvVar, mode):
out('Invocation{:} {{'.format(n))
if dmdEnvVar == None:
out(' $DMD is undefined')
else:
out(' $DMD = \'' + dmdEnvVar + '\'')
out(' Mode = \'' + mode + '\'')
out('}\n')
# Print command line. Strip dirs so the output is deterministic, which is
# needed for testing.
out(separator, end='')
out('# ' + ' '.join(map(os.path.basename, sys.argv)) + '\n')
# Print invocation(s).
if type(dmdEnvVar) is not tuple:
printInvocation('', dmdEnvVar, mode)
else:
printInvocation(' 1', dmdEnvVar[0], mode)
printInvocation(' 2', dmdEnvVar[1], mode)
# Print records.
if mode in ['live', 'cumulative']:
liveOrCumulativeUsableSize, liveOrCumulativeBlocks = \
printRecords(mode, liveOrCumulativeRecords, heapUsableSize)
elif mode == 'dark-matter':
twiceReportedUsableSize, twiceReportedBlocks = \
printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
unreportedUsableSize, unreportedBlocks = \
printRecords('unreported', unreportedRecords, heapUsableSize)
onceReportedUsableSize, onceReportedBlocks = \
printRecords('once-reported', onceReportedRecords, heapUsableSize)
# Print summary.
out(separator)
out('Summary {')
if mode in ['live', 'cumulative']:
out(' Total: {:} bytes in {:} blocks'.
format(number(liveOrCumulativeUsableSize),
number(liveOrCumulativeBlocks)))
elif mode == 'dark-matter':
fmt = ' {:15} {:>12} bytes ({:6.2f}%) in {:>7} blocks ({:6.2f}%)'
out(fmt.
format('Total:',
number(heapUsableSize),
100,
number(heapBlocks),
100))
out(fmt.
format('Unreported:',
number(unreportedUsableSize),
perc(unreportedUsableSize, heapUsableSize),
number(unreportedBlocks),
perc(unreportedBlocks, heapBlocks)))
out(fmt.
format('Once-reported:',
number(onceReportedUsableSize),
perc(onceReportedUsableSize, heapUsableSize),
number(onceReportedBlocks),
perc(onceReportedBlocks, heapBlocks)))
out(fmt.
format('Twice-reported:',
number(twiceReportedUsableSize),
perc(twiceReportedUsableSize, heapUsableSize),
number(twiceReportedBlocks),
perc(twiceReportedBlocks, heapBlocks)))
out('}\n')
#############################
# Pretty printer for DMD JSON
#############################
def prettyPrintDmdJson(out, j):
out.write('{\n')
out.write(' "version": {0},\n'.format(j['version']))
out.write(' "invocation": ')
json.dump(j['invocation'], out, sort_keys=True)
out.write(',\n')
out.write(' "blockList": [')
first = True
for b in j['blockList']:
out.write('' if first else ',')
out.write('\n ')
json.dump(b, out, sort_keys=True)
first = False
out.write('\n ],\n')
out.write(' "traceTable": {')
first = True
for k, l in j['traceTable'].iteritems():
out.write('' if first else ',')
out.write('\n "{0}": {1}'.format(k, json.dumps(l)))
first = False
out.write('\n },\n')
out.write(' "frameTable": {')
first = True
for k, v in j['frameTable'].iteritems():
out.write('' if first else ',')
out.write('\n "{0}": {1}'.format(k, json.dumps(v)))
first = False
out.write('\n }\n')
out.write('}\n')
##################################################################
# Code for clamping addresses using conservative pointer analysis.
##################################################################
# Start is the address of the first byte of the block, while end is
# the address of the first byte after the final byte in the block.
class AddrRange:
def __init__(self, block, length):
self.block = block
self.start = int(block, 16)
self.length = length
self.end = self.start + self.length
assert self.start > 0
assert length >= 0
class ClampStats:
def __init__(self):
# Number of pointers already pointing to the start of a block.
self.startBlockPtr = 0
# Number of pointers pointing to the middle of a block. These
# are clamped to the start of the block they point into.
self.midBlockPtr = 0
# Number of null pointers.
self.nullPtr = 0
# Number of non-null pointers that didn't point into the middle
# of any blocks. These are clamped to null.
self.nonNullNonBlockPtr = 0
def clampedBlockAddr(self, sameAddress):
if sameAddress:
self.startBlockPtr += 1
else:
self.midBlockPtr += 1
def nullAddr(self):
self.nullPtr += 1
def clampedNonBlockAddr(self):
self.nonNullNonBlockPtr += 1
def log(self):
sys.stderr.write('Results:\n')
sys.stderr.write(' Number of pointers already pointing to start of blocks: ' + str(self.startBlockPtr) + '\n')
sys.stderr.write(' Number of pointers clamped to start of blocks: ' + str(self.midBlockPtr) + '\n')
sys.stderr.write(' Number of non-null pointers not pointing into blocks clamped to null: ' + str(self.nonNullNonBlockPtr) + '\n')
sys.stderr.write(' Number of null pointers: ' + str(self.nullPtr) + '\n')
# Search the block ranges array for a block that address points into.
# The search is carried out in an array of starting addresses for each blocks
# because it is faster.
def clampAddress(blockRanges, blockStarts, clampStats, address):
i = bisect_right(blockStarts, address)
# Any addresses completely out of the range should have been eliminated already.
assert i > 0
r = blockRanges[i - 1]
assert r.start <= address
if address >= r.end:
assert address < blockRanges[i].start
clampStats.clampedNonBlockAddr()
return '0'
clampStats.clampedBlockAddr(r.start == address)
return r.block
def clampBlockList(args, inputFileName, isZipped, opener):
# XXX This isn't very efficient because we end up reading and writing
# the file multiple times.
with opener(inputFileName, 'rb') as f:
j = json.load(f)
if j['version'] != outputVersion:
raise Exception("'version' property isn't '{:d}'".format(outputVersion))
# Check that the invocation is reasonable for contents clamping.
invocation = j['invocation']
if invocation['mode'] != 'scan':
raise Exception("Log was taken in mode " + invocation['mode'] + " not scan")
sys.stderr.write('Creating block range list.\n')
blockList = j['blockList']
blockRanges = []
for block in blockList:
blockRanges.append(AddrRange(block['addr'], block['req']))
blockRanges.sort(key=lambda r: r.start)
# Make sure there are no overlapping blocks.
prevRange = blockRanges[0]
for currRange in blockRanges[1:]:
assert prevRange.end <= currRange.start
prevRange = currRange
sys.stderr.write('Clamping block contents.\n')
clampStats = ClampStats()
firstAddr = blockRanges[0].start
lastAddr = blockRanges[-1].end
blockStarts = []
for r in blockRanges:
blockStarts.append(r.start)
for block in blockList:
# Small blocks don't have any contents.
if not 'contents' in block:
continue
cont = block['contents']
for i in range(len(cont)):
address = int(cont[i], 16)
if address == 0:
clampStats.nullAddr()
continue
# If the address is before the first block or after the last
# block then it can't be within a block.
if address < firstAddr or address >= lastAddr:
clampStats.clampedNonBlockAddr()
cont[i] = '0'
continue
cont[i] = clampAddress(blockRanges, blockStarts, clampStats, address)
# Remove any trailing nulls.
while len(cont) and cont[-1] == '0':
cont.pop()
if args.print_clamp_stats:
clampStats.log()
sys.stderr.write('Saving file.\n')
tmpFile = tempfile.NamedTemporaryFile(delete=False)
tmpFilename = tmpFile.name
if isZipped:
tmpFile = gzip.GzipFile(filename='', fileobj=tmpFile)
prettyPrintDmdJson(tmpFile, j)
tmpFile.close()
shutil.move(tmpFilename, inputFileName)
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()

View file

@ -0,0 +1,39 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
EXPORTS += [
'DMD.h',
]
SOURCES += [
'../../../mfbt/HashFunctions.cpp',
'../../../mfbt/JSONWriter.cpp',
'../../../mfbt/Poison.cpp',
'../../../mozglue/misc/StackWalk.cpp',
'DMD.cpp',
]
SOURCES += [
'../../../nsprpub/lib/libc/src/strcpy.c',
]
SharedLibrary('dmd')
DEFINES['MOZ_NO_MOZALLOC'] = True
DEFINES['IMPL_MFBT'] = True
DEFINES['XPCOM_GLUE'] = True
if CONFIG['MOZ_OPTIMIZE']:
DEFINES['MOZ_OPTIMIZE'] = True
DISABLE_STL_WRAPPING = True
if CONFIG['OS_ARCH'] == 'WINNT':
OS_LIBS += [
'dbghelp',
]
TEST_DIRS += ['test']

View file

@ -0,0 +1,379 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
// This program is used by the DMD xpcshell test. It is run under DMD and
// produces some output. The xpcshell test then post-processes and checks this
// output.
//
// Note that this file does not have "Test" or "test" in its name, because that
// will cause the build system to not record breakpad symbols for it, which
// will stop the post-processing (which includes stack fixing) from working
// correctly.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "mozilla/Assertions.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/UniquePtr.h"
#include "DMD.h"
using mozilla::JSONWriter;
using mozilla::MakeUnique;
using namespace mozilla::dmd;
DMDFuncs::Singleton DMDFuncs::sSingleton;
class FpWriteFunc : public mozilla::JSONWriteFunc
{
public:
explicit FpWriteFunc(const char* aFilename)
{
mFp = fopen(aFilename, "w");
if (!mFp) {
fprintf(stderr, "SmokeDMD: can't create %s file: %s\n",
aFilename, strerror(errno));
exit(1);
}
}
~FpWriteFunc() { fclose(mFp); }
void Write(const char* aStr) { fputs(aStr, mFp); }
private:
FILE* mFp;
};
// This stops otherwise-unused variables from being optimized away.
static void
UseItOrLoseIt(void* aPtr, int aSeven)
{
char buf[64];
int n = sprintf(buf, "%p\n", aPtr);
if (n == 20 + aSeven) {
fprintf(stderr, "well, that is surprising");
}
}
// This function checks that heap blocks that have the same stack trace but
// different (or no) reporters get aggregated separately.
void Foo(int aSeven)
{
char* a[6];
for (int i = 0; i < aSeven - 1; i++) {
a[i] = (char*) malloc(128 - 16*i);
}
// Oddly, some versions of clang will cause identical stack traces to be
// generated for adjacent calls to Report(), which breaks the test. Inserting
// the UseItOrLoseIt() calls in between is enough to prevent this.
Report(a[2]); // reported
UseItOrLoseIt(a[2], aSeven);
for (int i = 0; i < aSeven - 5; i++) {
Report(a[i]); // reported
}
UseItOrLoseIt(a[2], aSeven);
Report(a[3]); // reported
// a[4], a[5] unreported
}
void
TestEmpty(const char* aTestName, const char* aMode)
{
char filename[128];
sprintf(filename, "complete-%s-%s.json", aTestName, aMode);
auto f = MakeUnique<FpWriteFunc>(filename);
char options[128];
sprintf(options, "--mode=%s --stacks=full", aMode);
ResetEverything(options);
// Zero for everything.
Analyze(Move(f));
}
void
TestFull(const char* aTestName, int aNum, const char* aMode, int aSeven)
{
char filename[128];
sprintf(filename, "complete-%s%d-%s.json", aTestName, aNum, aMode);
auto f = MakeUnique<FpWriteFunc>(filename);
// The --show-dump-stats=yes is there just to give that option some basic
// testing, e.g. ensure it doesn't crash. It's hard to test much beyond that.
char options[128];
sprintf(options, "--mode=%s --stacks=full --show-dump-stats=yes", aMode);
ResetEverything(options);
// Analyze 1: 1 freed, 9 out of 10 unreported.
// Analyze 2: still present and unreported.
int i;
char* a = nullptr;
for (i = 0; i < aSeven + 3; i++) {
a = (char*) malloc(100);
UseItOrLoseIt(a, aSeven);
}
free(a);
// A no-op.
free(nullptr);
// Note: 16 bytes is the smallest requested size that gives consistent
// behaviour across all platforms with jemalloc.
// Analyze 1: reported.
// Analyze 2: thrice-reported.
char* a2 = (char*) malloc(16);
Report(a2);
// Analyze 1: reported.
// Analyze 2: reportedness carries over, due to ReportOnAlloc.
char* b = (char*) malloc(10);
ReportOnAlloc(b);
// ReportOnAlloc, then freed.
// Analyze 1: freed, irrelevant.
// Analyze 2: freed, irrelevant.
char* b2 = (char*) malloc(16);
ReportOnAlloc(b2);
free(b2);
// Analyze 1: reported 4 times.
// Analyze 2: freed, irrelevant.
char* c = (char*) calloc(10, 3);
Report(c);
for (int i = 0; i < aSeven - 4; i++) {
Report(c);
}
// Analyze 1: ignored.
// Analyze 2: irrelevant.
Report((void*)(intptr_t)i);
// jemalloc rounds this up to 8192.
// Analyze 1: reported.
// Analyze 2: freed.
char* e = (char*) malloc(4096);
e = (char*) realloc(e, 7169);
Report(e);
// First realloc is like malloc; second realloc is shrinking.
// Analyze 1: reported.
// Analyze 2: re-reported.
char* e2 = (char*) realloc(nullptr, 1024);
e2 = (char*) realloc(e2, 512);
Report(e2);
// First realloc is like malloc; second realloc creates a min-sized block.
// XXX: on Windows, second realloc frees the block.
// Analyze 1: reported.
// Analyze 2: freed, irrelevant.
char* e3 = (char*) realloc(nullptr, 1023);
//e3 = (char*) realloc(e3, 0);
MOZ_ASSERT(e3);
Report(e3);
// Analyze 1: freed, irrelevant.
// Analyze 2: freed, irrelevant.
char* f1 = (char*) malloc(64);
free(f1);
// Analyze 1: ignored.
// Analyze 2: irrelevant.
Report((void*)(intptr_t)0x0);
// Analyze 1: mixture of reported and unreported.
// Analyze 2: all unreported.
Foo(aSeven);
// Analyze 1: twice-reported.
// Analyze 2: twice-reported.
char* g1 = (char*) malloc(77);
ReportOnAlloc(g1);
ReportOnAlloc(g1);
// Analyze 1: mixture of reported and unreported.
// Analyze 2: all unreported.
// Nb: this Foo() call is deliberately not adjacent to the previous one. See
// the comment about adjacent calls in Foo() for more details.
Foo(aSeven);
// Analyze 1: twice-reported.
// Analyze 2: once-reported.
char* g2 = (char*) malloc(78);
Report(g2);
ReportOnAlloc(g2);
// Analyze 1: twice-reported.
// Analyze 2: once-reported.
char* g3 = (char*) malloc(79);
ReportOnAlloc(g3);
Report(g3);
// All the odd-ball ones.
// Analyze 1: all unreported.
// Analyze 2: all freed, irrelevant.
// XXX: no memalign on Mac
//void* w = memalign(64, 65); // rounds up to 128
//UseItOrLoseIt(w, aSeven);
// XXX: posix_memalign doesn't work on B2G
//void* x;
//posix_memalign(&y, 128, 129); // rounds up to 256
//UseItOrLoseIt(x, aSeven);
// XXX: valloc doesn't work on Windows.
//void* y = valloc(1); // rounds up to 4096
//UseItOrLoseIt(y, aSeven);
// XXX: C11 only
//void* z = aligned_alloc(64, 256);
//UseItOrLoseIt(z, aSeven);
if (aNum == 1) {
// Analyze 1.
Analyze(Move(f));
}
ClearReports();
//---------
Report(a2);
Report(a2);
free(c);
free(e);
Report(e2);
free(e3);
//free(w);
//free(x);
//free(y);
//free(z);
// Do some allocations that will only show up in cumulative mode.
for (int i = 0; i < 100; i++) {
free(malloc(128));
}
if (aNum == 2) {
// Analyze 2.
Analyze(Move(f));
}
}
void
TestPartial(const char* aTestName, const char* aMode, int aSeven)
{
char filename[128];
sprintf(filename, "complete-%s-%s.json", aTestName, aMode);
auto f = MakeUnique<FpWriteFunc>(filename);
char options[128];
sprintf(options, "--mode=%s", aMode);
ResetEverything(options);
int kTenThousand = aSeven + 9993;
char* s;
// The output of this function is deterministic but it relies on the
// probability and seeds given to the FastBernoulliTrial instance in
// ResetBernoulli(). If they change, the output will change too.
// Expected fraction with stacks: (1 - (1 - 0.003) ** 16) = 0.0469.
// So we expect about 0.0469 * 10000 == 469.
// We actually get 511.
for (int i = 0; i < kTenThousand; i++) {
s = (char*) malloc(16);
UseItOrLoseIt(s, aSeven);
}
// Expected fraction with stacks: (1 - (1 - 0.003) ** 128) = 0.3193.
// So we expect about 0.3193 * 10000 == 3193.
// We actually get 3136.
for (int i = 0; i < kTenThousand; i++) {
s = (char*) malloc(128);
UseItOrLoseIt(s, aSeven);
}
// Expected fraction with stacks: (1 - (1 - 0.003) ** 1024) = 0.9539.
// So we expect about 0.9539 * 10000 == 9539.
// We actually get 9531.
for (int i = 0; i < kTenThousand; i++) {
s = (char*) malloc(1024);
UseItOrLoseIt(s, aSeven);
}
Analyze(Move(f));
}
void
TestScan(int aSeven)
{
auto f = MakeUnique<FpWriteFunc>("basic-scan.json");
ResetEverything("--mode=scan");
uintptr_t* p = (uintptr_t*) malloc(6 * sizeof(uintptr_t*));
UseItOrLoseIt(p, aSeven);
// Hard-coded values checked by scan-test.py
p[0] = 0x123; // outside a block, small value
p[1] = 0x0; // null
p[2] = (uintptr_t)((uint8_t*)p - 1); // pointer outside a block, but nearby
p[3] = (uintptr_t)p; // pointer to start of a block
p[4] = (uintptr_t)((uint8_t*)p + 1); // pointer into a block
p[5] = 0x0; // trailing null
Analyze(Move(f));
}
void
RunTests()
{
// This test relies on the compiler not doing various optimizations, such as
// eliding unused malloc() calls or unrolling loops with fixed iteration
// counts. So we compile it with -O0 (or equivalent), which probably prevents
// that. We also use the following variable for various loop iteration
// counts, just in case compilers might unroll very small loops even with
// -O0.
int seven = 7;
// Make sure that DMD is actually running; it is initialized on the first
// allocation.
int *x = (int*)malloc(100);
UseItOrLoseIt(x, seven);
MOZ_RELEASE_ASSERT(IsRunning());
// Please keep this in sync with run_test in test_dmd.js.
TestEmpty("empty", "live");
TestEmpty("empty", "dark-matter");
TestEmpty("empty", "cumulative");
TestFull("full", 1, "live", seven);
TestFull("full", 1, "dark-matter", seven);
TestFull("full", 2, "dark-matter", seven);
TestFull("full", 2, "cumulative", seven);
TestPartial("partial", "live", seven);
TestScan(seven);
}
int main()
{
RunTests();
return 0;
}

View file

@ -0,0 +1,25 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o basic-scan-32-actual.txt --clamp-contents basic-scan.json
Invocation {
$DMD = '--mode=scan'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
1 block in heap block record 1 of 1
32 bytes (24 requested / 8 slop)
100.00% of the heap (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 32 bytes in 1 blocks
}

View file

@ -0,0 +1,25 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o basic-scan-64-actual.txt --clamp-contents basic-scan.json
Invocation {
$DMD = '--mode=scan'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
1 block in heap block record 1 of 1
48 bytes (48 requested / 0 slop)
100.00% of the heap (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 48 bytes in 1 blocks
}

View file

@ -0,0 +1,18 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-empty-cumulative-actual.txt complete-empty-cumulative.json
Invocation {
$DMD = '--mode=cumulative --stacks=full'
Mode = 'cumulative'
}
#-----------------------------------------------------------------
# no cumulative heap blocks
#-----------------------------------------------------------------
Summary {
Total: 0 bytes in 0 blocks
}

View file

@ -0,0 +1,29 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-empty-dark-matter-actual.txt complete-empty-dark-matter.json
Invocation {
$DMD = '--mode=dark-matter --stacks=full'
Mode = 'dark-matter'
}
#-----------------------------------------------------------------
# no twice-reported heap blocks
#-----------------------------------------------------------------
# no unreported heap blocks
#-----------------------------------------------------------------
# no once-reported heap blocks
#-----------------------------------------------------------------
Summary {
Total: 0 bytes (100.00%) in 0 blocks (100.00%)
Unreported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
Once-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
Twice-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
}

View file

@ -0,0 +1,18 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-empty-live-actual.txt complete-empty-live.json
Invocation {
$DMD = '--mode=live --stacks=full'
Mode = 'live'
}
#-----------------------------------------------------------------
# no live heap blocks
#-----------------------------------------------------------------
Summary {
Total: 0 bytes in 0 blocks
}

View file

@ -0,0 +1,265 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-full1-dark-matter-actual.txt complete-full1-dark-matter.json
Invocation {
$DMD = '--mode=dark-matter --stacks=full --show-dump-stats=yes'
Mode = 'dark-matter'
}
#-----------------------------------------------------------------
Twice-reported {
1 block in heap block record 1 of 4
80 bytes (79 requested / 1 slop)
0.66% of the heap (0.66% cumulative)
29.41% of twice-reported (29.41% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
Reported again at {
#01: ... DMD.cpp ...
}
}
Twice-reported {
1 block in heap block record 2 of 4
80 bytes (78 requested / 2 slop)
0.66% of the heap (1.32% cumulative)
29.41% of twice-reported (58.82% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
Reported again at {
#01: ... DMD.cpp ...
}
}
Twice-reported {
1 block in heap block record 3 of 4
80 bytes (77 requested / 3 slop)
0.66% of the heap (1.98% cumulative)
29.41% of twice-reported (88.24% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
Reported again at {
#01: ... DMD.cpp ...
}
}
Twice-reported {
1 block in heap block record 4 of 4
32 bytes (30 requested / 2 slop)
0.26% of the heap (2.25% cumulative)
11.76% of twice-reported (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
Reported again at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Unreported {
9 blocks in heap block record 1 of 3
1,008 bytes (900 requested / 108 slop)
Individual block sizes: 112 x 9
8.33% of the heap (8.33% cumulative)
81.82% of unreported (81.82% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Unreported {
2 blocks in heap block record 2 of 3
112 bytes (112 requested / 0 slop)
Individual block sizes: 64; 48
0.93% of the heap (9.26% cumulative)
9.09% of unreported (90.91% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Unreported {
2 blocks in heap block record 3 of 3
112 bytes (112 requested / 0 slop)
Individual block sizes: 64; 48
0.93% of the heap (10.19% cumulative)
9.09% of unreported (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Once-reported {
1 block in heap block record 1 of 11
8,192 bytes (7,169 requested / 1,023 slop)
67.72% of the heap (67.72% cumulative)
77.34% of once-reported (77.34% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 2 of 11
1,024 bytes (1,023 requested / 1 slop)
8.47% of the heap (76.19% cumulative)
9.67% of once-reported (87.01% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 3 of 11
512 bytes (512 requested / 0 slop)
4.23% of the heap (80.42% cumulative)
4.83% of once-reported (91.84% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
2 blocks in heap block record 4 of 11
240 bytes (240 requested / 0 slop)
Individual block sizes: 128; 112
1.98% of the heap (82.41% cumulative)
2.27% of once-reported (94.11% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
2 blocks in heap block record 5 of 11
240 bytes (240 requested / 0 slop)
Individual block sizes: 128; 112
1.98% of the heap (84.39% cumulative)
2.27% of once-reported (96.37% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 6 of 11
96 bytes (96 requested / 0 slop)
0.79% of the heap (85.19% cumulative)
0.91% of once-reported (97.28% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 7 of 11
96 bytes (96 requested / 0 slop)
0.79% of the heap (85.98% cumulative)
0.91% of once-reported (98.19% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 8 of 11
80 bytes (80 requested / 0 slop)
0.66% of the heap (86.64% cumulative)
0.76% of once-reported (98.94% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 9 of 11
80 bytes (80 requested / 0 slop)
0.66% of the heap (87.30% cumulative)
0.76% of once-reported (99.70% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 10 of 11
16 bytes (16 requested / 0 slop)
0.13% of the heap (87.43% cumulative)
0.15% of once-reported (99.85% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 11 of 11
16 bytes (10 requested / 6 slop)
0.13% of the heap (87.57% cumulative)
0.15% of once-reported (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 12,096 bytes (100.00%) in 30 blocks (100.00%)
Unreported: 1,232 bytes ( 10.19%) in 13 blocks ( 43.33%)
Once-reported: 10,592 bytes ( 87.57%) in 13 blocks ( 43.33%)
Twice-reported: 272 bytes ( 2.25%) in 4 blocks ( 13.33%)
}

View file

@ -0,0 +1,127 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-full1-live-actual.txt complete-full1-live.json
Invocation {
$DMD = '--mode=live --stacks=full --show-dump-stats=yes'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
1 block in heap block record 1 of 12
8,192 bytes (7,169 requested / 1,023 slop)
67.72% of the heap (67.72% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 2 of 12
1,024 bytes (1,023 requested / 1 slop)
8.47% of the heap (76.19% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
9 blocks in heap block record 3 of 12
1,008 bytes (900 requested / 108 slop)
Individual block sizes: 112 x 9
8.33% of the heap (84.52% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
6 blocks in heap block record 4 of 12
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
4.37% of the heap (88.89% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
6 blocks in heap block record 5 of 12
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
4.37% of the heap (93.25% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 6 of 12
512 bytes (512 requested / 0 slop)
4.23% of the heap (97.49% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 7 of 12
80 bytes (79 requested / 1 slop)
0.66% of the heap (98.15% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 8 of 12
80 bytes (78 requested / 2 slop)
0.66% of the heap (98.81% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 9 of 12
80 bytes (77 requested / 3 slop)
0.66% of the heap (99.47% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 10 of 12
32 bytes (30 requested / 2 slop)
0.26% of the heap (99.74% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 11 of 12
16 bytes (16 requested / 0 slop)
0.13% of the heap (99.87% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
1 block in heap block record 12 of 12
16 bytes (10 requested / 6 slop)
0.13% of the heap (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 12,096 bytes in 30 blocks
}

View file

@ -0,0 +1,173 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-full2-cumulative-actual.txt complete-full2-cumulative.json
Invocation {
$DMD = '--mode=cumulative --stacks=full --show-dump-stats=yes'
Mode = 'cumulative'
}
#-----------------------------------------------------------------
Cumulative {
100 blocks in heap block record 1 of 17
12,800 bytes (12,800 requested / 0 slop)
Individual block sizes: 128 x 100
42.37% of the heap (42.37% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 2 of 17
8,192 bytes (7,169 requested / 1,023 slop)
27.12% of the heap (69.49% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 3 of 17
4,096 bytes (4,096 requested / 0 slop)
13.56% of the heap (83.05% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
10 blocks in heap block record 4 of 17
1,120 bytes (1,000 requested / 120 slop)
Individual block sizes: 112 x 10
3.71% of the heap (86.76% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 5 of 17
1,024 bytes (1,024 requested / 0 slop)
3.39% of the heap (90.15% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 6 of 17
1,024 bytes (1,023 requested / 1 slop)
3.39% of the heap (93.54% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
6 blocks in heap block record 7 of 17
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
1.75% of the heap (95.29% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
6 blocks in heap block record 8 of 17
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
1.75% of the heap (97.03% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 9 of 17
512 bytes (512 requested / 0 slop)
1.69% of the heap (98.73% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 10 of 17
80 bytes (79 requested / 1 slop)
0.26% of the heap (98.99% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 11 of 17
80 bytes (78 requested / 2 slop)
0.26% of the heap (99.26% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 12 of 17
80 bytes (77 requested / 3 slop)
0.26% of the heap (99.52% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 13 of 17
64 bytes (64 requested / 0 slop)
0.21% of the heap (99.74% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 14 of 17
32 bytes (30 requested / 2 slop)
0.11% of the heap (99.84% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 15 of 17
16 bytes (16 requested / 0 slop)
0.05% of the heap (99.89% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 16 of 17
16 bytes (16 requested / 0 slop)
0.05% of the heap (99.95% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Cumulative {
1 block in heap block record 17 of 17
16 bytes (10 requested / 6 slop)
0.05% of the heap (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 30,208 bytes in 135 blocks
}

View file

@ -0,0 +1,140 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-full2-dark-matter-actual.txt complete-full2-dark-matter.json
Invocation {
$DMD = '--mode=dark-matter --stacks=full --show-dump-stats=yes'
Mode = 'dark-matter'
}
#-----------------------------------------------------------------
Twice-reported {
1 block in heap block record 1 of 2
80 bytes (77 requested / 3 slop)
2.81% of the heap (2.81% cumulative)
83.33% of twice-reported (83.33% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
Reported again at {
#01: ... DMD.cpp ...
}
}
Twice-reported {
1 block in heap block record 2 of 2
16 bytes (16 requested / 0 slop)
0.56% of the heap (3.37% cumulative)
16.67% of twice-reported (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
Reported again at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Unreported {
9 blocks in heap block record 1 of 3
1,008 bytes (900 requested / 108 slop)
Individual block sizes: 112 x 9
35.39% of the heap (35.39% cumulative)
48.84% of unreported (48.84% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Unreported {
6 blocks in heap block record 2 of 3
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
18.54% of the heap (53.93% cumulative)
25.58% of unreported (74.42% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Unreported {
6 blocks in heap block record 3 of 3
528 bytes (528 requested / 0 slop)
Individual block sizes: 128; 112; 96; 80; 64; 48
18.54% of the heap (72.47% cumulative)
25.58% of unreported (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Once-reported {
1 block in heap block record 1 of 4
512 bytes (512 requested / 0 slop)
17.98% of the heap (17.98% cumulative)
74.42% of once-reported (74.42% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 2 of 4
80 bytes (79 requested / 1 slop)
2.81% of the heap (20.79% cumulative)
11.63% of once-reported (86.05% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 3 of 4
80 bytes (78 requested / 2 slop)
2.81% of the heap (23.60% cumulative)
11.63% of once-reported (97.67% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
Once-reported {
1 block in heap block record 4 of 4
16 bytes (10 requested / 6 slop)
0.56% of the heap (24.16% cumulative)
2.33% of once-reported (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
Reported at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 2,848 bytes (100.00%) in 27 blocks (100.00%)
Unreported: 2,064 bytes ( 72.47%) in 21 blocks ( 77.78%)
Once-reported: 688 bytes ( 24.16%) in 4 blocks ( 14.81%)
Twice-reported: 96 bytes ( 3.37%) in 2 blocks ( 7.41%)
}

View file

@ -0,0 +1,56 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o complete-partial-live-actual.txt complete-partial-live.json
Invocation {
$DMD = '--mode=live'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
9,531 blocks in heap block record 1 of 4
9,759,744 bytes (9,759,744 requested / 0 slop)
Individual block sizes: 1,024 x 9,531
83.56% of the heap (83.56% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
16,822 blocks in heap block record 2 of 4
1,510,672 bytes (1,510,672 requested / 0 slop)
Individual block sizes: 1,024 x 469; 128 x 6,864; 16 x 9,489
12.93% of the heap (96.49% cumulative)
Allocated at {
#01: (no stack trace recorded due to --stacks=partial)
}
}
Live {
3,136 blocks in heap block record 3 of 4
401,408 bytes (401,408 requested / 0 slop)
Individual block sizes: 128 x 3,136
3.44% of the heap (99.93% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
Live {
511 blocks in heap block record 4 of 4
8,176 bytes (8,176 requested / 0 slop)
Individual block sizes: 16 x 511
0.07% of the heap (100.00% cumulative)
Allocated at {
#01: ... DMD.cpp ...
}
}
#-----------------------------------------------------------------
Summary {
Total: 11,680,000 bytes in 30,000 blocks
}

View file

@ -0,0 +1,26 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
GeckoSimplePrograms([
'SmokeDMD',
], linkage=None)
# See the comment at the top of SmokeDMD.cpp:RunTests().
if CONFIG['OS_ARCH'] == 'WINNT':
CXXFLAGS += ['-Og-']
else:
CXXFLAGS += ['-O0']
DEFINES['MOZ_NO_MOZALLOC'] = True
DISABLE_STL_WRAPPING = True
XPCSHELL_TESTS_MANIFESTS += [
'xpcshell.ini',
]
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

View file

@ -0,0 +1,83 @@
#! /usr/bin/env 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/.
'''Testing for the JSON file emitted by DMD heap scan mode when running SmokeDMD.'''
from __future__ import print_function, division
import argparse
import gzip
import json
import sys
# The DMD output version this script handles.
outputVersion = 5
def parseCommandLine():
description = '''
Ensure that DMD heap scan mode creates the correct output when run with SmokeDMD.
This is only for testing. Input files can be gzipped.
'''
p = argparse.ArgumentParser(description=description)
p.add_argument('--clamp-contents', action='store_true',
help='expect that the contents of the JSON input file have had their addresses clamped')
p.add_argument('input_file',
help='a file produced by DMD')
return p.parse_args(sys.argv[1:])
def checkScanContents(contents, expected):
if len(contents) != len(expected):
raise Exception("Expected " + str(len(expected)) + " things in contents but found " + str(len(contents)))
for i in range(len(expected)):
if contents[i] != expected[i]:
raise Exception("Expected to find " + expected[i] + " at offset " + str(i) + " but found " + contents[i])
def main():
args = parseCommandLine()
# Handle gzipped input if necessary.
isZipped = args.input_file.endswith('.gz')
opener = gzip.open if isZipped else open
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))
invocation = j['invocation']
mode = invocation['mode']
if mode != 'scan':
raise Exception("bad 'mode' property: '{:s}'".format(mode))
blockList = j['blockList']
if len(blockList) != 1:
raise Exception("Expected only one block")
b = blockList[0]
# The expected values are based on hard-coded values in SmokeDMD.cpp.
if args.clamp_contents:
expected = ['0', '0', '0', b['addr'], b['addr']]
else:
addr = int(b['addr'], 16)
expected = ['123', '0', str(format(addr - 1, 'x')), b['addr'],
str(format(addr + 1, 'x')), '0']
checkScanContents(b['contents'], expected)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,127 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-diff-dark-matter-actual.txt script-diff-dark-matter1.json script-diff-dark-matter2.json
Invocation 1 {
$DMD = '--mode=dark-matter'
Mode = 'dark-matter'
}
Invocation 2 {
$DMD is undefined
Mode = 'dark-matter'
}
#-----------------------------------------------------------------
Twice-reported {
-1 blocks in heap block record 1 of 1
-1,088 bytes (-1,064 requested / -24 slop)
Individual block sizes: -1,024; -127; 63
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 5
16,384 bytes (16,384 requested / 0 slop)
Individual block sizes: 4,096 x 4
-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 5
-11,968 bytes (-12,016 requested / 48 slop)
Individual block sizes: -15,360; 2,048; 512 x 2; 128; -127; 64 x 4; 63
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 5
0 bytes (-384 requested / 384 slop)
Individual block sizes: (no change)
-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 5
0 bytes (0 requested / 0 slop)
Individual block sizes: 8,192 x 2; -4,096 x 4
-0.00% of the heap (-62.74% cumulative)
0.00% of unreported (100.00% cumulative)
Allocated at {
#01: B (B.cpp:99)
}
}
Unreported {
0 blocks in heap block record 5 of 5
0 bytes (0 requested / 0 slop)
Individual block sizes: 20,480; -16,384; -8,192; 4,096
-0.00% of the heap (-62.74% cumulative)
0.00% of unreported (100.00% cumulative)
Allocated at {
#01: (no stack trace recorded due to --stacks=partial)
}
}
#-----------------------------------------------------------------
Once-reported {
-3 blocks in heap block record 1 of 2
-10,240 bytes (-10,192 requested / -48 slop)
Individual block sizes: -4,096 x 2; -2,048
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%)
}

View file

@ -0,0 +1,51 @@
{
"version": 5,
"invocation": {
"dmdEnvVar": "--mode=dark-matter",
"mode": "dark-matter"
},
"blockList": [
{"req": 4096, "alloc": "A", "num": 4},
{"req": 4096, "alloc": "B", "num": 3},
{"req": 4096, "alloc": "B"},
{"req": 4096, "alloc": "C", "num": 2},
{"req": 4096, "alloc": "C", "num": 2},
{"req": 4096, "alloc": "D", "reps": ["R1"], "num": 2},
{"req": 2000, "slop": 48, "alloc": "D", "reps": ["R1"]},
{"req": 15360, "alloc": "F"},
{"req": 512, "alloc": "F", "num": 2},
{"req": 127, "alloc": "F"},
{"req": 1024, "alloc": "F", "reps": ["R1"]},
{"req": 127, "alloc": "F", "reps": ["R1"]},
{"req": 1000, "slop": 24, "alloc": "F", "reps": ["R1", "R2"]},
{"req": 127, "alloc": "F", "reps": ["R1", "R2"]},
{"req": 4096 },
{"req": 8192 },
{"req": 16384 }
],
"traceTable": {
"A": ["AA"],
"B": ["BB"],
"C": ["CC"],
"D": ["DD"],
"E": ["EE"],
"F": ["FF"],
"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)",
"RR1": "#00: R1 (R1.cpp:99)",
"RR2": "#00: R2 (R2.cpp:99)"
}
}

View file

@ -0,0 +1,51 @@
{
"version": 5,
"invocation": {
"dmdEnvVar": null,
"mode": "dark-matter"
},
"blockList": [
{"req": 4096, "alloc": "A", "num": 4},
{"req": 8192, "alloc": "B"},
{"req": 8192, "alloc": "B"},
{"req": 4000, "slop": 96, "alloc": "C", "num": 4},
{"req": 4096, "alloc": "E", "num": 4},
{"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"},
{"req": 63, "alloc": "F", "reps": ["R1", "R2"]},
{"req": 64, "alloc": "F", "num": 4},
{"req": 63, "alloc": "F"},
{"req": 4096, "num": 2 },
{"req": 20480 }
],
"traceTable": {
"A": ["AA"],
"B": ["BB"],
"C": ["CC"],
"D": ["DD"],
"E": ["EE"],
"F": ["FF"],
"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)",
"RR1": "#00: R1 (R1.cpp:99)",
"RR2": "#00: R2 (R2.cpp:99)"
}
}

View file

@ -0,0 +1,81 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-diff-live-actual.txt script-diff-live1.json script-diff-live2.json
Invocation 1 {
$DMD = '--mode=live'
Mode = 'live'
}
Invocation 2 {
$DMD = '--mode=live --stacks=partial'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
4 blocks in heap block record 1 of 6
16,384 bytes (16,384 requested / 0 slop)
Individual block sizes: 4,096 x 4
-232.76% of the heap (-232.76% cumulative)
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)
Individual block sizes: -15,360; 2,048; -1,024; 512 x 2; 128; -127 x 3; 64 x 4; 63 x 2
187.29% of the heap (-45.48% cumulative)
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)
Individual block sizes: -4,096 x 2; -2,048
145.48% of the heap (100.00% cumulative)
Allocated at {
#01: D (D.cpp:99)
}
}
Live {
0 blocks in heap block record 4 of 6
0 bytes (-384 requested / 384 slop)
Individual block sizes: (no change)
-0.00% of the heap (100.00% cumulative)
Allocated at {
#01: C (C.cpp:99)
}
}
Live {
0 blocks in heap block record 5 of 6
0 bytes (0 requested / 0 slop)
Individual block sizes: 20,480; -16,384; -8,192; 4,096
-0.00% of the heap (100.00% cumulative)
Allocated at {
#01: (no stack trace recorded due to --stacks=partial)
}
}
Live {
-2 blocks in heap block record 6 of 6
0 bytes (0 requested / 0 slop)
Individual block sizes: 8,192 x 2; -4,096 x 4
-0.00% of the heap (100.00% cumulative)
Allocated at {
#01: B (B.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: -7,039 bytes in 4 blocks
}

View file

@ -0,0 +1,51 @@
{
"version": 5,
"invocation": {
"dmdEnvVar": "--mode=live",
"mode": "live"
},
"blockList": [
{"req": 4096, "alloc": "A", "num": 4},
{"req": 4096, "alloc": "B", "num": 4},
{"req": 4096, "alloc": "C", "num": 4},
{"req": 4096, "alloc": "D"},
{"req": 4096, "alloc": "D"},
{"req": 2000, "slop": 48, "alloc": "D"},
{"req": 15360, "alloc": "F"},
{"req": 512, "alloc": "F"},
{"req": 512, "alloc": "F"},
{"req": 127, "alloc": "F"},
{"req": 1024, "alloc": "F"},
{"req": 127, "alloc": "F"},
{"req": 1000, "slop": 24, "alloc": "F"},
{"req": 127, "alloc": "F"},
{"req": 4096 },
{"req": 8192 },
{"req": 16384 }
],
"traceTable": {
"A": ["AA"],
"B": ["BB"],
"C": ["CC"],
"D": ["DD"],
"E": ["EE"],
"F": ["FF"],
"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)",
"RR1": "#00: R1 (R1.cpp:99)",
"RR2": "#00: R2 (R2.cpp:99)"
}
}

View file

@ -0,0 +1,53 @@
{
"version": 5,
"invocation": {
"dmdEnvVar": "--mode=live --stacks=partial",
"mode": "live"
},
"blockList": [
{"req": 4096, "alloc": "A", "num": 3},
{"req": 4096, "alloc": "A"},
{"req": 8192, "alloc": "B"},
{"req": 8192, "alloc": "B"},
{"req": 4000, "slop": 96, "alloc": "C", "num": 4},
{"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"},
{"req": 512, "alloc": "F", "num": 4},
{"req": 128, "alloc": "F"},
{"req": 63, "alloc": "F"},
{"req": 64, "alloc": "F", "num": 4},
{"req": 63, "alloc": "F"},
{"req": 4096 },
{"req": 4096 },
{"req": 20480 }
],
"traceTable": {
"A": ["AA"],
"B": ["BB"],
"C": ["CC"],
"D": ["DD"],
"E": ["EE"],
"F": ["FF"],
"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)",
"RR1": "#00: R1 (R1.cpp:99)",
"RR2": "#00: R2 (R2.cpp:99)"
}
}

View file

@ -0,0 +1,72 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-ignore-alloc-fns-actual.txt --ignore-alloc-fns script-ignore-alloc-fns.json
Invocation {
$DMD is undefined
Mode = 'dark-matter'
}
#-----------------------------------------------------------------
# no twice-reported heap blocks
#-----------------------------------------------------------------
Unreported {
1 block in heap block record 1 of 4
1,048,576 bytes (1,048,576 requested / 0 slop)
93.22% of the heap (93.22% cumulative)
93.22% of unreported (93.22% cumulative)
Allocated at {
#01: A (A.cpp:99)
}
}
Unreported {
1 block in heap block record 2 of 4
65,536 bytes (65,536 requested / 0 slop)
5.83% of the heap (99.05% cumulative)
5.83% of unreported (99.05% cumulative)
Allocated at {
#01: js::jit::JitRuntime::initialize(JSContext*) (Ion.cpp:301)
}
}
Unreported {
1 block in heap block record 3 of 4
8,192 bytes (8,000 requested / 192 slop)
0.73% of the heap (99.78% cumulative)
0.73% of unreported (99.78% cumulative)
Allocated at {
#01: mozilla::Vector::growStorageBy(unsigned long) (Vector.h:802)
#02: D (D.cpp:99)
}
}
Unreported {
1 block in heap block record 4 of 4
2,500 bytes (2,500 requested / 0 slop)
0.22% of the heap (100.00% cumulative)
0.22% of unreported (100.00% cumulative)
Allocated at {
#01: g_type_create_instance (/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0)
#02: not_an_alloc_function_so_alloc_functions_below_here_will_not_be_stripped (blah)
#03: replace_posix_memalign (replace_malloc.h:120)
#04: ??? (/lib/x86_64-linux-gnu/libglib-2.0.so.0)
#05: another_non_alloc_function (blah)
}
}
#-----------------------------------------------------------------
# no once-reported heap blocks
#-----------------------------------------------------------------
Summary {
Total: 1,124,804 bytes (100.00%) in 4 blocks (100.00%)
Unreported: 1,124,804 bytes (100.00%) in 4 blocks (100.00%)
Once-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
Twice-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
}

View file

@ -0,0 +1,46 @@
{
"version": 5,
"invocation": {
"dmdEnvVar": null,
"mode": "dark-matter"
},
"blockList": [
{"req": 1048576, "alloc": "A"},
{"req": 65536, "alloc": "B"},
{"req": 8000, "slop": 192, "alloc": "C"},
{"req": 2500, "alloc": "D"}
],
"traceTable": {
"A": ["AA", "AB", "AC", "AD"],
"B": ["BA", "BB", "BC"],
"C": ["CA", "CB", "CC", "CD"],
"D": ["DA", "DB", "DD", "DD", "DE", "DF", "DG", "DH", "DI", "DJ"]
},
"frameTable": {
"AA": "#00: replace_malloc (DMD.cpp:1106)",
"AB": "#00: moz_xmalloc (mozalloc.cpp:68)",
"AC": "#00: operator new(unsigned long) (mozalloc.h:208)",
"AD": "#00: A (A.cpp:99)",
"BA": "#00: replace_calloc (DMD.cpp:1125)",
"BB": "#00: js_calloc(unsigned long) (Utility.h:107)",
"BC": "#06: js::jit::JitRuntime::initialize(JSContext*) (Ion.cpp:301)",
"CA": "#00: replace_realloc (DMD.cpp:1153)",
"CB": "#00: bool* mozilla::MallocAllocPolicy::pod_realloc<bool>(bool*, unsigned long, unsigned long) (AllocPolicy.h:74)",
"CC": "#00: mozilla::Vector::growStorageBy(unsigned long) (Vector.h:802)",
"CD": "#00: D (D.cpp:99)",
"DA": "#00: replace_memalign (DMD.cpp:1181)",
"DB": "#00: replace_posix_memalign (replace_malloc.h:120)",
"DC": "#00: ??? (/lib/x86_64-linux-gnu/libglib-2.0.so.0)",
"DD": "#00: g_slice_alloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0)",
"DE": "#00: g_slice_alloc0 (/lib/x86_64-linux-gnu/libglib-2.0.so.0)",
"DF": "#00: g_type_create_instance (/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0)",
"DG": "#00: not_an_alloc_function_so_alloc_functions_below_here_will_not_be_stripped (blah)",
"DH": "#00: replace_posix_memalign (replace_malloc.h:120)",
"DI": "#00: ??? (/lib/x86_64-linux-gnu/libglib-2.0.so.0)",
"DJ": "#00: another_non_alloc_function (blah)"
}
}

View file

@ -0,0 +1,26 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-max-frames-1-actual.txt --max-frames=1 script-max-frames.json
Invocation {
$DMD = '--mode=live --stacks=full'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
4 blocks in heap block record 1 of 1
4,416 bytes (4,404 requested / 12 slop)
Individual block sizes: 4,096; 128; 112; 80
100.00% of the heap (100.00% cumulative)
Allocated at {
#01: E (E.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 4,416 bytes in 4 blocks
}

View file

@ -0,0 +1,48 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-max-frames-3-actual.txt --max-frames=3 --no-fix-stacks script-max-frames.json
Invocation {
$DMD = '--mode=live --stacks=full'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
2 blocks in heap block record 1 of 3
4,224 bytes (4,224 requested / 0 slop)
Individual block sizes: 4,096; 128
95.65% of the heap (95.65% cumulative)
Allocated at {
#01: E (E.cpp:99)
#02: F (F.cpp:99)
#03: G (G.cpp:99)
}
}
Live {
1 block in heap block record 2 of 3
112 bytes (100 requested / 12 slop)
2.54% of the heap (98.19% cumulative)
Allocated at {
#01: E (E.cpp:99)
#02: X (X.cpp:99)
#03: Y (Y.cpp:99)
}
}
Live {
1 block in heap block record 3 of 3
80 bytes (80 requested / 0 slop)
1.81% of the heap (100.00% cumulative)
Allocated at {
#01: E (E.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 4,416 bytes in 4 blocks
}

View file

@ -0,0 +1,69 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt --max-frames=8 script-max-frames.json
Invocation {
$DMD = '--mode=live --stacks=full'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
1 block in heap block record 1 of 4
4,096 bytes (4,096 requested / 0 slop)
92.75% of the heap (92.75% cumulative)
Allocated at {
#01: E (E.cpp:99)
#02: F (F.cpp:99)
#03: G (G.cpp:99)
#04: H (H.cpp:99)
#05: I (I.cpp:99)
#06: J (J.cpp:99)
#07: K (K.cpp:99)
#08: L (L.cpp:99)
}
}
Live {
1 block in heap block record 2 of 4
128 bytes (128 requested / 0 slop)
2.90% of the heap (95.65% cumulative)
Allocated at {
#01: E (E.cpp:99)
#02: F (F.cpp:99)
#03: G (G.cpp:99)
#04: R (R.cpp:99)
#05: S (S.cpp:99)
#06: T (T.cpp:99)
#07: U (U.cpp:99)
#08: V (V.cpp:99)
}
}
Live {
1 block in heap block record 3 of 4
112 bytes (100 requested / 12 slop)
2.54% of the heap (98.19% cumulative)
Allocated at {
#01: E (E.cpp:99)
#02: X (X.cpp:99)
#03: Y (Y.cpp:99)
#04: Z (Z.cpp:99)
}
}
Live {
1 block in heap block record 4 of 4
80 bytes (80 requested / 0 slop)
1.81% of the heap (100.00% cumulative)
Allocated at {
#01: E (E.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 4,416 bytes in 4 blocks
}

View file

@ -0,0 +1,43 @@
{
"version": 5,
"invocation": {
"dmdEnvVar": "--mode=live --stacks=full",
"mode": "live"
},
"blockList": [
{"req": 4096, "alloc": "A"},
{"req": 128, "alloc": "B"},
{"req": 100, "slop":12, "alloc": "C"},
{"req": 80, "alloc": "D"}
],
"traceTable": {
"A": ["E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"],
"B": ["E", "F", "G", "R", "S", "T", "U", "V"],
"C": ["E", "X", "Y", "Z"],
"D": ["E"]
},
"frameTable": {
"E": "#00: E (E.cpp:99)",
"F": "#00: F (F.cpp:99)",
"G": "#00: G (G.cpp:99)",
"H": "#00: H (H.cpp:99)",
"I": "#00: I (I.cpp:99)",
"J": "#00: J (J.cpp:99)",
"K": "#00: K (K.cpp:99)",
"L": "#00: L (L.cpp:99)",
"M": "#00: M (M.cpp:99)",
"N": "#00: N (N.cpp:99)",
"O": "#00: O (O.cpp:99)",
"P": "#00: P (P.cpp:99)",
"Q": "#00: Q (Q.cpp:99)",
"R": "#00: R (R.cpp:99)",
"S": "#00: S (S.cpp:99)",
"T": "#00: T (T.cpp:99)",
"U": "#00: U (U.cpp:99)",
"V": "#00: V (V.cpp:99)",
"W": "#00: W (W.cpp:99)",
"X": "#00: X (X.cpp:99)",
"Y": "#00: Y (Y.cpp:99)",
"Z": "#00: Z (Z.cpp:99)"
}
}

View file

@ -0,0 +1,46 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-sort-by-num-blocks-actual.txt --sort-by=num-blocks script-sort-by.json.gz
Invocation {
$DMD = '--mode=live'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
8 blocks in heap block record 1 of 3
16,384 bytes (8,200 requested / 8,184 slop)
Individual block sizes: 2,048 x 8
33.32% of the heap (33.32% cumulative)
Allocated at {
#01: C (C.cpp:99)
}
}
Live {
5 blocks in heap block record 2 of 3
16,400 bytes (12,016 requested / 4,384 slop)
Individual block sizes: 4,096 x 4; 16
33.35% of the heap (66.67% cumulative)
Allocated at {
#01: B (B.cpp:99)
}
}
Live {
5 blocks in heap block record 3 of 3
16,392 bytes (16,392 requested / 0 slop)
Individual block sizes: 4,096 x 4; 8
33.33% of the heap (100.00% cumulative)
Allocated at {
#01: A (A.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 49,176 bytes in 18 blocks
}

View file

@ -0,0 +1,46 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-sort-by-req-actual.txt --sort-by=req --no-fix-stacks script-sort-by.json.gz
Invocation {
$DMD = '--mode=live'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
5 blocks in heap block record 1 of 3
16,392 bytes (16,392 requested / 0 slop)
Individual block sizes: 4,096 x 4; 8
33.33% of the heap (33.33% cumulative)
Allocated at {
#01: A (A.cpp:99)
}
}
Live {
5 blocks in heap block record 2 of 3
16,400 bytes (12,016 requested / 4,384 slop)
Individual block sizes: 4,096 x 4; 16
33.35% of the heap (66.68% cumulative)
Allocated at {
#01: B (B.cpp:99)
}
}
Live {
8 blocks in heap block record 3 of 3
16,384 bytes (8,200 requested / 8,184 slop)
Individual block sizes: 2,048 x 8
33.32% of the heap (100.00% cumulative)
Allocated at {
#01: C (C.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 49,176 bytes in 18 blocks
}

View file

@ -0,0 +1,46 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-sort-by-slop-actual.txt --sort-by=slop script-sort-by.json.gz
Invocation {
$DMD = '--mode=live'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
8 blocks in heap block record 1 of 3
16,384 bytes (8,200 requested / 8,184 slop)
Individual block sizes: 2,048 x 8
33.32% of the heap (33.32% cumulative)
Allocated at {
#01: C (C.cpp:99)
}
}
Live {
5 blocks in heap block record 2 of 3
16,400 bytes (12,016 requested / 4,384 slop)
Individual block sizes: 4,096 x 4; 16
33.35% of the heap (66.67% cumulative)
Allocated at {
#01: B (B.cpp:99)
}
}
Live {
5 blocks in heap block record 3 of 3
16,392 bytes (16,392 requested / 0 slop)
Individual block sizes: 4,096 x 4; 8
33.33% of the heap (100.00% cumulative)
Allocated at {
#01: A (A.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 49,176 bytes in 18 blocks
}

View file

@ -0,0 +1,46 @@
#-----------------------------------------------------------------
# dmd.py --filter-stacks-for-testing -o script-sort-by-usable-actual.txt --sort-by=usable script-sort-by.json.gz
Invocation {
$DMD = '--mode=live'
Mode = 'live'
}
#-----------------------------------------------------------------
Live {
5 blocks in heap block record 1 of 3
16,400 bytes (12,016 requested / 4,384 slop)
Individual block sizes: 4,096 x 4; 16
33.35% of the heap (33.35% cumulative)
Allocated at {
#01: B (B.cpp:99)
}
}
Live {
5 blocks in heap block record 2 of 3
16,392 bytes (16,392 requested / 0 slop)
Individual block sizes: 4,096 x 4; 8
33.33% of the heap (66.68% cumulative)
Allocated at {
#01: A (A.cpp:99)
}
}
Live {
8 blocks in heap block record 3 of 3
16,384 bytes (8,200 requested / 8,184 slop)
Individual block sizes: 2,048 x 8
33.32% of the heap (100.00% cumulative)
Allocated at {
#01: C (C.cpp:99)
}
}
#-----------------------------------------------------------------
Summary {
Total: 49,176 bytes in 18 blocks
}

Binary file not shown.

View file

@ -0,0 +1,226 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
"use strict";
var {classes: Cc, interfaces: Ci, utils: Cu} = Components
Cu.import("resource://gre/modules/FileUtils.jsm");
// The xpcshell test harness sets PYTHON so we can read it here.
var gEnv = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
var gPythonName = gEnv.get("PYTHON");
// If we're testing locally, the executable file is in "CurProcD". Otherwise,
// it is in another location that we have to find.
function getExecutable(aFilename) {
let file = FileUtils.getFile("CurProcD", [aFilename]);
if (!file.exists()) {
file = FileUtils.getFile("CurWorkD", []);
while (file.path.includes("xpcshell")) {
file = file.parent;
}
file.append("bin");
file.append(aFilename);
}
return file;
}
var gIsWindows = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULRuntime).OS === "WINNT";
var gDmdTestFile = getExecutable("SmokeDMD" + (gIsWindows ? ".exe" : ""));
var gDmdScriptFile = getExecutable("dmd.py");
var gScanTestFile = FileUtils.getFile("CurWorkD", ["scan-test.py"]);
function readFile(aFile) {
let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Ci.nsIConverterInputStream);
fstream.init(aFile, -1, 0, 0);
cstream.init(fstream, "UTF-8", 0, 0);
let data = "";
let str = {};
let read = 0;
do {
// Read as much as we can and put it in str.value.
read = cstream.readString(0xffffffff, str);
data += str.value;
} while (read != 0);
cstream.close(); // this closes fstream
return data.replace(/\r/g, ""); // normalize line endings
}
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) {
// 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);
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.
let success;
try {
let rv = runProcess(new FileUtils.File("/usr/bin/diff"),
["-u", expectedFile.path, actualFile.path]);
success = rv == 0;
} catch (e) {
let expectedData = readFile(expectedFile);
let actualData = readFile(actualFile);
success = expectedData === actualData;
if (!success) {
expectedData = expectedData.split("\n");
actualData = actualData.split("\n");
for (let i = 0; i < expectedData.length; i++) {
print("EXPECTED:" + expectedData[i]);
}
for (let i = 0; i < actualData.length; i++) {
print(" ACTUAL:" + actualData[i]);
}
}
}
ok(success, aPrefix);
actualFile.remove(true);
}
// Run scan-test.py on the JSON file and see if it succeeds.
function scanTest(aJsonFilePath, aExtraArgs) {
let args = [
gScanTestFile.path,
aJsonFilePath,
].concat(aExtraArgs);
return runProcess(new FileUtils.File(gPythonName), args) == 0;
}
function run_test() {
let jsonFile, jsonFile2;
// These tests do complete 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 complete*.json files
// in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
// asynchronously.
gEnv.set(gEnv.get("DMD_PRELOAD_VAR"), gEnv.get("DMD_PRELOAD_VALUE"));
runProcess(gDmdTestFile, []);
function test2(aTestName, aMode) {
let name = "complete-" + aTestName + "-" + aMode;
jsonFile = FileUtils.getFile("CurWorkD", [name + ".json"]);
test(name, [jsonFile.path]);
jsonFile.remove(true);
}
// Please keep this in sync with RunTests() in SmokeDMD.cpp.
test2("empty", "live");
test2("empty", "dark-matter");
test2("empty", "cumulative");
test2("full1", "live");
test2("full1", "dark-matter");
test2("full2", "dark-matter");
test2("full2", "cumulative");
test2("partial", "live");
// Heap scan testing.
jsonFile = FileUtils.getFile("CurWorkD", ["basic-scan.json"]);
ok(scanTest(jsonFile.path), "Basic scan test");
let is64Bit = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULRuntime).is64Bit;
let basicScanFileName = "basic-scan-" + (is64Bit ? "64" : "32");
test(basicScanFileName, ["--clamp-contents", jsonFile.path]);
ok(scanTest(jsonFile.path, ["--clamp-contents"]), "Scan with address clamping");
// Run the generic test a second time to ensure that the first time produced
// valid JSON output. "--clamp-contents" is passed in so we don't have to have
// more variants of the files.
test(basicScanFileName, ["--clamp-contents", jsonFile.path]);
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",
["--max-frames=8", jsonFile.path]);
test("script-max-frames-3",
["--max-frames=3", "--no-fix-stacks", jsonFile.path]);
test("script-max-frames-1",
["--max-frames=1", jsonFile.path]);
// 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("script-sort-by-usable",
["--sort-by=usable", jsonFile.path]);
test("script-sort-by-req",
["--sort-by=req", "--no-fix-stacks", jsonFile.path]);
test("script-sort-by-slop",
["--sort-by=slop", jsonFile.path]);
test("script-sort-by-num-blocks",
["--sort-by=num-blocks", jsonFile.path]);
// 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("script-ignore-alloc-fns",
["--ignore-alloc-fns", jsonFile.path]);
// This tests "live"-mode diffs.
jsonFile = FileUtils.getFile("CurWorkD", ["script-diff-live1.json"]);
jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff-live2.json"]);
test("script-diff-live",
[jsonFile.path, jsonFile2.path]);
// This tests "dark-matter"-mode diffs.
jsonFile = FileUtils.getFile("CurWorkD", ["script-diff-dark-matter1.json"]);
jsonFile2 = FileUtils.getFile("CurWorkD", ["script-diff-dark-matter2.json"]);
test("script-diff-dark-matter",
[jsonFile.path, jsonFile2.path]);
}

View file

@ -0,0 +1,35 @@
[DEFAULT]
support-files =
basic-scan-32-expected.txt
basic-scan-64-expected.txt
complete-empty-live-expected.txt
complete-empty-dark-matter-expected.txt
complete-empty-cumulative-expected.txt
complete-full1-live-expected.txt
complete-full1-dark-matter-expected.txt
complete-full2-dark-matter-expected.txt
complete-full2-cumulative-expected.txt
complete-partial-live-expected.txt
scan-test.py
script-max-frames.json
script-max-frames-8-expected.txt
script-max-frames-3-expected.txt
script-max-frames-1-expected.txt
script-sort-by.json.gz
script-sort-by-usable-expected.txt
script-sort-by-req-expected.txt
script-sort-by-slop-expected.txt
script-sort-by-num-blocks-expected.txt
script-ignore-alloc-fns.json
script-ignore-alloc-fns-expected.txt
script-diff-live1.json
script-diff-live2.json
script-diff-live-expected.txt
script-diff-dark-matter1.json
script-diff-dark-matter2.json
script-diff-dark-matter-expected.txt
# Bug 1077230 explains why this test is disabled on Mac 10.6.
[test_dmd.js]
dmd = true
skip-if = !(os=='linux' || os=='win' || (os=='mac' && os_version!='10.6'))

View file

@ -0,0 +1,15 @@
/* 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/. */
#include "mozilla/Types.h"
/*
* Dummy functions for linking purpose on OSX with older XCode.
* See details in configure.in, under "Replace-malloc Mac linkage quirks"
*/
#define MALLOC_FUNCS MALLOC_FUNCS_ALL
#define MALLOC_DECL(name, ...) \
MOZ_EXPORT void replace_ ## name() { }
#include "malloc_decls.h"

View file

@ -0,0 +1,14 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
DIST_INSTALL = False
SOURCES += [
'dummy_replace_malloc.c',
]
SharedLibrary('dummy_replace_malloc')
DISABLE_STL_WRAPPING = True

View file

@ -0,0 +1,31 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
SOURCES += [
'../../build/jemalloc_config.cpp',
'../../build/mozjemalloc_compat.c',
]
# Android doesn't have pthread_atfork, so just implement a dummy function.
# It shouldn't make much problem, as the use of fork is pretty limited on
# Android.
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
SOURCES += [
'pthread_atfork.c',
]
SharedLibrary('replace_jemalloc')
DEFINES['MOZ_JEMALLOC4'] = True
DEFINES['MOZ_REPLACE_JEMALLOC'] = True
LOCAL_INCLUDES += ['!../../jemalloc/src/include']
if CONFIG['_MSC_VER']:
LOCAL_INCLUDES += ['/memory/jemalloc/src/include/msvc_compat']
if not CONFIG['HAVE_INTTYPES_H']:
LOCAL_INCLUDES += ['/memory/jemalloc/src/include/msvc_compat/C99']
DISABLE_STL_WRAPPING = True

View file

@ -0,0 +1,10 @@
/* 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/. */
int pthread_atfork(void (*prefork)(void),
void (*postfork_parent)(void),
void (*postfork_child)(void))
{
return 0;
}

View file

@ -0,0 +1,132 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include <cstdarg>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <cstring>
#include "mozilla/Assertions.h"
#include "mozilla/Unused.h"
/* Template class allowing a limited number of increments on a value */
template <typename T>
class CheckedIncrement
{
public:
CheckedIncrement(T aValue, size_t aMaxIncrement)
: mValue(aValue), mMaxIncrement(aMaxIncrement)
{}
T operator ++(int)
{
if (!mMaxIncrement) {
MOZ_CRASH("overflow detected");
}
mMaxIncrement--;
return mValue++;
}
T& operator ++()
{
(*this)++;
return mValue;
}
operator T() { return mValue; }
private:
T mValue;
size_t mMaxIncrement;
};
void
FdPrintf(intptr_t aFd, const char* aFormat, ...)
{
if (aFd == 0) {
return;
}
char buf[256];
CheckedIncrement<char*> b(buf, sizeof(buf));
CheckedIncrement<const char*> f(aFormat, strlen(aFormat) + 1);
va_list ap;
va_start(ap, aFormat);
while (true) {
switch (*f) {
case '\0':
goto out;
case '%':
switch (*++f) {
case 'z': {
if (*(++f) == 'u') {
size_t i = va_arg(ap, size_t);
size_t x = 1;
// Compute the number of digits.
while (x <= i / 10) {
x *= 10;
}
// Write the digits into the buffer.
do {
*(b++) = "0123456789"[(i / x) % 10];
x /= 10;
} while (x > 0);
} else {
// Write out the format specifier if it's unknown.
*(b++) = '%';
*(b++) = 'z';
*(b++) = *f;
}
break;
}
case 'p': {
intptr_t ptr = va_arg(ap, intptr_t);
*(b++) = '0';
*(b++) = 'x';
int x = sizeof(intptr_t) * 8;
bool wrote_msb = false;
do {
x -= 4;
size_t hex_digit = ptr >> x & 0xf;
if (hex_digit || wrote_msb) {
*(b++) = "0123456789abcdef"[hex_digit];
wrote_msb = true;
}
} while (x > 0);
if (!wrote_msb) {
*(b++) = '0';
}
break;
}
default:
// Write out the format specifier if it's unknown.
*(b++) = '%';
*(b++) = *f;
break;
}
break;
default:
*(b++) = *f;
break;
}
f++;
}
out:
#ifdef _WIN32
// See comment in FdPrintf.h as to why WriteFile is used.
DWORD written;
WriteFile(reinterpret_cast<HANDLE>(aFd), buf, b - buf, &written, nullptr);
#else
MOZ_UNUSED(write(aFd, buf, b - buf));
#endif
va_end(ap);
}

View file

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef __FdPrintf_h__
#define __FdPrintf_h__
/* We can't use libc's (f)printf because it would reenter in replace_malloc,
* So use a custom and simplified version.
* Only %p and %z are supported.
* /!\ This function used a fixed-size internal buffer. The caller is
* expected to not use a format string that may overflow.
* The aFd argument is a file descriptor on UNIX and a native win32 file
* handle on Windows (from CreateFile). We can't use the windows POSIX
* APIs is that they don't support O_APPEND in a multi-process-safe way,
* while CreateFile does.
*/
extern void FdPrintf(intptr_t aFd, const char* aFormat, ...)
#ifdef __GNUC__
__attribute__((format(printf, 2, 3)))
#endif
;
#endif /* __FdPrintf_h__ */

View file

@ -0,0 +1,276 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include <cstdlib>
#include <cstdio>
#include <fcntl.h>
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#include <process.h>
#else
#include <unistd.h>
#include <pthread.h>
#endif
#include "replace_malloc.h"
#include "FdPrintf.h"
#include "base/lock.h"
static const malloc_table_t* sFuncs = nullptr;
static intptr_t sFd = 0;
static bool sStdoutOrStderr = false;
static Lock sLock;
static void
prefork() {
sLock.Acquire();
}
static void
postfork() {
sLock.Release();
}
static size_t
GetPid()
{
return size_t(getpid());
}
static size_t
GetTid()
{
#if defined(_WIN32)
return size_t(GetCurrentThreadId());
#else
return size_t(pthread_self());
#endif
}
#ifdef ANDROID
/* See mozglue/android/APKOpen.cpp */
extern "C" MOZ_EXPORT __attribute__((weak))
void* __dso_handle;
/* Android doesn't have pthread_atfork defined in pthread.h */
extern "C" MOZ_EXPORT
int pthread_atfork(void (*)(void), void (*)(void), void (*)(void));
#endif
class LogAllocBridge : public ReplaceMallocBridge
{
virtual void InitDebugFd(mozilla::DebugFdRegistry& aRegistry) override {
if (!sStdoutOrStderr) {
aRegistry.RegisterHandle(sFd);
}
}
};
void
replace_init(const malloc_table_t* aTable)
{
sFuncs = aTable;
#ifndef _WIN32
/* When another thread has acquired a lock before forking, the child
* process will inherit the lock state but the thread, being nonexistent
* in the child process, will never release it, leading to a dead-lock
* whenever the child process gets the lock. We thus need to ensure no
* other thread is holding the lock before forking, by acquiring it
* ourselves, and releasing it after forking, both in the parent and child
* processes.
* Windows doesn't have this problem since there is no fork().
* The real allocator, however, might be doing the same thing (jemalloc
* does). But pthread_atfork `prepare` handlers (first argument) are
* processed in reverse order they were established. But replace_init
* runs before the real allocator has had any chance to initialize and
* call pthread_atfork itself. This leads to its prefork running before
* ours. This leads to a race condition that can lead to a deadlock like
* the following:
* - thread A forks.
* - libc calls real allocator's prefork, so thread A holds the real
* allocator lock.
* - thread B calls malloc, which calls our replace_malloc.
* - consequently, thread B holds our lock.
* - thread B then proceeds to call the real allocator's malloc, and
* waits for the real allocator's lock, which thread A holds.
* - libc calls our prefork, so thread A waits for our lock, which
* thread B holds.
* To avoid this race condition, the real allocator's prefork must be
* called after ours, which means it needs to be registered before ours.
* So trick the real allocator into initializing itself without more side
* effects by calling malloc with a size it can't possibly allocate. */
sFuncs->malloc(-1);
pthread_atfork(prefork, postfork, postfork);
#endif
/* Initialize output file descriptor from the MALLOC_LOG environment
* variable. Numbers up to 9999 are considered as a preopened file
* descriptor number. Other values are considered as a file name. */
char* log = getenv("MALLOC_LOG");
if (log && *log) {
int fd = 0;
const char *fd_num = log;
while (*fd_num) {
/* Reject non digits. */
if (*fd_num < '0' || *fd_num > '9') {
fd = -1;
break;
}
fd = fd * 10 + (*fd_num - '0');
/* Reject values >= 10000. */
if (fd >= 10000) {
fd = -1;
break;
}
fd_num++;
}
if (fd == 1 || fd == 2) {
sStdoutOrStderr = true;
}
#ifdef _WIN32
// See comment in FdPrintf.h as to why CreateFile is used.
HANDLE handle;
if (fd > 0) {
handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
} else {
handle = CreateFileA(log, FILE_APPEND_DATA, FILE_SHARE_READ |
FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
}
if (handle != INVALID_HANDLE_VALUE) {
sFd = reinterpret_cast<intptr_t>(handle);
}
#else
if (fd == -1) {
fd = open(log, O_WRONLY | O_CREAT | O_APPEND, 0644);
}
if (fd > 0) {
sFd = fd;
}
#endif
}
}
ReplaceMallocBridge*
replace_get_bridge()
{
static LogAllocBridge bridge;
return &bridge;
}
/* Do a simple, text-form, log of all calls to replace-malloc functions.
* Use locking to guarantee that an allocation that did happen is logged
* before any other allocation/free happens.
* TODO: Add a thread id to the log: different allocators, or even different
* configurations of jemalloc behave differently when allocations are coming
* from different threads. Reproducing those multi-threaded workloads would be
* useful to test those differences.
*/
void*
replace_malloc(size_t aSize)
{
AutoLock lock(sLock);
void* ptr = sFuncs->malloc(aSize);
if (ptr) {
FdPrintf(sFd, "%zu %zu malloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
}
return ptr;
}
int
replace_posix_memalign(void** aPtr, size_t aAlignment, size_t aSize)
{
AutoLock lock(sLock);
int ret = sFuncs->posix_memalign(aPtr, aAlignment, aSize);
if (ret == 0) {
FdPrintf(sFd, "%zu %zu posix_memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
aAlignment, aSize, *aPtr);
}
return ret;
}
void*
replace_aligned_alloc(size_t aAlignment, size_t aSize)
{
AutoLock lock(sLock);
void* ptr = sFuncs->aligned_alloc(aAlignment, aSize);
if (ptr) {
FdPrintf(sFd, "%zu %zu aligned_alloc(%zu,%zu)=%p\n", GetPid(), GetTid(),
aAlignment, aSize, ptr);
}
return ptr;
}
void*
replace_calloc(size_t aNum, size_t aSize)
{
AutoLock lock(sLock);
void* ptr = sFuncs->calloc(aNum, aSize);
if (ptr) {
FdPrintf(sFd, "%zu %zu calloc(%zu,%zu)=%p\n", GetPid(), GetTid(), aNum,
aSize, ptr);
}
return ptr;
}
void*
replace_realloc(void* aPtr, size_t aSize)
{
AutoLock lock(sLock);
void* new_ptr = sFuncs->realloc(aPtr, aSize);
if (new_ptr || !aSize) {
FdPrintf(sFd, "%zu %zu realloc(%p,%zu)=%p\n", GetPid(), GetTid(), aPtr,
aSize, new_ptr);
}
return new_ptr;
}
void
replace_free(void* aPtr)
{
AutoLock lock(sLock);
if (aPtr) {
FdPrintf(sFd, "%zu %zu free(%p)\n", GetPid(), GetTid(), aPtr);
}
sFuncs->free(aPtr);
}
void*
replace_memalign(size_t aAlignment, size_t aSize)
{
AutoLock lock(sLock);
void* ptr = sFuncs->memalign(aAlignment, aSize);
if (ptr) {
FdPrintf(sFd, "%zu %zu memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
aAlignment, aSize, ptr);
}
return ptr;
}
void*
replace_valloc(size_t aSize)
{
AutoLock lock(sLock);
void* ptr = sFuncs->valloc(aSize);
if (ptr) {
FdPrintf(sFd, "%zu %zu valloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
}
return ptr;
}
void
replace_jemalloc_stats(jemalloc_stats_t* aStats)
{
AutoLock lock(sLock);
sFuncs->jemalloc_stats(aStats);
FdPrintf(sFd, "%zu %zu jemalloc_stats()\n", GetPid(), GetTid());
}

View file

@ -0,0 +1,107 @@
Logalloc is a replace-malloc library for Firefox (see
memory/build/replace_malloc.h) that dumps a log of memory allocations to a
given file descriptor or file name. That log can then be replayed against
Firefox's default memory allocator independently or through another
replace-malloc library, allowing the testing of other allocators under the
exact same workload.
To get an allocation log the following environment variables need to be set
when starting Firefox:
- on Linux:
LD_PRELOAD=/path/to/liblogalloc.so
- on Mac OSX:
DYLD_INSERT_LIBRARIES=/path/to/liblogalloc.dylib
- on Windows:
MOZ_REPLACE_MALLOC_LIB=/path/to/logalloc.dll
- on Android:
MOZ_REPLACE_MALLOC_LIB=/path/to/liblogalloc.so
(see https://wiki.mozilla.org/Mobile/Fennec/Android#Arguments_and_Environment_Variables
for how to pass environment variables to Firefox for Android)
- on all platforms:
MALLOC_LOG=/path/to/log-file
or
MALLOC_LOG=number
When MALLOC_LOG is a number below 10000, it is considered as a file
descriptor number that is fed to Firefox when it is started. Otherwise,
it is considered as a file name.
As those allocation logs can grow large quite quickly, it can be useful
to pipe the output to a compression tool.
MALLOC_LOG=1 would send to Firefox's stdout, MALLOC_LOG=2 would send to
its stderr. Since in both cases that could be mixed with other output
from Firefox, it is usually better to use another file descriptor
by shell redirections, such as:
MALLOC_LOG=3 firefox 3>&1 1>&2 | gzip -c > log.gz
(3>&1 copies the `| gzip` pipe file descriptor to file descriptor #3, 1>&2
then copies stderr to stdout. This leads to: fd1 and fd2 sending to stderr
of the parent process (the shell), and fd3 sending to gzip.)
Each line of the allocations log is formatted as follows:
<pid> <tid> <function>([<args>])[=<result>]
where <args> is a comma separated list of values. The number of <args> and
the presence of <result> depend on the <function>.
Example log:
18545 18545 malloc(32)=0x7f90495120e0
18545 18545 calloc(1,148)=0x7f9049537480
18545 18545 realloc(0x7f90495120e0,64)=0x7f9049536680
18545 18545 posix_memalign(256,240)=0x7f9049583300
18545 18545 jemalloc_stats()
18545 18545 free(0x7f9049536680)
This log can be replayed with the logalloc-replay tool in
memory/replace/logalloc/replay. However, as the goal of that tool is to
reproduce the recorded memory allocations, it needs to avoid as much as
possible doing its own allocations for bookkeeping. Reading the logs as
they are would require data structures and memory allocations. As a
consequence, the logs need to be preprocessed beforehand.
The logalloc_munge.py script is responsible for that preprocessing. It simply
takes a raw log on its stdin, and outputs the preprocessed log on its stdout.
It replaces pointer addresses with indexes the logalloc-replay tool can use
in a large (almost) linear array of allocation tracking slots (prefixed with
'#'). It also replaces the pids with numbers starting from 1 (such as the
first seen pid number is 1, the second is 2, etc.).
The above example log would become the following, once preprocessed:
1 1 malloc(32)=#1
1 1 calloc(1,148)=#2
1 1 realloc(#1,64)=#1
1 1 posix_memalign(256,240)=#3
1 1 jemalloc_stats()
1 1 free(#1)
The logalloc-replay tool then takes the preprocessed log on its stdin and
replays the allocations printed there, but will only replay those with the
same process id as the first line (which normally is 1).
As the log files are simple text files, though, it is easy to separate out
the different processes log with e.g. grep, and feed the separate processes
logs to logalloc-replay.
The logalloc-replay program won't output anything unless jemalloc_stats
records appears in the log. You can expect those to be recorded when going
to about:memory in Firefox, but they can also be added after preprocessing.
Here is an example of what one can do:
gunzip -c log.gz | python logalloc_munge.py | \
awk '$1 == "2" { print $0 } !(NR % 10000) { print "2 1 jemalloc_stats()" }' | \
./logalloc-replay
The above command replays the allocations of process #2, with some stats
output every 10000 records.
The logalloc-replay tool itself being hooked with replace-malloc, it is possible
to set LD_PRELOAD/DYLD_INSERT_LIBRARIES/MOZ_REPLACE_MALLOC_LIB and replay a log
through a different allocator. For example:
LD_PRELOAD=libreplace_jemalloc.so logalloc-replay < log
Will replay the log against jemalloc4 (which is, as of writing, what
libreplace_jemalloc.so contains).

View file

@ -0,0 +1,41 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
SharedLibrary('logalloc')
SOURCES += [
'FdPrintf.cpp',
'LogAlloc.cpp',
]
DISABLE_STL_WRAPPING = True
USE_STATIC_LIBS = True
DEFINES['MOZ_NO_MOZALLOC'] = True
# Avoid Lock_impl code depending on mozilla::Logger.
DEFINES['NDEBUG'] = True
DEFINES['DEBUG'] = False
# Use locking code from the chromium stack.
if CONFIG['OS_TARGET'] == 'WINNT':
SOURCES += [
'../../../ipc/chromium/src/base/lock_impl_win.cc',
]
else:
SOURCES += [
'../../../ipc/chromium/src/base/lock_impl_posix.cc',
]
include('/ipc/chromium/chromium-config.mozbuild')
# Android doesn't have pthread_atfork, but we have our own in mozglue.
if CONFIG['OS_TARGET'] == 'Android':
USE_LIBS += [
'mozglue',
]
DIRS += [
'replay',
]

View file

@ -0,0 +1,32 @@
# 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/.
include $(topsrcdir)/mozglue/build/replace_malloc.mk
ifndef CROSS_COMPILE
ifeq ($(OS_TARGET),WINNT)
LOGALLOC = MOZ_REPLACE_MALLOC_LIB=$(CURDIR)/../logalloc.dll
else
ifeq ($(OS_TARGET),Darwin)
LOGALLOC = DYLD_INSERT_LIBRARIES=$(CURDIR)/../liblogalloc.dylib
else
LOGALLOC = LD_PRELOAD=$(CURDIR)/../$(DLL_PREFIX)logalloc$(DLL_SUFFIX)
endif
endif
expected_output.log: $(srcdir)/replay.log
# The logalloc-replay program will only replay entries from the first pid,
# so the expected output only contains entries beginning with "1 "
grep "^1 " $< > $@
check:: $(srcdir)/replay.log expected_output.log
# Test with MALLOC_LOG as a file descriptor number
MALLOC_LOG=1 $(LOGALLOC) ./$(PROGRAM) < $< | $(PYTHON) $(srcdir)/logalloc_munge.py | diff -w - expected_output.log
# Test with MALLOC_LOG as a file name
$(RM) test_output.log
MALLOC_LOG=test_output.log $(LOGALLOC) ./$(PROGRAM) < $<
$(PYTHON) $(srcdir)/logalloc_munge.py < test_output.log | diff -w - expected_output.log
endif

View file

@ -0,0 +1,558 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#define MOZ_MEMORY_IMPL
#include "mozmemory_wrap.h"
#ifdef _WIN32
#include <windows.h>
#include <io.h>
typedef int ssize_t;
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
#include <algorithm>
#include <cstdio>
#include <cstring>
#include "mozilla/Assertions.h"
#include "FdPrintf.h"
static void
die(const char* message)
{
/* Here, it doesn't matter that fprintf may allocate memory. */
fprintf(stderr, "%s\n", message);
exit(1);
}
/* We don't want to be using malloc() to allocate our internal tracking
* data, because that would change the parameters of what is being measured,
* so we want to use data types that directly use mmap/VirtualAlloc. */
template <typename T, size_t Len>
class MappedArray
{
public:
MappedArray(): mPtr(nullptr) {}
~MappedArray()
{
if (mPtr) {
#ifdef _WIN32
VirtualFree(mPtr, sizeof(T) * Len, MEM_RELEASE);
#else
munmap(mPtr, sizeof(T) * Len);
#endif
}
}
T& operator[] (size_t aIndex) const
{
if (mPtr) {
return mPtr[aIndex];
}
#ifdef _WIN32
mPtr = reinterpret_cast<T*>(VirtualAlloc(nullptr, sizeof(T) * Len,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (mPtr == nullptr) {
die("VirtualAlloc error");
}
#else
mPtr = reinterpret_cast<T*>(mmap(nullptr, sizeof(T) * Len,
PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0));
if (mPtr == MAP_FAILED) {
die("Mmap error");
}
#endif
return mPtr[aIndex];
}
private:
mutable T* mPtr;
};
/* Type for records of allocations. */
struct MemSlot
{
void* mPtr;
size_t mSize;
};
/* An almost infinite list of slots.
* In essence, this is a linked list of arrays of groups of slots.
* Each group is 1MB. On 64-bits, one group allows to store 64k allocations.
* Each MemSlotList instance can store 1023 such groups, which means more
* than 65M allocations. In case more would be needed, we chain to another
* MemSlotList, and so on.
* Using 1023 groups makes the MemSlotList itself page sized on 32-bits
* and 2 pages-sized on 64-bits.
*/
class MemSlotList
{
static const size_t kGroups = 1024 - 1;
static const size_t kGroupSize = (1024 * 1024) / sizeof(MemSlot);
MappedArray<MemSlot, kGroupSize> mSlots[kGroups];
MappedArray<MemSlotList, 1> mNext;
public:
MemSlot& operator[] (size_t aIndex) const
{
if (aIndex < kGroupSize * kGroups) {
return mSlots[aIndex / kGroupSize][aIndex % kGroupSize];
}
aIndex -= kGroupSize * kGroups;
return mNext[0][aIndex];
}
};
/* Helper class for memory buffers */
class Buffer
{
public:
Buffer() : mBuf(nullptr), mLength(0) {}
Buffer(const void* aBuf, size_t aLength)
: mBuf(reinterpret_cast<const char*>(aBuf)), mLength(aLength)
{}
/* Constructor for string literals. */
template <size_t Size>
explicit Buffer(const char (&aStr)[Size])
: mBuf(aStr), mLength(Size - 1)
{}
/* Returns a sub-buffer up-to but not including the given aNeedle character.
* The "parent" buffer itself is altered to begin after the aNeedle
* character.
* If the aNeedle character is not found, return the entire buffer, and empty
* the "parent" buffer. */
Buffer SplitChar(char aNeedle)
{
char* buf = const_cast<char*>(mBuf);
char* c = reinterpret_cast<char*>(memchr(buf, aNeedle, mLength));
if (!c) {
return Split(mLength);
}
Buffer result = Split(c - buf);
// Remove the aNeedle character itself.
Split(1);
return result;
}
/* Returns a sub-buffer of at most aLength characters. The "parent" buffer is
* amputated of those aLength characters. If the "parent" buffer is smaller
* than aLength, then its length is used instead. */
Buffer Split(size_t aLength)
{
Buffer result(mBuf, std::min(aLength, mLength));
mLength -= result.mLength;
mBuf += result.mLength;
return result;
}
/* Move the buffer (including its content) to the memory address of the aOther
* buffer. */
void Slide(Buffer aOther)
{
memmove(const_cast<char*>(aOther.mBuf), mBuf, mLength);
mBuf = aOther.mBuf;
}
/* Returns whether the two involved buffers have the same content. */
bool operator ==(Buffer aOther)
{
return mLength == aOther.mLength && (mBuf == aOther.mBuf ||
!strncmp(mBuf, aOther.mBuf, mLength));
}
/* Returns whether the buffer is empty. */
explicit operator bool() { return mLength; }
/* Returns the memory location of the buffer. */
const char* get() { return mBuf; }
/* Returns the memory location of the end of the buffer (technically, the
* first byte after the buffer). */
const char* GetEnd() { return mBuf + mLength; }
/* Extend the buffer over the content of the other buffer, assuming it is
* adjacent. */
void Extend(Buffer aOther)
{
MOZ_ASSERT(aOther.mBuf == GetEnd());
mLength += aOther.mLength;
}
private:
const char* mBuf;
size_t mLength;
};
/* Helper class to read from a file descriptor line by line. */
class FdReader {
public:
explicit FdReader(int aFd)
: mFd(aFd)
, mData(&mRawBuf, 0)
, mBuf(&mRawBuf, sizeof(mRawBuf))
{}
/* Read a line from the file descriptor and returns it as a Buffer instance */
Buffer ReadLine()
{
while (true) {
Buffer result = mData.SplitChar('\n');
/* There are essentially three different cases here:
* - '\n' was found "early". In this case, the end of the result buffer
* is before the beginning of the mData buffer (since SplitChar
* amputated it).
* - '\n' was found as the last character of mData. In this case, mData
* is empty, but still points at the end of mBuf. result points to what
* used to be in mData, without the last character.
* - '\n' was not found. In this case too, mData is empty and points at
* the end of mBuf. But result points to the entire buffer that used to
* be pointed by mData.
* Only in the latter case do both result and mData's end match, and it's
* the only case where we need to refill the buffer.
*/
if (result.GetEnd() != mData.GetEnd()) {
return result;
}
/* Since SplitChar emptied mData, make it point to what it had before. */
mData = result;
/* And move it to the beginning of the read buffer. */
mData.Slide(mBuf);
FillBuffer();
if (!mData) {
return Buffer();
}
}
}
private:
/* Fill the read buffer. */
void FillBuffer()
{
size_t size = mBuf.GetEnd() - mData.GetEnd();
Buffer remainder(mData.GetEnd(), size);
ssize_t len = 1;
while (remainder && len > 0) {
len = ::read(mFd, const_cast<char*>(remainder.get()), size);
if (len < 0) {
die("Read error");
}
size -= len;
mData.Extend(remainder.Split(len));
}
}
/* File descriptor to read from. */
int mFd;
/* Part of data that was read from the file descriptor but not returned with
* ReadLine yet. */
Buffer mData;
/* Buffer representation of mRawBuf */
Buffer mBuf;
/* read() buffer */
char mRawBuf[4096];
};
MOZ_BEGIN_EXTERN_C
/* Function declarations for all the replace_malloc _impl functions.
* See memory/build/replace_malloc.c */
#define MALLOC_DECL(name, return_type, ...) \
return_type name ## _impl(__VA_ARGS__);
#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC
#include "malloc_decls.h"
#define MALLOC_DECL(name, return_type, ...) \
return_type name ## _impl(__VA_ARGS__);
#define MALLOC_FUNCS MALLOC_FUNCS_JEMALLOC
#include "malloc_decls.h"
/* mozjemalloc relies on DllMain to initialize, but DllMain is not invoked
* for executables, so manually invoke mozjemalloc initialization. */
#if defined(_WIN32) && !defined(MOZ_JEMALLOC4)
void malloc_init_hard(void);
#endif
#ifdef ANDROID
/* mozjemalloc uses MozTagAnonymousMemory, which doesn't have an inline
* implementation on Android */
void
MozTagAnonymousMemory(const void* aPtr, size_t aLength, const char* aTag) {}
/* mozjemalloc and jemalloc use pthread_atfork, which Android doesn't have.
* While gecko has one in libmozglue, the replay program can't use that.
* Since we're not going to fork anyways, make it a dummy function. */
int
pthread_atfork(void (*aPrepare)(void), void (*aParent)(void),
void (*aChild)(void))
{
return 0;
}
#endif
MOZ_END_EXTERN_C
size_t parseNumber(Buffer aBuf)
{
if (!aBuf) {
die("Malformed input");
}
size_t result = 0;
for (const char* c = aBuf.get(), *end = aBuf.GetEnd(); c < end; c++) {
if (*c < '0' || *c > '9') {
die("Malformed input");
}
result *= 10;
result += *c - '0';
}
return result;
}
/* Class to handle dispatching the replay function calls to replace-malloc. */
class Replay
{
public:
Replay(): mOps(0) {
#ifdef _WIN32
// See comment in FdPrintf.h as to why native win32 handles are used.
mStdErr = reinterpret_cast<intptr_t>(GetStdHandle(STD_ERROR_HANDLE));
#else
mStdErr = fileno(stderr);
#endif
}
MemSlot& operator[] (size_t index) const
{
return mSlots[index];
}
void malloc(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
size_t size = parseNumber(aArgs);
aSlot.mPtr = ::malloc_impl(size);
aSlot.mSize = size;
Commit(aSlot);
}
void posix_memalign(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
size_t alignment = parseNumber(aArgs.SplitChar(','));
size_t size = parseNumber(aArgs);
void* ptr;
if (::posix_memalign_impl(&ptr, alignment, size) == 0) {
aSlot.mPtr = ptr;
aSlot.mSize = size;
} else {
aSlot.mPtr = nullptr;
aSlot.mSize = 0;
}
Commit(aSlot);
}
void aligned_alloc(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
size_t alignment = parseNumber(aArgs.SplitChar(','));
size_t size = parseNumber(aArgs);
aSlot.mPtr = ::aligned_alloc_impl(alignment, size);
aSlot.mSize = size;
Commit(aSlot);
}
void calloc(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
size_t num = parseNumber(aArgs.SplitChar(','));
size_t size = parseNumber(aArgs);
aSlot.mPtr = ::calloc_impl(num, size);
aSlot.mSize = size * num;
Commit(aSlot);
}
void realloc(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
Buffer dummy = aArgs.SplitChar('#');
if (dummy) {
die("Malformed input");
}
size_t slot_id = parseNumber(aArgs.SplitChar(','));
size_t size = parseNumber(aArgs);
MemSlot& old_slot = (*this)[slot_id];
void* old_ptr = old_slot.mPtr;
old_slot.mPtr = nullptr;
old_slot.mSize = 0;
aSlot.mPtr = ::realloc_impl(old_ptr, size);
aSlot.mSize = size;
Commit(aSlot);
}
void free(Buffer& aArgs)
{
mOps++;
Buffer dummy = aArgs.SplitChar('#');
if (dummy) {
die("Malformed input");
}
size_t slot_id = parseNumber(aArgs);
MemSlot& slot = (*this)[slot_id];
::free_impl(slot.mPtr);
slot.mPtr = nullptr;
slot.mSize = 0;
}
void memalign(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
size_t alignment = parseNumber(aArgs.SplitChar(','));
size_t size = parseNumber(aArgs);
aSlot.mPtr = ::memalign_impl(alignment, size);
aSlot.mSize = size;
Commit(aSlot);
}
void valloc(MemSlot& aSlot, Buffer& aArgs)
{
mOps++;
size_t size = parseNumber(aArgs);
aSlot.mPtr = ::valloc_impl(size);
aSlot.mSize = size;
Commit(aSlot);
}
void jemalloc_stats(Buffer& aArgs)
{
if (aArgs) {
die("Malformed input");
}
jemalloc_stats_t stats;
::jemalloc_stats_impl(&stats);
FdPrintf(mStdErr,
"#%zu mapped: %zu; allocated: %zu; waste: %zu; dirty: %zu; "
"bookkeep: %zu; binunused: %zu\n", mOps, stats.mapped,
stats.allocated, stats.waste, stats.page_cache,
stats.bookkeeping, stats.bin_unused);
/* TODO: Add more data, like actual RSS as measured by OS, but compensated
* for the replay internal data. */
}
private:
void Commit(MemSlot& aSlot)
{
memset(aSlot.mPtr, 0x5a, aSlot.mSize);
}
intptr_t mStdErr;
size_t mOps;
MemSlotList mSlots;
};
int
main()
{
size_t first_pid = 0;
FdReader reader(0);
Replay replay;
#if defined(_WIN32) && !defined(MOZ_JEMALLOC4)
malloc_init_hard();
#endif
/* Read log from stdin and dispatch function calls to the Replay instance.
* The log format is essentially:
* <pid> <function>([<args>])[=<result>]
* <args> is a comma separated list of arguments.
*
* The logs are expected to be preprocessed so that allocations are
* attributed a tracking slot. The input is trusted not to have crazy
* values for these slot numbers.
*
* <result>, as well as some of the args to some of the function calls are
* such slot numbers.
*/
while (true) {
Buffer line = reader.ReadLine();
if (!line) {
break;
}
size_t pid = parseNumber(line.SplitChar(' '));
if (!first_pid) {
first_pid = pid;
}
/* The log may contain data for several processes, only entries for the
* very first that appears are treated. */
if (first_pid != pid) {
continue;
}
/* The log contains thread ids for manual analysis, but we just ignore them
* for now. */
parseNumber(line.SplitChar(' '));
Buffer func = line.SplitChar('(');
Buffer args = line.SplitChar(')');
/* jemalloc_stats and free are functions with no result. */
if (func == Buffer("jemalloc_stats")) {
replay.jemalloc_stats(args);
continue;
} else if (func == Buffer("free")) {
replay.free(args);
continue;
}
/* Parse result value and get the corresponding slot. */
Buffer dummy = line.SplitChar('=');
Buffer dummy2 = line.SplitChar('#');
if (dummy || dummy2) {
die("Malformed input");
}
size_t slot_id = parseNumber(line);
MemSlot& slot = replay[slot_id];
if (func == Buffer("malloc")) {
replay.malloc(slot, args);
} else if (func == Buffer("posix_memalign")) {
replay.posix_memalign(slot, args);
} else if (func == Buffer("aligned_alloc")) {
replay.aligned_alloc(slot, args);
} else if (func == Buffer("calloc")) {
replay.calloc(slot, args);
} else if (func == Buffer("realloc")) {
replay.realloc(slot, args);
} else if (func == Buffer("memalign")) {
replay.memalign(slot, args);
} else if (func == Buffer("valloc")) {
replay.valloc(slot, args);
} else {
die("Malformed input");
}
}
return 0;
}

View file

@ -0,0 +1,147 @@
# 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/.
"""
This script takes a log from the replace-malloc logalloc library on stdin
and munges it so that it can be used with the logalloc-replay tool.
Given the following output:
13663 malloc(42)=0x7f0c33502040
13663 malloc(24)=0x7f0c33503040
13663 free(0x7f0c33502040)
The resulting output is:
1 malloc(42)=#1
1 malloc(24)=#2
1 free(#1)
See README for more details.
"""
from __future__ import print_function
import sys
from collections import (
defaultdict,
deque,
)
class IdMapping(object):
"""Class to map values to ids.
Each value is associated to an increasing id, starting from 1.
When a value is removed, its id is recycled and will be reused for
subsequent values.
"""
def __init__(self):
self.id = 1
self._values = {}
self._recycle = deque()
def __getitem__(self, value):
if value not in self._values:
if self._recycle:
self._values[value] = self._recycle.popleft()
else:
self._values[value] = self.id
self.id += 1
return self._values[value]
def __delitem__(self, value):
if value == 0:
return
self._recycle.append(self._values[value])
del self._values[value]
def __contains__(self, value):
return value == 0 or value in self._values
class Ignored(Exception): pass
def split_log_line(line):
try:
# The format for each line is:
# <pid> [<tid>] <function>([<args>])[=<result>]
#
# The original format didn't include the tid, so we try to parse
# lines whether they have one or not.
pid, func_call = line.split(' ', 1)
call, result = func_call.split(')')
func, args = call.split('(')
args = args.split(',') if args else []
if result:
if result[0] != '=':
raise Ignored('Malformed input')
result = result[1:]
if ' ' in func:
tid, func = func.split(' ', 1)
else:
tid = pid
return pid, tid, func, args, result
except:
raise Ignored('Malformed input')
NUM_ARGUMENTS = {
'jemalloc_stats': 0,
'free': 1,
'malloc': 1,
'posix_memalign': 2,
'aligned_alloc': 2,
'calloc': 2,
'realloc': 2,
'memalign': 2,
'valloc': 1,
}
def main():
pids = IdMapping()
processes = defaultdict(lambda: { 'pointers': IdMapping(),
'tids': IdMapping() })
for line in sys.stdin:
line = line.strip()
try:
pid, tid, func, args, result = split_log_line(line)
# Replace pid with an id.
pid = pids[int(pid)]
process = processes[pid]
tid = process['tids'][int(tid)]
pointers = process['pointers']
if func not in NUM_ARGUMENTS:
raise Ignored('Unknown function')
if len(args) != NUM_ARGUMENTS[func]:
raise Ignored('Malformed input')
if func in ('jemalloc_stats', 'free') and result:
raise Ignored('Malformed input')
if func in ('free', 'realloc'):
ptr = int(args[0], 16)
if ptr and ptr not in pointers:
raise Ignored('Did not see an alloc for pointer')
args[0] = "#%d" % pointers[ptr]
del pointers[ptr]
if result:
result = int(result, 16)
if not result:
raise Ignored('Result is NULL')
result = "#%d" % pointers[result]
print('%d %d %s(%s)%s' % (pid, tid, func, ','.join(args),
'=%s' % result if result else ''))
except Exception as e:
print('Ignored "%s": %s' % (line, e.message), file=sys.stderr)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,27 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
Program('logalloc-replay')
SOURCES += [
'../FdPrintf.cpp',
'/mfbt/Assertions.cpp',
'Replay.cpp',
]
LOCAL_INCLUDES += [
'..',
]
# Link replace-malloc and the default allocator.
USE_LIBS += [
'memory',
]
# The memory library defines this, so it's needed here too.
DEFINES['IMPL_MFBT'] = True
DISABLE_STL_WRAPPING = True

View file

@ -0,0 +1,17 @@
1 1 malloc(42)=#1
1 1 malloc(24)=#2
2 2 malloc(42)=#1
1 1 free(#1)
1 1 posix_memalign(4096,1024)=#1
1 1 calloc(4,42)=#3
1 1 free(#2)
1 1 realloc(#3,84)=#2
1 1 aligned_alloc(512,1024)=#3
1 1 memalign(512,1024)=#4
1 1 valloc(1024)=#5
1 1 jemalloc_stats()
1 1 free(#5)
1 1 free(#4)
1 1 free(#3)
1 1 free(#2)
1 1 free(#1)

20
memory/replace/moz.build Normal file
View file

@ -0,0 +1,20 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
DIRS += [
'logalloc',
'replace',
]
# Build jemalloc3 as a replace-malloc lib when building with mozjemalloc
if not CONFIG['MOZ_JEMALLOC4']:
DIRS += ['jemalloc']
if CONFIG['MOZ_REPLACE_MALLOC_LINKAGE'] == 'dummy library':
DIRS += ['dummy']
if CONFIG['MOZ_DMD']:
DIRS += ['dmd']

View file

@ -0,0 +1,253 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "replace_malloc.h"
#include <errno.h>
#include "mozilla/CheckedInt.h"
#include "mozilla/Atomics.h"
/* Replace-malloc library allowing different kinds of dispatch.
* The long term goal is to allow multiple replace-malloc libraries
* to be loaded and coexist properly.
* This is however a limited version to fulfil more immediate needs.
*/
static const malloc_table_t* gFuncs = nullptr;
static mozilla::Atomic<const malloc_hook_table_t*> gHookTable(nullptr);
class GenericReplaceMallocBridge : public ReplaceMallocBridge
{
virtual const malloc_table_t*
RegisterHook(const char* aName, const malloc_table_t* aTable,
const malloc_hook_table_t* aHookTable) override
{
// Can't register a hook before replace_init is called.
if (!gFuncs) {
return nullptr;
}
// Expect a name to be given.
if (!aName) {
return nullptr;
}
// Giving a malloc_table_t is not supported yet.
if (aTable) {
return nullptr;
}
if (aHookTable) {
// Expect at least a malloc and a free hook.
if (!aHookTable->malloc_hook || !aHookTable->free_hook) {
return nullptr;
}
gHookTable = const_cast<malloc_hook_table_t*>(aHookTable);
return gFuncs;
}
gHookTable = nullptr;
return nullptr;
}
};
void
replace_init(const malloc_table_t* aTable)
{
gFuncs = aTable;
}
ReplaceMallocBridge*
replace_get_bridge()
{
static GenericReplaceMallocBridge bridge;
return &bridge;
}
void*
replace_malloc(size_t aSize)
{
void* ptr = gFuncs->malloc(aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
return hook_table->malloc_hook(ptr, aSize);
}
return ptr;
}
int
replace_posix_memalign(void** aPtr, size_t aAlignment, size_t aSize)
{
int ret = gFuncs->posix_memalign(aPtr, aAlignment, aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
if (hook_table->posix_memalign_hook) {
return hook_table->posix_memalign_hook(ret, aPtr, aAlignment, aSize);
}
void* ptr = hook_table->malloc_hook(*aPtr, aSize);
if (!ptr && *aPtr) {
*aPtr = ptr;
ret = ENOMEM;
}
}
return ret;
}
void*
replace_aligned_alloc(size_t aAlignment, size_t aSize)
{
void* ptr = gFuncs->aligned_alloc(aAlignment, aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
if (hook_table->aligned_alloc_hook) {
return hook_table->aligned_alloc_hook(ptr, aAlignment, aSize);
}
return hook_table->malloc_hook(ptr, aSize);
}
return ptr;
}
void*
replace_calloc(size_t aNum, size_t aSize)
{
void* ptr = gFuncs->calloc(aNum, aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
if (hook_table->calloc_hook) {
return hook_table->calloc_hook(ptr, aNum, aSize);
}
mozilla::CheckedInt<size_t> size = mozilla::CheckedInt<size_t>(aNum) * aSize;
if (size.isValid()) {
return hook_table->malloc_hook(ptr, size.value());
}
/* If the multiplication above overflows, calloc will have failed, so ptr
* is null. But the hook might still be interested in knowing about the
* allocation attempt. The choice made is to indicate the overflow with
* the biggest value of a size_t, which is not that bad an indicator:
* there are only 5 prime factors to 2^32 - 1 and 7 prime factors to
* 2^64 - 1 and none of them is going to come directly out of sizeof().
* IOW, the likelyhood of aNum * aSize being exactly SIZE_MAX is low
* enough, and SIZE_MAX still conveys that the attempted allocation was
* too big anyways. */
return hook_table->malloc_hook(ptr, SIZE_MAX);
}
return ptr;
}
void*
replace_realloc(void* aPtr, size_t aSize)
{
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
if (hook_table->realloc_hook_before) {
hook_table->realloc_hook_before(aPtr);
} else {
hook_table->free_hook(aPtr);
}
}
void* new_ptr = gFuncs->realloc(aPtr, aSize);
/* The hook table might have changed since before realloc was called,
* either because of unregistration or registration of a new table.
* We however go with consistency and use the same hook table as the
* one that was used before the call to realloc. */
if (hook_table) {
if (hook_table->realloc_hook) {
/* aPtr is likely invalid when reaching here, it is only given for
* tracking purposes, and should not be dereferenced. */
return hook_table->realloc_hook(new_ptr, aPtr, aSize);
}
return hook_table->malloc_hook(new_ptr, aSize);
}
return new_ptr;
}
void
replace_free(void* aPtr)
{
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
hook_table->free_hook(aPtr);
}
gFuncs->free(aPtr);
}
void*
replace_memalign(size_t aAlignment, size_t aSize)
{
void* ptr = gFuncs->memalign(aAlignment, aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
if (hook_table->memalign_hook) {
return hook_table->memalign_hook(ptr, aAlignment, aSize);
}
return hook_table->malloc_hook(ptr, aSize);
}
return ptr;
}
void*
replace_valloc(size_t aSize)
{
void* ptr = gFuncs->valloc(aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table) {
if (hook_table->valloc_hook) {
return hook_table->valloc_hook(ptr, aSize);
}
return hook_table->malloc_hook(ptr, aSize);
}
return ptr;
}
size_t
replace_malloc_usable_size(usable_ptr_t aPtr)
{
size_t ret = gFuncs->malloc_usable_size(aPtr);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table && hook_table->malloc_usable_size_hook) {
return hook_table->malloc_usable_size_hook(ret, aPtr);
}
return ret;
}
size_t
replace_malloc_good_size(size_t aSize)
{
size_t ret = gFuncs->malloc_good_size(aSize);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table && hook_table->malloc_good_size_hook) {
return hook_table->malloc_good_size_hook(ret, aSize);
}
return ret;
}
void
replace_jemalloc_stats(jemalloc_stats_t* aStats)
{
gFuncs->jemalloc_stats(aStats);
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table && hook_table->jemalloc_stats_hook) {
hook_table->jemalloc_stats_hook(aStats);
}
}
void
replace_jemalloc_purge_freed_pages(void)
{
gFuncs->jemalloc_purge_freed_pages();
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table && hook_table->jemalloc_purge_freed_pages_hook) {
hook_table->jemalloc_purge_freed_pages_hook();
}
}
void
replace_jemalloc_free_dirty_pages(void)
{
gFuncs->jemalloc_free_dirty_pages();
const malloc_hook_table_t* hook_table = gHookTable;
if (hook_table && hook_table->jemalloc_free_dirty_pages_hook) {
hook_table->jemalloc_free_dirty_pages_hook();
}
}

View file

@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=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/.
SharedLibrary('replace_malloc')
SOURCES += [
'ReplaceMalloc.cpp',
]
DISABLE_STL_WRAPPING = True