Hi,

I'm trying to use the object metadata hook. I want to record the allocation
point (filename + line number) where each object is allocated, in order to dump
that info for each object measure by the JS memory reporter.

The object metadata hook is odd. I suspect this is because it's not actually
been used in anger. For example:

- The only input that the callback function receives is a |cx|. The actual
  allocated object isn't available. This means that about the only interesting
  thing you could record is... the allocation point, which happens to suit my
  current needs perfectly. Still, odd.

- The callback needs to be installed for every compartment. It's not at all
  clear how to do that. I ended up plumping for doing it in
  CreateGlobalObject() in nsXPConnect.cpp, and in WorkerRun() in
  WorkerPrivate.cpp. But I had to disable a bunch of assertions relating to
  GlobalObjects... there seem to be problems.

- My code is flaky and sometimes crashes, and lots of the measured objects lack
  a metadata object. I don't understand why.

I've included my draft patch below; it's quite a mess. I'd appreciate any
suggestions about the proper way to do this. Thanks!

Nick


diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -866,16 +866,18 @@ private:
   {
     JS::Rooted<JSObject*> global(aCx,
       aWorkerPrivate->CreateGlobalScope(aCx));
     if (!global) {
       NS_WARNING("Failed to make global!");
       return false;
     }

+    JS_MyHook(aCx);
+
     JSAutoCompartment ac(aCx, global);
     return scriptloader::LoadWorkerScript(aCx);
   }
 };

 class CloseEventRunnable MOZ_FINAL : public WorkerRunnable
 {
 public:
diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -141,21 +141,25 @@ struct ClassInfo
     void add(const ClassInfo &other) {
         FOR_EACH_SIZE(ADD_OTHER_SIZE)
     }

     void subtract(const ClassInfo &other) {
         FOR_EACH_SIZE(SUB_OTHER_SIZE)
     }

+    size_t sizeOf() const {
+        size_t n = 0;
+        FOR_EACH_SIZE(ADD_SIZE_TO_N)
+        return n;
+    }
+
     bool isNotable() const {
         static const size_t NotabilityThreshold = 16 * 1024;
-        size_t n = 0;
-        FOR_EACH_SIZE(ADD_SIZE_TO_N)
-        return n >= NotabilityThreshold;
+        return sizeOf() >= NotabilityThreshold;
     }

     size_t sizeOfLiveGCThings() const {
         size_t n = 0;
         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
         return n;
     }

diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2493,16 +2493,52 @@ JS::CompartmentOptionsRef(JSCompartment
 }

 JS::CompartmentOptions &
 JS::CompartmentOptionsRef(JSContext *cx)
 {
     return cx->compartment()->options();
 }

+static bool
+MyObjectMetadataCallback(JSContext *cx, JSObject **pmetadata)
+{
+    // Record the allocation point as a string in the |allocPoint| property of
+    // the metadata object.
+
+    ScriptFrameIter i(cx);
+    static const size_t BUFLEN = 512;
+    char buf[BUFLEN];
+    const char *s;
+    if (i.done()) {
+        s = "<internal>";
+    } else if (i.activation()->scriptedCallerIsHidden()) {
+        s = "<hidden>";
+    } else {
+        s = buf;
+        JS_snprintf(buf, BUFLEN, "%s:%d\n", i.script()->filename(),
+                    js::PCToLineNumber(i.script(), i.pc()));
+    }
+
+    JSAtom *atom = Atomize(cx, s, strlen(s));
+    RootedObject metadata(cx, JS_NewObject(cx, nullptr, NullPtr(), NullPtr()));
+    RootedValue val(cx, STRING_TO_JSVAL(atom));
+    *pmetadata = metadata;
+    return JSObject::defineProperty(cx, metadata,
cx->runtime()->allocPointString,
+                                    val, nullptr, nullptr,
+                                    JSPROP_READONLY | JSPROP_PERMANENT);
+}
+
+JS_PUBLIC_API(void)
+JS_MyHook(JSContext *cx)
+{
+    fprintf(stderr, "JS_MyHook\n");
+    js::SetObjectMetadataCallback(cx, MyObjectMetadataCallback);
+}
+
 JS_PUBLIC_API(JSObject *)
 JS_NewGlobalObject(JSContext *cx, const JSClass *clasp, JSPrincipals
*principals,
                    JS::OnNewGlobalHookOption hookOption,
                    const JS::CompartmentOptions &options /* =
JS::CompartmentOptions() */)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     JS_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2723,16 +2723,19 @@ CompartmentOptionsRef(JSContext *cx);
 // breakpoints.
 enum OnNewGlobalHookOption {
     FireOnNewGlobalHook,
     DontFireOnNewGlobalHook
 };

 } /* namespace JS */

+extern JS_PUBLIC_API(void)
+JS_MyHook(JSContext *cx);
+
 extern JS_PUBLIC_API(JSObject *)
 JS_NewGlobalObject(JSContext *cx, const JSClass *clasp, JSPrincipals
*principals,
                    JS::OnNewGlobalHookOption hookOption,
                    const JS::CompartmentOptions &options =
JS::CompartmentOptions());

 extern JS_PUBLIC_API(void)
 JS_FireOnNewGlobalObject(JSContext *cx, JS::HandleObject global);

diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -120,16 +120,17 @@ JSRuntime::initializeAtoms(JSContext *cx
     atoms_ = cx->new_<AtomSet>();
     if (!atoms_ || !atoms_->init(JS_STRING_HASH_COUNT))
         return false;

     if (parentRuntime) {
         staticStrings = parentRuntime->staticStrings;
         commonNames = parentRuntime->commonNames;
         emptyString = parentRuntime->emptyString;
+        allocPointString = parentRuntime->allocPointString;
         permanentAtoms = parentRuntime->permanentAtoms;
         return true;
     }

     permanentAtoms = cx->new_<AtomSet>();
     if (!permanentAtoms || !permanentAtoms->init(JS_STRING_HASH_COUNT))
         return false;

@@ -155,16 +156,17 @@ JSRuntime::initializeAtoms(JSContext *cx
         JSAtom *atom = Atomize(cx, cachedNames[i].str,
cachedNames[i].length, InternAtom);
         if (!atom)
             return false;
         names->init(atom->asPropertyName());
     }
     JS_ASSERT(uintptr_t(names) == uintptr_t(commonNames + 1));

     emptyString = commonNames->empty;
+    allocPointString = commonNames->allocPoint;
     return true;
 }

 void
 JSRuntime::finishAtoms()
 {
     if (atoms_)
         js_delete(atoms_);
@@ -180,16 +182,17 @@ JSRuntime::finishAtoms()
             js_delete(permanentAtoms);
     }

     atoms_ = nullptr;
     staticStrings = nullptr;
     commonNames = nullptr;
     permanentAtoms = nullptr;
     emptyString = nullptr;
+    allocPointString = nullptr;
 }

 void
 js::MarkAtoms(JSTracer *trc)
 {
     JSRuntime *rt = trc->runtime;
     for (AtomSet::Enum e(rt->atoms()); !e.empty(); e.popFront()) {
         const AtomStateEntry &entry = e.front();
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -7,16 +7,17 @@
 /* A higher-order macro for enumerating all cached property names. */

 #ifndef vm_CommonPropertyNames_h
 #define vm_CommonPropertyNames_h

 #include "jsprototypes.h"

 #define FOR_EACH_COMMON_PROPERTYNAME(macro) \
+    macro(allocPoint, allocPoint, "allocPoint") \
     macro(anonymous, anonymous, "anonymous") \
     macro(Any, Any, "Any") \
     macro(apply, apply, "apply") \
     macro(arguments, arguments, "arguments") \
     macro(as, as, "as") \
     macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
     macro(ArrayType, ArrayType, "ArrayType") \
     macro(ArrayValues, ArrayValues, "ArrayValues") \
diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -127,32 +127,36 @@ class GlobalObject : public JSObject
     static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
                   "global object slot counts are inconsistent");

     /* Initialize the Function and Object classes.  Must only be
called once! */
     JSObject *
     initFunctionAndObjectClasses(JSContext *cx);

     void setThrowTypeError(JSFunction *fun) {
-        JS_ASSERT(getSlotRef(THROWTYPEERROR).isUndefined());
+        // njn
+        //JS_ASSERT(getSlotRef(THROWTYPEERROR).isUndefined());
         setSlot(THROWTYPEERROR, ObjectValue(*fun));
     }

     void setOriginalEval(JSObject *evalobj) {
-        JS_ASSERT(getSlotRef(EVAL).isUndefined());
+        // njn
+        //JS_ASSERT(getSlotRef(EVAL).isUndefined());
         setSlot(EVAL, ObjectValue(*evalobj));
     }

     void setProtoGetter(JSFunction *protoGetter) {
-        JS_ASSERT(getSlotRef(PROTO_GETTER).isUndefined());
+        // njn
+        //JS_ASSERT(getSlotRef(PROTO_GETTER).isUndefined());
         setSlot(PROTO_GETTER, ObjectValue(*protoGetter));
     }

     void setIntrinsicsHolder(JSObject *obj) {
-        JS_ASSERT(getSlotRef(INTRINSICS).isUndefined());
+        // njn
+        //JS_ASSERT(getSlotRef(INTRINSICS).isUndefined());
         setSlot(INTRINSICS, ObjectValue(*obj));
     }

     // Emit the specified warning if the given slot in |obj|'s global isn't
     // true, then set the slot to true.  Thus calling this method warns once
     // for each global object it's called on, and every other call does
     // nothing.
     static bool
@@ -225,19 +229,20 @@ class GlobalObject : public JSObject

     bool isStandardClassResolved(const js::Class *clasp) const {
         JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp);
         return isStandardClassResolved(key);
     }

   private:
     void setDetailsForKey(JSProtoKey key, JSObject *ctor, JSObject *proto) {
-        JS_ASSERT(getConstructor(key).isUndefined());
-        JS_ASSERT(getPrototype(key).isUndefined());
-        JS_ASSERT(getConstructorPropertySlot(key).isUndefined());
+        // njn: hmm
+        //JS_ASSERT(getConstructor(key).isUndefined());
+        //JS_ASSERT(getPrototype(key).isUndefined());
+        //JS_ASSERT(getConstructorPropertySlot(key).isUndefined());
         setConstructor(key, ObjectValue(*ctor));
         setPrototype(key, ObjectValue(*proto));
         setConstructorPropertySlot(key, ObjectValue(*ctor));
     }

     void setObjectClassDetails(JSFunction *ctor, JSObject *proto) {
         setDetailsForKey(JSProto_Object, ctor, proto);
     }
diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -357,16 +357,40 @@ StatsCellCallback(JSRuntime *rt, void *d
       case JSTRACE_OBJECT: {
         JSObject *obj = static_cast<JSObject *>(thing);
         CompartmentStats *cStats = GetCompartmentStats(obj->compartment());

         JS::ClassInfo info;        // This zeroes all the sizes.
         info.objectsGCHeap += thingSize;
         obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);

+        const char* s;
+        JSObject *metadata = obj->getMetadata();
+        if (metadata) {
+            // Global objects lack metadata.
+            // njn: null cx is dodgy
+            JSContext *cx = nullptr;
+            JS::Value value;
+            // njn: allocPointString isn't a great name
+            GetPropertyPure(cx, metadata, rt->allocPointString, &value);
+            static const size_t BUFLEN = 256;
+            char buf[BUFLEN];
+            if (value.isString()) {
+                JSString *str = value.toString();
+                s = buf;
+                // njn: prolly should check asFlat() is ok
+                PutEscapedString(buf, BUFLEN, str->asFlat().chars(),
str->length(), /* quote = */ 0);
+            } else {
+                s = "???";
+            }
+        } else {
+            s = "(no metadata)";
+        }
+        fprintf(stderr, "live: %s : %d\n", s, int(info.sizeOf()));
+
         cStats->classInfo.add(info);

         const char* className = obj->getClass()->name;
         AddClassInfo(granularity, cStats, className, info);

         if (ObjectPrivateVisitor *opv = closure->opv) {
             nsISupports *iface;
             if (opv->getISupports_(obj, &iface) && iface)
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -243,16 +243,17 @@ JSRuntime::JSRuntime(JSRuntime *parentRu
 #ifdef JS_ARM_SIMULATOR
     simulatorRuntime_(nullptr),
 #endif
     scriptAndCountsVector(nullptr),
     NaNValue(DoubleNaNValue()),
     negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
     positiveInfinityValue(DoubleValue(PositiveInfinity<double>())),
     emptyString(nullptr),
+    allocPointString(nullptr),
     debugMode(false),
     spsProfiler(thisFromCtor()),
     profilingScripts(false),
     alwaysPreserveCode(false),
     hadOutOfMemory(false),
     haveCreatedContext(false),
     data(nullptr),
     gcLock(nullptr),
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1302,16 +1302,19 @@ struct JSRuntime : public JS::shadow::Ru
     js::ScriptAndCountsVector *scriptAndCountsVector;

     /* Well-known numbers held for use by this runtime's contexts. */
     const js::Value     NaNValue;
     const js::Value     negativeInfinityValue;
     const js::Value     positiveInfinityValue;

     js::PropertyName    *emptyString;
+    // njn: this must be in the JSRuntime instead of the JSContext so that it
+    // can be used from the memory reporting code.
+    js::PropertyName    *allocPointString;

     /* List of active contexts sharing this runtime. */
     mozilla::LinkedList<JSContext> contextList;

     bool hasContexts() const {
         return !contextList.isEmpty();
     }

diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1089,17 +1089,17 @@ JSObject::removeProperty(ExclusiveContex
 #ifdef DEBUG
             /*
              * Check the consistency of the table but limit the number of
              * checks not to alter significantly the complexity of the
              * delete in debug builds, see bug 534493.
              */
             Shape *aprop = self->lastProperty();
             for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent)
-                JS_ASSERT_IF(aprop != shape, self->nativeContains(cx, aprop));
+                {}//JS_ASSERT_IF(aprop != shape,
self->nativeContains(cx, aprop));
 #endif
         }

         {
             /* Remove shape from its non-circular doubly linked list. */
             Shape *oldLastProp = self->lastProperty();
             shape->removeFromDictionary(self);

diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -394,16 +394,18 @@ CreateGlobalObject(JSContext *cx, const
         MOZ_ASSERT(trc.ok, "Trace hook on global needs to call
TraceXPCGlobal for XPConnect compartments.");
     }
 #endif

     if (clasp->flags & JSCLASS_DOM_GLOBAL) {
         AllocateProtoAndIfaceCache(global);
     }

+    JS_MyHook(cx);
+
     return global;
 }

 } // namespace xpc

 NS_IMETHODIMP
 nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext,
                                              nsISupports *aCOMObj,
_______________________________________________
dev-tech-js-engine-internals mailing list
[email protected]
https://lists.mozilla.org/listinfo/dev-tech-js-engine-internals

Reply via email to