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 318132 77054c32f5dfb00945a85ea175af677a918c2120
parent 318131 bb4cb6a2db958cc42758bafd7eca92ac3eb2e3e4
child 318133 73a60f9b3c2f64768a312327f5c0befd460de446
push id33211
push usercbook@mozilla.com
push dateMon, 17 Oct 2016 09:38:38 +0000
treeherderautoland@e4ef6fa03aa8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond, ehsan
bugs1275314
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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>