diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-06.js b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js index 09cff91ff39..6c70092d717 100644 --- a/js/src/jit-test/tests/debug/Memory-takeCensus-06.js +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js @@ -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. diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-07.js b/js/src/jit-test/tests/debug/Memory-takeCensus-07.js index eabc056feb4..6dae65eacb7 100644 --- a/js/src/jit-test/tests/debug/Memory-takeCensus-07.js +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-07.js @@ -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 "ಠ_ಠ" } } diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-10.js b/js/src/jit-test/tests/debug/Memory-takeCensus-10.js new file mode 100644 index 00000000000..79474da1ff1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-10.js @@ -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); diff --git a/js/src/vm/DebuggerMemory.cpp b/js/src/vm/DebuggerMemory.cpp index 7c447b53493..8dde3540f34 100644 --- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -536,22 +536,36 @@ CountDeleter::operator()(CountBase* ptr) // The simplest type: just count everything. class SimpleCount : public CountType { - UniquePtr label; struct Count : CountBase { - explicit Count(SimpleCount& count) : CountBase(count) { } + size_t totalBytes_; + + explicit Count(SimpleCount& count) + : CountBase(count), + totalBytes_(0) + { } }; + UniquePtr label; + bool reportCount : 1; + bool reportBytes : 1; + public: SimpleCount(Census& census, - UniquePtr& label) + UniquePtr& 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(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(countBase); + RootedPlainObject obj(census.cx, NewBuiltinClassInstance(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; } - 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; + 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_(census, labelUnique)); + CountTypePtr simple(census.new_(census, + labelUnique, + ToBoolean(countValue), + ToBoolean(bytesValue))); return simple; }