Bug 1275314 - Add an API to allow flushing out in-progress checkerboard reports. r=botond,ehsan
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 14 Oct 2016 15:37:58 -0400
changeset 425733 77054c32f5dfb00945a85ea175af677a918c2120
parent 425732 bb4cb6a2db958cc42758bafd7eca92ac3eb2e3e4
child 425734 73a60f9b3c2f64768a312327f5c0befd460de446
push id32504
push userbmo:gasolin@mozilla.com
push dateMon, 17 Oct 2016 02:37:54 +0000
reviewersbotond, ehsan
bugs1275314
milestone52.0a1
Bug 1275314 - Add an API to allow flushing out in-progress checkerboard reports. r=botond,ehsan This is useful for talos tests that record checkerboarding. In those tests, the page might still be in a checkerboard state at the end of the test, so it may be necessary to flush out the report for measurement. MozReview-Commit-ID: CtafG4NAGHN
dom/webidl/CheckerboardReportService.webidl
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/util/CheckerboardReportService.cpp
gfx/layers/apz/util/CheckerboardReportService.h
toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js
toolkit/components/aboutcheckerboard/content/aboutCheckerboard.xhtml
--- a/dom/webidl/CheckerboardReportService.webidl
+++ b/dom/webidl/CheckerboardReportService.webidl
@@ -40,9 +40,19 @@ interface CheckerboardReportService {
    * Gets the state of the apz.record_checkerboarding pref.
    */
   boolean isRecordingEnabled();
 
   /**
    * Sets the state of the apz.record_checkerboarding pref.
    */
   void setRecordingEnabled(boolean aEnabled);
+
+  /**
+   * Flush any in-progress checkerboard reports. Since this happens
+   * asynchronously, the caller may register an observer with the observer
+   * service to be notified when this operation is complete. The observer should
+   * listen for the topic "APZ:FlushActiveCheckerboard:Done". Upon receiving
+   * this notification, the caller may call getReports() to obtain the flushed
+   * reports, along with any other reports that are available.
+   */
+  void flushActiveReports();
 };
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -79,31 +79,95 @@ struct APZCTreeManager::TreeBuildingStat
   nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
 
   // This map is populated as we place APZCs into the new tree. Its purpose is
   // to facilitate re-using the same APZC for different layers that scroll
   // together (and thus have the same ScrollableLayerGuid).
   std::map<ScrollableLayerGuid, AsyncPanZoomController*> mApzcMap;
 };
 
+class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager)
+    : mTreeManager(aTreeManager)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+    MOZ_ASSERT(obsSvc);
+    if (obsSvc) {
+      obsSvc->AddObserver(this, "APZ:FlushActiveCheckerboard", false);
+    }
+  }
+
+  void Unregister()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+    if (obsSvc) {
+      obsSvc->RemoveObserver(this, "APZ:FlushActiveCheckerboard");
+    }
+    mTreeManager = nullptr;
+  }
+
+protected:
+  virtual ~CheckerboardFlushObserver() {}
+
+private:
+  RefPtr<APZCTreeManager> mTreeManager;
+};
+
+NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver, nsIObserver)
+
+NS_IMETHODIMP
+APZCTreeManager::CheckerboardFlushObserver::Observe(nsISupports* aSubject,
+                                                    const char* aTopic,
+                                                    const char16_t*)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mTreeManager.get());
+
+  MutexAutoLock lock(mTreeManager->mTreeLock);
+  if (mTreeManager->mRootNode) {
+    ForEachNode<ReverseIterator>(mTreeManager->mRootNode.get(),
+        [](HitTestingTreeNode* aNode)
+        {
+          if (aNode->IsPrimaryHolder()) {
+            MOZ_ASSERT(aNode->GetApzc());
+            aNode->GetApzc()->FlushActiveCheckerboardReport();
+          }
+        });
+  }
+  MOZ_ASSERT(XRE_IsParentProcess());
+  nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+  if (obsSvc) {
+    obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done", nullptr);
+  }
+  return NS_OK;
+}
+
+
 /*static*/ const ScreenMargin
 APZCTreeManager::CalculatePendingDisplayPort(
   const FrameMetrics& aFrameMetrics,
   const ParentLayerPoint& aVelocity)
 {
   return AsyncPanZoomController::CalculatePendingDisplayPort(
     aFrameMetrics, aVelocity);
 }
 
 APZCTreeManager::APZCTreeManager()
     : mInputQueue(new InputQueue()),
       mTreeLock("APZCTreeLock"),
       mHitResultForInputBlock(HitNothing),
       mRetainedTouchIdentifier(-1),
-      mApzcTreeLog("apzctree")
+      mApzcTreeLog("apzctree"),
+      mFlushObserver(new CheckerboardFlushObserver(this))
 {
   AsyncPanZoomController::InitializeGlobalState();
   mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
 }
 
 APZCTreeManager::~APZCTreeManager()
 {
 }
@@ -1270,16 +1334,22 @@ APZCTreeManager::ClearTree()
       {
         nodesToDestroy.AppendElement(aNode);
       });
 
   for (size_t i = 0; i < nodesToDestroy.Length(); i++) {
     nodesToDestroy[i]->Destroy();
   }
   mRootNode = nullptr;
+
+  RefPtr<APZCTreeManager> self(this);
+  NS_DispatchToMainThread(NS_NewRunnableFunction([self] {
+    self->mFlushObserver->Unregister();
+    self->mFlushObserver = nullptr;
+  }));
 }
 
 RefPtr<HitTestingTreeNode>
 APZCTreeManager::GetRootNode() const
 {
   MutexAutoLock lock(mTreeLock);
   return mRootNode;
 }
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -237,20 +237,20 @@ public:
    * documentation on AsyncPanZoomController::AdjustScrollForSurfaceShift for
    * some more details. This is only currently needed due to surface shifts
    * caused by the dynamic toolbar on Android.
    */
   void AdjustScrollForSurfaceShift(const ScreenPoint& aShift) override;
 
   /**
    * Calls Destroy() on all APZC instances attached to the tree, and resets the
-   * tree back to empty. This function may be called multiple times during the
-   * lifetime of this APZCTreeManager, but it must always be called at least once
-   * when this APZCTreeManager is no longer needed. Failing to call this function
-   * may prevent objects from being freed properly.
+   * tree back to empty. This function must be called exactly once during the
+   * lifetime of this APZCTreeManager, when this APZCTreeManager is no longer
+   * needed. Failing to call this function may prevent objects from being freed
+   * properly.
    */
   void ClearTree();
 
   /**
    * Tests if a screen point intersect an apz in the tree.
    */
   bool HitTestAPZC(const ScreenIntPoint& aPoint);
 
@@ -513,15 +513,19 @@ private:
   int32_t mRetainedTouchIdentifier;
   /* Tracks the number of touch points we are tracking that are currently on
    * the screen. */
   TouchCounter mTouchCounter;
   /* For logging the APZC tree for debugging (enabled by the apz.printtree
    * pref). */
   gfx::TreeLog mApzcTreeLog;
 
+  class CheckerboardFlushObserver;
+  friend class CheckerboardFlushObserver;
+  RefPtr<CheckerboardFlushObserver> mFlushObserver;
+
   static float sDPI;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_PanZoomController_h
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -3236,39 +3236,55 @@ AsyncPanZoomController::ReportCheckerboa
   MutexAutoLock lock(mCheckerboardEventLock);
   if (!mCheckerboardEvent && (recordTrace || forTelemetry)) {
     mCheckerboardEvent = MakeUnique<CheckerboardEvent>(recordTrace);
   }
   mPotentialCheckerboardTracker.InTransform(IsTransformingState(mState));
   if (magnitude) {
     mPotentialCheckerboardTracker.CheckerboardSeen();
   }
-  if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(magnitude)) {
+  UpdateCheckerboardEvent(lock, magnitude);
+}
+
+void
+AsyncPanZoomController::UpdateCheckerboardEvent(const MutexAutoLock& aProofOfLock,
+                                                uint32_t aMagnitude)
+{
+  if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(aMagnitude)) {
     // This checkerboard event is done. Report some metrics to telemetry.
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_SEVERITY,
       mCheckerboardEvent->GetSeverity());
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_PEAK,
       mCheckerboardEvent->GetPeak());
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_DURATION,
       (uint32_t)mCheckerboardEvent->GetDuration().ToMilliseconds());
 
     mPotentialCheckerboardTracker.CheckerboardDone();
 
-    if (recordTrace) {
+    if (gfxPrefs::APZRecordCheckerboarding()) {
       // if the pref is enabled, also send it to the storage class. it may be
       // chosen for public display on about:checkerboard, the hall of fame for
       // checkerboard events.
       uint32_t severity = mCheckerboardEvent->GetSeverity();
       std::string log = mCheckerboardEvent->GetLog();
       CheckerboardEventStorage::Report(severity, log);
     }
     mCheckerboardEvent = nullptr;
   }
 }
 
+void
+AsyncPanZoomController::FlushActiveCheckerboardReport()
+{
+  MutexAutoLock lock(mCheckerboardEventLock);
+  // Pretend like we got a frame with 0 pixels checkerboarded. This will
+  // terminate the checkerboard event and flush it out
+  UpdateCheckerboardEvent(lock, 0);
+}
+
 bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   if (!gfxPrefs::APZAllowCheckerboarding() || mScrollMetadata.IsApzForceDisabled()) {
     return false;
   }
 
   CSSPoint currentScrollOffset = mFrameMetrics.GetScrollOffset() + mTestAsyncScrollOffset;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -230,16 +230,25 @@ public:
   uint32_t GetCheckerboardMagnitude() const;
 
   /**
    * Report the number of CSSPixel-milliseconds of checkerboard to telemetry.
    */
   void ReportCheckerboard(const TimeStamp& aSampleTime);
 
   /**
+   * Flush any active checkerboard report that's in progress. This basically
+   * pretends like any in-progress checkerboard event has terminated, and pushes
+   * out the report to the checkerboard reporting service and telemetry. If the
+   * checkerboard event has not really finished, it will start a new event
+   * on the next composite.
+   */
+  void FlushActiveCheckerboardReport();
+
+  /**
    * Returns whether or not the APZC is currently in a state of checkerboarding.
    * This is a simple computation based on the last-painted content and whether
    * the async transform has pushed it so far that it doesn't fully contain the
    * composition bounds.
    */
   bool IsCurrentlyCheckerboarding() const;
 
   /**
@@ -1162,16 +1171,20 @@ private:
   bool mAsyncTransformAppliedToContent;
 
 
   /* ===================================================================
    * The functions and members in this section are used for checkerboard
    * recording.
    */
 private:
+  // Helper function to update the in-progress checkerboard event, if any.
+  void UpdateCheckerboardEvent(const MutexAutoLock& aProofOfLock,
+                               uint32_t aMagnitude);
+
   // Mutex protecting mCheckerboardEvent
   Mutex mCheckerboardEventLock;
   // This is created when this APZC instance is first included as part of a
   // composite. If a checkerboard event takes place, this is destroyed at the
   // end of the event, and a new one is created on the next composite.
   UniquePtr<CheckerboardEvent> mCheckerboardEvent;
   // This is used to track the total amount of time that we could reasonably
   // be checkerboarding. Combined with other info, this allows us to meaningfully
--- a/gfx/layers/apz/util/CheckerboardReportService.cpp
+++ b/gfx/layers/apz/util/CheckerboardReportService.cpp
@@ -202,10 +202,22 @@ CheckerboardReportService::IsRecordingEn
 }
 
 void
 CheckerboardReportService::SetRecordingEnabled(bool aEnabled)
 {
   gfxPrefs::SetAPZRecordCheckerboarding(aEnabled);
 }
 
+void
+CheckerboardReportService::FlushActiveReports()
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+  MOZ_ASSERT(obsSvc);
+  if (obsSvc) {
+    obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard", nullptr);
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/gfx/layers/apz/util/CheckerboardReportService.h
+++ b/gfx/layers/apz/util/CheckerboardReportService.h
@@ -125,16 +125,17 @@ public:
 
 public:
   /*
    * The methods exposed via the webidl.
    */
   void GetReports(nsTArray<dom::CheckerboardReport>& aOutReports);
   bool IsRecordingEnabled() const;
   void SetRecordingEnabled(bool aEnabled);
+  void FlushActiveReports();
 
 private:
   virtual ~CheckerboardReportService() {}
 
   nsCOMPtr<nsISupports> mParent;
 };
 
 } // namespace dom
--- a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js
+++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js
@@ -35,16 +35,20 @@ function updateEnabled() {
   }
 }
 
 function toggleEnabled() {
   service.setRecordingEnabled(!service.isRecordingEnabled());
   updateEnabled();
 }
 
+function flushReports() {
+  service.flushActiveReports();
+}
+
 function showReport(index) {
   trace.value = reports[index].log;
   loadData();
 }
 
 // -- Code to load and render the trace --
 
 const CANVAS_USE_RATIO = 0.75;
--- a/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.xhtml
+++ b/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.xhtml
@@ -9,16 +9,18 @@
   <meta name="viewport" content="width=device-width"/>
   <link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/>
   <script type="text/javascript;version=1.8" src="chrome://global/content/aboutCheckerboard.js"></script>
  </head>
 
  <body onload="onLoad()">
   <p>Checkerboard recording is <span id="enabled" style="color: red">undetermined</span>.
      <button onclick="toggleEnabled()">Toggle it!</button>.</p>
+  <p>If there are active reports in progress, you can stop and flush them by clicking here:
+     <button onclick="flushReports()">Flush active reports</button></p>
   <table class="listing" cellspacing="0">
    <tr>
     <th>Most severe checkerboarding reports</th>
     <th>Most recent checkerboarding reports</th>
    </tr>
    <tr>
     <td><ul id="severe"></ul></td>
     <td><ul id="recent"></ul></td>