Collect diagnostics on paint times for the compositor overlay. (bug 1352151 part 1, r=mattwoodrow)
authorDavid Anderson <dvander@alliedmods.net>
Mon, 10 Apr 2017 19:44:45 -0700
changeset 560301 38f9f8c344da0b8d093a0072f149fff87f246676
parent 560300 4947199c855f546bf4199c090e705c73b149143d
child 560302 c661d28e055b0e1c3391af9d12748b1bc1d0635b
push id53365
push userjichen@mozilla.com
push dateTue, 11 Apr 2017 08:35:12 +0000
reviewersmattwoodrow
bugs1352151
milestone55.0a1
Collect diagnostics on paint times for the compositor overlay. (bug 1352151 part 1, r=mattwoodrow)
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientLayerManager.h
gfx/layers/composite/Diagnostics.cpp
gfx/layers/composite/Diagnostics.h
gfx/layers/composite/LayerManagerComposite.cpp
gfx/layers/composite/LayerManagerComposite.h
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
gfx/layers/ipc/LayerTransactionParent.cpp
gfx/layers/ipc/LayerTransactionParent.h
gfx/layers/ipc/LayersMessages.ipdlh
gfx/layers/ipc/PLayerTransaction.ipdl
gfx/layers/ipc/ShadowLayers.cpp
gfx/layers/ipc/ShadowLayers.h
gfx/layers/moz.build
layout/base/nsLayoutUtils.cpp
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -311,16 +311,21 @@ ClientLayerManager::BeginTransaction()
 bool
 ClientLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback,
                                            void* aCallbackData,
                                            EndTransactionFlags)
 {
   PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Rasterization);
   GeckoProfilerTracingRAII tracer("Paint", "Rasterize");
 
+  Maybe<TimeStamp> startTime;
+  if (gfxPrefs::LayersDrawFPS()) {
+    startTime = Some(TimeStamp::Now());
+  }
+
 #ifdef WIN32
   if (aCallbackData) {
     // Content processes don't get OnPaint called. So update here whenever we
     // may do Thebes drawing.
     gfxDWriteFont::UpdateClearTypeUsage();
   }
 #endif
 
@@ -379,16 +384,21 @@ ClientLayerManager::EndTransactionIntern
 
   NS_ASSERTION(!aCallback || !mTransactionIncomplete,
                "If callback is not null, transaction must be complete");
 
   if (gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     FrameLayerBuilder::InvalidateAllLayers(this);
   }
 
+  if (startTime) {
+    PaintTiming& pt = mForwarder->GetPaintTiming();
+    pt.rasterMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
+  }
+
   return !mTransactionIncomplete;
 }
 
 void
 ClientLayerManager::StorePluginWidgetConfigurations(const nsTArray<nsIWidget::Configuration>& aConfigurations)
 {
   if (mForwarder) {
     mForwarder->StorePluginWidgetConfigurations(aConfigurations);
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -235,16 +235,26 @@ public:
   virtual void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch) override;
 
   virtual void AddDidCompositeObserver(DidCompositeObserver* aObserver) override;
   virtual void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override;
 
   virtual already_AddRefed<PersistentBufferProvider>
   CreatePersistentBufferProvider(const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override;
 
+  static PaintTiming* MaybeGetPaintTiming(LayerManager* aManager) {
+    if (!aManager) {
+      return nullptr;
+    }
+    if (ClientLayerManager* lm = aManager->AsClientLayerManager()) {
+      return &lm->AsShadowForwarder()->GetPaintTiming();
+    }
+    return nullptr;
+  }
+
 protected:
   enum TransactionPhase {
     PHASE_NONE, PHASE_CONSTRUCTION, PHASE_DRAWING, PHASE_FORWARD
   };
   TransactionPhase mPhase;
 
 private:
   // Listen memory-pressure event for ClientLayerManager
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/Diagnostics.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#include "Diagnostics.h"
+#include "mozilla/layers/LayersMessages.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace layers {
+
+float
+TimedMetric::Average() const
+{
+  // We take at most 2 seconds of history.
+  TimeStamp latest = TimeStamp::Now();
+  float total = 0.0f;
+  size_t count = 0;
+  for (auto iter = mHistory.rbegin(); iter != mHistory.rend(); iter++) {
+    if ((latest - iter->second).ToSeconds() > 2.0f) {
+      break;
+    }
+    total += iter->first;
+    count++;
+  }
+
+  if (!count) {
+    return 0.0f;
+  }
+  return total / float(count);
+}
+
+Diagnostics::Diagnostics()
+ : mCompositeFps("Compositor"),
+   mTransactionFps("LayerTransactions")
+{
+}
+
+void
+Diagnostics::RecordPaintTimes(const PaintTiming& aPaintTimes)
+{
+  mDlbMs.Add(aPaintTimes.dlMs());
+  mFlbMs.Add(aPaintTimes.flbMs());
+  mRasterMs.Add(aPaintTimes.rasterMs());
+  mSerializeMs.Add(aPaintTimes.serializeMs());
+  mSendMs.Add(aPaintTimes.sendMs());
+}
+
+std::string
+Diagnostics::GetFrameOverlayString()
+{
+  TimeStamp now = TimeStamp::Now();
+  unsigned fps = unsigned(mCompositeFps.AddFrameAndGetFps(now));
+  unsigned txnFps = unsigned(mTransactionFps.GetFPS(now));
+
+  // DL  = nsDisplayListBuilder
+  // FLB = FrameLayerBuilder
+  // R   = ClientLayerManager::EndTransaction
+  // CP  = ShadowLayerForwarder::EndTransaction (txn build)
+  // TX  = LayerTransactionChild::SendUpdate (IPDL serialize+send)
+  // UP  = LayerTransactionParent::RecvUpdate (IPDL deserialize, update, APZ update)
+  // CC_BUILD = Container prepare/composite frame building
+  // CC_EXEC  = Container render/composite drawing
+  nsPrintfCString line1("FPS: %d (TXN: %d)", fps, txnFps);
+  nsPrintfCString line2("CC_BUILD: %0.1f CC_EXEC: %0.1f",
+    mPrepareMs.Average(),
+    mCompositeMs.Average());
+  nsPrintfCString line3("DL: %0.1f FLB: %0.1f R: %0.1f CP: %0.1f TX: %0.1f UP: %0.1f",
+    mDlbMs.Average(),
+    mFlbMs.Average(),
+    mRasterMs.Average(),
+    mSerializeMs.Average(),
+    mSendMs.Average(),
+    mUpdateMs.Average());
+
+  return std::string(line1.get()) + ", " + std::string(line2.get()) + "\n" + std::string(line3.get());
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/Diagnostics.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 20; 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_gfx_layers_composite_Diagnostics_h
+#define mozilla_gfx_layers_composite_Diagnostics_h
+
+#include "FPSCounter.h"
+#include "gfxPrefs.h"
+#include "mozilla/TimeStamp.h"
+#include <deque>
+#include <string>
+#include <utility>
+
+namespace mozilla {
+namespace layers {
+
+class PaintTiming;
+
+class TimedMetric
+{
+  typedef std::pair<float, TimeStamp> Entry;
+
+public:
+  void Add(float aValue) {
+    if (mHistory.size() > kMaxHistory) {
+      mHistory.pop_front();
+    }
+    mHistory.push_back(Entry(aValue, TimeStamp::Now()));
+  }
+
+  float Average() const;
+
+private:
+  static const size_t kMaxHistory = 60;
+
+  std::deque<Entry> mHistory;
+};
+
+class Diagnostics
+{
+public:
+  Diagnostics();
+
+  void RecordPaintTimes(const PaintTiming& aPaintTimes);
+  void RecordUpdateTime(float aValue) {
+    mUpdateMs.Add(aValue);
+  }
+  void RecordPrepareTime(float aValue) {
+    mPrepareMs.Add(aValue);
+  }
+  void RecordCompositeTime(float aValue) {
+    mCompositeMs.Add(aValue);
+  }
+  void AddTxnFrame() {
+    mTransactionFps.AddFrame(TimeStamp::Now());
+  }
+
+  std::string GetFrameOverlayString();
+
+  class Record {
+  public:
+    Record() {
+      if (gfxPrefs::LayersDrawFPS()) {
+        mStart = TimeStamp::Now();
+      }
+    }
+    bool Recording() const {
+      return !mStart.IsNull();
+    }
+    float Duration() const {
+      return (TimeStamp::Now() - mStart).ToMilliseconds();
+    }
+
+  private:
+    TimeStamp mStart;
+  };
+
+private:
+  FPSCounter mCompositeFps;
+  FPSCounter mTransactionFps;
+  TimedMetric mDlbMs;
+  TimedMetric mFlbMs;
+  TimedMetric mRasterMs;
+  TimedMetric mSerializeMs;
+  TimedMetric mSendMs;
+  TimedMetric mUpdateMs;
+  TimedMetric mPrepareMs;
+  TimedMetric mCompositeMs;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_gfx_layers_composite_Diagnostics_h
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -5,16 +5,17 @@
 
 #include "LayerManagerComposite.h"
 #include <stddef.h>                     // for size_t
 #include <stdint.h>                     // for uint16_t, uint32_t
 #include "CanvasLayerComposite.h"       // for CanvasLayerComposite
 #include "ColorLayerComposite.h"        // for ColorLayerComposite
 #include "CompositableHost.h"           // for CompositableHost
 #include "ContainerLayerComposite.h"    // for ContainerLayerComposite, etc
+#include "Diagnostics.h"
 #include "FPSCounter.h"                 // for FPSState, FPSCounter
 #include "FrameMetrics.h"               // for FrameMetrics
 #include "GeckoProfiler.h"              // for profiler_set_frame_number, etc
 #include "ImageLayerComposite.h"        // for ImageLayerComposite
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "LayerScope.h"                 // for LayerScope Tool
 #include "protobuf/LayerScopePacket.pb.h" // for protobuf (LayerScope)
 #include "PaintedLayerComposite.h"      // for PaintedLayerComposite
@@ -119,28 +120,42 @@ HostLayerManager::HostLayerManager()
   , mWindowOverlayChanged(false)
   , mLastPaintTime(TimeDuration::Forever())
   , mRenderStartTime(TimeStamp::Now())
 {}
 
 HostLayerManager::~HostLayerManager()
 {}
 
+void
+HostLayerManager::RecordPaintTimes(const PaintTiming& aTiming)
+{
+  mDiagnostics->RecordPaintTimes(aTiming);
+}
+
+void
+HostLayerManager::RecordUpdateTime(float aValue)
+{
+  mDiagnostics->RecordUpdateTime(aValue);
+}
+
+
 /**
  * LayerManagerComposite
  */
 LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor)
 : mUnusedApzTransformWarning(false)
 , mDisabledApzWarning(false)
 , mCompositor(aCompositor)
 , mInTransaction(false)
 , mIsCompositorReady(false)
 , mGeometryChanged(true)
 {
   mTextRenderer = new TextRenderer(aCompositor);
+  mDiagnostics = MakeUnique<Diagnostics>();
   MOZ_ASSERT(aCompositor);
 
 #ifdef USE_SKIA
   mPaintCounter = nullptr;
 #endif
 }
 
 LayerManagerComposite::~LayerManagerComposite()
@@ -920,18 +935,31 @@ LayerManagerComposite::Render(const nsIn
   RefPtr<CompositingRenderTarget> previousTarget;
   if (haveLayerEffects) {
     previousTarget = PushGroupForLayerEffects();
   } else {
     mTwoPassTmpTarget = nullptr;
   }
 
   // Render our layers.
-  RootLayer()->Prepare(ViewAs<RenderTargetPixel>(clipRect, PixelCastJustification::RenderTargetIsParentLayerForRoot));
-  RootLayer()->RenderLayer(clipRect.ToUnknownRect(), Nothing());
+  {
+    Diagnostics::Record record;
+    RootLayer()->Prepare(ViewAs<RenderTargetPixel>(clipRect, PixelCastJustification::RenderTargetIsParentLayerForRoot));
+    if (record.Recording()) {
+      mDiagnostics->RecordPrepareTime(record.Duration());
+    }
+  }
+  // Execute draw commands.
+  {
+    Diagnostics::Record record;
+    RootLayer()->RenderLayer(clipRect.ToUnknownRect(), Nothing());
+    if (record.Recording()) {
+      mDiagnostics->RecordCompositeTime(record.Duration());
+    }
+  }
   RootLayer()->Cleanup();
 
   if (!mRegionToClear.IsEmpty()) {
     for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) {
       const IntRect& r = iter.Get();
       mCompositor->ClearRect(Rect(r.x, r.y, r.width, r.height));
     }
   }
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -47,16 +47,17 @@ class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 
 class CanvasLayerComposite;
 class ColorLayerComposite;
 class Compositor;
 class ContainerLayerComposite;
+class Diagnostics;
 struct EffectChain;
 class ImageLayer;
 class ImageLayerComposite;
 class LayerComposite;
 class RefLayerComposite;
 class PaintedLayerComposite;
 class TextRenderer;
 class CompositingRenderTarget;
@@ -164,16 +165,19 @@ public:
   void SetWindowOverlayChanged() { mWindowOverlayChanged = true; }
 
   void SetPaintTime(const TimeDuration& aPaintTime) { mLastPaintTime = aPaintTime; }
 
   virtual bool AlwaysScheduleComposite() const {
     return false;
   }
 
+  void RecordPaintTimes(const PaintTiming& aTiming);
+  void RecordUpdateTime(float aValue);
+
   TimeStamp GetCompositionTime() const {
     return mCompositionTime;
   }
   void SetCompositionTime(TimeStamp aTimeStamp) {
     mCompositionTime = aTimeStamp;
     if (!mCompositionTime.IsNull() && !mCompositeUntilTime.IsNull() &&
         mCompositionTime >= mCompositeUntilTime) {
       mCompositeUntilTime = TimeStamp();
@@ -191,16 +195,17 @@ public:
 
 protected:
   bool mDebugOverlayWantsNextFrame;
   nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
   // Testing property. If hardware composer is supported, this will return
   // true if the last frame was deemed 'too complicated' to be rendered.
   float mWarningLevel;
   mozilla::TimeStamp mWarnTime;
+  UniquePtr<Diagnostics> mDiagnostics;
 
   bool mWindowOverlayChanged;
   TimeDuration mLastPaintTime;
   TimeStamp mRenderStartTime;
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -142,16 +142,20 @@ public:
   NS_IMETHOD_(MozExternalRefCountType) Release() override { return HostIPCAllocator::Release(); }
   base::ProcessId RemotePid() override;
   bool StartSharingMetrics(mozilla::ipc::SharedMemoryBasic::Handle aHandle,
                            CrossProcessMutexHandle aMutexHandle,
                            uint64_t aLayersId,
                            uint32_t aApzcId) override;
   bool StopSharingMetrics(FrameMetrics::ViewID aScrollId,
                           uint32_t aApzcId) override;
+
+  virtual bool IsRemote() const {
+    return false;
+  }
 };
 
 class CompositorBridgeParent final : public CompositorBridgeParentBase
                                    , public CompositorController
                                    , public CompositorVsyncSchedulerOwner
 {
   friend class CompositorThreadHolder;
   friend class InProcessCompositorSession;
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -156,16 +156,20 @@ public:
   PWebRenderBridgeParent* AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                       const LayoutDeviceIntSize& aSize,
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                       uint32_t* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
 
   void ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive) override;
 
+  bool IsRemote() const override {
+    return true;
+  }
+
 protected:
   void OnChannelConnected(int32_t pid) override {
     mCompositorThreadHolder = CompositorThreadHolder::GetSingleton();
   }
 private:
   // Private destructor, to discourage deletion outside of Release():
   virtual ~CrossProcessCompositorBridgeParent();
 
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -144,19 +144,17 @@ LayerTransactionParent::RecvInitReadLock
 
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvUpdate(const TransactionInfo& aInfo)
 {
   GeckoProfilerTracingRAII tracer("Paint", "LayerTransaction");
   PROFILER_LABEL("LayerTransactionParent", "RecvUpdate",
     js::ProfileEntry::Category::GRAPHICS);
 
-#ifdef COMPOSITOR_PERFORMANCE_WARNING
   TimeStamp updateStart = TimeStamp::Now();
-#endif
 
   MOZ_LAYERS_LOG(("[ParentSide] received txn with %" PRIuSIZE " edits", aInfo.cset().Length()));
 
   UpdateFwdTransactionId(aInfo.fwdTransactionId());
   AutoClearReadLocks clearLocks(mReadLocks);
 
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     for (const auto& op : aInfo.toDestroy()) {
@@ -501,16 +499,18 @@ LayerTransactionParent::RecvUpdate(const
       if (severity > 1.f) {
         severity = 1.f;
       }
       mLayerManager->VisualFrameWarning(severity);
       printf_stderr("LayerTransactionParent::RecvUpdate transaction from process %d took %f ms",
                     OtherPid(),
                     latency.ToMilliseconds());
     }
+
+    mLayerManager->RecordUpdateTime((TimeStamp::Now() - updateStart).ToMilliseconds());
   }
 
   return IPC_OK();
 }
 
 bool
 LayerTransactionParent::SetLayerAttributes(const OpSetLayerAttributes& aOp)
 {
@@ -1021,10 +1021,22 @@ LayerTransactionParent::RecvReleaseLayer
 
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvReleaseCompositable(const CompositableHandle& aHandle)
 {
   ReleaseCompositable(aHandle);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+LayerTransactionParent::RecvRecordPaintTimes(const PaintTiming& aTiming)
+{
+  // Currently we only add paint timings for remote layers. In the future
+  // we could be smarter and use paint timings from the UI process, either
+  // as a separate overlay or if no remote layers are attached.
+  if (mLayerManager && mCompositorBridge->IsRemote()) {
+    mLayerManager->RecordPaintTimes(aTiming);
+  }
+  return IPC_OK();
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -135,16 +135,17 @@ protected:
                                                            const float& aX, const float& aY) override;
   virtual mozilla::ipc::IPCResult RecvSetAsyncZoom(const FrameMetrics::ViewID& aId,
                                                    const float& aValue) override;
   virtual mozilla::ipc::IPCResult RecvFlushApzRepaints() override;
   virtual mozilla::ipc::IPCResult RecvGetAPZTestData(APZTestData* aOutData) override;
   virtual mozilla::ipc::IPCResult RecvRequestProperty(const nsString& aProperty, float* aValue) override;
   virtual mozilla::ipc::IPCResult RecvSetConfirmedTargetAPZC(const uint64_t& aBlockId,
                                                              nsTArray<ScrollableLayerGuid>&& aTargets) override;
+  virtual mozilla::ipc::IPCResult RecvRecordPaintTimes(const PaintTiming& aTiming) override;
 
   bool SetLayerAttributes(const OpSetLayerAttributes& aOp);
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   template <typename T>
   bool BindLayer(const RefPtr<Layer>& aLayer, const T& aCreateOp) {
     return BindLayerToHandle(aLayer, aCreateOp.layer());
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -529,16 +529,24 @@ struct ImageCompositeNotification {
   uint32_t frameID;
   uint32_t producerID;
 };
 
 union AsyncParentMessageData {
   OpNotifyNotUsed;
 };
 
+struct PaintTiming {
+  float serializeMs;
+  float sendMs;
+  float dlMs;
+  float flbMs;
+  float rasterMs;
+};
+
 struct TransactionInfo
 {
   Edit[] cset;
   OpSetSimpleLayerAttributes[] setSimpleAttrs;
   OpSetLayerAttributes[] setAttrs;
   CompositableOperation[] paints;
   OpDestroy[] toDestroy;
   uint64_t fwdTransactionId;
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -117,15 +117,17 @@ parent:
 
   // Query a named property from the last frame
   sync RequestProperty(nsString property) returns (float value);
 
   // Tell the compositor to notify APZ that a layer has been confirmed for an
   // input event.
   async SetConfirmedTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] aTargets);
 
+  async RecordPaintTimes(PaintTiming timing);
+
   async Shutdown();
 child:
   async __delete__();
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -10,16 +10,17 @@
 #include <set>                          // for _Rb_tree_const_iterator, etc
 #include <vector>                       // for vector
 #include "GeckoProfiler.h"              // for PROFILER_LABEL
 #include "ISurfaceAllocator.h"          // for IsSurfaceDescriptorValid
 #include "Layers.h"                     // for Layer
 #include "RenderTrace.h"                // for RenderTraceScope
 #include "gfx2DGlue.h"                  // for Moz2D transition helpers
 #include "gfxPlatform.h"                // for gfxImageFormat, gfxPlatform
+#include "gfxPrefs.h"
 //#include "gfxSharedImageSurface.h"      // for gfxSharedImageSurface
 #include "ipc/IPCMessageUtils.h"        // for gfxContentType, null_t
 #include "IPDLActor.h"
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/layers/CompositableClient.h"  // for CompositableClient, etc
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ContentClient.h"
@@ -581,16 +582,21 @@ ShadowLayerForwarder::EndTransaction(con
 
   TransactionInfo info;
 
   MOZ_ASSERT(IPCOpen(), "no manager to forward to");
   if (!IPCOpen()) {
     return false;
   }
 
+  Maybe<TimeStamp> startTime;
+  if (gfxPrefs::LayersDrawFPS()) {
+    startTime = Some(TimeStamp::Now());
+  }
+
   GetCompositorBridgeChild()->WillEndTransaction();
 
   MOZ_ASSERT(aId);
 
   PROFILER_LABEL("ShadowLayerForwarder", "EndTransaction",
     js::ProfileEntry::Category::GRAPHICS);
 
   RenderTraceScope rendertrace("Foward Transaction", "000091");
@@ -719,32 +725,42 @@ ShadowLayerForwarder::EndTransaction(con
                             aRegionToClear);
   info.targetConfig() = targetConfig;
 
   if (!GetTextureForwarder()->IsSameProcess()) {
     MOZ_LAYERS_LOG(("[LayersForwarder] syncing before send..."));
     PlatformSyncBeforeUpdate();
   }
 
+  if (startTime) {
+    mPaintTiming.serializeMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
+    startTime = Some(TimeStamp::Now());
+  }
+
   for (ReadLockVector& locks : mTxn->mReadLocks) {
     if (locks.Length()) {
       if (!mShadowManager->SendInitReadLocks(locks)) {
         MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending read locks failed!"));
         return false;
       }
     }
   }
 
   MOZ_LAYERS_LOG(("[LayersForwarder] sending transaction..."));
   RenderTraceScope rendertrace3("Forward Transaction", "000093");
   if (!mShadowManager->SendUpdate(info)) {
     MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending transaction failed!"));
     return false;
   }
 
+  if (startTime) {
+    mPaintTiming.sendMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
+    mShadowManager->SendRecordPaintTimes(mPaintTiming);
+  }
+
   *aSent = true;
   mIsFirstPaint = false;
   mPaintSyncId = 0;
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
 RefPtr<CompositableClient>
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -387,16 +387,20 @@ public:
   virtual uint64_t GetFwdTransactionId() override;
 
   void ReleaseLayer(const LayerHandle& aHandle);
 
   bool InForwarderThread() override {
     return NS_IsMainThread();
   }
 
+  PaintTiming& GetPaintTiming() {
+    return mPaintTiming;
+  }
+
   // Returns true if aSurface wraps a Shmem.
   static bool IsShmem(SurfaceDescriptor* aSurface);
 
   /**
    * Sends a synchronous ping to the compsoitor.
    *
    * This is bad for performance and should only be called as a last resort if the
    * compositor may be blocked for a long period of time, to avoid that the content
@@ -437,16 +441,17 @@ private:
   DiagnosticTypes mDiagnosticTypes;
   bool mIsFirstPaint;
   bool mWindowOverlayChanged;
   int32_t mPaintSyncId;
   InfallibleTArray<PluginWindowData> mPluginWindowData;
   UniquePtr<ActiveResourceTracker> mActiveResourceTracker;
   uint64_t mNextLayerHandle;
   nsDataHashtable<nsUint64HashKey, CompositableClient*> mCompositables;
+  PaintTiming mPaintTiming;
 };
 
 class CompositableClient;
 
 /**
  * A ShadowableLayer is a Layer can be shared with a parent context
  * through a ShadowLayerForwarder.  A ShadowableLayer maps to a
  * Shadow*Layer in a parent context.
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -311,16 +311,17 @@ UNIFIED_SOURCES += [
     'client/TiledContentClient.cpp',
     'composite/AnimationMetricsTracker.cpp',
     'composite/AsyncCompositionManager.cpp',
     'composite/CanvasLayerComposite.cpp',
     'composite/ColorLayerComposite.cpp',
     'composite/CompositableHost.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
+    'composite/Diagnostics.cpp',
     'composite/FPSCounter.cpp',
     'composite/FrameUniformityData.cpp',
     'composite/GPUVideoTextureHost.cpp',
     'composite/ImageComposite.cpp',
     'composite/ImageHost.cpp',
     'composite/ImageLayerComposite.cpp',
     'composite/LayerManagerComposite.cpp',
     'composite/PaintedLayerComposite.cpp',
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3667,16 +3667,24 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
       canvasArea.IntersectRect(canvasArea, visibleRegion.GetBounds());
       nsDisplayListBuilder::AutoBuildingDisplayList
         buildingDisplayList(&builder, aFrame, canvasArea, false);
       presShell->AddCanvasBackgroundColorItem(
              builder, list, aFrame, canvasArea, aBackstop);
     }
 
     builder.LeavePresShell(aFrame, &list);
+
+    if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) {
+      if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
+        if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
+          pt->dlMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds();
+        }
+      }
+    }
   }
 
   Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME,
                                  startBuildDisplayList);
 
   bool profilerNeedsDisplayList = profiler_feature_active("displaylistdump");
   bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint();
 #ifdef MOZ_DUMP_PAINTING
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -78,16 +78,17 @@
 #include "mozilla/GeckoRestyleManager.h"
 #include "nsCaret.h"
 #include "nsISelection.h"
 #include "nsDOMTokenList.h"
 #include "mozilla/RuleNodeCacheConditions.h"
 #include "nsCSSProps.h"
 #include "nsPluginFrame.h"
 #include "nsSVGMaskFrame.h"
+#include "ClientLayerManager.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/WebRenderDisplayItemLayer.h"
 #include "mozilla/layers/WebRenderMessages.h"
 #include "mozilla/layers/WebRenderDisplayItemLayer.h"
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount().
@@ -2110,19 +2111,26 @@ already_AddRefed<LayerManager> nsDisplay
       rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>());
     }
 
     ContainerLayerParameters containerParameters
       (presShell->GetResolution(), presShell->GetResolution());
 
     {
       PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization);
+
       root = layerBuilder->
         BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this,
                                containerParameters, nullptr);
+
+      if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) {
+        if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(layerManager)) {
+          pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds();
+        }
+      }
     }
 
     if (!root) {
       layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
       return nullptr;
     }
     // Root is being scaled up by the X/Y resolution. Scale it back down.
     root->SetPostScale(1.0f/containerParameters.mXScale,
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -4917,16 +4917,20 @@ class PaintTelemetry
     COUNT,
   };
 
   class AutoRecord
   {
    public:
     explicit AutoRecord(Metric aMetric);
     ~AutoRecord();
+
+    TimeStamp GetStart() const {
+      return mStart;
+    }
    private:
     Metric mMetric;
     mozilla::TimeStamp mStart;
   };
 
   class AutoRecordPaint
   {
    public: