зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1545582 - Add a JS runtime level of allocation logging; r=jimb
Differential Revision: https://phabricator.services.mozilla.com/D28142 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
438420c8fc
Коммит
c46b8c0571
|
@ -0,0 +1,75 @@
|
|||
/* -*- 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 js_AllocationRecording_h
|
||||
#define js_AllocationRecording_h
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "js/Utility.h"
|
||||
|
||||
namespace JS {
|
||||
|
||||
/**
|
||||
* This struct holds the information needed to create a profiler marker payload
|
||||
* that can represent a JS allocation. It translates JS engine specific classes,
|
||||
* into something that can be used in the profiler.
|
||||
*/
|
||||
struct RecordAllocationInfo {
|
||||
RecordAllocationInfo(const char16_t* typeName, const char* className,
|
||||
const char16_t* descriptiveTypeName,
|
||||
const char* scriptFilename, const char* coarseType,
|
||||
uint64_t size, bool inNursery)
|
||||
: typeName(typeName),
|
||||
className(className),
|
||||
descriptiveTypeName(descriptiveTypeName),
|
||||
scriptFilename(scriptFilename),
|
||||
coarseType(coarseType),
|
||||
size(size),
|
||||
inNursery(inNursery) {}
|
||||
|
||||
// These pointers are borrowed from the UbiNode, and can point to live data.
|
||||
// It is important for the consumers of this struct to correctly
|
||||
// duplicate the strings to take ownership of them.
|
||||
const char16_t* typeName;
|
||||
const char* className;
|
||||
const char16_t* descriptiveTypeName;
|
||||
const char* scriptFilename;
|
||||
|
||||
// The coarseType points to a string literal, so does not need to be
|
||||
// duplicated.
|
||||
const char* coarseType;
|
||||
|
||||
// The size in bytes of the allocation.
|
||||
uint64_t size;
|
||||
|
||||
// Whether or not the allocation is in the nursery or not.
|
||||
bool inNursery;
|
||||
};
|
||||
|
||||
typedef void (*RecordAllocationsCallback)(RecordAllocationInfo&& info);
|
||||
|
||||
/**
|
||||
* Enable recording JS allocations. This feature hooks into the object creation
|
||||
* in the JavaScript engine, and reports back the allocation info through the
|
||||
* callback. This allocation tracking is turned on for all encountered realms.
|
||||
* The JS Debugger API can also turn on allocation tracking with its own
|
||||
* probability. If both allocation tracking mechanisms are turned on at the same
|
||||
* time, the Debugger's probability defers to the EnableRecordingAllocations's
|
||||
* probability setting.
|
||||
*/
|
||||
JS_FRIEND_API void EnableRecordingAllocations(
|
||||
JSContext* cx, RecordAllocationsCallback callback, double probability);
|
||||
|
||||
/**
|
||||
* Turn off JS allocation recording. If any JS Debuggers are also recording
|
||||
* allocations, then the probability will be reset to the Debugger's desired
|
||||
* setting.
|
||||
*/
|
||||
JS_FRIEND_API void DisableRecordingAllocations(JSContext* cx);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
#endif /* js_AllocationRecording_h */
|
|
@ -509,6 +509,11 @@ enum class CoarseType : uint32_t {
|
|||
LAST = DOMNode
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a CoarseType enum into a string. The string is statically allocated.
|
||||
*/
|
||||
JS_PUBLIC_API const char* CoarseTypeToString(CoarseType type);
|
||||
|
||||
inline uint32_t CoarseTypeToUint32(CoarseType type) {
|
||||
return static_cast<uint32_t>(type);
|
||||
}
|
||||
|
|
|
@ -1707,6 +1707,7 @@ JS_PUBLIC_API void JS_FireOnNewGlobalObject(JSContext* cx,
|
|||
cx->check(global);
|
||||
Rooted<js::GlobalObject*> globalObject(cx, &global->as<GlobalObject>());
|
||||
Debugger::onNewGlobalObject(cx, globalObject);
|
||||
cx->runtime()->ensureRealmIsRecordingAllocations(globalObject);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API JSObject* JS_NewObject(JSContext* cx, const JSClass* jsclasp) {
|
||||
|
|
|
@ -116,6 +116,7 @@ EXPORTS += [
|
|||
]
|
||||
|
||||
EXPORTS.js += [
|
||||
'../public/AllocationRecording.h',
|
||||
'../public/AllocPolicy.h',
|
||||
'../public/ArrayBuffer.h',
|
||||
'../public/BuildId.h',
|
||||
|
|
|
@ -3327,7 +3327,12 @@ void Debugger::removeAllocationsTracking(GlobalObject& global) {
|
|||
return;
|
||||
}
|
||||
|
||||
global.realm()->forgetAllocationMetadataBuilder();
|
||||
if (!global.realm()->runtimeFromMainThread()->recordAllocationCallback) {
|
||||
// Something like the Gecko Profiler could request from the the JS runtime
|
||||
// to record allocations. If it is recording allocations, then do not
|
||||
// destroy the allocation metadata builder at this time.
|
||||
global.realm()->forgetAllocationMetadataBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
bool Debugger::addAllocationsTrackingForAllDebuggees(JSContext* cx) {
|
||||
|
|
|
@ -87,7 +87,6 @@ enum class ResumeMode {
|
|||
Return,
|
||||
};
|
||||
|
||||
|
||||
typedef HashSet<WeakHeapPtrGlobalObject,
|
||||
MovableCellHasher<WeakHeapPtrGlobalObject>, ZoneAllocPolicy>
|
||||
WeakGlobalObjectSet;
|
||||
|
@ -391,6 +390,12 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
|||
#ifdef DEBUG
|
||||
static void assertThingIsNotGray(Debugger* dbg) { return; }
|
||||
#endif
|
||||
/*
|
||||
* Return true if the given global is being observed by at least one
|
||||
* Debugger that is tracking allocations.
|
||||
*/
|
||||
static bool isObservedByDebuggerTrackingAllocations(
|
||||
const GlobalObject& debuggee);
|
||||
|
||||
private:
|
||||
GCPtrNativeObject object; /* The Debugger object. Strong reference. */
|
||||
|
@ -449,13 +454,6 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
|||
*/
|
||||
static bool cannotTrackAllocations(const GlobalObject& global);
|
||||
|
||||
/*
|
||||
* Return true if the given global is being observed by at least one
|
||||
* Debugger that is tracking allocations.
|
||||
*/
|
||||
static bool isObservedByDebuggerTrackingAllocations(
|
||||
const GlobalObject& global);
|
||||
|
||||
/*
|
||||
* Add allocations tracking for objects allocated within the given
|
||||
* debuggee's compartment. The given debuggee global must be observed by at
|
||||
|
|
|
@ -613,6 +613,11 @@ void Realm::clearTables() {
|
|||
varNames_.clear();
|
||||
}
|
||||
|
||||
// Check to see if this individual realm is recording allocations. Debuggers or
|
||||
// runtimes can try and record allocations, so this method can check to see if
|
||||
// any initialization is needed.
|
||||
bool Realm::isRecordingAllocations() { return !!allocationMetadataBuilder_; }
|
||||
|
||||
void Realm::setAllocationMetadataBuilder(
|
||||
const js::AllocationMetadataBuilder* builder) {
|
||||
// Clear any jitcode in the runtime, which behaves differently depending on
|
||||
|
@ -623,6 +628,8 @@ void Realm::setAllocationMetadataBuilder(
|
|||
}
|
||||
|
||||
void Realm::forgetAllocationMetadataBuilder() {
|
||||
MOZ_ASSERT(allocationMetadataBuilder_);
|
||||
|
||||
// Unlike setAllocationMetadataBuilder, we don't have to discard all JIT
|
||||
// code here (code is still valid, just a bit slower because it doesn't do
|
||||
// inline GC allocations when a metadata builder is present), but we do want
|
||||
|
|
|
@ -605,6 +605,7 @@ class JS::Realm : public JS::shadow::Realm {
|
|||
const void* addressOfMetadataBuilder() const {
|
||||
return &allocationMetadataBuilder_;
|
||||
}
|
||||
bool isRecordingAllocations();
|
||||
void setAllocationMetadataBuilder(
|
||||
const js::AllocationMetadataBuilder* builder);
|
||||
void forgetAllocationMetadataBuilder();
|
||||
|
@ -797,8 +798,9 @@ class JS::Realm : public JS::shadow::Realm {
|
|||
|
||||
// Recompute the probability with which this realm should record
|
||||
// profiling data (stack traces, allocations log, etc.) about each
|
||||
// allocation. We consult the probabilities requested by the Debugger
|
||||
// instances observing us, if any.
|
||||
// allocation. We first consult the JS runtime to see if it is recording
|
||||
// allocations, and if not then check the probabilities requested by the
|
||||
// Debugger instances observing us, if any.
|
||||
void chooseAllocationSamplingProbability() {
|
||||
savedStacks_.chooseSamplingProbability(this);
|
||||
}
|
||||
|
|
|
@ -847,7 +847,62 @@ JS_FRIEND_API bool JS::IsProfilingEnabledForContext(JSContext* cx) {
|
|||
return cx->runtime()->geckoProfiler().enabled();
|
||||
}
|
||||
|
||||
JS_FRIEND_API void JS::EnableRecordingAllocations(
|
||||
JSContext* cx, JS::RecordAllocationsCallback callback, double probability) {
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(cx->isMainThreadContext());
|
||||
cx->runtime()->startRecordingAllocations(probability, callback);
|
||||
}
|
||||
|
||||
JS_FRIEND_API void JS::DisableRecordingAllocations(JSContext* cx) {
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(cx->isMainThreadContext());
|
||||
cx->runtime()->stopRecordingAllocations();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API void JS::shadow::RegisterWeakCache(
|
||||
JSRuntime* rt, detail::WeakCacheBase* cachep) {
|
||||
rt->registerWeakCache(cachep);
|
||||
}
|
||||
|
||||
void JSRuntime::startRecordingAllocations(
|
||||
double probability, JS::RecordAllocationsCallback callback) {
|
||||
allocationSamplingProbability = probability;
|
||||
recordAllocationCallback = callback;
|
||||
|
||||
// Go through all of the existing realms, and turn on allocation tracking.
|
||||
for (RealmsIter realm(this); !realm.done(); realm.next()) {
|
||||
realm->setAllocationMetadataBuilder(&SavedStacks::metadataBuilder);
|
||||
realm->chooseAllocationSamplingProbability();
|
||||
}
|
||||
}
|
||||
|
||||
void JSRuntime::stopRecordingAllocations() {
|
||||
recordAllocationCallback = nullptr;
|
||||
// Go through all of the existing realms, and turn on allocation tracking.
|
||||
for (RealmsIter realm(this); !realm.done(); realm.next()) {
|
||||
js::GlobalObject* global = realm->maybeGlobal();
|
||||
if (!realm->isDebuggee() || !global ||
|
||||
!Debugger::isObservedByDebuggerTrackingAllocations(*global)) {
|
||||
// Only remove the allocation metadata builder if no Debuggers are
|
||||
// tracking allocations.
|
||||
realm->forgetAllocationMetadataBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function can run to ensure that when new realms are created
|
||||
// they have allocation logging turned on.
|
||||
void JSRuntime::ensureRealmIsRecordingAllocations(
|
||||
Handle<GlobalObject*> global) {
|
||||
if (recordAllocationCallback) {
|
||||
if (!global->realm()->isRecordingAllocations()) {
|
||||
// This is a new realm, turn on allocations for it.
|
||||
global->realm()->setAllocationMetadataBuilder(
|
||||
&SavedStacks::metadataBuilder);
|
||||
}
|
||||
// Ensure the probability is up to date with the current combination of
|
||||
// debuggers and runtime profiling.
|
||||
global->realm()->chooseAllocationSamplingProbability();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "gc/GCRuntime.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "irregexp/RegExpStack.h"
|
||||
#include "js/AllocationRecording.h"
|
||||
#include "js/BuildId.h" // JS::BuildIdOp
|
||||
#include "js/Debug.h"
|
||||
#include "js/experimental/SourceHook.h" // js::SourceHook
|
||||
|
@ -524,6 +525,11 @@ struct JSRuntime : public js::MallocProvider<JSRuntime> {
|
|||
// number of realms visited by RealmsIter.
|
||||
js::MainThreadData<size_t> numRealms;
|
||||
|
||||
// The Gecko Profiler may want to sample the allocations happening across the
|
||||
// browser. This callback can be registered to record the allocation.
|
||||
js::MainThreadData<JS::RecordAllocationsCallback> recordAllocationCallback;
|
||||
js::MainThreadData<double> allocationSamplingProbability;
|
||||
|
||||
private:
|
||||
// Number of debuggee realms in the runtime.
|
||||
js::MainThreadData<size_t> numDebuggeeRealms_;
|
||||
|
@ -540,6 +546,11 @@ struct JSRuntime : public js::MallocProvider<JSRuntime> {
|
|||
void incrementNumDebuggeeRealmsObservingCoverage();
|
||||
void decrementNumDebuggeeRealmsObservingCoverage();
|
||||
|
||||
void startRecordingAllocations(double probability,
|
||||
JS::RecordAllocationsCallback callback);
|
||||
void stopRecordingAllocations();
|
||||
void ensureRealmIsRecordingAllocations(JS::Handle<js::GlobalObject*> global);
|
||||
|
||||
/* Locale-specific callbacks for string conversion. */
|
||||
js::MainThreadData<const JSLocaleCallbacks*> localeCallbacks;
|
||||
|
||||
|
|
|
@ -1798,6 +1798,16 @@ bool SavedStacks::getLocation(JSContext* cx, const FrameIter& iter,
|
|||
}
|
||||
|
||||
void SavedStacks::chooseSamplingProbability(Realm* realm) {
|
||||
{
|
||||
JSRuntime* runtime = realm->runtimeFromMainThread();
|
||||
if (runtime->recordAllocationCallback) {
|
||||
// The runtime is tracking allocations across all realms, in this case
|
||||
// ignore all of the debugger values, and use the runtime's probability.
|
||||
this->setSamplingProbability(runtime->allocationSamplingProbability);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Use unbarriered version to prevent triggering read barrier while
|
||||
// collecting, this is safe as long as global does not escape.
|
||||
GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal();
|
||||
|
@ -1829,6 +1839,10 @@ void SavedStacks::chooseSamplingProbability(Realm* realm) {
|
|||
}
|
||||
MOZ_ASSERT(foundAnyDebuggers);
|
||||
|
||||
this->setSamplingProbability(probability);
|
||||
}
|
||||
|
||||
void SavedStacks::setSamplingProbability(double probability) {
|
||||
if (!bernoulliSeeded) {
|
||||
mozilla::Array<uint64_t, 2> seed;
|
||||
GenerateXorShift128PlusSeed(seed);
|
||||
|
@ -1859,6 +1873,29 @@ JSObject* SavedStacks::MetadataBuilder::build(
|
|||
oomUnsafe.crash("SavedStacksMetadataBuilder");
|
||||
}
|
||||
|
||||
auto recordAllocationCallback =
|
||||
cx->realm()->runtimeFromMainThread()->recordAllocationCallback;
|
||||
if (recordAllocationCallback) {
|
||||
// The following code translates the JS-specific information, into an
|
||||
// RecordAllocationInfo object that can be consumed outside of SpiderMonkey.
|
||||
|
||||
// Do not GC during this operation, strings are being copied out of the JS
|
||||
// engine.
|
||||
AutoCheckCannotGC nogc;
|
||||
|
||||
auto node = JS::ubi::Node(obj.get());
|
||||
|
||||
// Pass the non-SpiderMonkey specific information back to the
|
||||
// callback to get it out of the JS engine. Strings will need to be
|
||||
// copied by the callback. After it is done we release the
|
||||
// AutoCheckCannotGC.
|
||||
recordAllocationCallback(JS::RecordAllocationInfo{
|
||||
node.typeName(), node.jsObjectClassName(), node.descriptiveTypeName(),
|
||||
node.scriptFilename(), JS::ubi::CoarseTypeToString(node.coarseType()),
|
||||
node.size(cx->runtime()->debuggerMallocSizeOf),
|
||||
gc::IsInsideNursery(obj)});
|
||||
}
|
||||
|
||||
MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>());
|
||||
return frame;
|
||||
}
|
||||
|
|
|
@ -227,6 +227,7 @@ class SavedStacks {
|
|||
Handle<SavedFrame::Lookup> lookup);
|
||||
SavedFrame* createFrameFromLookup(JSContext* cx,
|
||||
Handle<SavedFrame::Lookup> lookup);
|
||||
void setSamplingProbability(double probability);
|
||||
|
||||
// Cache for memoizing PCToLineNumber lookups.
|
||||
|
||||
|
|
|
@ -524,5 +524,22 @@ void SetConstructUbiNodeForDOMObjectCallback(JSContext* cx,
|
|||
cx->runtime()->constructUbiNodeForDOMObjectCallback = callback;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API const char* CoarseTypeToString(CoarseType type) {
|
||||
switch (type) {
|
||||
case CoarseType::Other:
|
||||
return "Other";
|
||||
case CoarseType::Object:
|
||||
return "Object";
|
||||
case CoarseType::Script:
|
||||
return "Script";
|
||||
case CoarseType::String:
|
||||
return "String";
|
||||
case CoarseType::DOMNode:
|
||||
return "DOMNode";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ubi
|
||||
} // namespace JS
|
||||
|
|
Загрузка…
Ссылка в новой задаче