Bug 1578319: Telemetry for total time spent in layout per Refresh Driver tick. r=heycam
authorDan Glastonbury <dan.glastonbury@gmail.com>
Fri, 01 Nov 2019 04:33:48 +0000
changeset 500121 a118361746d6ae53d115c2f71a403d55a4f3620b
parent 500120 272775d127445bbbd2cbe9895828e198f79f2b8a
child 500122 28aa763e7834023b28c2462a078f1bd91baa7f7b
push id114164
push useraiakab@mozilla.com
push dateTue, 05 Nov 2019 10:06:15 +0000
treeherdermozilla-inbound@4d585c7edc76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1578319
milestone72.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 1578319: Telemetry for total time spent in layout per Refresh Driver tick. r=heycam Differential Revision: https://phabricator.services.mozilla.com/D44427
layout/base/LayoutTelemetryTools.cpp
layout/base/LayoutTelemetryTools.h
layout/base/PresShell.cpp
layout/base/PresShell.h
layout/base/PresShellInlines.h
layout/base/moz.build
layout/base/nsRefreshDriver.cpp
toolkit/components/telemetry/Histograms.json
new file mode 100644
--- /dev/null
+++ b/layout/base/LayoutTelemetryTools.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "mozilla/layout/LayoutTelemetryTools.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::layout_telemetry;
+
+// Returns the key name expected by telemetry. Keep to date with
+// toolkits/components/telemetry/Histograms.json.
+static nsLiteralCString SubsystemTelemetryKey(LayoutSubsystem aSubsystem) {
+  switch (aSubsystem) {
+    default:
+      MOZ_CRASH("Unexpected LayoutSubsystem value");
+    case LayoutSubsystem::Restyle:
+      return nsLiteralCString("Restyle");
+    case LayoutSubsystem::Reflow:
+      return nsLiteralCString("ReflowOther");
+    case LayoutSubsystem::ReflowFlex:
+      return nsLiteralCString("ReflowFlex");
+    case LayoutSubsystem::ReflowGrid:
+      return nsLiteralCString("ReflowGrid");
+    case LayoutSubsystem::ReflowTable:
+      return nsLiteralCString("ReflowTable");
+    case LayoutSubsystem::ReflowText:
+      return nsLiteralCString("ReflowText");
+  }
+}
+
+static AutoRecord* sCurrentRecord;
+
+static FlushKind ToKind(FlushType aFlushType) {
+  switch (aFlushType) {
+    default:
+      MOZ_CRASH("Expected FlushType::Style or FlushType::Layout");
+    case FlushType::Style:
+      return FlushKind::Style;
+    case FlushType::Layout:
+      return FlushKind::Layout;
+  }
+}
+
+namespace mozilla {
+namespace layout_telemetry {
+
+Data::Data() {
+  PodZero(&mReqsPerFlush);
+  PodZero(&mFlushesPerTick);
+  PodZero(&mLayoutSubsystemDurationMs);
+}
+
+void Data::IncReqsPerFlush(FlushType aFlushType) {
+  mReqsPerFlush[ToKind(aFlushType)]++;
+}
+
+void Data::IncFlushesPerTick(FlushType aFlushType) {
+  mFlushesPerTick[ToKind(aFlushType)]++;
+}
+
+void Data::PingReqsPerFlushTelemetry(FlushType aFlushType) {
+  auto flushKind = ToKind(aFlushType);
+  if (flushKind == FlushKind::Layout) {
+    auto styleFlushReqs = mReqsPerFlush[FlushKind::Style].value();
+    auto layoutFlushReqs = mReqsPerFlush[FlushKind::Layout].value();
+    Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_LAYOUT_FLUSH,
+                          NS_LITERAL_CSTRING("Style"), styleFlushReqs);
+    Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_LAYOUT_FLUSH,
+                          NS_LITERAL_CSTRING("Layout"), layoutFlushReqs);
+    mReqsPerFlush[FlushKind::Style] = SaturateUint8(0);
+    mReqsPerFlush[FlushKind::Layout] = SaturateUint8(0);
+  } else {
+    auto styleFlushReqs = mReqsPerFlush[FlushKind::Style].value();
+    Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_STYLE_FLUSH,
+                          styleFlushReqs);
+    mReqsPerFlush[FlushKind::Style] = SaturateUint8(0);
+  }
+}
+
+void Data::PingFlushPerTickTelemetry(FlushType aFlushType) {
+  auto flushKind = ToKind(aFlushType);
+  auto styleFlushes = mFlushesPerTick[FlushKind::Style].value();
+  if (styleFlushes > 0) {
+    Telemetry::Accumulate(Telemetry::PRESSHELL_FLUSHES_PER_TICK,
+                          NS_LITERAL_CSTRING("Style"), styleFlushes);
+    mFlushesPerTick[FlushKind::Style] = SaturateUint8(0);
+  }
+
+  auto layoutFlushes = mFlushesPerTick[FlushKind::Layout].value();
+  if (flushKind == FlushKind::Layout && layoutFlushes > 0) {
+    Telemetry::Accumulate(Telemetry::PRESSHELL_FLUSHES_PER_TICK,
+                          NS_LITERAL_CSTRING("Layout"), layoutFlushes);
+    mFlushesPerTick[FlushKind::Layout] = SaturateUint8(0);
+  }
+}
+
+void Data::PingTotalMsPerTickTelemetry(FlushType aFlushType) {
+  auto flushKind = ToKind(aFlushType);
+  auto range = (flushKind == FlushKind::Style)
+                   ? MakeEnumeratedRange(LayoutSubsystem::Restyle,
+                                         LayoutSubsystem::Reflow)
+                   : MakeEnumeratedRange(LayoutSubsystem::Reflow,
+                                         LayoutSubsystem::Count);
+
+  for (auto subsystem : range) {
+    auto key = SubsystemTelemetryKey(subsystem);
+    double& duration = mLayoutSubsystemDurationMs[subsystem];
+    if (duration > 0.0) {
+      Telemetry::Accumulate(Telemetry::PRESSHELL_LAYOUT_TOTAL_MS_PER_TICK, key,
+                            static_cast<uint32_t>(duration));
+      duration = 0.0;
+    }
+  }
+}
+
+void Data::PingPerTickTelemetry(FlushType aFlushType) {
+  PingFlushPerTickTelemetry(aFlushType);
+  PingTotalMsPerTickTelemetry(aFlushType);
+}
+
+AutoRecord::AutoRecord(LayoutSubsystem aSubsystem)
+    : AutoRecord(nullptr, aSubsystem) {}
+
+AutoRecord::AutoRecord(Data* aLayoutTelemetry, LayoutSubsystem aSubsystem)
+    : mParentRecord(sCurrentRecord),
+      mLayoutTelemetry(aLayoutTelemetry),
+      mSubsystem(aSubsystem),
+      mStartTime(TimeStamp::Now()),
+      mDurationMs(0.0) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If we're re-entering the same subsystem, don't update the current record.
+  if (mParentRecord) {
+    if (mParentRecord->mSubsystem == mSubsystem) {
+      return;
+    }
+
+    mLayoutTelemetry = mParentRecord->mLayoutTelemetry;
+    MOZ_ASSERT(mLayoutTelemetry);
+
+    // If we're entering a new subsystem, record the amount of time spent in the
+    // parent record before setting the new current record.
+    mParentRecord->mDurationMs +=
+        (mStartTime - mParentRecord->mStartTime).ToMilliseconds();
+  }
+
+  sCurrentRecord = this;
+}
+
+AutoRecord::~AutoRecord() {
+  if (sCurrentRecord != this) {
+    // If this record is not head of the list, do nothing.
+    return;
+  }
+
+  TimeStamp now = TimeStamp::Now();
+  mDurationMs += (now - mStartTime).ToMilliseconds();
+  mLayoutTelemetry->mLayoutSubsystemDurationMs[mSubsystem] += mDurationMs;
+
+  if (mParentRecord) {
+    // Restart the parent recording from this point
+    mParentRecord->mStartTime = now;
+  }
+
+  // Unlink this record from the current record list
+  sCurrentRecord = mParentRecord;
+}
+
+}  // namespace layout_telemetry
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/base/LayoutTelemetryTools.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+/* Tools for collecting and reporting layout and style telemetry */
+
+#ifndef mozilla_LayoutTelemetryTools_h
+#define mozilla_LayoutTelemetryTools_h
+
+#include "mozilla/TimeStamp.h"
+
+#define LAYOUT_TELEMETRY_RECORD(subsystem) \
+  layout_telemetry::AutoRecord a(layout_telemetry::LayoutSubsystem::subsystem)
+
+#define LAYOUT_TELEMETRY_RECORD_BASE(subsystem)     \
+  layout_telemetry::AutoRecord a(&mLayoutTelemetry, \
+                                 layout_telemetry::LayoutSubsystem::subsystem)
+
+namespace mozilla {
+namespace layout_telemetry {
+
+enum class FlushKind : uint8_t { Style, Layout, Count };
+
+enum class LayoutSubsystem : uint8_t {
+  Restyle,
+  Reflow,
+  ReflowFlex,
+  ReflowGrid,
+  ReflowTable,
+  ReflowText,
+  Count
+};
+
+using LayoutSubsystemDurations =
+    EnumeratedArray<LayoutSubsystem, LayoutSubsystem::Count, double>;
+using LayoutFlushCount =
+    EnumeratedArray<FlushKind, FlushKind::Count, SaturateUint8>;
+
+struct Data {
+  Data();
+
+  void IncReqsPerFlush(FlushType aFlushType);
+  void IncFlushesPerTick(FlushType aFlushType);
+
+  void PingTelemetry();
+
+  // Send the current number of flush requests for aFlushType to telemetry and
+  // reset the count.
+  void PingReqsPerFlushTelemetry(FlushType aFlushType);
+
+  // Send the current non-zero number of style and layout flushes to telemetry
+  // and reset the count.
+  void PingFlushPerTickTelemetry(FlushType aFlushType);
+
+  // Send the current non-zero time spent under style and layout processing this
+  // tick to telemetry and reset the total.
+  void PingTotalMsPerTickTelemetry(FlushType aFlushType);
+
+  // Send the current per-tick telemetry for `aFlushType`.
+  void PingPerTickTelemetry(FlushType aFlushType);
+
+  LayoutFlushCount mReqsPerFlush;
+  LayoutFlushCount mFlushesPerTick;
+  LayoutSubsystemDurations mLayoutSubsystemDurationMs;
+};
+
+class AutoRecord {
+ public:
+  explicit AutoRecord(LayoutSubsystem aSubsystem);
+  AutoRecord(Data* aLayoutTelemetry, LayoutSubsystem aSubsystem);
+  ~AutoRecord();
+
+ private:
+  AutoRecord* mParentRecord;
+  Data* mLayoutTelemetry;
+  LayoutSubsystem mSubsystem;
+  TimeStamp mStartTime;
+  double mDurationMs;
+};
+
+}  // namespace layout_telemetry
+}  // namespace mozilla
+
+#endif  // !mozilla_LayoutTelemetryTools_h
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -846,18 +846,16 @@ PresShell::PresShell()
   MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
 
 #ifdef MOZ_REFLOW_PERF
   mReflowCountMgr = MakeUnique<ReflowCountMgr>();
   mReflowCountMgr->SetPresContext(mPresContext);
   mReflowCountMgr->SetPresShell(this);
 #endif
   mLastOSWake = mLoadBegin = TimeStamp::Now();
-  PodZero(&mReqsPerFlush);
-  PodZero(&mFlushesPerTick);
 
   PointerEventHandler::Initialize();
 }
 
 NS_INTERFACE_TABLE_HEAD(PresShell)
   NS_INTERFACE_TABLE_BEGIN
     // In most cases, PresShell should be treated as concrete class, but need to
     // QI for weak reference.  Therefore, the case needed by do_QueryReferent()
@@ -4109,16 +4107,17 @@ void PresShell::DoFlushPendingNotificati
       Maybe<uint64_t> innerWindowID;
       if (auto* window = mDocument->GetInnerWindow()) {
         innerWindowID = Some(window->WindowID());
       }
       AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
                                                 innerWindowID);
 #endif
       PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
+      LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
 
       mPresContext->RestyleManager()->ProcessPendingRestyles();
     }
 
 #ifdef MOZ_XBL
     // Process whatever XBL constructors those restyles queued up.  This
     // ensures that onload doesn't fire too early and that we won't do extra
     // reflows after those constructors run.
@@ -4140,16 +4139,17 @@ void PresShell::DoFlushPendingNotificati
       Maybe<uint64_t> innerWindowID;
       if (auto* window = mDocument->GetInnerWindow()) {
         innerWindowID = Some(window->WindowID());
       }
       AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
                                                 innerWindowID);
 #endif
       PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
+      LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
 
       mPresContext->RestyleManager()->ProcessPendingRestyles();
       // Clear mNeedStyleFlush here agagin to make this flag work properly for
       // optimization since the flag might have set in ProcessPendingRestyles().
       mNeedStyleFlush = false;
     }
 
     AssertFrameTreeIsSane(*this);
@@ -4196,36 +4196,36 @@ void PresShell::DoFlushPendingNotificati
     // We suppressed this flush either due to it not being safe to flush,
     // or due to SuppressInterruptibleReflows().  Either way, the
     // mNeedLayoutFlush flag needs to be re-set.
     SetNeedLayoutFlush();
   }
 
   // Update flush counters
   if (didStyleFlush) {
-    mFlushesPerTick[FlushKind::Style]++;
+    mLayoutTelemetry.IncReqsPerFlush(FlushType::Style);
   }
 
   if (didLayoutFlush) {
-    mFlushesPerTick[FlushKind::Layout]++;
+    mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
   }
 
   // Record telemetry for the number of requests per each flush type.
   //
   // Flushes happen as style or style+layout. This depends upon the `flushType`
   // where flushType >= InterruptibleLayout means flush layout and flushType >=
   // Style means flush style. We only report if didLayoutFlush or didStyleFlush
   // is true because we care if a flush really did take place. (Flush is guarded
   // by `isSafeToFlush == true`.)
   if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) {
     MOZ_ASSERT(didLayoutFlush == didStyleFlush);
-    PingReqsPerFlushTelemetry(FlushKind::Layout);
+    mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout);
   } else if (flushType >= FlushType::Style && didStyleFlush) {
     MOZ_ASSERT(!didLayoutFlush);
-    PingReqsPerFlushTelemetry(FlushKind::Style);
+    mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style);
   }
 }
 
 void PresShell::CharacterDataChanged(nsIContent* aContent,
                                      const CharacterDataChangeInfo& aInfo) {
   MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged");
   MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document");
 
@@ -9096,16 +9096,19 @@ bool PresShell::ScheduleReflowOffTimer()
 bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible,
                          OverflowChangedTracker* aOverflowTracker) {
 #ifdef MOZ_GECKO_PROFILER
   nsIURI* uri = mDocument->GetDocumentURI();
   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
       "Reflow", LAYOUT_Reflow,
       uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A"));
 #endif
+
+  LAYOUT_TELEMETRY_RECORD_BASE(Reflow);
+
   PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording;
 
   gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics();
   TimeStamp timeStart;
   if (tp) {
     tp->Accumulate();
     tp->reflowCount++;
     timeStart = TimeStamp::Now();
@@ -11217,42 +11220,11 @@ static bool EndPaintHelper(Document* aDo
 void PresShell::EndPaint() {
   ClearPendingVisualScrollUpdate();
 
   if (mDocument) {
     mDocument->EnumerateSubDocuments(EndPaintHelper, nullptr);
   }
 }
 
-void PresShell::PingReqsPerFlushTelemetry(FlushKind aFlushKind) {
-  if (aFlushKind == FlushKind::Layout) {
-    auto styleFlushReqs = mReqsPerFlush[FlushKind::Style].value();
-    auto layoutFlushReqs = mReqsPerFlush[FlushKind::Layout].value();
-    Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_LAYOUT_FLUSH,
-                          NS_LITERAL_CSTRING("Style"), styleFlushReqs);
-    Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_LAYOUT_FLUSH,
-                          NS_LITERAL_CSTRING("Layout"), layoutFlushReqs);
-    mReqsPerFlush[FlushKind::Style] = SaturateUint8(0);
-    mReqsPerFlush[FlushKind::Layout] = SaturateUint8(0);
-  } else {
-    auto styleFlushReqs = mReqsPerFlush[FlushKind::Style].value();
-    Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_STYLE_FLUSH,
-                          styleFlushReqs);
-    mReqsPerFlush[FlushKind::Style] = SaturateUint8(0);
-  }
-}
-
-void PresShell::PingFlushPerTickTelemetry(FlushType aFlushType) {
-  MOZ_ASSERT(aFlushType == FlushType::Style || aFlushType == FlushType::Layout);
-  auto styleFlushes = mFlushesPerTick[FlushKind::Style].value();
-  if (styleFlushes > 0) {
-    Telemetry::Accumulate(Telemetry::PRESSHELL_FLUSHES_PER_TICK,
-                          NS_LITERAL_CSTRING("Style"), styleFlushes);
-    mFlushesPerTick[FlushKind::Style] = SaturateUint8(0);
-  }
-
-  auto layoutFlushes = mFlushesPerTick[FlushKind::Layout].value();
-  if (aFlushType == FlushType::Layout && layoutFlushes > 0) {
-    Telemetry::Accumulate(Telemetry::PRESSHELL_FLUSHES_PER_TICK,
-                          NS_LITERAL_CSTRING("Layout"), layoutFlushes);
-    mFlushesPerTick[FlushKind::Layout] = SaturateUint8(0);
-  }
-}
+void PresShell::PingPerTickTelemetry(FlushType aFlushType) {
+  mLayoutTelemetry.PingPerTickTelemetry(aFlushType);
+}
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -18,26 +18,26 @@
 #include "Units.h"
 #include "ZoomConstraintsClient.h"
 #include "gfxPoint.h"
 #include "mozilla/ArenaObjectID.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/FlushType.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/Saturate.h"
 #include "mozilla/ScrollTypes.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/ServoStyleConsts.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/dom/HTMLDocumentBinding.h"
 #include "mozilla/layers/FocusTarget.h"
+#include "mozilla/layout/LayoutTelemetryTools.h"
 #include "nsChangeHint.h"
 #include "nsClassHashtable.h"
 #include "nsColor.h"
 #include "nsCOMArray.h"
 #include "nsCoord.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsFrameManager.h"
 #include "nsFrameState.h"
@@ -2789,25 +2789,20 @@ class PresShell final : public nsStubDoc
   bool mInVerifyReflow = false;
   // The reflow root under which we're currently reflowing.  Null when
   // not in reflow.
   nsIFrame* mCurrentReflowRoot = nullptr;
 
   nsIFrame* mDrawEventTargetFrame = nullptr;
 #endif  // #ifdef DEBUG
 
-  enum class FlushKind : uint8_t { Style, Layout, Count };
-
-  // Send the current number of flush requests for aFlushType to telemetry and
-  // reset the count.
-  void PingReqsPerFlushTelemetry(FlushKind aFlushKind);
-
-  // Send the current non-zero number of style and layout flushes to telemetry
-  // and reset the count.
-  void PingFlushPerTickTelemetry(FlushType aFlushType);
+  // Send, and reset, the current per tick telemetry. This includes:
+  // * non-zero number of style and layout flushes
+  // * non-zero ms duration spent in style and reflow since the last tick.
+  void PingPerTickTelemetry(FlushType aFlushType);
 
  private:
   // IMPORTANT: The ownership implicit in the following member variables
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
 
   // These are the same Document and PresContext owned by the DocViewer.
   // we must share ownership.
@@ -3162,17 +3157,16 @@ class PresShell final : public nsStubDoc
     bool mPreventDrag;
   };
   static CapturingContentInfo sCapturingContentInfo;
 
   static bool sDisableNonTestMouseEvents;
 
   static bool sProcessInteractable;
 
-  EnumeratedArray<FlushKind, FlushKind::Count, SaturateUint8> mReqsPerFlush;
-  EnumeratedArray<FlushKind, FlushKind::Count, SaturateUint8> mFlushesPerTick;
+  layout_telemetry::Data mLayoutTelemetry;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(PresShell, NS_PRESSHELL_IID)
 
 }  // namespace mozilla
 
 #endif  // mozilla_PresShell_h
--- a/layout/base/PresShellInlines.h
+++ b/layout/base/PresShellInlines.h
@@ -22,34 +22,34 @@ void PresShell::SetNeedLayoutFlush() {
   }
 
 #ifdef MOZ_GECKO_PROFILER
   if (!mReflowCause) {
     mReflowCause = profiler_get_backtrace();
   }
 #endif
 
-  mReqsPerFlush[FlushKind::Layout]++;
+  mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
 }
 
 void PresShell::SetNeedStyleFlush() {
   mNeedStyleFlush = true;
   if (dom::Document* doc = mDocument->GetDisplayDocument()) {
     if (PresShell* presShell = doc->GetPresShell()) {
       presShell->mNeedStyleFlush = true;
     }
   }
 
 #ifdef MOZ_GECKO_PROFILER
   if (!mStyleCause) {
     mStyleCause = profiler_get_backtrace();
   }
 #endif
 
-  mReqsPerFlush[FlushKind::Style]++;
+  mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
 }
 
 void PresShell::EnsureStyleFlush() {
   SetNeedStyleFlush();
   ObserveStyleFlushes();
 }
 
 void PresShell::SetNeedThrottledAnimationFlush() {
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -83,23 +83,28 @@ EXPORTS.mozilla += [
     'PresShellInlines.h',
     'RestyleManager.h',
     'ScrollStyles.h',
     'ScrollTypes.h',
     'ShapeUtils.h',
     'StaticPresData.h',
 ]
 
+EXPORTS.mozilla.layout += [
+    'LayoutTelemetryTools.h',
+]
+
 UNIFIED_SOURCES += [
     'AccessibleCaret.cpp',
     'AccessibleCaretEventHub.cpp',
     'AccessibleCaretManager.cpp',
     'GeckoMVMContext.cpp',
     'GeometryUtils.cpp',
     'LayoutLogging.cpp',
+    'LayoutTelemetryTools.cpp',
     'MobileViewportManager.cpp',
     'MotionPathUtils.cpp',
     'nsBidi.cpp',
     'nsBidiPresUtils.cpp',
     'nsCaret.cpp',
     'nsCounterManager.cpp',
     'nsCSSColorUtils.cpp',
     'nsCSSFrameConstructor.cpp',
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1991,19 +1991,18 @@ void nsRefreshDriver::Tick(VsyncId aId, 
           presShell->FlushPendingNotifications(
               ChangesToFlush(FlushType::Style, false));
           // Inform the FontFaceSet that we ticked, so that it can resolve its
           // ready promise if it needs to (though it might still be waiting on
           // a layout flush).
           presShell->NotifyFontFaceSetOnRefresh();
           mNeedToRecomputeVisibility = true;
 
-          // Record the telemetry for the # of flushes that occurred between
-          // ticks.
-          presShell->PingFlushPerTickTelemetry(FlushType::Style);
+          // Record the telemetry for events that occurred between ticks.
+          presShell->PingPerTickTelemetry(FlushType::Style);
         }
       }
     } else if (i == 2) {
       // This is the FlushType::Layout case.
       AutoTArray<PresShell*, 16> observers;
       observers.AppendElements(mLayoutFlushObservers);
       for (uint32_t j = observers.Length();
            j && mPresContext && mPresContext->GetPresShell(); --j) {
@@ -2020,19 +2019,18 @@ void nsRefreshDriver::Tick(VsyncId aId, 
                                   ? FlushType::Layout
                                   : FlushType::InterruptibleLayout;
         presShell->FlushPendingNotifications(ChangesToFlush(flushType, false));
         // Inform the FontFaceSet that we ticked, so that it can resolve its
         // ready promise if it needs to.
         presShell->NotifyFontFaceSetOnRefresh();
         mNeedToRecomputeVisibility = true;
 
-        // Record the telemetry for the # of flushes that occured between
-        // ticks.
-        presShell->PingFlushPerTickTelemetry(FlushType::Layout);
+        // Record the telemetry for events that occurred between ticks.
+        presShell->PingPerTickTelemetry(FlushType::Layout);
       }
     }
 
     // The pres context may be destroyed during we do the flushing.
     if (!mPresContext || !mPresContext->GetPresShell()) {
       StopTimer();
       return;
     }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -15586,10 +15586,30 @@
     "alert_emails": ["seceng-telemetry@mozilla.com", "kjacobs@mozilla.com"],
     "expires_in_version": "80",
     "releaseChannelCollection": "opt-out",
     "kind": "exponential",
     "high": 2000,
     "n_buckets": 100,
     "bug_numbers": [1564179],
     "description": "milliseconds to complete a TLS handshake that used delegated credentials"
+  },
+  "PRESSHELL_LAYOUT_TOTAL_MS_PER_TICK": {
+    "record_in_processes": ["main", "content"],
+    "products": ["firefox", "fennec", "geckoview"],
+    "alert_emails": ["dglastonbury@mozilla.com"],
+    "bug_numbers": [1578319],
+    "expires_in_version": "76",
+    "keyed": true,
+    "keys": [
+      "Restyle",
+      "ReflowOther",
+      "ReflowFlex",
+      "ReflowGrid",
+      "ReflowTable",
+      "ReflowText"
+    ],
+    "kind": "exponential",
+    "high": 1000,
+    "n_buckets": 50,
+    "description": "Time in milliseconds spent in the layout system per Refresh Driver tick."
   }
 }