Ignore:
Timestamp:
Mar 2, 2016, 9:15:56 PM (9 years ago)
Author:
[email protected]
Message:

Add ability to generate a Heap Snapshot
https://bugs.webkit.org/show_bug.cgi?id=154847

Patch by Joseph Pecoraro <Joseph Pecoraro> on 2016-03-02
Reviewed by Mark Lam.

This adds HeapSnapshot, HeapSnapshotBuilder, and HeapProfiler.

HeapProfiler hangs off of the VM and holds the list of snapshots.
I expect to add other HeapProfiling features, such as allocation
tracking, to the profiler.

HeapSnapshot contains a collection of live cells and their identifiers.
It can point to a previous HeapSnapshot, to ensure that a cell that
already received an identifier maintains the same identifier across
multiple snapshots. When a snapshotted cell gets garbage collected,
the cell will be swept from the HeapSnapshot at the end of collection
to ensure the list contains only live cells.

When building a HeapSnapshot nodes are added in increasing node
identifier order. When done building, the list of nodes is complete
and the snapshot is finalized. At this point the nodes are sorted
by JSCell* address to allow for quick lookup of a JSCell*.

HeapSnapshotBuilder is where snapshotting begins. The builder
will initiate a specialized heap snapshotting garbage collection.
During this collection the builder will be notified of all marked
(live) cells, and connections between cells, as seen by SlotVisitors.
The builder can reference the previous, readonly, HeapSnapshots to
avoid creating new nodes for cells that have already been snapshotted.
When it is determined that we are visiting a live cell for the first
time, we give the cell a unique identifier and add it to the the
snapshot we are building.

Since edge data is costly, and of little long term utility, this
data is only held by the builder for serialization, and not stored
long term with the HeapSnapshot node data.

The goals of HeapSnapshotting at this time are:

  • minimal impact on performance when not profiling the heap
  • unique identifier for cells, so they may be identified across multiple snapshots
  • nodes and edges to be able to construct a graph of which nodes reference/retain which other nodes
  • node data - identifier, type (class name), size
  • edge data - from cell, to cell, type / data (to come in a follow-up patch)

Add new files to the build.

  • heap/Heap.cpp:

(JSC::Heap::isHeapSnapshotting):
(JSC::RemoveDeadHeapSnapshotNodes::RemoveDeadHeapSnapshotNodes):
(JSC::RemoveDeadHeapSnapshotNodes::operator()):
(JSC::Heap::removeDeadHeapSnapshotNodes):
(JSC::Heap::collectImpl):
After every collection, sweep dead cells from in memory snapshots.

  • runtime/VM.cpp:

(JSC::VM::ensureHeapProfiler):

  • runtime/VM.h:

(JSC::VM::heapProfiler):

  • heap/Heap.h:
  • heap/HeapProfiler.cpp: Added.

(JSC::HeapProfiler::HeapProfiler):
(JSC::HeapProfiler::~HeapProfiler):
(JSC::HeapProfiler::mostRecentSnapshot):
(JSC::HeapProfiler::appendSnapshot):
(JSC::HeapProfiler::clearSnapshots):
(JSC::HeapProfiler::setActiveSnapshotBuilder):

  • heap/HeapProfiler.h: Added.

(JSC::HeapProfiler::vm):
(JSC::HeapProfiler::activeSnapshotBuilder):
VM and Heap can look at the profiler to determine if we are building a
snapshot, or the "head" snapshot to use for sweeping.

  • heap/HeapSnapshot.cpp: Added.

(JSC::HeapSnapshot::HeapSnapshot):
(JSC::HeapSnapshot::~HeapSnapshot):
(JSC::HeapSnapshot::appendNode):
Add a node to the unfinalized list of new cells.

(JSC::HeapSnapshot::sweepCell):
(JSC::HeapSnapshot::shrinkToFit):
Collect a list of cells for sweeping and then remove them all at once
in shrinkToFit. This is done to avoid thrashing of individual removes
that could cause many overlapping moves within the Vector.

(JSC::HeapSnapshot::finalize):
Sort the list, and also cache the bounding start/stop identifiers.
No other snapshot can contain an identifier in this range, so it will
improve lookup of a node from an identifier.

(JSC::HeapSnapshot::nodeForCell):
(JSC::HeapSnapshot::nodeForObjectIdentifier):
Search helpers.

  • heap/HeapSnapshotBuilder.h: Added.

(JSC::HeapSnapshotNode::HeapSnapshotNode):
(JSC::HeapSnapshotEdge::HeapSnapshotEdge):
Node and Edge struct types the builder creates.

  • heap/HeapSnapshotBuilder.cpp: Added.

(JSC::HeapSnapshotBuilder::getNextObjectIdentifier):
(JSC::HeapSnapshotBuilder::HeapSnapshotBuilder):
(JSC::HeapSnapshotBuilder::~HeapSnapshotBuilder):
(JSC::HeapSnapshotBuilder::buildSnapshot):
(JSC::HeapSnapshotBuilder::appendNode):
(JSC::HeapSnapshotBuilder::appendEdge):
When building the snapshot, generating the next identifier, and
appending to any of the lists must be guarded by a lock because
SlotVisitors running in parallel may be accessing the builder.

(JSC::HeapSnapshotBuilder::hasExistingNodeForCell):
Looking up if a node already exists in a previous snapshot can be
done without a lock because at this point the data is readonly.

(JSC::edgeTypeToNumber):
(JSC::edgeTypeToString):
(JSC::HeapSnapshotBuilder::json):
JSON serialization of a heap snapshot contains node and edge data.

  • heap/SlotVisitor.h:
  • heap/SlotVisitor.cpp:

(JSC::SlotVisitor::didStartMarking):
(JSC::SlotVisitor::reset):
Set/clear the active snapshot builder to know if this will be a
snapshotting GC or not.

(JSC::SlotVisitor::append):
(JSC::SlotVisitor::setMarkedAndAppendToMarkStack):
Inform the builder of a new node or edge.

(JSC::SlotVisitor::visitChildren):
Remember the current cell we are visiting so that if we need to
inform the builder of edges we know the "from" cell.

  • jsc.cpp:

(SimpleObject::SimpleObject):
(SimpleObject::create):
(SimpleObject::finishCreation):
(SimpleObject::visitChildren):
(SimpleObject::createStructure):
(SimpleObject::hiddenValue):
(SimpleObject::setHiddenValue):
Create a new class "SimpleObject" that can be used by heap snapshotting
tests. It is easy to filter for this new class name and test internal
edge relationships created by garbage collection visiting the cell.

(functionCreateSimpleObject):
(functionGetHiddenValue):
(functionSetHiddenValue):
Expose methods to create and interact with a SimpleObject.

(functionGenerateHeapSnapshot):
Expose methods to create a heap snapshot. This currently automatically
turns the serialized string into a JSON object. That may change.

  • tests/heapProfiler.yaml: Added.
  • tests/heapProfiler/basic-edges.js: Added.

(excludeStructure):

  • tests/heapProfiler/basic-nodes.js: Added.

(hasDifferentSizeNodes):
(hasAllInternalNodes):
Add tests for basic node and edge data.

  • tests/heapProfiler/driver/driver.js: Added.

(assert):
(CheapHeapSnapshotNode):
(CheapHeapSnapshotEdge):
(CheapHeapSnapshotEdge.prototype.get from):
(CheapHeapSnapshotEdge.prototype.get to):
(CheapHeapSnapshot):
(CheapHeapSnapshot.prototype.get nodes):
(CheapHeapSnapshot.prototype.get edges):
(CheapHeapSnapshot.prototype.nodeWithIdentifier):
(CheapHeapSnapshot.prototype.nodesWithClassName):
(CheapHeapSnapshot.prototype.classNameFromTableIndex):
(CheapHeapSnapshot.prototype.edgeTypeFromTableIndex):
(createCheapHeapSnapshot):
(HeapSnapshotNode):
(HeapSnapshotEdge):
(HeapSnapshot):
(HeapSnapshot.prototype.nodesWithClassName):
(createHeapSnapshot):
Add two HeapSnapshot representations.
CheapHeapSnapshot creates two lists of node and edge data that
lazily creates objects as needed.
HeapSnapshot creates an object for each node and edge. This
is wasteful but easier to use.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/jsc.cpp

    r197261 r197489  
    3333#include "Exception.h"
    3434#include "ExceptionHelpers.h"
     35#include "HeapProfiler.h"
     36#include "HeapSnapshotBuilder.h"
    3537#include "HeapStatistics.h"
    3638#include "InitializeThreading.h"
     
    466468};
    467469
     470class SimpleObject : public JSNonFinalObject {
     471public:
     472    SimpleObject(VM& vm, Structure* structure)
     473        : Base(vm, structure)
     474    {
     475    }
     476
     477    typedef JSNonFinalObject Base;
     478    static const bool needsDestruction = false;
     479
     480    static SimpleObject* create(VM& vm, JSGlobalObject* globalObject)
     481    {
     482        Structure* structure = createStructure(vm, globalObject, jsNull());
     483        SimpleObject* simpleObject = new (NotNull, allocateCell<SimpleObject>(vm.heap, sizeof(SimpleObject))) SimpleObject(vm, structure);
     484        simpleObject->finishCreation(vm);
     485        return simpleObject;
     486    }
     487
     488    void finishCreation(VM& vm)
     489    {
     490        Base::finishCreation(vm);
     491    }
     492
     493    static void visitChildren(JSCell* cell, SlotVisitor& visitor)
     494    {
     495        SimpleObject* thisObject = jsCast<SimpleObject*>(cell);
     496        ASSERT_GC_OBJECT_INHERITS(thisObject, info());
     497        Base::visitChildren(thisObject, visitor);
     498        visitor.append(&thisObject->m_hiddenValue);
     499    }
     500
     501    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
     502    {
     503        return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
     504    }
     505
     506    JSValue hiddenValue()
     507    {
     508        return m_hiddenValue.get();
     509    }
     510
     511    void setHiddenValue(VM& vm, JSValue value)
     512    {
     513        ASSERT(value.isCell());
     514        m_hiddenValue.set(vm, this, value);
     515    }
     516
     517    DECLARE_INFO;
     518
     519private:
     520    WriteBarrier<Unknown> m_hiddenValue;
     521};
     522
     523
    468524const ClassInfo Element::s_info = { "Element", &Base::s_info, 0, CREATE_METHOD_TABLE(Element) };
    469525const ClassInfo Masquerader::s_info = { "Masquerader", &Base::s_info, 0, CREATE_METHOD_TABLE(Masquerader) };
     
    472528const ClassInfo CustomGetter::s_info = { "CustomGetter", &Base::s_info, 0, CREATE_METHOD_TABLE(CustomGetter) };
    473529const ClassInfo RuntimeArray::s_info = { "RuntimeArray", &Base::s_info, 0, CREATE_METHOD_TABLE(RuntimeArray) };
     530const ClassInfo SimpleObject::s_info = { "SimpleObject", &Base::s_info, 0, CREATE_METHOD_TABLE(SimpleObject) };
    474531
    475532ElementHandleOwner* Element::handleOwner()
     
    502559static EncodedJSValue JSC_HOST_CALL functionCreateElement(ExecState*);
    503560static EncodedJSValue JSC_HOST_CALL functionGetElement(ExecState*);
     561static EncodedJSValue JSC_HOST_CALL functionCreateSimpleObject(ExecState*);
     562static EncodedJSValue JSC_HOST_CALL functionGetHiddenValue(ExecState*);
     563static EncodedJSValue JSC_HOST_CALL functionSetHiddenValue(ExecState*);
    504564static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState*);
    505565static EncodedJSValue JSC_HOST_CALL functionDebug(ExecState*);
     
    556616static EncodedJSValue JSC_HOST_CALL functionCheckModuleSyntax(ExecState*);
    557617static EncodedJSValue JSC_HOST_CALL functionPlatformSupportsSamplingProfiler(ExecState*);
     618static EncodedJSValue JSC_HOST_CALL functionGenerateHeapSnapshot(ExecState*);
    558619#if ENABLE(SAMPLING_PROFILER)
    559620static EncodedJSValue JSC_HOST_CALL functionStartSamplingProfiler(ExecState*);
     
    709770        addFunction(vm, "setElementRoot", functionSetElementRoot, 2);
    710771       
     772        addConstructableFunction(vm, "SimpleObject", functionCreateSimpleObject, 0);
     773        addFunction(vm, "getHiddenValue", functionGetHiddenValue, 1);
     774        addFunction(vm, "setHiddenValue", functionSetHiddenValue, 2);
     775       
    711776        putDirectNativeFunction(vm, this, Identifier::fromString(&vm, "DFGTrue"), 0, functionFalse1, DFGTrueIntrinsic, DontEnum);
    712777        putDirectNativeFunction(vm, this, Identifier::fromString(&vm, "OSRExit"), 0, functionUndefined1, OSRExitIntrinsic, DontEnum);
     
    748813
    749814        addFunction(vm, "platformSupportsSamplingProfiler", functionPlatformSupportsSamplingProfiler, 0);
     815        addFunction(vm, "generateHeapSnapshot", functionGenerateHeapSnapshot, 0);
    750816#if ENABLE(SAMPLING_PROFILER)
    751817        addFunction(vm, "startSamplingProfiler", functionStartSamplingProfiler, 0);
     
    11341200}
    11351201
     1202EncodedJSValue JSC_HOST_CALL functionCreateSimpleObject(ExecState* exec)
     1203{
     1204    JSLockHolder lock(exec);
     1205    return JSValue::encode(SimpleObject::create(exec->vm(), exec->lexicalGlobalObject()));
     1206}
     1207
     1208EncodedJSValue JSC_HOST_CALL functionGetHiddenValue(ExecState* exec)
     1209{
     1210    JSLockHolder lock(exec);
     1211    SimpleObject* simpleObject = jsCast<SimpleObject*>(exec->argument(0).asCell());
     1212    return JSValue::encode(simpleObject->hiddenValue());
     1213}
     1214
     1215EncodedJSValue JSC_HOST_CALL functionSetHiddenValue(ExecState* exec)
     1216{
     1217    JSLockHolder lock(exec);
     1218    SimpleObject* simpleObject = jsCast<SimpleObject*>(exec->argument(0).asCell());
     1219    JSValue value = exec->argument(1);
     1220    simpleObject->setHiddenValue(exec->vm(), value);
     1221    return JSValue::encode(jsUndefined());
     1222}
     1223
    11361224EncodedJSValue JSC_HOST_CALL functionCreateProxy(ExecState* exec)
    11371225{
     
    16451733    return JSValue::encode(JSValue(JSC::JSValue::JSFalse));
    16461734#endif
     1735}
     1736
     1737EncodedJSValue JSC_HOST_CALL functionGenerateHeapSnapshot(ExecState* exec)
     1738{
     1739    JSLockHolder lock(exec);
     1740
     1741    HeapSnapshotBuilder snapshotBuilder(exec->vm().ensureHeapProfiler());
     1742    snapshotBuilder.buildSnapshot();
     1743
     1744    String jsonString = snapshotBuilder.json();
     1745    EncodedJSValue result = JSValue::encode(JSONParse(exec, jsonString));
     1746    RELEASE_ASSERT(!exec->hadException());
     1747    return result;
    16471748}
    16481749
Note: See TracChangeset for help on using the changeset viewer.