mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1012456: Add non-trivial breakdowns to Debugger.Memory.prototype.takeCensus. r=terrence
This commit is contained in:
parent
46ff4b8265
commit
c88922f0bd
@ -65,11 +65,35 @@ per-category counts, whose size depends on the number of categories.
|
||||
: Carry out a census of the debuggee compartments' contents. Return an
|
||||
object of the form:
|
||||
|
||||
<pre class='language-js'><code>
|
||||
{
|
||||
"objects": { <i>class</i>: <i>tally</i>, ... },
|
||||
"scripts": <i>tally</i>,
|
||||
"strings": <i>tally</i>,
|
||||
"other": { <i>type name</i>: <i>tally</i>, ... }
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
Each <i>tally</i> has the form:
|
||||
|
||||
<pre class='language-js'><code>
|
||||
{ "count": <i>count</i> }
|
||||
</code></pre>
|
||||
|
||||
where <i>count</i> is the number of nodes found.
|
||||
where <i>count</i> is the number of items in the category.
|
||||
|
||||
The `"objects"` property's value contains the tallies of JavaScript objects,
|
||||
broken down by their ECMAScript `[[Class]]` internal property values. Each
|
||||
<i>class</i> is a string.
|
||||
|
||||
The `"scripts"` property's value tallies the in-memory representation of
|
||||
JavaScript code.
|
||||
|
||||
The `"strings"` property's value tallies the debuggee's strings.
|
||||
|
||||
The `"other"` property's value contains the tallies of other items used
|
||||
internally by SpiderMonkey, broken down by their C++ type name.
|
||||
|
||||
|
||||
Memory Use Analysis Exposes Implementation Details
|
||||
--------------------------------------------------
|
||||
|
@ -8,8 +8,8 @@ Census.walkCensus(census0, "census0", Census.assertAllZeros);
|
||||
|
||||
var g1 = newGlobal();
|
||||
g1.eval('var a = [];');
|
||||
g1.eval('function add() { a.push({}); }');
|
||||
g1.eval('function remove() { a.pop({}); }');
|
||||
g1.eval('function add(f) { a.push({}); a.push(f ? (() => undefined) : null); }');
|
||||
g1.eval('function remove() { a.pop(); a.pop(); }');
|
||||
g1.add();
|
||||
g1.remove();
|
||||
|
||||
@ -18,26 +18,37 @@ dbg.addDebuggee(g1);
|
||||
var census1 = dbg.memory.takeCensus();
|
||||
Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0));
|
||||
|
||||
function pointCheck(label, lhs, rhs, objComp, funComp) {
|
||||
print(label);
|
||||
assertEq(objComp(lhs.objects.Object.count, rhs.objects.Object.count), true);
|
||||
assertEq(funComp(lhs.objects.Function.count, rhs.objects.Function.count), true);
|
||||
}
|
||||
|
||||
function eq(lhs, rhs) { return lhs === rhs; }
|
||||
function lt(lhs, rhs) { return lhs < rhs; }
|
||||
function gt(lhs, rhs) { return lhs > rhs; }
|
||||
|
||||
// As we increase the number of reachable objects, the census should
|
||||
// reflect that.
|
||||
g1.add();
|
||||
g1.add(false);
|
||||
var census2 = dbg.memory.takeCensus();
|
||||
assertEq(census2.count > census1.count, true);
|
||||
pointCheck("census2", census2, census1, gt, eq);
|
||||
|
||||
g1.add();
|
||||
g1.add(true);
|
||||
var census3 = dbg.memory.takeCensus();
|
||||
assertEq(census3.count > census2.count, true);
|
||||
pointCheck("census3", census3, census2, gt, gt);
|
||||
|
||||
g1.add();
|
||||
g1.add(false);
|
||||
var census4 = dbg.memory.takeCensus();
|
||||
assertEq(census4.count > census3.count, true);
|
||||
pointCheck("census4", census4, census3, gt, eq);
|
||||
|
||||
// As we decrease the number of reachable objects, the census counts should go
|
||||
// down.
|
||||
// down. Note that since the census does its own reachability analysis, we don't
|
||||
// need to GC here to see the counts drop.
|
||||
g1.remove();
|
||||
var census5 = dbg.memory.takeCensus();
|
||||
assertEq(census5.count < census4.count, true);
|
||||
pointCheck("census5", census5, census4, lt, eq);
|
||||
|
||||
g1.remove();
|
||||
var census6 = dbg.memory.takeCensus();
|
||||
assertEq(census6.count < census5.count, true);
|
||||
pointCheck("census6", census6, census5, lt, lt);
|
||||
|
@ -316,7 +316,252 @@ class Tally {
|
||||
}
|
||||
};
|
||||
|
||||
// A ubi::BreadthFirst handler type that conducts a census, using Assorter
|
||||
// An assorter that breaks nodes down by their JavaScript type --- 'objects',
|
||||
// 'strings', 'scripts', and 'other' --- and then passes the nodes to
|
||||
// sub-assorters. The template arguments must themselves be assorter types.
|
||||
//
|
||||
// Implementation details of scripts like jitted code are counted under
|
||||
// 'scripts'.
|
||||
template<typename EachObject = Tally,
|
||||
typename EachScript = Tally,
|
||||
typename EachString = Tally,
|
||||
typename EachOther = Tally>
|
||||
class ByJSType {
|
||||
EachObject objects;
|
||||
EachScript scripts;
|
||||
EachString strings;
|
||||
EachOther other;
|
||||
|
||||
public:
|
||||
ByJSType(Census &census)
|
||||
: objects(census),
|
||||
scripts(census),
|
||||
strings(census),
|
||||
other(census)
|
||||
{ }
|
||||
ByJSType(ByJSType &&rhs)
|
||||
: objects(Move(rhs.objects)),
|
||||
scripts(move(rhs.scripts)),
|
||||
strings(move(rhs.strings)),
|
||||
other(move(rhs.other))
|
||||
{ }
|
||||
ByJSType &operator=(ByJSType &&rhs) {
|
||||
MOZ_ASSERT(&rhs != this);
|
||||
this->~ByJSType();
|
||||
new (this) ByJSType(Move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool init(Census &census) {
|
||||
return objects.init(census) &&
|
||||
scripts.init(census) &&
|
||||
strings.init(census) &&
|
||||
other.init(census);
|
||||
}
|
||||
|
||||
bool count(Census &census, const Node &node) {
|
||||
if (node.is<JSObject>())
|
||||
return objects.count(census, node);
|
||||
if (node.is<JSScript>() || node.is<LazyScript>() || node.is<jit::JitCode>())
|
||||
return scripts.count(census, node);
|
||||
if (node.is<JSString>())
|
||||
return strings.count(census, node);
|
||||
return other.count(census, node);
|
||||
}
|
||||
|
||||
bool report(Census &census, MutableHandleValue report) {
|
||||
JSContext *cx = census.cx;
|
||||
|
||||
RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
RootedValue objectsReport(cx);
|
||||
if (!objects.report(census, &objectsReport) ||
|
||||
!JSObject::defineProperty(cx, obj, cx->names().objects, objectsReport))
|
||||
return false;
|
||||
|
||||
RootedValue scriptsReport(cx);
|
||||
if (!scripts.report(census, &scriptsReport) ||
|
||||
!JSObject::defineProperty(cx, obj, cx->names().scripts, scriptsReport))
|
||||
return false;
|
||||
|
||||
RootedValue stringsReport(cx);
|
||||
if (!strings.report(census, &stringsReport) ||
|
||||
!JSObject::defineProperty(cx, obj, cx->names().strings, stringsReport))
|
||||
return false;
|
||||
|
||||
RootedValue otherReport(cx);
|
||||
if (!other.report(census, &otherReport) ||
|
||||
!JSObject::defineProperty(cx, obj, cx->names().other, otherReport))
|
||||
return false;
|
||||
|
||||
report.setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// An assorter that categorizes nodes that are JSObjects by their class, and
|
||||
// places all other nodes in an 'other' category. The template arguments must be
|
||||
// assorter types; each JSObject class gets an EachClass assorter, and the
|
||||
// 'other' category gets an EachOther assorter.
|
||||
template<typename EachClass = Tally,
|
||||
typename EachOther = Tally>
|
||||
class ByObjectClass {
|
||||
// A hash policy that compares js::Classes by name.
|
||||
struct HashPolicy {
|
||||
typedef const js::Class *Lookup;
|
||||
static js::HashNumber hash(Lookup l) { return mozilla::HashString(l->name); }
|
||||
static bool match(const js::Class *key, Lookup lookup) {
|
||||
return strcmp(key->name, lookup->name) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// A table mapping classes to their counts. Note that this table treats
|
||||
// js::Class instances with the same name as equal keys. If you have several
|
||||
// js::Classes with equal names (and we do; as of this writing there were
|
||||
// six named "Object"), you will get several different Classes being counted
|
||||
// in the same table entry.
|
||||
typedef HashMap<const js::Class *, EachClass, HashPolicy, SystemAllocPolicy> Table;
|
||||
Table table;
|
||||
EachOther other;
|
||||
|
||||
public:
|
||||
ByObjectClass(Census &census) : other(census) { }
|
||||
ByObjectClass(ByObjectClass &&rhs) : table(Move(rhs.table)), other(Move(rhs.other)) { }
|
||||
ByObjectClass &operator=(ByObjectClass &&rhs) {
|
||||
MOZ_ASSERT(&rhs != this);
|
||||
this->~ByObjectClass();
|
||||
new (this) ByObjectClass(Move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool init(Census &census) { return table.init() && other.init(census); }
|
||||
|
||||
bool count(Census &census, const Node &node) {
|
||||
if (!node.is<JSObject>())
|
||||
return other.count(census, node);
|
||||
|
||||
const js::Class *key = node.as<JSObject>()->getClass();
|
||||
typename Table::AddPtr p = table.lookupForAdd(key);
|
||||
if (!p) {
|
||||
if (!table.add(p, key, EachClass(census)))
|
||||
return false;
|
||||
if (!p->value().init(census))
|
||||
return false;
|
||||
}
|
||||
return p->value().count(census, node);
|
||||
}
|
||||
|
||||
bool report(Census &census, MutableHandleValue report) {
|
||||
JSContext *cx = census.cx;
|
||||
|
||||
RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
for (typename Table::Range r = table.all(); !r.empty(); r.popFront()) {
|
||||
EachClass &entry = r.front().value();
|
||||
RootedValue entryReport(cx);
|
||||
if (!entry.report(census, &entryReport))
|
||||
return false;
|
||||
|
||||
const char *name = r.front().key()->name;
|
||||
MOZ_ASSERT(name);
|
||||
JSAtom *atom = Atomize(census.cx, name, strlen(name));
|
||||
if (!atom)
|
||||
return false;
|
||||
RootedId entryId(cx, AtomToId(atom));
|
||||
|
||||
#ifdef DEBUG
|
||||
// We have multiple js::Classes out there with the same name (for
|
||||
// example, JSObject::class_, Debugger.Object, and CollatorClass are
|
||||
// all "Object"), so let's make sure our hash table treats them all
|
||||
// as equivalent.
|
||||
bool has;
|
||||
if (!JSObject::hasProperty(cx, obj, entryId, &has))
|
||||
return false;
|
||||
if (has) {
|
||||
fprintf(stderr, "already has %s\n", name);
|
||||
MOZ_ASSERT(!has);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!JSObject::defineGeneric(cx, obj, entryId, entryReport))
|
||||
return false;
|
||||
}
|
||||
|
||||
report.setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// An assorter that categorizes nodes by their ubi::Node::typeName.
|
||||
template<typename EachType = Tally>
|
||||
class ByUbinodeType {
|
||||
// Note that, because ubi::Node::typeName promises to return a specific
|
||||
// pointer, not just any string whose contents are correct, we can use their
|
||||
// addresses as hash table keys.
|
||||
typedef HashMap<const jschar *, EachType, DefaultHasher<const jschar *>, SystemAllocPolicy> Table;
|
||||
Table table;
|
||||
|
||||
public:
|
||||
ByUbinodeType(Census &census) { }
|
||||
ByUbinodeType(ByUbinodeType &&rhs) : table(Move(rhs.table)) { }
|
||||
ByUbinodeType &operator=(ByUbinodeType &&rhs) {
|
||||
MOZ_ASSERT(&rhs != this);
|
||||
this->~ByUbinodeType();
|
||||
new (this) ByUbinodeType(Move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool init(Census &census) { return table.init(); }
|
||||
|
||||
bool count(Census &census, const Node &node) {
|
||||
const jschar *key = node.typeName();
|
||||
typename Table::AddPtr p = table.lookupForAdd(key);
|
||||
if (!p) {
|
||||
if (!table.add(p, key, EachType(census)))
|
||||
return false;
|
||||
if (!p->value().init(census))
|
||||
return false;
|
||||
}
|
||||
return p->value().count(census, node);
|
||||
}
|
||||
|
||||
bool report(Census &census, MutableHandleValue report) {
|
||||
JSContext *cx = census.cx;
|
||||
|
||||
RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
for (typename Table::Range r = table.all(); !r.empty(); r.popFront()) {
|
||||
EachType &entry = r.front().value();
|
||||
RootedValue entryReport(cx);
|
||||
if (!entry.report(census, &entryReport))
|
||||
return false;
|
||||
|
||||
const jschar *name = r.front().key();
|
||||
MOZ_ASSERT(name);
|
||||
JSAtom *atom = AtomizeChars(cx, name, js_strlen(name));
|
||||
if (!atom)
|
||||
return false;
|
||||
RootedId entryId(cx, AtomToId(atom));
|
||||
|
||||
if (!JSObject::defineGeneric(cx, obj, entryId, entryReport))
|
||||
return false;
|
||||
}
|
||||
|
||||
report.setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A BreadthFirst handler type that conducts a census, using Assorter
|
||||
// to categorize and count each node.
|
||||
template<typename Assorter>
|
||||
class CensusHandler {
|
||||
@ -366,10 +611,15 @@ class CensusHandler {
|
||||
}
|
||||
};
|
||||
|
||||
// The assorter that Debugger.Memory.prototype.takeCensus uses by default.
|
||||
// (Eventually, we hope to add parameters that let you specify dynamically how
|
||||
// the census should assort the nodes it finds.) Categorize nodes by JS type,
|
||||
// and then objects by object class.
|
||||
typedef ByJSType<ByObjectClass<Tally>, Tally, Tally, ByUbinodeType<Tally> > DefaultAssorter;
|
||||
|
||||
// A traversal that conducts a trivial census.
|
||||
typedef CensusHandler<Tally> TallyingHandler;
|
||||
typedef BreadthFirst<TallyingHandler> TallyingTraversal;
|
||||
// A traversal that conducts a census using DefaultAssorter.
|
||||
typedef CensusHandler<DefaultAssorter> DefaultCensusHandler;
|
||||
typedef BreadthFirst<DefaultCensusHandler> DefaultCensusTraversal;
|
||||
|
||||
} // namespace dbg
|
||||
} // namespace js
|
||||
@ -383,14 +633,14 @@ DebuggerMemory::takeCensus(JSContext *cx, unsigned argc, Value *vp)
|
||||
dbg::Census census(cx);
|
||||
if (!census.init())
|
||||
return false;
|
||||
dbg::TallyingHandler handler(census);
|
||||
dbg::DefaultCensusHandler handler(census);
|
||||
if (!handler.init(census))
|
||||
return false;
|
||||
|
||||
{
|
||||
JS::AutoCheckCannotGC noGC;
|
||||
|
||||
dbg::TallyingTraversal traversal(cx, handler, noGC);
|
||||
dbg::DefaultCensusTraversal traversal(cx, handler, noGC);
|
||||
if (!traversal.init())
|
||||
return false;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user