Bug 1275314 - Add an API to allow flushing out in-progress checkerboard reports. r?botond
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: D5XSDeyqizd
--- 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,16 +79,79 @@ 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);
}
@@ -97,16 +160,17 @@ APZCTreeManager::APZCTreeManager()
: mInputQueue(new InputQueue()),
mTreeLock("APZCTreeLock"),
mHitResultForInputBlock(HitNothing),
mRetainedTouchIdentifier(-1),
mApzcTreeLog("apzctree")
{
AsyncPanZoomController::InitializeGlobalState();
mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
+ mFlushObserver = new CheckerboardFlushObserver(this);
}
APZCTreeManager::~APZCTreeManager()
{
}
/*static*/ void
APZCTreeManager::InitializeGlobalState()
@@ -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>