Bug 1060567: Debugger.Memory.prototype.takeCensus: provide byte counts on request. r=fitzgen

This commit is contained in:
Jim Blandy 2015-06-04 09:55:48 -07:00
parent 048f2b5f03
commit 76347c9f12
4 changed files with 143 additions and 18 deletions

View File

@ -6,9 +6,27 @@ var Pattern = Match.Pattern;
var g = newGlobal();
var dbg = new Debugger(g);
Pattern({ count: Pattern.NATURAL })
Pattern({ count: Pattern.NATURAL,
bytes: Pattern.NATURAL })
.assert(dbg.memory.takeCensus({ breakdown: { by: 'count' } }));
let census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: false, bytes: false } });
assertEq('count' in census, false);
assertEq('bytes' in census, false);
let census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: true, bytes: false } });
assertEq('count' in census, true);
assertEq('bytes' in census, false);
let census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: false, bytes: true } });
assertEq('count' in census, false);
assertEq('bytes' in census, true);
let census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: true, bytes: true } });
assertEq('count' in census, true);
assertEq('bytes' in census, true);
// Pattern doesn't mind objects with extra properties, so we'll restrict this
// list to the object classes we're pretty sure are going to stick around for
// the forseeable future.

View File

@ -14,6 +14,20 @@ assertThrowsValue(() => {
assertThrowsValue(() => {
dbg.memory.takeCensus({
breakdown: { by: 'count', get count() { throw "ಠ_ಠ" } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
dbg.memory.takeCensus({
breakdown: { by: 'count', get bytes() { throw "ಠ_ಠ" } }
});
}, "ಠ_ಠ");
assertThrowsValue(() => {
dbg.memory.takeCensus({
breakdown: { by: 'objectClass', get then() { throw "ಠ_ಠ" } }

View File

@ -0,0 +1,57 @@
// Check byte counts produced by takeCensus.
let g = newGlobal();
let dbg = new Debugger(g);
let sizeOfAM = byteSize(allocationMarker());
// Allocate a single allocation marker, and check that we can find it.
g.eval('let hold = allocationMarker();');
let census = dbg.memory.takeCensus({ breakdown: { by: 'objectClass' } });
assertEq(census.AllocationMarker.count, 1);
assertEq(census.AllocationMarker.bytes, sizeOfAM);
g.evaluate(`
var objs = [];
function fnerd() {
objs.push(allocationMarker());
for (let i = 0; i < 10; i++)
objs.push(allocationMarker());
}
`,
{ fileName: 'J. Edgar Hoover', lineNumber: 2000 });
dbg.memory.allocationSamplingProbability = 1;
dbg.memory.trackingAllocationSites = true;
g.hold = null;
g.fnerd();
let census = dbg.memory.takeCensus({
breakdown: { by: 'objectClass',
then: { by: 'allocationStack' }
}
});
let seen = 0;
census.AllocationMarker.forEach((v, k) => {
assertEq(k.functionDisplayName, 'fnerd');
assertEq(k.source, 'J. Edgar Hoover');
switch (k.line) {
case 2003:
assertEq(v.count, 1);
assertEq(v.bytes, sizeOfAM);
seen++;
break;
case 2005:
assertEq(v.count, 10);
assertEq(v.bytes, 10 * sizeOfAM);
seen++;
break;
default: assertEq(true, false);
}
});
assertEq(seen, 2);

View File

@ -536,22 +536,36 @@ CountDeleter::operator()(CountBase* ptr)
// The simplest type: just count everything.
class SimpleCount : public CountType {
UniquePtr<char16_t[], JS::FreePolicy> label;
struct Count : CountBase {
explicit Count(SimpleCount& count) : CountBase(count) { }
size_t totalBytes_;
explicit Count(SimpleCount& count)
: CountBase(count),
totalBytes_(0)
{ }
};
UniquePtr<char16_t[], JS::FreePolicy> label;
bool reportCount : 1;
bool reportBytes : 1;
public:
SimpleCount(Census& census,
UniquePtr<char16_t[], JS::FreePolicy>& label)
UniquePtr<char16_t[], JS::FreePolicy>& label,
bool reportCount=true,
bool reportBytes=true)
: CountType(census),
label(Move(label))
label(Move(label)),
reportCount(reportCount),
reportBytes(reportBytes)
{ }
explicit SimpleCount(Census& census)
: CountType(census),
label(nullptr)
label(nullptr),
reportCount(true),
reportBytes(true)
{ }
CountBasePtr makeCount() override {
@ -568,16 +582,24 @@ class SimpleCount : public CountType {
bool count(CountBase& countBase, const Node& node) override {
Count& count = static_cast<Count&>(countBase);
count.total_++;
if (reportBytes)
count.totalBytes_ += node.size(census.cx->runtime()->debuggerMallocSizeOf);
return true;
}
bool report(CountBase& countBase, MutableHandleValue report) override {
Count& count = static_cast<Count&>(countBase);
RootedPlainObject obj(census.cx, NewBuiltinClassInstance<PlainObject>(census.cx));
RootedValue countValue(census.cx, NumberValue(count.total_));
if (!obj)
return false;
if (!DefineProperty(census.cx, obj, census.cx->names().count, countValue))
RootedValue countValue(census.cx, NumberValue(count.total_));
if (reportCount && !DefineProperty(census.cx, obj, census.cx->names().count, countValue))
return false;
RootedValue bytesValue(census.cx, NumberValue(count.totalBytes_));
if (reportBytes && !DefineProperty(census.cx, obj, census.cx->names().bytes, bytesValue))
return false;
if (label) {
@ -1131,12 +1153,14 @@ class ByAllocationStack : public CountType {
return false;
}
if (count.noStack->total_ > 0) {
RootedValue noStackReport(cx);
if (!count.noStack->report(&noStackReport))
return false;
RootedValue noStack(cx, StringValue(cx->names().noStack));
if (!MapObject::set(cx, map, noStack, noStackReport))
return false;
}
MOZ_ASSERT(generation == count.table.generation());
@ -1237,6 +1261,15 @@ ParseBreakdown(Census& census, HandleValue breakdownValue)
return nullptr;
if (StringEqualsAscii(by, "count")) {
RootedValue countValue(cx), bytesValue(cx);
if (!GetProperty(cx, breakdown, breakdown, cx->names().count, &countValue) ||
!GetProperty(cx, breakdown, breakdown, cx->names().bytes, &bytesValue))
return nullptr;
// Both 'count' and 'bytes' default to true if omitted, but ToBoolean
// naturally treats 'undefined' as false; fix this up.
if (countValue.isUndefined()) countValue.setBoolean(true);
if (bytesValue.isUndefined()) bytesValue.setBoolean(true);
// Undocumented feature, for testing: { by: 'count' } breakdowns can have
// a 'label' property whose value is converted to a string and included as
@ -1253,21 +1286,24 @@ ParseBreakdown(Census& census, HandleValue breakdownValue)
JSFlatString* flat = labelString->ensureFlat(cx);
if (!flat)
return false;
return nullptr;
AutoStableStringChars chars(cx);
if (!chars.initTwoByte(cx, flat))
return false;
return nullptr;
// Since flat strings are null-terminated, and AutoStableStringChars
// null- terminates if it needs to make a copy, we know that
// chars.twoByteChars() is null-terminated.
labelUnique = DuplicateString(cx, chars.twoByteChars());
if (!labelUnique)
return false;
return nullptr;
}
CountTypePtr simple(census.new_<SimpleCount>(census, labelUnique));
CountTypePtr simple(census.new_<SimpleCount>(census,
labelUnique,
ToBoolean(countValue),
ToBoolean(bytesValue)));
return simple;
}