Bug 1159507 - make allocation times consistent with timeline; r=fitzgen,mccr8
--- 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