Bug 1159507 - make allocation times consistent with timeline; r=fitzgen,mccr8
authorTom Tromey <tromey@mozilla.com>
Wed, 17 Jun 2015 13:01:27 -0700
changeset 273144 08fd89d1427fd71a133041136800c197742a7539
parent 273143 20e75f85523ee382a1a5778b92f8cc0c1f65a353
child 273145 df312e4cadefa13a0bb001b360091f636f03c794
push id2946
push usercliu@mozilla.com
push dateThu, 18 Jun 2015 00:55:19 +0000
reviewersfitzgen, mccr8
bugs1159507
milestone41.0a1
Bug 1159507 - make allocation times consistent with timeline; r=fitzgen,mccr8
js/src/doc/Debugger/Conventions.md
js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/SavedStacks.cpp
toolkit/devtools/server/tests/mochitest/test_memory_allocations_05.html
xpcom/build/XPCOMInit.cpp
--- a/js/src/doc/Debugger/Conventions.md
+++ b/js/src/doc/Debugger/Conventions.md
@@ -133,18 +133,19 @@ If a function that would normally return
 how the debuggee should continue instead throws an exception, we never
 propagate such an exception to the debuggee; instead, we call the
 associated `Debugger` instance's `uncaughtExceptionHook` property, as
 described below.
 
 
 ## Timestamps
 
-Timestamps are expressed in units of microseconds since the epoch (midnight,
-January 1st, 1970).
+Timestamps are expressed in units of milliseconds since an arbitrary,
+but fixed, epoch.  The resolution of timestamps is generally greater
+than milliseconds, though no specific resolution is guaranteed.
 
 
 ## The `Debugger.DebuggeeWouldRun` Exception
 
 Some debugger operations that appear to simply inspect the debuggee's state
 may actually cause debuggee code to run. For example, reading a variable
 might run a getter function on the global or on a `with` expression's
 operand; and getting an object's property descriptor will run a handler
--- a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js
@@ -1,28 +1,28 @@
 // Test that drainAllocationsLog returns some timestamps.
 
 load(libdir + 'asserts.js');
 
 var allocTimes = [];
 
-allocTimes.push(1000 * dateNow());
+allocTimes.push(dateNow());
 
 const root = newGlobal();
 const dbg = new Debugger(root);
 
 dbg.memory.trackingAllocationSites = true;
 root.eval("this.alloc1 = {}");
-allocTimes.push(1000 * dateNow());
+allocTimes.push(dateNow());
 root.eval("this.alloc2 = {}");
-allocTimes.push(1000 * dateNow());
+allocTimes.push(dateNow());
 root.eval("this.alloc3 = {}");
-allocTimes.push(1000 * dateNow());
+allocTimes.push(dateNow());
 root.eval("this.alloc4 = {}");
-allocTimes.push(1000 * dateNow());
+allocTimes.push(dateNow());
 
 allocs = dbg.memory.drainAllocationsLog();
 assertEq(allocs.length >= 4, true);
 assertEq(allocs[0].timestamp >= allocTimes[0], true);
 var seenAlloc = 0;
 var lastIndexSeenAllocIncremented = 0;
 for (i = 1; i < allocs.length; ++i) {
     assertEq(allocs[i].timestamp >= allocs[i - 1].timestamp, true);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -658,16 +658,32 @@ JS_SetICUMemoryFunctions(JS_ICUAllocFn a
     UErrorCode status = U_ZERO_ERROR;
     u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status);
     return U_SUCCESS(status);
 #else
     return true;
 #endif
 }
 
+static JS_CurrentEmbedderTimeFunction currentEmbedderTimeFunction;
+
+JS_PUBLIC_API(void)
+JS_SetCurrentEmbedderTimeFunction(JS_CurrentEmbedderTimeFunction timeFn)
+{
+    currentEmbedderTimeFunction = timeFn;
+}
+
+JS_PUBLIC_API(double)
+JS_GetCurrentEmbedderTime()
+{
+    if (currentEmbedderTimeFunction)
+        return currentEmbedderTimeFunction();
+    return PRMJ_Now() / static_cast<double>(PRMJ_USEC_PER_MSEC);
+}
+
 JS_PUBLIC_API(void*)
 JS_GetRuntimePrivate(JSRuntime* rt)
 {
     return rt->data;
 }
 
 JS_PUBLIC_API(void)
 JS_SetRuntimePrivate(JSRuntime* rt, void* data)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1048,16 +1048,35 @@ typedef void* (*JS_ICUAllocFn)(const voi
 typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size);
 typedef void (*JS_ICUFreeFn)(const void*, void* p);
 
 // This function can be used to track memory used by ICU.
 // Do not use it unless you know what you are doing!
 extern JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn);
 
+typedef double (*JS_CurrentEmbedderTimeFunction)();
+
+/*
+ * The embedding can specify a time function that will be used in some
+ * situations.  The function can return the time however it likes; but
+ * the norm is to return times in units of milliseconds since an
+ * arbitrary, but consistent, epoch.  If the time function is not set,
+ * a built-in default will be used.
+ */
+JS_PUBLIC_API(void)
+JS_SetCurrentEmbedderTimeFunction(JS_CurrentEmbedderTimeFunction timeFn);
+
+/*
+ * Return the time as computed using the current time function, or a
+ * suitable default if one has not been set.
+ */
+JS_PUBLIC_API(double)
+JS_GetCurrentEmbedderTime();
+
 JS_PUBLIC_API(void*)
 JS_GetRuntimePrivate(JSRuntime* rt);
 
 extern JS_PUBLIC_API(JSRuntime*)
 JS_GetRuntime(JSContext* cx);
 
 extern JS_PUBLIC_API(JSRuntime*)
 JS_GetParentRuntime(JSContext* cx);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1647,17 +1647,17 @@ Debugger::slowPathOnNewGlobalObject(JSCo
                 break;
         }
     }
     MOZ_ASSERT(!cx->isExceptionPending());
 }
 
 /* static */ bool
 Debugger::slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
-                                      int64_t when, GlobalObject::DebuggerVector& dbgs)
+                                      double when, GlobalObject::DebuggerVector& dbgs)
 {
     MOZ_ASSERT(!dbgs.empty());
     mozilla::DebugOnly<Debugger**> begin = dbgs.begin();
 
     for (Debugger** dbgp = dbgs.begin(); dbgp < dbgs.end(); dbgp++) {
         // The set of debuggers had better not change while we're iterating,
         // such that the vector gets reallocated.
         MOZ_ASSERT(dbgs.begin() == begin);
@@ -1695,17 +1695,17 @@ Debugger::slowPathOnIonCompilation(JSCon
 bool
 Debugger::isDebuggee(const JSCompartment* compartment) const
 {
     MOZ_ASSERT(compartment);
     return compartment->isDebuggee() && debuggees.has(compartment->maybeGlobal());
 }
 
 /* static */ Debugger::AllocationSite*
-Debugger::AllocationSite::create(JSContext* cx, HandleObject frame, int64_t when, HandleObject obj)
+Debugger::AllocationSite::create(JSContext* cx, HandleObject frame, double when, HandleObject obj)
 {
     assertSameCompartment(cx, frame);
 
     RootedAtom ctorName(cx);
     {
         AutoCompartment ac(cx, obj);
         if (!obj->constructorDisplayAtom(cx, &ctorName))
             return nullptr;
@@ -1718,17 +1718,17 @@ Debugger::AllocationSite::create(JSConte
     allocSite->className = obj->getClass()->name;
     allocSite->ctorName = ctorName.get();
     return allocSite;
 }
 
 
 bool
 Debugger::appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
-                               int64_t when)
+                               double when)
 {
     MOZ_ASSERT(trackingAllocationSites);
 
     AutoCompartment ac(cx, object);
     RootedObject wrappedFrame(cx, frame);
     if (!cx->compartment()->wrap(cx, &wrappedFrame))
         return false;
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -269,47 +269,47 @@ class Debugger : private mozilla::Linked
     JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */
 
     // The set of GC numbers for which one or more of this Debugger's observed
     // debuggees participated in.
     js::HashSet<uint64_t> observedGCs;
 
     struct AllocationSite : public mozilla::LinkedListElement<AllocationSite>
     {
-        AllocationSite(HandleObject frame, int64_t when)
+        AllocationSite(HandleObject frame, double when)
             : frame(frame),
               when(when),
               className(nullptr),
               ctorName(nullptr)
         {
             MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
         };
 
-        static AllocationSite* create(JSContext* cx, HandleObject frame, int64_t when,
+        static AllocationSite* create(JSContext* cx, HandleObject frame, double when,
                                       HandleObject obj);
 
         RelocatablePtrObject frame;
-        int64_t when;
+        double when;
         const char* className;
         RelocatablePtrAtom ctorName;
     };
     typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;
 
     bool allowUnobservedAsmJS;
     bool trackingAllocationSites;
     double allocationSamplingProbability;
     AllocationSiteList allocationsLog;
     size_t allocationsLogLength;
     size_t maxAllocationsLogLength;
     bool allocationsLogOverflowed;
 
     static const size_t DEFAULT_MAX_ALLOCATIONS_LOG_LENGTH = 5000;
 
     bool appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
-                              int64_t when);
+                              double when);
     void emptyAllocationsLog();
 
     /*
      * Return true if there is an existing object metadata callback for the
      * given global's compartment that will prevent our instrumentation of
      * allocations.
      */
     static bool cannotTrackAllocations(const GlobalObject& global);
@@ -545,17 +545,17 @@ class Debugger : private mozilla::Linked
 
     static JSTrapStatus slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame);
     static bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok);
     static JSTrapStatus slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame);
     static JSTrapStatus slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame);
     static void slowPathOnNewScript(JSContext* cx, HandleScript script);
     static void slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
     static bool slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
-                                            int64_t when, GlobalObject::DebuggerVector& dbgs);
+                                            double when, GlobalObject::DebuggerVector& dbgs);
     static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);
     static void slowPathOnIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph);
 
     template <typename HookIsEnabledFun /* bool (Debugger*) */,
               typename FireHookFun /* JSTrapStatus (Debugger*) */>
     static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
                                      FireHookFun fireHook);
 
@@ -708,17 +708,17 @@ class Debugger : private mozilla::Linked
      * throw, or vice versa: we can redirect to a complete copy of the
      * alternative path, containing its own call to onLeaveFrame.)
      */
     static inline bool onLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok);
 
     static inline void onNewScript(JSContext* cx, HandleScript script);
     static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
     static inline bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame,
-                                           int64_t when);
+                                           double when);
     static inline bool observesIonCompilation(JSContext* cx);
     static inline void onIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph);
     static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp);
     static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp);
     static bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to);
     static bool handleIonBailout(JSContext* cx, jit::RematerializedFrame* from, jit::BaselineFrame* to);
     static void handleUnrecoverableIonBailoutError(JSContext* cx, jit::RematerializedFrame* frame);
     static void propagateForcedReturn(JSContext* cx, AbstractFramePtr frame, HandleValue rval);
@@ -1011,17 +1011,17 @@ Debugger::onNewGlobalObject(JSContext* c
 #ifdef DEBUG
     global->compartment()->firedOnNewGlobalObject = true;
 #endif
     if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers))
         Debugger::slowPathOnNewGlobalObject(cx, global);
 }
 
 /* static */ bool
-Debugger::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, int64_t when)
+Debugger::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, double when)
 {
     GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers();
     if (!dbgs || dbgs->empty())
         return true;
     RootedObject hobj(cx, obj);
     return Debugger::slowPathOnLogAllocationSite(cx, hobj, frame, when, *dbgs);
 }
 
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -1216,17 +1216,17 @@ SavedStacksMetadataCallback(JSContext* c
         stacks.allocationSkipCount = std::floor(std::log(random_nextDouble(&stacks.rngState)) /
                                                 std::log(notSamplingProb));
     }
 
     RootedSavedFrame frame(cx);
     if (!stacks.saveCurrentStack(cx, &frame))
         CrashAtUnhandlableOOM("SavedStacksMetadataCallback");
 
-    if (!Debugger::onLogAllocationSite(cx, obj, frame, PRMJ_Now()))
+    if (!Debugger::onLogAllocationSite(cx, obj, frame, JS_GetCurrentEmbedderTime()))
         CrashAtUnhandlableOOM("SavedStacksMetadataCallback");
 
     MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>());
     return frame;
 }
 
 JS_FRIEND_API(JSPrincipals*)
 GetSavedFramePrincipals(HandleObject savedFrame)
--- a/toolkit/devtools/server/tests/mochitest/test_memory_allocations_05.html
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_allocations_05.html
@@ -65,17 +65,17 @@ window.onload = function() {
     for (var i = 0; i < 3; i++) {
       var timestamp = response.allocationsTimestamps[allocatorIndices[i]];
       info("timestamp", timestamp);
       ok(timestamp, "We should have a timestamp for the `allocator` allocation.");
 
       if (lastTimestamp) {
         var delta = timestamp - lastTimestamp;
         info("delta since last timestamp", delta);
-        ok(delta >= 1000 /* 1 ms */,
+        ok(delta >= 1 /* ms */,
            "The timestamp should be about 1 ms after the last timestamp.");
       }
 
       lastTimestamp = timestamp;
     }
 
     yield memory.detach();
     destroyServerAndFinish(client);
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -467,16 +467,23 @@ private:
 };
 
 NS_IMPL_ISUPPORTS(NesteggReporter, nsIMemoryReporter)
 
 /* static */ template<> Atomic<size_t>
 CountingAllocatorBase<NesteggReporter>::sAmount(0);
 #endif /* MOZ_WEBM */
 
+static double
+TimeSinceProcessCreation()
+{
+  bool ignore;
+  return (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds();
+}
+
 // Note that on OSX, aBinDirectory will point to .app/Contents/Resources/browser
 EXPORT_XPCOM_API(nsresult)
 NS_InitXPCOM2(nsIServiceManager** aResult,
               nsIFile* aBinDirectory,
               nsIDirectoryServiceProvider* aAppFileLocationProvider)
 {
   static bool sInitialized = false;
   if (sInitialized) {
@@ -484,16 +491,18 @@ NS_InitXPCOM2(nsIServiceManager** aResul
   }
 
   sInitialized = true;
 
   mozPoisonValueInit();
 
   NS_LogInit();
 
+  JS_SetCurrentEmbedderTimeFunction(TimeSinceProcessCreation);
+
   char aLocal;
   profiler_init(&aLocal);
   nsresult rv = NS_OK;
 
   // We are not shutting down
   gXPCOMShuttingDown = false;
 
   // Initialize the available memory tracker before other threads have had a