Bug 1507680 - Record detailed statistics about slow WebRender frames in about:support. r=jrmuizel
authorMatt Woodrow <mwoodrow@mozilla.com>
Fri, 16 Nov 2018 15:13:56 +1300
changeset 503771 52a798ad6583b97fb3faf68f1849e7098d9ce4ae
parent 503770 25d79223c2dedd0b06abd48dbfc815c750b6473c
child 503772 8991d660f20e3eea652e060c30e17670b45a9257
child 503831 1ba4426e7a591d0d8712dc21f8875bcdbe9b3b76
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1507680
milestone65.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 1507680 - Record detailed statistics about slow WebRender frames in about:support. r=jrmuizel MozReview-Commit-ID: 84SjN1RvvAA Differential Revision: https://phabricator.services.mozilla.com/D12372
gfx/layers/ipc/CompositorBridgeChild.cpp
gfx/layers/ipc/CompositorBridgeChild.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/PCompositorBridge.ipdl
gfx/layers/wr/WebRenderBridgeChild.cpp
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.h
gfx/layers/wr/WebRenderLayerManager.cpp
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/webrender_bindings/RenderThread.cpp
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -548,16 +548,22 @@ CompositorBridgeChild::RecvDidComposite(
 
   for (size_t i = 0; i < texturePools.Length(); i++) {
     texturePools[i]->ReturnDeferredClients();
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+CompositorBridgeChild::RecvNotifyFrameStats(nsTArray<FrameStats>&& aFrameStats)
+{
+  gfxPlatform::GetPlatform()->NotifyFrameStats(std::move(aFrameStats));
+  return IPC_OK();
+}
 
 void
 CompositorBridgeChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   if (aWhy == AbnormalShutdown) {
     // If the parent side runs into a problem then the actor will be destroyed.
     // There is nothing we can do in the child side, here sets mCanSend as false.
     gfxCriticalNote << "Receive IPC close with reason=AbnormalShutdown";
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -87,16 +87,19 @@ public:
 
   virtual mozilla::ipc::IPCResult
   RecvDidComposite(const LayersId& aId,
                    const TransactionId& aTransactionId,
                    const TimeStamp& aCompositeStart,
                    const TimeStamp& aCompositeEnd) override;
 
   virtual mozilla::ipc::IPCResult
+  RecvNotifyFrameStats(nsTArray<FrameStats>&& aFrameStats) override;
+
+  virtual mozilla::ipc::IPCResult
   RecvInvalidateLayers(const LayersId& aLayersId) override;
 
   virtual mozilla::ipc::IPCResult
   RecvUpdatePluginConfigurations(const LayoutDeviceIntPoint& aContentOffset,
                                  const LayoutDeviceIntRegion& aVisibleRegion,
                                  nsTArray<PluginWindowData>&& aPlugins) override;
 
   virtual mozilla::ipc::IPCResult
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -760,17 +760,17 @@ CompositorBridgeParent::PauseComposition
 
     TimeStamp now = TimeStamp::Now();
     if (mCompositor) {
       mCompositor->Pause();
       DidComposite(now, now);
     } else if (mWrBridge) {
       mWrBridge->Pause();
       NotifyPipelineRendered(mWrBridge->PipelineId(), mWrBridge->GetCurrentEpoch(),
-                             now, now);
+                             now, now, now);
     }
   }
 
   // if anyone's waiting to make sure that composition really got paused, tell them
   lock.NotifyAll();
 }
 
 void
@@ -1844,17 +1844,17 @@ CompositorBridgeParent::RecvAdoptChild(c
     api = api->Clone();
     wr::Epoch newEpoch = childWrBridge->UpdateWebRender(mWrBridge->CompositorScheduler(),
                                    api,
                                    mWrBridge->AsyncImageManager(),
                                    GetAnimationStorage(),
                                    mWrBridge->GetTextureFactoryIdentifier());
     // Pretend we composited, since parent CompositorBridgeParent was replaced.
     TimeStamp now = TimeStamp::Now();
-    NotifyPipelineRendered(childWrBridge->PipelineId(), newEpoch, now, now);
+    NotifyPipelineRendered(childWrBridge->PipelineId(), newEpoch, now, now, now);
   }
 
   if (oldApzUpdater) {
     // We don't support moving a child from an APZ-enabled compositor to a
     // APZ-disabled compositor. The mOptions assertion above should already
     // ensure this, since APZ-ness is one of the things in mOptions. Note
     // however it is possible for mApzUpdater to be non-null here with
     // oldApzUpdater null, because the child may not have been previously
@@ -2204,51 +2204,58 @@ CompositorBridgeParent::DidComposite(Tim
     mPendingTransaction = TransactionId{0};
   }
 }
 
 void
 CompositorBridgeParent::NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                                                const wr::Epoch& aEpoch,
                                                TimeStamp& aCompositeStart,
+                                               TimeStamp& aRenderStart,
                                                TimeStamp& aCompositeEnd,
                                                wr::RendererStats* aStats)
 {
   if (!mWrBridge || !mAsyncImageManager) {
     return;
   }
 
+  nsTArray<FrameStats> stats;
+
   RefPtr<UiCompositorControllerParent> uiController =
     UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID);
 
   if (mWrBridge->PipelineId() == aPipelineId) {
     mWrBridge->RemoveEpochDataPriorTo(aEpoch);
 
     if (!mPaused) {
-      TransactionId transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeEnd, uiController);
+      TransactionId transactionId = mWrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeStart, aRenderStart, aCompositeEnd, uiController);
       Unused << SendDidComposite(LayersId{0}, transactionId, aCompositeStart, aCompositeEnd);
 
       nsTArray<ImageCompositeNotificationInfo> notifications;
       mWrBridge->ExtractImageCompositeNotifications(&notifications);
       if (!notifications.IsEmpty()) {
         Unused << ImageBridgeParent::NotifyImageComposites(notifications);
       }
     }
     return;
   }
 
   auto wrBridge = mAsyncImageManager->GetWrBridge(aPipelineId);
   if (wrBridge && wrBridge->GetCompositorBridge()) {
       MOZ_ASSERT(!wrBridge->IsRootWebRenderBridgeParent());
       wrBridge->RemoveEpochDataPriorTo(aEpoch);
       if (!mPaused) {
-        TransactionId transactionId = wrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeEnd, uiController, aStats);
+        TransactionId transactionId = wrBridge->FlushTransactionIdsForEpoch(aEpoch, aCompositeStart, aRenderStart, aCompositeEnd, uiController, aStats, &stats);
         Unused << wrBridge->GetCompositorBridge()->SendDidComposite(wrBridge->GetLayersId(), transactionId, aCompositeStart, aCompositeEnd);
       }
   }
+
+  if (!stats.IsEmpty()) {
+    Unused << SendNotifyFrameStats(stats);
+  }
 }
 
 RefPtr<AsyncImagePipelineManager>
 CompositorBridgeParent::GetAsyncImagePipelineManager() const
 {
   return mAsyncImageManager;
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -279,16 +279,17 @@ public:
 
   bool IsSameProcess() const override;
 
   void NotifyWebRenderError(wr::WebRenderError aError);
   void NotifyWebRenderContextPurge();
   void NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                               const wr::Epoch& aEpoch,
                               TimeStamp& aCompositeStart,
+                              TimeStamp& aRenderStart,
                               TimeStamp& aCompositeEnd,
                               wr::RendererStats* aStats = nullptr);
   RefPtr<AsyncImagePipelineManager> GetAsyncImagePipelineManager() const;
 
   PCompositorWidgetParent* AllocPCompositorWidgetParent(const CompositorWidgetInitData& aInitData) override;
   bool DeallocPCompositorWidgetParent(PCompositorWidgetParent* aActor) override;
 
   void ObserveLayersUpdate(LayersId aLayersId, LayersObserverEpoch aEpoch, bool aActive) override { }
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -43,16 +43,32 @@ using base::ProcessId from "base/process
 using mozilla::wr::MaybeExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::WebRenderError from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h";
 
 namespace mozilla {
 namespace layers {
 
+struct FrameStats {
+  TransactionId id;
+  TimeStamp compositeStart;
+  TimeStamp renderStart;
+  TimeStamp compositeEnd;
+  int32_t contentFrameTime;
+  double resourceUploadTime;
+  double gpuCacheUploadTime;
+  TimeStamp transactionStart;
+  TimeStamp refreshStart;
+  TimeStamp fwdTime;
+  TimeStamp sceneBuiltTime;
+  uint32_t skippedComposites;
+  nsCString url;
+};
+
 
 /**
  * The PCompositorBridge protocol is a top-level protocol for the compositor.
  * There is an instance of the protocol for each compositor, plus one for each
  * content process. In other words:
  * - There is a CompositorBridgeParent/CompositorBridgeChild pair created
  *   for each "top level browser window", which has its own compositor. The
  *   CompositorBridgeChild instance lives in the UI process, and the
@@ -95,16 +111,18 @@ child:
   // The compositor completed a layers transaction. id is the layers id
   // of the child layer tree that was composited (or 0 when notifying
   // the root layer tree).
   // transactionId is the id of the transaction before this composite, or 0
   // if there was no transaction since the last composite.
   async DidComposite(LayersId id, TransactionId transactionId,
                      TimeStamp compositeStart, TimeStamp compositeEnd);
 
+  async NotifyFrameStats(FrameStats[] aFrameStats);
+
   /**
    * Parent informs the child that the graphics objects are ready for
    * compositing.  This usually means that the graphics objects (textures
    * and the like) are available on the GPU.  This is used for chrome UI.
    * @see RequestNotifyAfterRemotePaint
    * @see PBrowser
    */
   async RemotePaintIsReady();
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -131,20 +131,17 @@ WebRenderBridgeChild::EndTransaction(con
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   ByteBuf dlData(aDL.dl.inner.data, aDL.dl.inner.length, aDL.dl.inner.capacity);
   aDL.dl.inner.capacity = 0;
   aDL.dl.inner.data = nullptr;
 
-  TimeStamp fwdTime;
-#if defined(ENABLE_FRAME_LATENCY_LOG)
-  fwdTime = TimeStamp::Now();
-#endif
+  TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
   nsTArray<RefCountedShmem> smallShmems;
   nsTArray<ipc::Shmem> largeShmems;
   aResources.Flush(resourceUpdates, smallShmems, largeShmems);
 
   this->SendSetDisplayList(aSize, mParentCommands, mDestroyedActors,
                            GetFwdTransactionId(), aTransactionId,
@@ -165,20 +162,17 @@ WebRenderBridgeChild::EndEmptyTransactio
                                           TransactionId aTransactionId,
                                           const mozilla::TimeStamp& aRefreshStartTime,
                                           const mozilla::TimeStamp& aTxnStartTime,
                                           const nsCString& aTxnURL)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
-  TimeStamp fwdTime;
-#if defined(ENABLE_FRAME_LATENCY_LOG)
-  fwdTime = TimeStamp::Now();
-#endif
+  TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
   nsTArray<RefCountedShmem> smallShmems;
   nsTArray<ipc::Shmem> largeShmems;
   if (aResources) {
     aResources->Flush(resourceUpdates, smallShmems, largeShmems);
     aResources.reset();
   }
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -194,24 +194,28 @@ protected:
   RefPtr<CompositorBridgeParentBase> mBridge;
   LayersId mLayersId;
   LayersObserverEpoch mObserverEpoch;
   bool mIsActive;
 };
 
 class SceneBuiltNotification: public wr::NotificationHandler {
 public:
-  explicit SceneBuiltNotification(TimeStamp aTxnStartTime)
-  : mTxnStartTime(aTxnStartTime)
+  explicit SceneBuiltNotification(WebRenderBridgeParent* aParent, wr::Epoch aEpoch, TimeStamp aTxnStartTime)
+  : mParent(aParent)
+  , mEpoch(aEpoch)
+  , mTxnStartTime(aTxnStartTime)
   {}
 
   virtual void Notify(wr::Checkpoint) override {
     auto startTime = this->mTxnStartTime;
+    RefPtr<WebRenderBridgeParent> parent = mParent;
+    wr::Epoch epoch = mEpoch;
     CompositorThreadHolder::Loop()->PostTask(
-      NS_NewRunnableFunction("SceneBuiltNotificationRunnable", [startTime]() {
+      NS_NewRunnableFunction("SceneBuiltNotificationRunnable", [parent, epoch, startTime]() {
         auto endTime = TimeStamp::Now();
 #ifdef MOZ_GECKO_PROFILER
         if (profiler_is_active()) {
           class ContentFullPaintPayload : public ProfilerMarkerPayload
           {
           public:
             ContentFullPaintPayload(const mozilla::TimeStamp& aStartTime,
                                     const mozilla::TimeStamp& aEndTime)
@@ -231,19 +235,22 @@ public:
 
           profiler_add_marker_for_thread(profiler_current_thread_id(),
                                          "CONTENT_FULL_PAINT_TIME",
                                          MakeUnique<ContentFullPaintPayload>(startTime, endTime));
         }
 #endif
         Telemetry::Accumulate(Telemetry::CONTENT_FULL_PAINT_TIME,
                               static_cast<uint32_t>((endTime - startTime).ToMilliseconds()));
+        parent->NotifySceneBuiltForEpoch(epoch, endTime);
       }));
   }
 protected:
+  RefPtr<WebRenderBridgeParent> mParent;
+  wr::Epoch mEpoch;
   TimeStamp mTxnStartTime;
 };
 
 
 class WebRenderBridgeParent::ScheduleSharedSurfaceRelease final
   : public wr::NotificationHandler
 {
 public:
@@ -981,16 +988,18 @@ WebRenderBridgeParent::RecvSetDisplayLis
           true
         )
       );
     }
 
     txn.Notify(
       wr::Checkpoint::SceneBuilt,
       MakeUnique<SceneBuiltNotification>(
+        this,
+        wrEpoch,
         aTxnStartTime
       )
     );
 
     mApi->SendTransaction(txn);
 
     // We will schedule generating a frame after the scene
     // build is done, so we don't need to do it here.
@@ -1002,17 +1011,17 @@ WebRenderBridgeParent::RecvSetDisplayLis
                            aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime, mIsFirstPaint);
   mIsFirstPaint = false;
 
   if (!validTransaction) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
       TimeStamp now = TimeStamp::Now();
-      cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, now, now);
+      cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, now, now, now);
     }
   }
 
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
   return IPC_OK();
 }
 
@@ -1122,17 +1131,17 @@ WebRenderBridgeParent::RecvEmptyTransact
   if (scheduleComposite) {
     ScheduleGenerateFrame();
   } else if (sendDidComposite) {
     // The only thing in the pending transaction id queue should be the entry
     // we just added, and now we're going to pretend we rendered it
     MOZ_ASSERT(mPendingTransactionIds.size() == 1);
     if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
       TimeStamp now = TimeStamp::Now();
-      cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, now, now);
+      cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, now, now, now);
     }
   }
 
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
   return IPC_OK();
 }
 
@@ -1750,16 +1759,24 @@ WebRenderBridgeParent::CompositeToTarget
     mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   if (wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
     // Render thread is busy, try next time.
     mCompositorScheduler->ScheduleComposition();
     mPreviousFrameTimeStamp = TimeStamp();
+
+    // Record that we skipped presenting a frame for
+    // all pending transactions that have finished scene building.
+    for (auto& id : mPendingTransactionIds) {
+      if (id.mSceneBuiltTime) {
+        id.mSkippedComposites++;
+      }
+    }
     return;
   }
   MaybeGenerateFrame(/* aForceGenerateFrame */ false);
 }
 
 TimeDuration
 WebRenderBridgeParent::GetVsyncInterval() const
 {
@@ -1841,41 +1858,56 @@ WebRenderBridgeParent::HoldPendingTransa
                                                 const TimeStamp& aRefreshStartTime,
                                                 const TimeStamp& aTxnStartTime,
                                                 const nsCString& aTxnURL,
                                                 const TimeStamp& aFwdTime,
                                                 const bool aIsFirstPaint,
                                                 const bool aUseForTelemetry)
 {
   MOZ_ASSERT(aTransactionId > LastPendingTransactionId());
-  mPendingTransactionIds.push(PendingTransactionId(aWrEpoch,
-                                                   aTransactionId,
-                                                   aContainsSVGGroup,
-                                                   aRefreshStartTime,
-                                                   aTxnStartTime,
-                                                   aTxnURL,
-                                                   aFwdTime,
-                                                   aIsFirstPaint,
-                                                   aUseForTelemetry));
+  mPendingTransactionIds.push_back(PendingTransactionId(aWrEpoch,
+                                                        aTransactionId,
+                                                        aContainsSVGGroup,
+                                                        aRefreshStartTime,
+                                                        aTxnStartTime,
+                                                        aTxnURL,
+                                                        aFwdTime,
+                                                        aIsFirstPaint,
+                                                        aUseForTelemetry));
 }
 
 TransactionId
 WebRenderBridgeParent::LastPendingTransactionId()
 {
   TransactionId id{0};
   if (!mPendingTransactionIds.empty()) {
     id = mPendingTransactionIds.back().mId;
   }
   return id;
 }
 
+void
+WebRenderBridgeParent::NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime)
+{
+  for (auto& id : mPendingTransactionIds) {
+    if (id.mEpoch.mHandle == aEpoch.mHandle) {
+      id.mSceneBuiltTime = aEndTime;
+      break;
+    }
+  }
+}
+
 TransactionId
-WebRenderBridgeParent::FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime,
+WebRenderBridgeParent::FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch,
+                                                   const TimeStamp& aCompositeStartTime,
+                                                   const TimeStamp& aRenderStartTime,
+                                                   const TimeStamp& aEndTime,
                                                    UiCompositorControllerParent* aUiController,
-                                                   wr::RendererStats* aStats)
+                                                   wr::RendererStats* aStats,
+                                                   nsTArray<FrameStats>* aOutputStats)
 {
   TransactionId id{0};
   while (!mPendingTransactionIds.empty()) {
     const auto& transactionId = mPendingTransactionIds.front();
 
     if (aEpoch.mHandle < transactionId.mEpoch.mHandle) {
       break;
     }
@@ -1891,21 +1923,37 @@ WebRenderBridgeParent::FlushTransactionI
           public:
             ContentFramePayload(const mozilla::TimeStamp& aStartTime, const mozilla::TimeStamp& aEndTime)
               : ProfilerMarkerPayload(aStartTime, aEndTime)
             {}
             virtual void StreamPayload(SpliceableJSONWriter& aWriter, const TimeStamp& aProcessStartTime, UniqueStacks& aUniqueStacks) override {
               StreamCommonProps("CONTENT_FRAME_TIME", aWriter, aProcessStartTime, aUniqueStacks);
             }
         };
-        profiler_add_marker_for_thread(profiler_current_thread_id(), "CONTENT_FRAME_TIME", MakeUnique<ContentFramePayload>(mPendingTransactionIds.front().mTxnStartTime,
+        profiler_add_marker_for_thread(profiler_current_thread_id(), "CONTENT_FRAME_TIME", MakeUnique<ContentFramePayload>(transactionId.mTxnStartTime,
                                                                                                                            aEndTime));
       }
 #endif
 
+      if (fracLatencyNorm > 200) {
+        aOutputStats->AppendElement(FrameStats(transactionId.mId,
+                                               aCompositeStartTime,
+                                               aRenderStartTime,
+                                               aEndTime,
+                                               fracLatencyNorm,
+                                               aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0,
+                                               aStats ? (double(aStats->gpu_cache_upload_time) / 1000000.0) : 0.0,
+                                               transactionId.mTxnStartTime,
+                                               transactionId.mRefreshStartTime,
+                                               transactionId.mFwdTime,
+                                               transactionId.mSceneBuiltTime,
+                                               transactionId.mSkippedComposites,
+                                               transactionId.mTxnURL));
+      }
+
       Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME, fracLatencyNorm);
       if (fracLatencyNorm > 200) {
         wr::RenderThread::Get()->NotifySlowFrame(mApi->GetId());
       }
       if (transactionId.mContainsSVGGroup) {
         Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITH_SVG, fracLatencyNorm);
       }
 
@@ -1935,17 +1983,17 @@ WebRenderBridgeParent::FlushTransactionI
     }
 #endif
 
     if (aUiController && transactionId.mIsFirstPaint) {
       aUiController->NotifyFirstPaint();
     }
 
     id = transactionId.mId;
-    mPendingTransactionIds.pop();
+    mPendingTransactionIds.pop_front();
   }
   return id;
 }
 
 LayersId
 WebRenderBridgeParent::GetLayersId() const
 {
   return wr::AsLayersId(mPipelineId);
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -167,19 +167,24 @@ public:
                                 bool aContainsSVGGroup,
                                 const TimeStamp& aRefreshStartTime,
                                 const TimeStamp& aTxnStartTime,
                                 const nsCString& aTxnURL,
                                 const TimeStamp& aFwdTime,
                                 const bool aIsFirstPaint,
                                 const bool aUseForTelemetry = true);
   TransactionId LastPendingTransactionId();
-  TransactionId FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime,
+  TransactionId FlushTransactionIdsForEpoch(const wr::Epoch& aEpoch,
+                                            const TimeStamp& aCompositeStartTime,
+                                            const TimeStamp& aRenderStartTime,
+                                            const TimeStamp& aEndTime,
                                             UiCompositorControllerParent* aUiController,
-                                            wr::RendererStats* aStats = nullptr);
+                                            wr::RendererStats* aStats = nullptr,
+                                            nsTArray<FrameStats>* aOutputStats = nullptr);
+  void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime);
 
   TextureFactoryIdentifier GetTextureFactoryIdentifier();
 
   void ExtractImageCompositeNotifications(nsTArray<ImageCompositeNotificationInfo>* aNotifications);
 
   wr::Epoch GetCurrentEpoch() const { return mWrEpoch; }
   wr::IdNamespace GetIdNamespace()
   {
@@ -309,26 +314,29 @@ private:
                          const bool aIsFirstPaint,
                          const bool aUseForTelemetry)
       : mEpoch(aEpoch)
       , mId(aId)
       , mRefreshStartTime(aRefreshStartTime)
       , mTxnStartTime(aTxnStartTime)
       , mTxnURL(aTxnURL)
       , mFwdTime(aFwdTime)
+      , mSkippedComposites(0)
       , mContainsSVGGroup(aContainsSVGGroup)
       , mIsFirstPaint(aIsFirstPaint)
       , mUseForTelemetry(aUseForTelemetry)
     {}
     wr::Epoch mEpoch;
     TransactionId mId;
     TimeStamp mRefreshStartTime;
     TimeStamp mTxnStartTime;
     nsCString mTxnURL;
     TimeStamp mFwdTime;
+    TimeStamp mSceneBuiltTime;
+    uint32_t mSkippedComposites;
     bool mContainsSVGGroup;
     bool mIsFirstPaint;
     bool mUseForTelemetry;
   };
 
   struct CompositorAnimationIdsForEpoch {
     CompositorAnimationIdsForEpoch(const wr::Epoch& aEpoch, InfallibleTArray<uint64_t>&& aIds)
       : mEpoch(aEpoch)
@@ -358,17 +366,17 @@ private:
   TimeStamp mPreviousFrameTimeStamp;
   // These fields keep track of the latest layer observer epoch values in the child and the
   // parent. mChildLayersObserverEpoch is the latest epoch value received from the child.
   // mParentLayersObserverEpoch is the latest epoch value that we have told TabParent about
   // (via ObserveLayerUpdate).
   LayersObserverEpoch mChildLayersObserverEpoch;
   LayersObserverEpoch mParentLayersObserverEpoch;
 
-  std::queue<PendingTransactionId> mPendingTransactionIds;
+  std::deque<PendingTransactionId> mPendingTransactionIds;
   std::queue<CompositorAnimationIdsForEpoch> mCompositorAnimationsToDelete;
   wr::Epoch mWrEpoch;
   wr::IdNamespace mIdNamespace;
 
   bool mPaused;
   bool mDestroyed;
   bool mReceivedDisplayList;
   bool mIsFirstPaint;
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -231,17 +231,22 @@ WebRenderLayerManager::EndEmptyTransacti
     return true;
   }
 
   LayoutDeviceIntSize size = mWidget->GetClientSize();
   WrBridge()->BeginTransaction();
 
   mWebRenderCommandBuilder.EmptyTransaction();
 
+  // Get the time of when the refresh driver start its tick (if available), otherwise
+  // use the time of when LayerManager::BeginTransaction was called.
   TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
+  if (!refreshStart) {
+    refreshStart = mTransactionStart;
+  }
 
   // Skip the synchronization for buffer since we also skip the painting during
   // device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
     }
@@ -337,17 +342,23 @@ WebRenderLayerManager::EndTransactionWit
     mScrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
   }
   // Since we're sending a full mScrollData that will include the new scroll
   // offsets, and we can throw away the pending scroll updates we had kept for
   // an empty transaction.
   ClearPendingScrollInfoUpdate();
 
   mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true);
+
+  // Get the time of when the refresh driver start its tick (if available), otherwise
+  // use the time of when LayerManager::BeginTransaction was called.
   TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
+  if (!refreshStart) {
+    refreshStart = mTransactionStart;
+  }
 
   if (mAsyncResourceUpdates) {
     if (resourceUpdates.IsEmpty()) {
       resourceUpdates = std::move(mAsyncResourceUpdates.ref());
     } else {
       // If we can't just swap the queue, we need to take the slow path and
       // send the update as a separate message. We don't need to schedule a
       // composite however because that will happen with EndTransaction.
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -5,27 +5,29 @@
 
 #include "mozilla/FontPropertyTypes.h"
 #include "mozilla/RDDProcessManager.h"
 #include "mozilla/image/ImageMemoryReporter.h"
 #include "mozilla/layers/CompositorManagerChild.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
+#include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/layers/PaintThread.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/gfx/GraphicsMessages.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
+#include "mozilla/IntegerPrintfMacros.h"
 
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 
 #include "gfxCrashReporterUtils.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
@@ -485,16 +487,17 @@ gfxPlatform::OnMemoryPressure(layers::Me
     }
 }
 
 gfxPlatform::gfxPlatform()
   : mHasVariationFontSupport(false)
   , mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
   , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
   , mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo)
+  , mFrameStatsCollector(this, &gfxPlatform::GetFrameStats)
   , mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
   , mScreenDepth(0)
 {
     mAllowDownloadableFonts = UNINITIALIZED_VALUE;
     mFallbackUsesCmaps = UNINITIALIZED_VALUE;
 
     mWordCacheCharLimit = UNINITIALIZED_VALUE;
     mWordCacheMaxEntries = UNINITIALIZED_VALUE;
@@ -3151,16 +3154,64 @@ gfxPlatform::GetTilesSupportInfo(mozilla
     return;
   }
 
   IntSize tileSize = gfxVars::TileSize();
   aObj.DefineProperty("TileHeight", tileSize.height);
   aObj.DefineProperty("TileWidth", tileSize.width);
 }
 
+void
+gfxPlatform::GetFrameStats(mozilla::widget::InfoObject& aObj)
+{
+  uint32_t i = 0;
+  for (FrameStats& f : mFrameStats) {
+    nsPrintfCString name("Slow Frame #%02u", ++i);
+
+    nsPrintfCString value("Frame %" PRIu64 "(%s) CONTENT_FRAME_TIME %d - Transaction start %f, main-thread time %f, full paint time %f, Skipped composites %u, Composite start %f, Resource upload time %f, GPU cache upload time %f, Render time %f, Composite time %f",
+                          f.id().mId,
+                          f.url().get(),
+                          f.contentFrameTime(),
+                          (f.transactionStart() - f.refreshStart()).ToMilliseconds(),
+                          (f.fwdTime() - f.transactionStart()).ToMilliseconds(),
+                          f.sceneBuiltTime() ? (f.sceneBuiltTime() - f.transactionStart()).ToMilliseconds() : 0.0,
+                          f.skippedComposites(),
+                          (f.compositeStart() - f.refreshStart()).ToMilliseconds(),
+                          f.resourceUploadTime(),
+                          f.gpuCacheUploadTime(),
+                          (f.compositeEnd() - f.renderStart()).ToMilliseconds(),
+                          (f.compositeEnd() - f.compositeStart()).ToMilliseconds());
+    aObj.DefineProperty(name.get(), value.get());
+  }
+}
+
+class FrameStatsComparator
+{
+public:
+  bool Equals(const FrameStats& aA, const FrameStats& aB) const {
+    return aA.contentFrameTime() == aB.contentFrameTime();
+  }
+  // Reverse the condition here since we want the array sorted largest to smallest.
+  bool LessThan(const FrameStats& aA, const FrameStats& aB) const {
+    return aA.contentFrameTime() > aB.contentFrameTime();
+  }
+};
+
+void
+gfxPlatform::NotifyFrameStats(nsTArray<FrameStats>&& aFrameStats)
+{
+  FrameStatsComparator comp;
+  for (FrameStats& f : aFrameStats) {
+    mFrameStats.InsertElementSorted(f, comp);
+  }
+  if (mFrameStats.Length() > 10) {
+    mFrameStats.SetLength(10);
+  }
+}
+
 /*static*/ bool
 gfxPlatform::AsyncPanZoomEnabled()
 {
 #if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT)
   // For XUL applications (everything but Firefox on Android)
   // we only want to use APZ when E10S is enabled. If
   // we ever get input events off the main thread we can consider relaxing
   // this requirement.
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -42,16 +42,19 @@ class nsIObserver;
 class SRGBOverrideObserver;
 class gfxTextPerfMetrics;
 typedef struct FT_LibraryRec_ *FT_Library;
 
 namespace mozilla {
 namespace gl {
 class SkiaGLGlue;
 } // namespace gl
+namespace layers {
+class FrameStats;
+}
 namespace gfx {
 class DrawTarget;
 class SourceSurface;
 class DataSourceSurface;
 class ScaledFont;
 class DrawEventRecorder;
 class VsyncSource;
 class ContentDeviceData;
@@ -294,16 +297,17 @@ public:
     virtual bool AllowOpenGLCanvas();
     virtual void InitializeSkiaCacheLimits();
 
     static bool AsyncPanZoomEnabled();
 
     virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj);
     void GetApzSupportInfo(mozilla::widget::InfoObject& aObj);
     void GetTilesSupportInfo(mozilla::widget::InfoObject& aObj);
+    void GetFrameStats(mozilla::widget::InfoObject& aObj);
 
     // Get the default content backend that will be used with the default
     // compositor. If the compositor is known when calling this function,
     // GetContentBackendFor() should be called instead.
     mozilla::gfx::BackendType GetDefaultContentBackend() const {
       return mContentBackend;
     }
 
@@ -743,16 +747,18 @@ public:
       return mHasNativeColrFontSupport;
     }
 
     // you probably want to use gfxVars::UseWebRender() instead of this
     static bool WebRenderPrefEnabled();
     // you probably want to use gfxVars::UseWebRender() instead of this
     static bool WebRenderEnvvarEnabled();
 
+    void NotifyFrameStats(nsTArray<mozilla::layers::FrameStats>&& aFrameStats);
+
     virtual void
     OnMemoryPressure(mozilla::layers::MemoryPressureReason aWhy) override;
 protected:
     gfxPlatform();
     virtual ~gfxPlatform();
 
     virtual bool HasBattery() {
       return true;
@@ -908,16 +914,19 @@ private:
     // The backend to use when we need it not to be accelerated.
     mozilla::gfx::BackendType mSoftwareBackend;
     // Bitmask of backend types we can use to render content
     uint32_t mContentBackendBitmask;
 
     mozilla::widget::GfxInfoCollector<gfxPlatform> mAzureCanvasBackendCollector;
     mozilla::widget::GfxInfoCollector<gfxPlatform> mApzSupportCollector;
     mozilla::widget::GfxInfoCollector<gfxPlatform> mTilesInfoCollector;
+    mozilla::widget::GfxInfoCollector<gfxPlatform> mFrameStatsCollector;
+
+    nsTArray<mozilla::layers::FrameStats> mFrameStats;
 
     RefPtr<mozilla::gfx::DrawEventRecorder> mRecorder;
     RefPtr<mozilla::gl::SkiaGLGlue> mSkiaGlue;
 
     // Backend that we are compositing with. NONE, if no compositor has been
     // created yet.
     mozilla::layers::LayersBackend mCompositorBackend;
 
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -337,17 +337,18 @@ RenderThread::RunEvent(wr::WindowId aWin
 
   aEvent->Run(*this, aWindowId);
   aEvent = nullptr;
 }
 
 static void
 NotifyDidRender(layers::CompositorBridgeParent* aBridge,
                 RefPtr<WebRenderPipelineInfo> aInfo,
-                TimeStamp aStart,
+                TimeStamp aCompositeStart,
+                TimeStamp aRenderStart,
                 TimeStamp aEnd,
                 bool aRender,
                 RendererStats aStats)
 {
   if (aRender && aBridge->GetWrBridge()) {
     // We call this here to mimic the behavior in LayerManagerComposite, as to
     // not change what Talos measures. That is, we do not record an empty frame
     // as a frame.
@@ -355,17 +356,18 @@ NotifyDidRender(layers::CompositorBridge
   }
 
   auto info = aInfo->Raw();
 
   for (uintptr_t i = 0; i < info.epochs.length; i++) {
     aBridge->NotifyPipelineRendered(
         info.epochs.data[i].pipeline_id,
         info.epochs.data[i].epoch,
-        aStart,
+        aCompositeStart,
+        aRenderStart,
         aEnd,
         &aStats);
   }
 }
 
 void
 RenderThread::UpdateAndRender(wr::WindowId aWindowId,
                               const TimeStamp& aStartTime,
@@ -379,16 +381,18 @@ RenderThread::UpdateAndRender(wr::Window
   MOZ_ASSERT(aRender || aReadbackBuffer.isNothing());
 
   auto it = mRenderers.find(aWindowId);
   MOZ_ASSERT(it != mRenderers.end());
   if (it == mRenderers.end()) {
     return;
   }
 
+  TimeStamp start = TimeStamp::Now();
+
   auto& renderer = it->second;
   bool rendered = false;
   RendererStats stats = { 0 };
   if (aRender) {
     rendered = renderer->UpdateAndRender(aReadbackSize, aReadbackBuffer, aHadSlowFrame, &stats);
   } else {
     renderer->Update();
   }
@@ -398,17 +402,17 @@ RenderThread::UpdateAndRender(wr::Window
   TimeStamp end = TimeStamp::Now();
   auto info = renderer->FlushPipelineInfo();
 
   layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
     "NotifyDidRenderRunnable",
     &NotifyDidRender,
     renderer->GetCompositorBridge(),
     info,
-    aStartTime, end,
+    aStartTime, start, end,
     aRender,
     stats
   ));
 
   if (rendered) {
     // Wait for GPU after posting NotifyDidRender, since the wait is not
     // necessary for the NotifyDidRender.
     // The wait is necessary for Textures recycling of AsyncImagePipelineManager