Bug 870043 - Add a script-accessible statistics for various watchdog events. r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Wed, 24 Jul 2013 15:33:31 -0700
changeset 139887 6064e8e0439bf0e598c346f656dfdc8545389103
parent 139886 3f13ae245c8815f868452107bf29ae3a50e3f9ac
child 139888 be80ef1cacc25cca2cc92d7038d1aef5bacc6e70
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmrbkap
bugs870043
milestone25.0a1
Bug 870043 - Add a script-accessible statistics for various watchdog events. r=mrbkap We need this even for testing wakeups, because we can't be certain that any given operation callback was necessarily triggered from the watchdog thread (since it's triggered from within the JS engine in various cases as well).
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -114,17 +114,17 @@ interface nsIXPCComponents_utils_Sandbox
 interface ScheduledGCCallback : nsISupports
 {
     void callback();
 };
 
 /**
 * interface of Components.utils
 */
-[scriptable, uuid(fdd32d38-9341-4067-9000-d781075a60c9)]
+[scriptable, uuid(35d5b0e5-9b29-48de-9f79-d2534b497435)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
      * It will report a JS Error object to the JS console, and return. It
      * is meant for use in exception handler blocks which want to "eat"
      * an exception, but still want to report it to the console.
@@ -421,16 +421,28 @@ interface nsIXPCComponents_Utils : nsISu
     [implicit_jscontext]
     string getClassName(in jsval aObj, in bool aUnwrap);
 
     /**
      * Get a DOM classinfo for the given classname.  Only some class
      * names are supported.
      */
     nsIClassInfo getDOMClassInfo(in AString aClassName);
+
+    /**
+      * Retrieve the last time, in microseconds since epoch, that a given
+      * watchdog-related event occured.
+      *
+      * Valid categories:
+      *   "RuntimeStateChange"      - Runtime switching between active and inactive states
+      *   "WatchdogWakeup"          - Watchdog waking up from sleeping
+      *   "WatchdogHibernateStart"  - Watchdog begins hibernating
+      *   "WatchdogHibernateStop"   - Watchdog stops hibernating
+      */
+    PRTime getWatchdogTimestamp(in AString aCategory);
 };
 
 /**
 * interface of JavaScript's 'Components' object
 */
 [scriptable, uuid(8406dedb-23cc-42db-9f69-1f18785091b5)]
 interface nsIXPCComponents : nsISupports
 {
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -4536,16 +4536,34 @@ nsXPCComponents_Utils::GetClassName(cons
 NS_IMETHODIMP
 nsXPCComponents_Utils::GetDOMClassInfo(const nsAString& aClassName,
                                        nsIClassInfo** aClassInfo)
 {
     *aClassInfo = nullptr;
     return NS_ERROR_NOT_AVAILABLE;
 }
 
+NS_IMETHODIMP
+nsXPCComponents_Utils::GetWatchdogTimestamp(const nsAString& aCategory, PRTime *aOut)
+{
+    WatchdogTimestampCategory category;
+    if (aCategory.EqualsLiteral("RuntimeStateChange"))
+        category = TimestampRuntimeStateChange;
+    else if (aCategory.EqualsLiteral("WatchdogWakeup"))
+        category = TimestampWatchdogWakeup;
+    else if (aCategory.EqualsLiteral("WatchdogHibernateStart"))
+        category = TimestampWatchdogHibernateStart;
+    else if (aCategory.EqualsLiteral("WatchdogHibernateStop"))
+        category = TimestampWatchdogHibernateStop;
+    else
+        return NS_ERROR_INVALID_ARG;
+    *aOut = XPCJSRuntime::Get()->GetWatchdogTimestamp(category);
+    return NS_OK;
+}
+
 /***************************************************************************/
 /***************************************************************************/
 /***************************************************************************/
 
 // XXXjband We ought to cache the wrapper in the object's slots rather than
 // re-wrapping on demand
 
 NS_INTERFACE_MAP_BEGIN(nsXPCComponents)
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -990,21 +990,25 @@ class Watchdog
     bool mHibernating;
     bool mInitialized;
     bool mShuttingDown;
 };
 
 class WatchdogManager : public nsIObserver
 {
   public:
+
     NS_DECL_ISUPPORTS
     WatchdogManager(XPCJSRuntime *aRuntime) : mRuntime(aRuntime)
                                             , mRuntimeState(RUNTIME_INACTIVE)
-                                            , mTimeAtLastRuntimeStateChange(PR_Now())
     {
+        // All the timestamps start at zero except for runtime state change.
+        PodArrayZero(mTimestamps);
+        mTimestamps[TimestampRuntimeStateChange] = PR_Now();
+
         // Enable the watchdog, if appropriate.
         RefreshWatchdog();
 
         // Register ourselves as an observer to get updates on the pref.
         mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog");
     }
     virtual ~WatchdogManager()
     {
@@ -1024,33 +1028,53 @@ class WatchdogManager : public nsIObserv
 
     // Runtime statistics. These live on the watchdog manager, are written
     // from the main thread, and are read from the watchdog thread (holding
     // the lock in each case).
     void
     RecordRuntimeActivity(bool active)
     {
         // The watchdog reads this state, so acquire the lock before writing it.
+        MOZ_ASSERT(NS_IsMainThread());
         Maybe<AutoLockWatchdog> lock;
         if (mWatchdog)
             lock.construct(mWatchdog);
 
         // Write state.
-        mTimeAtLastRuntimeStateChange = PR_Now();
+        mTimestamps[TimestampRuntimeStateChange] = PR_Now();
         mRuntimeState = active ? RUNTIME_ACTIVE : RUNTIME_INACTIVE;
 
         // The watchdog may be hibernating, waiting for the runtime to go
         // active. Wake it up if necessary.
         if (active && mWatchdog && mWatchdog->Hibernating())
             mWatchdog->WakeUp();
     }
     bool IsRuntimeActive() { return mRuntimeState == RUNTIME_ACTIVE; }
     PRTime TimeSinceLastRuntimeStateChange()
     {
-        return PR_Now() - mTimeAtLastRuntimeStateChange;
+        return PR_Now() - GetTimestamp(TimestampRuntimeStateChange);
+    }
+
+    // Note - Because of the runtime activity timestamp, these are read and
+    // written from both threads.
+    void RecordTimestamp(WatchdogTimestampCategory aCategory)
+    {
+        // The watchdog thread always holds the lock when it runs.
+        Maybe<AutoLockWatchdog> maybeLock;
+        if (NS_IsMainThread() && mWatchdog)
+            maybeLock.construct(mWatchdog);
+        mTimestamps[aCategory] = PR_Now();
+    }
+    PRTime GetTimestamp(WatchdogTimestampCategory aCategory)
+    {
+        // The watchdog thread always holds the lock when it runs.
+        Maybe<AutoLockWatchdog> maybeLock;
+        if (NS_IsMainThread() && mWatchdog)
+            maybeLock.construct(mWatchdog);
+        return mTimestamps[aCategory];
     }
 
     XPCJSRuntime* Runtime() { return mRuntime; }
     Watchdog* GetWatchdog() { return mWatchdog; }
 
     void RefreshWatchdog()
     {
         bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true);
@@ -1076,17 +1100,17 @@ class WatchdogManager : public nsIObserv
         mWatchdog = nullptr;
     }
 
   private:
     XPCJSRuntime *mRuntime;
     nsAutoPtr<Watchdog> mWatchdog;
 
     enum { RUNTIME_ACTIVE, RUNTIME_INACTIVE } mRuntimeState;
-    PRTime mTimeAtLastRuntimeStateChange;
+    PRTime mTimestamps[TimestampCount];
 };
 
 NS_IMPL_ISUPPORTS1(WatchdogManager, nsIObserver)
 
 AutoLockWatchdog::AutoLockWatchdog(Watchdog *aWatchdog) : mWatchdog(aWatchdog)
 {
     PR_Lock(mWatchdog->GetLock());
 }
@@ -1111,33 +1135,44 @@ WatchdogMain(void *arg)
     MOZ_ASSERT(!self->ShuttingDown());
     while (!self->ShuttingDown()) {
         // Sleep only 1 second if recently (or currently) active; otherwise, hibernate
         if (manager->IsRuntimeActive() ||
             manager->TimeSinceLastRuntimeStateChange() <= PRTime(2*PR_USEC_PER_SEC))
         {
             self->Sleep(PR_TicksPerSecond());
         } else {
+            manager->RecordTimestamp(TimestampWatchdogHibernateStart);
             self->Hibernate();
+            manager->RecordTimestamp(TimestampWatchdogHibernateStop);
         }
 
+        // Rise and shine.
+        manager->RecordTimestamp(TimestampWatchdogWakeup);
+
         // Don't trigger the operation callback if activity started less than one second ago.
         // The callback is only used for detecting long running scripts, and triggering the
         // callback from off the main thread can be expensive.
         if (manager->IsRuntimeActive() &&
             manager->TimeSinceLastRuntimeStateChange() >= PRTime(PR_USEC_PER_SEC))
         {
             JS_TriggerOperationCallback(manager->Runtime()->Runtime());
         }
     }
 
     // Tell the manager that we've shut down.
     self->Finished();
 }
 
+PRTime
+XPCJSRuntime::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory)
+{
+    return mWatchdogManager->GetTimestamp(aCategory);
+}
+
 //static
 void
 XPCJSRuntime::ActivityCallback(void *arg, JSBool active)
 {
     XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
     self->mWatchdogManager->RecordRuntimeActivity(active);
 }
 
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -591,16 +591,26 @@ public:
     }
 };
 
 // In the current xpconnect system there can only be one XPCJSRuntime.
 // So, xpconnect can only be used on one JSRuntime within the process.
 
 class XPCJSContextStack;
 class WatchdogManager;
+
+enum WatchdogTimestampCategory
+{
+    TimestampRuntimeStateChange = 0,
+    TimestampWatchdogWakeup,
+    TimestampWatchdogHibernateStart,
+    TimestampWatchdogHibernateStop,
+    TimestampCount
+};
+
 class XPCJSRuntime : public mozilla::CycleCollectedJSRuntime
 {
 public:
     static XPCJSRuntime* newXPCJSRuntime(nsXPConnect* aXPConnect);
     static XPCJSRuntime* Get() { return nsXPConnect::XPConnect()->GetRuntime(); }
 
     // Make this public for now.  Ideally we'd hide the JSRuntime inside.
     JSRuntime* Runtime() const
@@ -812,16 +822,19 @@ public:
                                        js::CTypesActivityType type);
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     AutoMarkingPtr**  GetAutoRootsAdr() {return &mAutoRoots;}
 
     JSObject* GetJunkScope();
     void DeleteJunkScope();
+
+    PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory);
+
 private:
     XPCJSRuntime(); // no implementation
     XPCJSRuntime(nsXPConnect* aXPConnect);
 
     void ReleaseIncrementally(nsTArray<nsISupports *> &array);
 
     static const char* mStrings[IDX_TOTAL_COUNT];
     jsid mStrIDs[IDX_TOTAL_COUNT];