Bug 1226826 - Add a CheckerboardEvent class to record stuff when checkerboarding happens. r?botond draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 13 Jan 2016 13:53:58 -0500
changeset 321500 a981c20654905338e0bd946e63f81f1354c37e46
parent 321369 531d1f6d1cde1182e9f7f9dff81a4fc5abc0a601
child 321501 abed56e0e9c99b2eb661c31ba1e6ef5b6254a0d6
push id9390
push userkgupta@mozilla.com
push dateWed, 13 Jan 2016 18:54:16 +0000
reviewersbotond
bugs1226826
milestone46.0a1
Bug 1226826 - Add a CheckerboardEvent class to record stuff when checkerboarding happens. r?botond
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/CheckerboardEvent.cpp
gfx/layers/apz/src/CheckerboardEvent.h
gfx/layers/moz.build
gfx/thebes/gfxPrefs.h
modules/libpref/init/all.js
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -5,31 +5,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <math.h>                       // for fabsf, fabs, atan2
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include <sys/types.h>                  // for int32_t
 #include <algorithm>                    // for max, min
 #include "AsyncPanZoomController.h"     // for AsyncPanZoomController, etc
 #include "Axis.h"                       // for AxisX, AxisY, Axis, etc
+#include "CheckerboardEvent.h"          // for CheckerboardEvent
 #include "Compositor.h"                 // for Compositor
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "GestureEventListener.h"       // for GestureEventListener
 #include "HitTestingTreeNode.h"         // for HitTestingTreeNode
 #include "InputData.h"                  // for MultiTouchInput, etc
 #include "InputBlockState.h"            // for InputBlockState, TouchBlockState
 #include "InputQueue.h"                 // for InputQueue
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
 #include "Units.h"                      // for CSSRect, CSSPoint, etc
 #include "UnitTransforms.h"             // for TransformTo
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for NewRunnableMethod, etc
 #include "base/tracked.h"               // for FROM_HERE
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxTypes.h"                   // for gfxFloat
+#include "LayersLogging.h"              // for print_stderr
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/BasicEvents.h"        // for Modifiers, MODIFIER_*
 #include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
 #include "mozilla/EventForwards.h"      // for nsEventStatus_*
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/ReentrantMonitor.h"   // for ReentrantMonitorAutoEnter, etc
 #include "mozilla/StaticPtr.h"          // for StaticAutoPtr
 #include "mozilla/Telemetry.h"          // for Telemetry
@@ -62,23 +64,20 @@
 #include "nsStyleConsts.h"
 #include "nsStyleStruct.h"              // for nsTimingFunction
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "prsystem.h"                   // for PR_GetPhysicalMemorySize
 #include "SharedMemoryBasic.h"          // for SharedMemoryBasic
 #include "WheelScrollAnimation.h"
 
-// #define APZC_ENABLE_RENDERTRACE
-
 #define ENABLE_APZC_LOGGING 0
 // #define ENABLE_APZC_LOGGING 1
 
 #if ENABLE_APZC_LOGGING
-#  include "LayersLogging.h"
 #  define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
 #  define APZC_LOG_FM(fm, prefix, ...) \
     { std::stringstream ss; \
       ss << nsPrintfCString(prefix, __VA_ARGS__).get(); \
       AppendToString(ss, fm, ":", "", true); \
       APZC_LOG("%s\n", ss.str().c_str()); \
     }
 #else
@@ -271,16 +270,19 @@ using mozilla::gfx::PointTyped;
  * and the velocity fall below their thresholds, we stop oscillating.\n
  * Units: screen pixels (for distance)
  *        screen pixels per millisecond (for velocity)
  *
  * \li\b apz.pan_repaint_interval
  * Maximum amount of time while panning before sending a viewport change. This
  * will asynchronously repaint the page. It is also forced when panning stops.
  *
+ * \li\b apz.record_checkerboarding
+ * Whether or not to record detailed info on checkerboarding events.
+ *
  * \li\b apz.smooth_scroll_repaint_interval
  * Maximum amount of time doing a smooth scroll before sending a viewport
  * change. This will asynchronously repaint the page.\n
  * Units: milliseconds
  *
  * \li\b apz.test.logging_enabled
  * Enable logging of APZ test data (see bug 961289).
  *
@@ -383,28 +385,16 @@ static bool IsCloseToHorizontal(float aA
 }
 
 // As above, but for the vertical axis.
 static bool IsCloseToVertical(float aAngle, float aThreshold)
 {
   return (fabs(aAngle - (M_PI / 2)) < aThreshold);
 }
 
-static inline void LogRendertraceRect(const ScrollableLayerGuid& aGuid, const char* aDesc, const char* aColor, const CSSRect& aRect)
-{
-#ifdef APZC_ENABLE_RENDERTRACE
-  static const TimeStamp sRenderStart = TimeStamp::Now();
-  TimeDuration delta = TimeStamp::Now() - sRenderStart;
-  printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n",
-    aGuid.mLayersId, aGuid.mPresShellId, aGuid.mScrollId,
-    aDesc, delta.ToMilliseconds(), aColor,
-    aRect.x, aRect.y, aRect.width, aRect.height);
-#endif
-}
-
 // Counter used to give each APZC a unique id
 static uint32_t sAsyncPanZoomControllerCount = 0;
 
 TimeStamp
 AsyncPanZoomController::GetFrameTime() const
 {
   APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
   return treeManagerLocal ? treeManagerLocal->GetFrameTime() : TimeStamp::Now();
@@ -2855,17 +2845,20 @@ GetDisplayPortRect(const FrameMetrics& a
 void
 AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics) {
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
 
   APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
-  LogRendertraceRect(GetGuid(), "requested displayport", "yellow", GetDisplayPortRect(aFrameMetrics));
+  if (mCheckerboardEvent) {
+    mCheckerboardEvent->UpdateRendertraceProperty(
+        CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics));
+  }
 
   if (NS_IsMainThread()) {
     controller->RequestContentRepaint(aFrameMetrics);
   } else {
     NS_DispatchToMainThread(NS_NewRunnableMethodWithArg<FrameMetrics>(
       controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics));
   }
   mExpectedGeckoMetrics = aFrameMetrics;
@@ -2963,19 +2956,22 @@ bool AsyncPanZoomController::AdvanceAnim
   bool requestAnimationFrame = false;
   Vector<Task*> deferredTasks;
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks);
 
-    LogRendertraceRect(GetGuid(), "viewport", "red",
-      CSSRect(mFrameMetrics.GetScrollOffset(),
-              mFrameMetrics.CalculateCompositedSizeInCssPixels()));
+    if (mCheckerboardEvent) {
+      mCheckerboardEvent->UpdateRendertraceProperty(
+          CheckerboardEvent::UserVisible,
+          CSSRect(mFrameMetrics.GetScrollOffset(),
+                  mFrameMetrics.CalculateCompositedSizeInCssPixels()));
+    }
   }
 
   // Execute any deferred tasks queued up by mAnimation's Sample() (called by
   // UpdateAnimation()). This needs to be done after the monitor is released
   // since the tasks are allowed to call APZCTreeManager methods which can grab
   // the tree lock.
   for (uint32_t i = 0; i < deferredTasks.length(); ++i) {
     deferredTasks[i]->Run();
@@ -3087,16 +3083,29 @@ AsyncPanZoomController::ReportCheckerboa
     return;
   }
   uint32_t time = (aSampleTime - mLastCheckerboardReport).ToMilliseconds();
   uint32_t magnitude = GetCheckerboardMagnitude();
   // TODO: make this a function of velocity
   mozilla::Telemetry::Accumulate(
       mozilla::Telemetry::CHECKERBOARDED_CSSPIXELS_MS, magnitude * time);
   mLastCheckerboardReport = aSampleTime;
+
+  if (!mCheckerboardEvent && gfxPrefs::APZRecordCheckerboarding()) {
+    mCheckerboardEvent = MakeUnique<CheckerboardEvent>();
+  }
+  if (mCheckerboardEvent) {
+    if (mCheckerboardEvent->RecordFrameInfo(aSampleTime, magnitude)) {
+      // This checkerboard event is done. TODO: save the info somewhere or
+      // dispatch it to telemetry or something. For now we just print it.
+      std::stringstream log(mCheckerboardEvent->GetLog());
+      print_stderr(log);
+      mCheckerboardEvent = nullptr;
+    }
+  }
 }
 
 bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   if (!gfxPrefs::APZAllowCheckerboarding()) {
     return false;
   }
@@ -3119,22 +3128,27 @@ void AsyncPanZoomController::NotifyLayer
   ReentrantMonitorAutoEnter lock(mMonitor);
   bool isDefault = mFrameMetrics.IsDefault();
 
   mLastContentPaintMetrics = aLayerMetrics;
 
   mFrameMetrics.SetScrollParentId(aLayerMetrics.GetScrollParentId());
   APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
 
-  LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.GetScrollableRect());
-  LogRendertraceRect(GetGuid(), "painted displayport", "lightgreen",
-    aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset());
-  if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) {
-    LogRendertraceRect(GetGuid(), "painted critical displayport", "darkgreen",
-      aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset());
+  if (mCheckerboardEvent) {
+    mCheckerboardEvent->UpdateRendertraceProperty(
+        CheckerboardEvent::Page, aLayerMetrics.GetScrollableRect());
+    mCheckerboardEvent->UpdateRendertraceProperty(
+        CheckerboardEvent::PaintedDisplayPort,
+        aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset());
+    if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) {
+      mCheckerboardEvent->UpdateRendertraceProperty(
+          CheckerboardEvent::PaintedCriticalDisplayPort,
+          aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset());
+    }
   }
 
   bool needContentRepaint = false;
   bool viewportUpdated = false;
   if (FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().width, mFrameMetrics.GetCompositionBounds().width) &&
       FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().height, mFrameMetrics.GetCompositionBounds().height)) {
     // Remote content has sync'd up to the composition geometry
     // change, so we can accept the viewport it's calculated.
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -47,16 +47,17 @@ class PCompositorParent;
 struct AsyncTransform;
 class AsyncPanZoomAnimation;
 class FlingAnimation;
 class InputBlockState;
 class TouchBlockState;
 class PanGestureBlockState;
 class OverscrollHandoffChain;
 class StateChangeNotificationBlocker;
+class CheckerboardEvent;
 
 /**
  * Controller for all panning and zooming logic. Any time a user input is
  * detected and it must be processed in some way to affect what the user sees,
  * it goes through here. Listens for any input event from InputData and can
  * optionally handle WidgetGUIEvent-derived touch events, but this must be done
  * on the main thread. Note that this class completely cross-platform.
  *
@@ -1094,14 +1095,25 @@ public:
 private:
   // Extra offset to add in SampleContentTransformForFrame for testing
   CSSPoint mTestAsyncScrollOffset;
   // Extra zoom to include in SampleContentTransformForFrame for testing
   LayerToParentLayerScale mTestAsyncZoom;
   // Flag to track whether or not the APZ transform is not used. This
   // flag is recomputed for every composition frame.
   bool mAsyncTransformAppliedToContent;
+
+
+  /* ===================================================================
+   * The functions and members in this section are used for checkerboard
+   * recording.
+   */
+private:
+  // 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;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_PanZoomController_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/CheckerboardEvent.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CheckerboardEvent.h"
+
+#include <algorithm> // for std::sort
+
+namespace mozilla {
+namespace layers {
+
+// Relatively arbitrary limit to prevent a perma-checkerboard event from
+// eating up gobs of memory. Ideally we shouldn't have perma-checkerboarding
+// but better to guard against it.
+#define LOG_LENGTH_LIMIT (50 * 1024)
+
+const char* CheckerboardEvent::sDescriptions[] = {
+  "page",
+  "painted critical displayport",
+  "painted displayport",
+  "requested displayport",
+  "viewport",
+};
+
+const char* CheckerboardEvent::sColors[] = {
+  "brown",
+  "darkgreen",
+  "lightgreen",
+  "yellow",
+  "red",
+};
+
+CheckerboardEvent::CheckerboardEvent()
+  : mOriginTime(TimeStamp::Now())
+  , mCheckerboardingActive(false)
+  , mLastSampleTime(mOriginTime)
+  , mFrameCount(0)
+  , mTotalPixelMs(0)
+  , mPeakPixels(0)
+  , mRendertraceLock("Rendertrace")
+{
+}
+
+uint64_t
+CheckerboardEvent::GetSeverity()
+{
+  return mTotalPixelMs;
+}
+
+std::string
+CheckerboardEvent::GetLog()
+{
+  MonitorAutoLock lock(mRendertraceLock);
+  return mRendertraceInfo.str();
+}
+
+void
+CheckerboardEvent::UpdateRendertraceProperty(RendertraceProperty aProperty,
+                                             const CSSRect& aRect)
+{
+  MonitorAutoLock lock(mRendertraceLock);
+  if (!mCheckerboardingActive) {
+    mBufferedProperties[aProperty].Update(aProperty, aRect, lock);
+  } else {
+    LogInfo(aProperty, TimeStamp::Now(), aRect, lock);
+  }
+}
+
+void
+CheckerboardEvent::LogInfo(RendertraceProperty aProperty,
+                           const TimeStamp& aTimestamp,
+                           const CSSRect& aRect,
+                           const MonitorAutoLock& aProofOfLock)
+{
+  if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) {
+    // The log is already long enough, don't put more things into it. We'll
+    // append a truncation message when this event ends.
+    return;
+  }
+  // The log is consumed by the page at http://people.mozilla.org/~kgupta/rendertrace.html
+  // and will move to about:checkerboard in bug 1238042. The format is not
+  // formally specced, but an informal description can be found at
+  // https://github.com/staktrace/rendertrace/blob/master/index.html#L30
+  mRendertraceInfo << "RENDERTRACE "
+      << (aTimestamp - mOriginTime).ToMilliseconds() << " rect "
+      << sColors[aProperty] << " "
+      << aRect.x << " "
+      << aRect.y << " "
+      << aRect.width << " "
+      << aRect.height << " "
+      << "// " << sDescriptions[aProperty] << std::endl;
+}
+
+bool
+CheckerboardEvent::RecordFrameInfo(const TimeStamp& aSampleTime,
+                                   uint32_t aCssPixelsCheckerboarded)
+{
+  bool eventEnding = false;
+  if (aCssPixelsCheckerboarded > 0) {
+    if (!mCheckerboardingActive) {
+      StartEvent();
+    }
+    MOZ_ASSERT(mCheckerboardingActive);
+    MOZ_ASSERT(aSampleTime >= mLastSampleTime);
+    mTotalPixelMs += (uint64_t)((aSampleTime - mLastSampleTime).ToMilliseconds() * aCssPixelsCheckerboarded);
+    if (aCssPixelsCheckerboarded > mPeakPixels) {
+      mPeakPixels = aCssPixelsCheckerboarded;
+    }
+    mFrameCount++;
+  } else {
+    if (mCheckerboardingActive) {
+      StopEvent();
+      eventEnding = true;
+    }
+    MOZ_ASSERT(!mCheckerboardingActive);
+  }
+  mLastSampleTime = aSampleTime;
+  return eventEnding;
+}
+
+void
+CheckerboardEvent::StartEvent()
+{
+  MOZ_ASSERT(!mCheckerboardingActive);
+  mCheckerboardingActive = true;
+  mStartTime = TimeStamp::Now();
+
+  MonitorAutoLock lock(mRendertraceLock);
+  std::vector<PropertyValue> history;
+  for (int i = 0; i < MAX_RendertraceProperty; i++) {
+    mBufferedProperties[i].Flush(history, lock);
+  }
+  std::sort(history.begin(), history.end());
+  for (const PropertyValue& p : history) {
+    LogInfo(p.mProperty, p.mTimeStamp, p.mRect, lock);
+  }
+  mRendertraceInfo << " -- checkerboarding starts below --" << std::endl;
+}
+
+void
+CheckerboardEvent::StopEvent()
+{
+  mCheckerboardingActive = false;
+  mEndTime = TimeStamp::Now();
+
+  MonitorAutoLock lock(mRendertraceLock);
+  if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) {
+    mRendertraceInfo << "[logging aborted due to length limitations]\n";
+  }
+  mRendertraceInfo << "Checkerboarded for " << mFrameCount << " frames ("
+    << (mEndTime - mStartTime).ToMilliseconds() << " ms), "
+    << mPeakPixels << " peak, " << GetSeverity() << " severity." << std::endl;
+}
+
+bool
+CheckerboardEvent::PropertyValue::operator<(const PropertyValue& aOther) const
+{
+  if (mTimeStamp < aOther.mTimeStamp) {
+    return true;
+  } else if (mTimeStamp > aOther.mTimeStamp) {
+    return false;
+  }
+  return mProperty < aOther.mProperty;
+}
+
+CheckerboardEvent::PropertyBuffer::PropertyBuffer()
+  : mIndex(0)
+{
+}
+
+void
+CheckerboardEvent::PropertyBuffer::Update(RendertraceProperty aProperty,
+                                          const CSSRect& aRect,
+                                          const MonitorAutoLock& aProofOfLock)
+{
+  mValues[mIndex] = { aProperty, TimeStamp::Now(), aRect };
+  mIndex = (mIndex + 1) % BUFFER_SIZE;
+}
+
+void
+CheckerboardEvent::PropertyBuffer::Flush(std::vector<PropertyValue>& aOut,
+                                         const MonitorAutoLock& aProofOfLock)
+{
+  for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
+    uint32_t ix = (mIndex + i) % BUFFER_SIZE;
+    if (!mValues[ix].mTimeStamp.IsNull()) {
+      aOut.push_back(mValues[ix]);
+      mValues[ix].mTimeStamp = TimeStamp();
+    }
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/CheckerboardEvent.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_CheckerboardEvent_h
+#define mozilla_layers_CheckerboardEvent_h
+
+#include "mozilla/Monitor.h"
+#include "mozilla/TimeStamp.h"
+#include <sstream>
+#include "Units.h"
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This class records information relevant to one "checkerboard event", which is
+ * a contiguous set of frames where a given APZC was checkerboarding. The intent
+ * of this class is to record enough information that it can provide actionable
+ * steps to reduce the occurrence of checkerboarding. Furthermore, it records
+ * information about the severity of the checkerboarding so as to allow
+ * prioritizing the debugging of some checkerboarding events over others.
+ */
+class CheckerboardEvent {
+public:
+  enum RendertraceProperty {
+    Page,
+    PaintedCriticalDisplayPort,
+    PaintedDisplayPort,
+    RequestedDisplayPort,
+    UserVisible,
+
+    // sentinel final value
+    MAX_RendertraceProperty
+  };
+
+  static const char* sDescriptions[MAX_RendertraceProperty];
+  static const char* sColors[MAX_RendertraceProperty];
+
+public:
+  CheckerboardEvent();
+
+  /**
+   * Gets the "severity" of the checkerboard event. This doesn't have units,
+   * it's just useful for comparing two checkerboard events to see which one
+   * is worse, for some implementation-specific definition of "worse".
+   */
+  uint64_t GetSeverity();
+
+  /**
+   * Gets the raw log of the checkerboard event. This can be called any time,
+   * although it really only makes sense to pull once the event is done, after
+   * RecordFrameInfo returns true.
+   */
+  std::string GetLog();
+
+  /**
+   * Provide a new value for one of the rects that is tracked for
+   * checkerboard events.
+   */
+  void UpdateRendertraceProperty(RendertraceProperty aProperty,
+                                 const CSSRect& aRect);
+
+  /**
+   * Provide the number of CSS pixels that are checkerboarded in a composite
+   * at the given sample time. The sample times must be non-decreasing, and
+   * should generally be one vsync apart.
+   * @return true if the checkerboard event has completed. The caller should
+   * stop updating this object once this happens.
+   */
+  bool RecordFrameInfo(const TimeStamp& aSampleTime,
+                       uint32_t aCssPixelsCheckerboarded);
+
+private:
+  /**
+   * Helper method to do stuff when checkeboarding starts.
+   */
+  void StartEvent();
+  /**
+   * Helper method to do stuff when checkerboarding stops.
+   */
+  void StopEvent();
+
+  /**
+   * Helper method to log a rendertrace property and its value to the
+   * rendertrace info buffer (mRendertraceInfo).
+   */
+  void LogInfo(RendertraceProperty aProperty,
+               const TimeStamp& aTimestamp,
+               const CSSRect& aRect,
+               const MonitorAutoLock& aProofOfLock);
+
+  /**
+   * Helper struct that holds a single rendertrace property value.
+   */
+  struct PropertyValue
+  {
+    RendertraceProperty mProperty;
+    TimeStamp mTimeStamp;
+    CSSRect mRect;
+
+    bool operator<(const PropertyValue& aOther) const;
+  };
+
+  /**
+   * A circular buffer that stores the most recent BUFFER_SIZE values of a
+   * given property.
+   */
+  class PropertyBuffer
+  {
+  public:
+    PropertyBuffer();
+    /**
+     * Add a new value to the buffer, overwriting the oldest one if needed.
+     */
+    void Update(RendertraceProperty aProperty, const CSSRect& aRect,
+                const MonitorAutoLock& aProofOfLock);
+    /**
+     * Dump the recorded values, oldest to newest, to the given vector, and
+     * remove them from this buffer.
+     */
+    void Flush(std::vector<PropertyValue>& aOut,
+               const MonitorAutoLock& aProofOfLock);
+
+  private:
+    static const uint32_t BUFFER_SIZE = 5;
+
+    /**
+     * The index of the oldest value in the buffer. This is the next index
+     * that will be written to.
+     */
+    uint32_t mIndex;
+    PropertyValue mValues[BUFFER_SIZE];
+  };
+
+private:
+  /**
+   * A base time so that the other timestamps can be turned into durations.
+   */
+  const TimeStamp mOriginTime;
+  /**
+   * Whether or not a checkerboard event is currently occurring.
+   */
+  bool mCheckerboardingActive;
+
+  /**
+   * The start time of the checkerboard event.
+   */
+  TimeStamp mStartTime;
+  /**
+   * The end time of the checkerboard event.
+   */
+  TimeStamp mEndTime;
+  /**
+   * The sample time of the last frame recorded.
+   */
+  TimeStamp mLastSampleTime;
+  /**
+   * The number of contiguous frames with checkerboard.
+   */
+  uint32_t mFrameCount;
+  /**
+   * The total number of pixel-milliseconds of checkerboarding visible to
+   * the user during the checkerboarding event.
+   */
+  uint64_t mTotalPixelMs;
+  /**
+   * The largest number of pixels of checkerboarding visible to the user
+   * during any one frame, during this checkerboarding event.
+   */
+  uint32_t mPeakPixels;
+
+  /**
+   * Monitor that needs to be acquired before touching mBufferedProperties
+   * or mRendertraceInfo.
+   */
+  mutable Monitor mRendertraceLock;
+  /**
+   * A circular buffer to store some properties. This is used before the
+   * checkerboarding actually starts, so that we have some data on what
+   * was happening before the checkerboarding started.
+   */
+  PropertyBuffer mBufferedProperties[MAX_RendertraceProperty];
+  /**
+   * The rendertrace info buffer that gives us info on what was happening
+   * during the checkerboard event.
+   */
+  std::ostringstream mRendertraceInfo;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_CheckerboardEvent_h
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -243,16 +243,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
         'ipc/GonkNativeHandleUtils.cpp',
         'ipc/ShadowLayerUtilsGralloc.cpp',
     ]
 
 UNIFIED_SOURCES += [
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',
+    'apz/src/CheckerboardEvent.cpp',
     'apz/src/GestureEventListener.cpp',
     'apz/src/HitTestingTreeNode.cpp',
     'apz/src/InputBlockState.cpp',
     'apz/src/InputQueue.cpp',
     'apz/src/OverscrollHandoffState.cpp',
     'apz/src/TouchCounter.cpp',
     'apz/src/WheelScrollAnimation.cpp',
     'apz/testutil/APZTestData.cpp',
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -171,16 +171,17 @@ private:
   DECL_GFX_PREF(Live, "apz.overscroll.min_pan_distance_ratio", APZMinPanDistanceRatio, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.overscroll.spring_friction",        APZOverscrollSpringFriction, float, 0.015f);
   DECL_GFX_PREF(Live, "apz.overscroll.spring_stiffness",       APZOverscrollSpringStiffness, float, 0.001f);
   DECL_GFX_PREF(Live, "apz.overscroll.stop_distance_threshold", APZOverscrollStopDistanceThreshold, float, 5.0f);
   DECL_GFX_PREF(Live, "apz.overscroll.stop_velocity_threshold", APZOverscrollStopVelocityThreshold, float, 0.01f);
   DECL_GFX_PREF(Live, "apz.overscroll.stretch_factor",         APZOverscrollStretchFactor, float, 0.5f);
   DECL_GFX_PREF(Live, "apz.pan_repaint_interval",              APZPanRepaintInterval, int32_t, 250);
   DECL_GFX_PREF(Live, "apz.printtree",                         APZPrintTree, bool, false);
+  DECL_GFX_PREF(Live, "apz.record_checkerboarding",            APZRecordCheckerboarding, bool, false);
   DECL_GFX_PREF(Live, "apz.smooth_scroll_repaint_interval",    APZSmoothScrollRepaintInterval, int32_t, 75);
   DECL_GFX_PREF(Live, "apz.test.logging_enabled",              APZTestLoggingEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.touch_move_tolerance",              APZTouchMoveTolerance, float, 0.0);
   DECL_GFX_PREF(Live, "apz.touch_start_tolerance",             APZTouchStartTolerance, float, 1.0f/4.5f);
   DECL_GFX_PREF(Live, "apz.velocity_bias",                     APZVelocityBias, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.velocity_relevance_time_ms",        APZVelocityRelevanceTime, uint32_t, 150);
   DECL_GFX_PREF(Live, "apz.x_skate_highmem_adjust",            APZXSkateHighMemAdjust, float, 0.0f);
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -569,16 +569,21 @@ pref("apz.overscroll.spring_stiffness", 
 pref("apz.overscroll.stop_distance_threshold", "5.0");
 pref("apz.overscroll.stop_velocity_threshold", "0.01");
 pref("apz.overscroll.stretch_factor", "0.35");
 pref("apz.pan_repaint_interval", 16);
 
 // Whether to print the APZC tree for debugging
 pref("apz.printtree", false);
 
+#ifdef NIGHTLY_BUILD
+pref("apz.record_checkerboarding", true);
+#else
+pref("apz.record_checkerboarding", false);
+#endif
 pref("apz.smooth_scroll_repaint_interval", 16);
 pref("apz.test.logging_enabled", false);
 pref("apz.touch_start_tolerance", "0.1");
 pref("apz.touch_move_tolerance", "0.03");
 pref("apz.velocity_bias", "1.0");
 pref("apz.velocity_relevance_time_ms", 150);
 pref("apz.x_skate_highmem_adjust", "0.0");
 pref("apz.y_skate_highmem_adjust", "0.0");