[go: nahoru, domu]

[cc/metrics] Introduce FrameInfo.

FrameInfo contains a summary for each frame. This summary info can
be used for various analysis and for generating various metrics.
In the future, it might contain additional information (e.g. the
latencies for each rendering stage, etc.).

BUG=1273920

Change-Id: I7cad7ecea53f575fd696f361d0570c63b05e24b2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3303955
Reviewed-by: Behdad Bakhshinategh <behdadb@chromium.org>
Commit-Queue: Sadrul Chowdhury <sadrul@chromium.org>
Cr-Commit-Position: refs/heads/main@{#947634}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 2d1335b3..d9bc808 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -184,6 +184,8 @@
     "metrics/event_metrics.h",
     "metrics/events_metrics_manager.cc",
     "metrics/events_metrics_manager.h",
+    "metrics/frame_info.cc",
+    "metrics/frame_info.h",
     "metrics/frame_sequence_metrics.cc",
     "metrics/frame_sequence_metrics.h",
     "metrics/frame_sequence_tracker.cc",
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index 44361ee..eaa81a9b 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -32,6 +32,7 @@
 using FrameReportType = CompositorFrameReporter::FrameReportType;
 using BlinkBreakdown = CompositorFrameReporter::BlinkBreakdown;
 using VizBreakdown = CompositorFrameReporter::VizBreakdown;
+using FrameFinalState = FrameInfo::FrameFinalState;
 
 constexpr int kFrameReportTypeCount =
     static_cast<int>(FrameReportType::kMaxValue) + 1;
@@ -703,56 +704,36 @@
       std::make_unique<ProcessedVizBreakdown>(viz_start_time_, viz_breakdown_);
 
   DCHECK_EQ(current_stage_.start_time, base::TimeTicks());
-  switch (frame_termination_status_) {
-    case FrameTerminationStatus::kPresentedFrame:
+  const FrameInfo frame_info = GenerateFrameInfo();
+  switch (frame_info.final_state) {
+    case FrameFinalState::kDropped:
+      EnableReportType(FrameReportType::kDroppedFrame);
+      break;
+
+    case FrameFinalState::kNoUpdateDesired:
+      // If this reporter was cloned, and the cloned reporter was marked as
+      // containing 'partial update' (i.e. missing desired updates from the
+      // main-thread), but this reporter terminated with 'no damage', then reset
+      // the 'partial update' flag from the cloned reporter (as well as other
+      // depending reporters).
+      while (!partial_update_dependents_.empty()) {
+        auto dependent = partial_update_dependents_.front();
+        if (dependent)
+          dependent->set_has_partial_update(false);
+        partial_update_dependents_.pop();
+      }
+      break;
+
+    case FrameFinalState::kPresentedAll:
+    case FrameFinalState::kPresentedPartialNewMain:
+    case FrameFinalState::kPresentedPartialOldMain:
       EnableReportType(FrameReportType::kNonDroppedFrame);
       if (ComputeSafeDeadlineForFrame(args_) < frame_termination_time_)
         EnableReportType(FrameReportType::kMissedDeadlineFrame);
       break;
-    case FrameTerminationStatus::kDidNotPresentFrame:
-      EnableReportType(FrameReportType::kDroppedFrame);
-      break;
-    case FrameTerminationStatus::kReplacedByNewReporter:
-      EnableReportType(FrameReportType::kDroppedFrame);
-      break;
-    case FrameTerminationStatus::kDidNotProduceFrame: {
-      const bool no_update_from_main =
-          frame_skip_reason_.has_value() &&
-          frame_skip_reason() == FrameSkippedReason::kNoDamage;
-      const bool no_update_from_compositor =
-          !has_partial_update_ && frame_skip_reason_.has_value() &&
-          frame_skip_reason() == FrameSkippedReason::kWaitingOnMain;
-      const bool draw_is_throttled =
-          frame_skip_reason_.has_value() &&
-          frame_skip_reason() == FrameSkippedReason::kDrawThrottled;
-
-      if (no_update_from_main) {
-        // If this reporter was cloned, and the cloned reporter was marked as
-        // containing 'partial update' (i.e. missing desired updates from the
-        // main-thread), but this reporter terminated with 'no damage', then
-        // reset the 'partial update' flag from the cloned reporter (as well as
-        // other depending reporters).
-        while (!partial_update_dependents_.empty()) {
-          auto dependent = partial_update_dependents_.front();
-          if (dependent)
-            dependent->set_has_partial_update(false);
-          partial_update_dependents_.pop();
-        }
-      } else if (!no_update_from_compositor) {
-        // If rather main thread has damage or compositor thread has partial
-        // damage, then it's a dropped frame.
-        EnableReportType(FrameReportType::kDroppedFrame);
-      } else if (draw_is_throttled) {
-        EnableReportType(FrameReportType::kDroppedFrame);
-      }
-
-      break;
-    }
-    case FrameTerminationStatus::kUnknown:
-      break;
   }
 
-  ReportCompositorLatencyTraceEvents();
+  ReportCompositorLatencyTraceEvents(frame_info);
   if (TestReportType(FrameReportType::kNonDroppedFrame))
     ReportEventLatencyTraceEvents();
 
@@ -782,8 +763,7 @@
         dropped_frame_counter->AddGoodFrame();
     }
 
-    dropped_frame_counter->OnEndFrame(args_,
-                                      IsDroppedFrameAffectingSmoothness());
+    dropped_frame_counter->OnEndFrame(args_, frame_info);
   }
 
   if (discarded_partial_update_dependents_count_ > 0)
@@ -1054,11 +1034,12 @@
   }
 }
 
-void CompositorFrameReporter::ReportCompositorLatencyTraceEvents() const {
+void CompositorFrameReporter::ReportCompositorLatencyTraceEvents(
+    const FrameInfo& info) const {
   if (stage_history_.empty())
     return;
 
-  if (IsDroppedFrameAffectingSmoothness()) {
+  if (info.IsDroppedAffectingSmoothness()) {
     devtools_instrumentation::DidDropSmoothnessFrame(
         layer_tree_host_id_, args_.frame_time, args_.frame_id.sequence_number,
         has_partial_update_);
@@ -1088,7 +1069,7 @@
         reporter->set_frame_sequence(args_.frame_id.sequence_number);
         reporter->set_layer_tree_host_id(layer_tree_host_id_);
         reporter->set_has_missing_content(has_missing_content_);
-        if (IsDroppedFrameAffectingSmoothness()) {
+        if (info.IsDroppedAffectingSmoothness()) {
           DCHECK(state == ChromeFrameReporter::STATE_DROPPED ||
                  state == ChromeFrameReporter::STATE_PRESENTED_PARTIAL);
           reporter->set_affects_smoothness(true);
@@ -1339,33 +1320,6 @@
   return tick_clock_->NowTicks();
 }
 
-bool CompositorFrameReporter::IsDroppedFrameAffectingSmoothness() const {
-  // If the frame was not shown, then it hurt smoothness only if either of the
-  // threads is affecting smoothness (e.g. running an animation, scroll, pinch,
-  // etc.).
-  if (TestReportType(FrameReportType::kDroppedFrame)) {
-    return smooth_thread_ != SmoothThread::kSmoothNone;
-  }
-
-  // If the frame includes new main-thread update, even if it's for an earlier
-  // begin-frame, then do not count it as a dropped frame affecting smoothness.
-  if (is_accompanied_by_main_thread_update_) {
-    return false;
-  }
-
-  // If the frame was shown, but included only partial updates, then it hurt
-  // smoothness only if the main-thread is affecting smoothness (e.g. running an
-  // animation, or scroll etc.).
-  if (has_partial_update_) {
-    return smooth_thread_ == SmoothThread::kSmoothMain ||
-           smooth_thread_ == SmoothThread::kSmoothBoth;
-  }
-
-  // If the frame was shown, and did not include partial updates, then this
-  // frame did not hurt smoothness.
-  return false;
-}
-
 void CompositorFrameReporter::AdoptReporter(
     std::unique_ptr<CompositorFrameReporter> reporter) {
   // If |this| reporter is dependent on another reporter to decide about partial
@@ -1409,4 +1363,55 @@
   return weak_factory_.GetWeakPtr();
 }
 
+FrameInfo CompositorFrameReporter::GenerateFrameInfo() const {
+  FrameFinalState final_state = FrameFinalState::kNoUpdateDesired;
+  switch (frame_termination_status_) {
+    case FrameTerminationStatus::kPresentedFrame:
+      if (has_partial_update_) {
+        final_state = is_accompanied_by_main_thread_update_
+                          ? FrameFinalState::kPresentedPartialNewMain
+                          : FrameFinalState::kPresentedPartialOldMain;
+      } else {
+        final_state = FrameFinalState::kPresentedAll;
+      }
+      break;
+
+    case FrameTerminationStatus::kDidNotPresentFrame:
+    case FrameTerminationStatus::kReplacedByNewReporter:
+      final_state = FrameFinalState::kDropped;
+      break;
+
+    case FrameTerminationStatus::kDidNotProduceFrame: {
+      const bool no_update_expected_from_main =
+          frame_skip_reason_.has_value() &&
+          frame_skip_reason() == FrameSkippedReason::kNoDamage;
+      const bool no_update_expected_from_compositor =
+          !has_partial_update_ && frame_skip_reason_.has_value() &&
+          frame_skip_reason() == FrameSkippedReason::kWaitingOnMain;
+      const bool draw_is_throttled =
+          frame_skip_reason_.has_value() &&
+          frame_skip_reason() == FrameSkippedReason::kDrawThrottled;
+
+      if (!no_update_expected_from_main &&
+          !no_update_expected_from_compositor) {
+        final_state = FrameFinalState::kDropped;
+      } else if (draw_is_throttled) {
+        final_state = FrameFinalState::kDropped;
+      } else {
+        final_state = FrameFinalState::kNoUpdateDesired;
+      }
+      break;
+    }
+
+    case FrameTerminationStatus::kUnknown:
+      break;
+  }
+
+  FrameInfo info;
+  info.final_state = final_state;
+  info.smooth_thread = smooth_thread_;
+  info.has_missing_content = has_missing_content_;
+  return info;
+}
+
 }  // namespace cc
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index c438761..9511365 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -19,6 +19,7 @@
 #include "cc/cc_export.h"
 #include "cc/metrics/begin_main_frame_metrics.h"
 #include "cc/metrics/event_metrics.h"
+#include "cc/metrics/frame_info.h"
 #include "cc/metrics/frame_sequence_metrics.h"
 #include "cc/scheduler/scheduler.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
@@ -140,12 +141,7 @@
     ~StageData();
   };
 
-  enum SmoothThread {
-    kSmoothNone,
-    kSmoothCompositor,
-    kSmoothMain,
-    kSmoothBoth
-  };
+  using SmoothThread = FrameInfo::SmoothThread;
 
   // Holds a processed list of Blink breakdowns with an `Iterator` class to
   // easily iterator over them.
@@ -355,7 +351,7 @@
       base::TimeDelta time_delta) const;
 
   void ReportEventLatencyHistograms() const;
-  void ReportCompositorLatencyTraceEvents() const;
+  void ReportCompositorLatencyTraceEvents(const FrameInfo& info) const;
   void ReportEventLatencyTraceEvents() const;
 
   void EnableReportType(FrameReportType report_type) {
@@ -373,7 +369,7 @@
 
   base::TimeTicks Now() const;
 
-  bool IsDroppedFrameAffectingSmoothness() const;
+  FrameInfo GenerateFrameInfo() const;
 
   base::WeakPtr<CompositorFrameReporter> GetWeakPtr();
 
diff --git a/cc/metrics/dropped_frame_counter.cc b/cc/metrics/dropped_frame_counter.cc
index 047d130..db2a684 100644
--- a/cc/metrics/dropped_frame_counter.cc
+++ b/cc/metrics/dropped_frame_counter.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/histogram.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/trace_event.h"
+#include "cc/metrics/frame_info.h"
 #include "cc/metrics/frame_sorter.h"
 #include "cc/metrics/total_frame_counter.h"
 #include "cc/metrics/ukm_smoothness_data.h"
@@ -190,7 +191,8 @@
 }
 
 void DroppedFrameCounter::OnEndFrame(const viz::BeginFrameArgs& args,
-                                     bool is_dropped) {
+                                     const FrameInfo& frame_info) {
+  const bool is_dropped = frame_info.IsDroppedAffectingSmoothness();
   if (!args.interval.is_zero())
     total_frames_in_window_ = kSlidingWindowInterval / args.interval;
 
@@ -225,7 +227,7 @@
   }
 
   if (fcp_received_)
-    frame_sorter_.AddFrameResult(args, is_dropped);
+    frame_sorter_.AddFrameResult(args, frame_info);
 }
 
 void DroppedFrameCounter::ReportFrames() {
@@ -356,7 +358,7 @@
 }
 
 void DroppedFrameCounter::NotifyFrameResult(const viz::BeginFrameArgs& args,
-                                            bool is_dropped) {
+                                            const FrameInfo& frame_info) {
   // Entirely disregard the frames with interval larger than the window --
   // these are violating the assumptions in the below code and should
   // only occur with external frame control, where dropped frame stats
@@ -364,6 +366,7 @@
   if (args.interval >= kSlidingWindowInterval)
     return;
 
+  const bool is_dropped = frame_info.IsDroppedAffectingSmoothness();
   sliding_window_.push({args, is_dropped});
 
   if (is_dropped)
diff --git a/cc/metrics/dropped_frame_counter.h b/cc/metrics/dropped_frame_counter.h
index e24ce36dd..1995b798 100644
--- a/cc/metrics/dropped_frame_counter.h
+++ b/cc/metrics/dropped_frame_counter.h
@@ -19,6 +19,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace cc {
+struct FrameInfo;
 class TotalFrameCounter;
 
 // This class maintains a counter for produced/dropped frames, and can be used
@@ -75,7 +76,7 @@
   void ReportFramesForUI();
 
   void OnBeginFrame(const viz::BeginFrameArgs& args, bool is_scroll_active);
-  void OnEndFrame(const viz::BeginFrameArgs& args, bool is_dropped);
+  void OnEndFrame(const viz::BeginFrameArgs& args, const FrameInfo& frame_info);
   void SetUkmSmoothnessDestination(UkmSmoothnessDataShared* smoothness_data);
   void OnFcpReceived();
 
@@ -126,7 +127,8 @@
   }
 
  private:
-  void NotifyFrameResult(const viz::BeginFrameArgs& args, bool is_dropped);
+  void NotifyFrameResult(const viz::BeginFrameArgs& args,
+                         const FrameInfo& frame_info);
   base::TimeDelta ComputeCurrentWindowSize() const;
 
   void PopSlidingWindow();
diff --git a/cc/metrics/dropped_frame_counter_unittest.cc b/cc/metrics/dropped_frame_counter_unittest.cc
index b3d64ff..986daff 100644
--- a/cc/metrics/dropped_frame_counter_unittest.cc
+++ b/cc/metrics/dropped_frame_counter_unittest.cc
@@ -19,6 +19,12 @@
 namespace cc {
 namespace {
 
+FrameInfo CreateStubFrameInfo(bool is_dropped) {
+  return {is_dropped ? FrameInfo::FrameFinalState::kDropped
+                     : FrameInfo::FrameFinalState::kPresentedAll,
+          FrameInfo::SmoothThread::kSmoothBoth};
+}
+
 class DroppedFrameCounterTestBase : public LayerTreeTest {
  public:
   DroppedFrameCounterTestBase() = default;
@@ -269,7 +275,8 @@
       for (auto is_dropped : frame_states) {
         viz::BeginFrameArgs args_ = SimulateBeginFrameArgs();
         dropped_frame_counter_.OnBeginFrame(args_, /*is_scroll_active=*/false);
-        dropped_frame_counter_.OnEndFrame(args_, is_dropped);
+        dropped_frame_counter_.OnEndFrame(args_,
+                                          CreateStubFrameInfo(is_dropped));
         sequence_number_++;
         frame_time_ += interval_;
       }
@@ -302,8 +309,8 @@
     viz::BeginFrameArgs args_ = SimulateBeginFrameArgs();
     dropped_frame_counter_.OnBeginFrame(args_, /*is_scroll_active=*/false);
     dropped_frame_counter_.OnBeginFrame(args_, /*is_scroll_active=*/false);
-    dropped_frame_counter_.OnEndFrame(args_, main_dropped);
-    dropped_frame_counter_.OnEndFrame(args_, impl_dropped);
+    dropped_frame_counter_.OnEndFrame(args_, CreateStubFrameInfo(main_dropped));
+    dropped_frame_counter_.OnEndFrame(args_, CreateStubFrameInfo(impl_dropped));
     sequence_number_++;
     frame_time_ += interval_;
   }
@@ -773,7 +780,7 @@
   // End each of the frames as dropped. The first three should not count for
   // smoothness, only the last two.
   for (const auto& frame : pending_frames) {
-    dropped_frame_counter_.OnEndFrame(frame, true);
+    dropped_frame_counter_.OnEndFrame(frame, CreateStubFrameInfo(true));
   }
   EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 2u);
 }
@@ -816,7 +823,7 @@
   // End each of the pending frames as dropped. These shouldn't affect any of
   // the metrics.
   for (const auto& frame : pending_frames) {
-    dropped_frame_counter_.OnEndFrame(frame, true);
+    dropped_frame_counter_.OnEndFrame(frame, CreateStubFrameInfo(true));
   }
 
   // After FCP time, add a second each of 80% and 60%, and three seconds of 40%
diff --git a/cc/metrics/frame_info.cc b/cc/metrics/frame_info.cc
new file mode 100644
index 0000000..ed8c28ec
--- /dev/null
+++ b/cc/metrics/frame_info.cc
@@ -0,0 +1,72 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/metrics/frame_info.h"
+
+namespace cc {
+
+namespace {
+
+bool IsCompositorSmooth(FrameInfo::SmoothThread thread) {
+  return thread == FrameInfo::SmoothThread::kSmoothCompositor ||
+         thread == FrameInfo::SmoothThread::kSmoothBoth;
+}
+
+bool IsMainSmooth(FrameInfo::SmoothThread thread) {
+  return thread == FrameInfo::SmoothThread::kSmoothMain ||
+         thread == FrameInfo::SmoothThread::kSmoothBoth;
+}
+
+}  // namespace
+
+bool FrameInfo::IsDroppedAffectingSmoothness() const {
+  // If neither of the threads are expected to be smooth, then this frame cannot
+  // affect smoothness.
+  if (smooth_thread == SmoothThread::kSmoothNone)
+    return false;
+
+  switch (final_state) {
+    case FrameFinalState::kDropped:
+      return true;
+
+    case FrameFinalState::kPresentedAll:
+    case FrameFinalState::kPresentedPartialNewMain:
+      // If the frame includes new main-thread update, even if it's for an
+      // earlier begin-frame, then do not count it as a dropped frame affecting
+      // smoothness.
+      return false;
+
+    case FrameFinalState::kPresentedPartialOldMain:
+      // Partial-update frames without new updates from the main-thread affect
+      // smoothness if the main-thread is expected to be smooth.
+      return smooth_thread == SmoothThread::kSmoothBoth ||
+             smooth_thread == SmoothThread::kSmoothMain;
+
+    case FrameFinalState::kNoUpdateDesired:
+      return false;
+  }
+}
+
+void FrameInfo::MergeWith(const FrameInfo& info) {
+  if (info.has_missing_content)
+    has_missing_content = true;
+  if (info.final_state == FrameFinalState::kDropped)
+    final_state = FrameFinalState::kDropped;
+
+  const bool is_compositor_smooth = IsCompositorSmooth(smooth_thread) ||
+                                    IsCompositorSmooth(info.smooth_thread);
+  const bool is_main_smooth =
+      IsMainSmooth(smooth_thread) || IsMainSmooth(info.smooth_thread);
+  if (is_compositor_smooth && is_main_smooth) {
+    smooth_thread = SmoothThread::kSmoothBoth;
+  } else if (is_compositor_smooth) {
+    smooth_thread = SmoothThread::kSmoothCompositor;
+  } else if (is_main_smooth) {
+    smooth_thread = SmoothThread::kSmoothMain;
+  } else {
+    smooth_thread = SmoothThread::kSmoothNone;
+  }
+}
+
+}  // namespace cc
diff --git a/cc/metrics/frame_info.h b/cc/metrics/frame_info.h
new file mode 100644
index 0000000..a1bd0fc
--- /dev/null
+++ b/cc/metrics/frame_info.h
@@ -0,0 +1,55 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_METRICS_FRAME_INFO_H_
+#define CC_METRICS_FRAME_INFO_H_
+
+#include "cc/cc_export.h"
+
+namespace cc {
+
+struct CC_EXPORT FrameInfo {
+  enum class FrameFinalState {
+    kNoUpdateDesired,
+    kDropped,
+
+    // A `presented all` frame contains all the desired update for this vsync.
+    // Note that this doesn't necessarily mean the frame included updates from
+    // both the main and the compositor thread. For example, if there's only a
+    // main-thread animation running, and the animation update was included in
+    // the frame produced, then it's `presented all`, although the compositor
+    // thread did not have any updates for this frame.
+    kPresentedAll,
+
+    // A `partial update` frame contains updates from a compositor frame, but
+    // misses the update from the main-thread for the same vsync. However, it is
+    // still possible for such a `partial update` frame to contain new update
+    // from an earlier main-thread.
+    //
+    // `kPresentedPartialOldMain` represents a partial update frame without any
+    //     new update from the main-thread.
+    // `kPresentedPartialNewMain` represents a partial update frame with some
+    //     new update from the main-thread.
+    kPresentedPartialOldMain,
+    kPresentedPartialNewMain,
+  };
+  FrameFinalState final_state = FrameFinalState::kNoUpdateDesired;
+
+  enum class SmoothThread {
+    kSmoothNone,
+    kSmoothCompositor,
+    kSmoothMain,
+    kSmoothBoth
+  };
+  SmoothThread smooth_thread = SmoothThread::kSmoothNone;
+
+  bool has_missing_content = false;
+
+  bool IsDroppedAffectingSmoothness() const;
+  void MergeWith(const FrameInfo& info);
+};
+
+}  // namespace cc
+
+#endif  // CC_METRICS_FRAME_INFO_H_
diff --git a/cc/metrics/frame_sorter.cc b/cc/metrics/frame_sorter.cc
index 2c1bcc8..f14d4c84 100644
--- a/cc/metrics/frame_sorter.cc
+++ b/cc/metrics/frame_sorter.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "cc/metrics/frame_info.h"
+
 namespace cc {
 
 using FrameState = FrameSorter::FrameState;
@@ -67,7 +69,7 @@
 }
 
 void FrameSorter::AddFrameResult(const viz::BeginFrameArgs& args,
-                                 bool is_dropped) {
+                                 const FrameInfo& frame_info) {
   if (pending_frames_.empty() || current_source_id_ > args.frame_id.source_id) {
     // The change in source_id can be as a result of crash on gpu process,
     // and as a result the corresponding frame to result does not exist.
@@ -79,6 +81,15 @@
   // - When the frame was in pending_frames_ and was removed because of reset.
   if (!frame_states_.count(args.frame_id))
     return;
+
+  const auto f = frame_infos_.find(args.frame_id);
+  if (f != frame_infos_.end()) {
+    f->second.MergeWith(frame_info);
+  } else {
+    frame_infos_[args.frame_id] = frame_info;
+  }
+
+  const bool is_dropped = frame_info.IsDroppedAffectingSmoothness();
   auto& frame_state = frame_states_[args.frame_id];
   frame_state.OnAck(is_dropped);
   if (!frame_state.IsComplete()) {
@@ -116,10 +127,12 @@
 
 void FrameSorter::Reset() {
   for (auto pending_frame : pending_frames_) {
-    auto& frame_state = frame_states_[pending_frame.frame_id];
+    const auto& frame_id = pending_frame.frame_id;
+    auto& frame_state = frame_states_[frame_id];
     if (frame_state.IsComplete() && !frame_state.should_ignore()) {
-      flush_callback_.Run(pending_frame, frame_state.is_dropped());
-      frame_states_.erase(pending_frame.frame_id);
+      flush_callback_.Run(pending_frame, frame_infos_[frame_id]);
+      frame_states_.erase(frame_id);
+      frame_infos_.erase(frame_id);
       continue;
     }
     frame_state.OnReset();
@@ -132,12 +145,14 @@
   size_t flushed_count = 0;
   while (!pending_frames_.empty()) {
     const auto& first = pending_frames_.front();
-    auto& frame_state = frame_states_[first.frame_id];
+    const auto& frame_id = first.frame_id;
+    auto& frame_state = frame_states_[frame_id];
     if (!frame_state.IsComplete())
       break;
     ++flushed_count;
-    flush_callback_.Run(first, frame_state.is_dropped());
-    frame_states_.erase(first.frame_id);
+    flush_callback_.Run(first, frame_infos_[frame_id]);
+    frame_states_.erase(frame_id);
+    frame_infos_.erase(frame_id);
     pending_frames_.pop_front();
   }
   DCHECK_GT(flushed_count, 0u);
diff --git a/cc/metrics/frame_sorter.h b/cc/metrics/frame_sorter.h
index 3feb538..f06221f 100644
--- a/cc/metrics/frame_sorter.h
+++ b/cc/metrics/frame_sorter.h
@@ -17,6 +17,8 @@
 
 namespace cc {
 
+struct FrameInfo;
+
 // This class is used to process the frames in order of initiation.
 // So regardless of which order frames are terminated, the  callback function
 // will frames sorter will br called on the frames in the order of initiation
@@ -41,7 +43,7 @@
 
   using InOrderBeginFramesCallback =
       base::RepeatingCallback<void(const viz::BeginFrameArgs&,
-                                   bool /*is_dropped*/)>;
+                                   const FrameInfo&)>;
   explicit FrameSorter(InOrderBeginFramesCallback callback);
   ~FrameSorter();
 
@@ -53,7 +55,8 @@
 
   // The results can be added in any order. However, the frame must have been
   // added by an earlier call to |AddNewFrame()|.
-  void AddFrameResult(const viz::BeginFrameArgs& args, bool is_dropped);
+  void AddFrameResult(const viz::BeginFrameArgs& args,
+                      const FrameInfo& frame_info);
 
   // Check if a frame has been previously reported as dropped.
   bool IsAlreadyReportedDropped(const viz::BeginFrameId& id) const;
@@ -73,6 +76,7 @@
 
   // State of each frame in terms of ack expectation.
   std::map<viz::BeginFrameId, FrameState> frame_states_;
+  std::map<viz::BeginFrameId, FrameInfo> frame_infos_;
 
   absl::optional<uint64_t> current_source_id_;
 };
diff --git a/cc/metrics/frame_sorter_unittest.cc b/cc/metrics/frame_sorter_unittest.cc
index e8f58f43..bbe7ece 100644
--- a/cc/metrics/frame_sorter_unittest.cc
+++ b/cc/metrics/frame_sorter_unittest.cc
@@ -6,9 +6,11 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/strings/string_number_conversions.h"
+#include "cc/metrics/frame_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cc {
@@ -60,12 +62,16 @@
         case 'S':
           frame_sorter_.AddNewFrame(args_[id]);
           break;
-        case 'D':
-          frame_sorter_.AddFrameResult(args_[id], true);
+        case 'D': {
+          FrameInfo info = {FrameInfo::FrameFinalState::kDropped,
+                            FrameInfo::SmoothThread::kSmoothBoth};
+          frame_sorter_.AddFrameResult(args_[id], info);
           break;
-        case 'P':
-          frame_sorter_.AddFrameResult(args_[id], false);
+        }
+        case 'P': {
+          frame_sorter_.AddFrameResult(args_[id], {});
           break;
+        }
         case 'I':
           IncreaseSourceId();
           break;
@@ -89,8 +95,8 @@
   }
 
  private:
-  void FlushFrame(const viz::BeginFrameArgs& args, bool is_dropped) {
-    sorted_frames_.emplace_back(args, is_dropped);
+  void FlushFrame(const viz::BeginFrameArgs& args, const FrameInfo& frame) {
+    sorted_frames_.emplace_back(args, frame.IsDroppedAffectingSmoothness());
   }
 
   FrameSorter frame_sorter_;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index b4e539b..8083faf 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -14046,7 +14046,9 @@
       BEGINFRAME_FROM_HERE, 1u /*source_id*/, 2u /*sequence_number*/, now,
       deadline, interval, viz::BeginFrameArgs::NORMAL);
 
-  dropped_frame_counter->OnEndFrame(args, true);
+  dropped_frame_counter->OnEndFrame(args,
+                                    {FrameInfo::FrameFinalState::kDropped,
+                                     FrameInfo::SmoothThread::kSmoothBoth});
   // FCP not received, so the total_smoothness_dropped_ won't increase.
   EXPECT_EQ(dropped_frame_counter->total_smoothness_dropped(), 0u);
 
@@ -14054,7 +14056,9 @@
   begin_frame_metrics.should_measure_smoothness = true;
   host_impl_->ReadyToCommit(args, &begin_frame_metrics);
   dropped_frame_counter->SetTimeFcpReceivedForTesting(args.frame_time);
-  dropped_frame_counter->OnEndFrame(args, true);
+  dropped_frame_counter->OnEndFrame(args,
+                                    {FrameInfo::FrameFinalState::kDropped,
+                                     FrameInfo::SmoothThread::kSmoothBoth});
   EXPECT_EQ(dropped_frame_counter->total_smoothness_dropped(), 1u);
 
   total_frame_counter->set_total_frames_for_testing(1u);
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 2408e0dd..be7e9f2 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -9919,7 +9919,9 @@
     }
 
     // Mark every frame as a dropped frame affecting smoothness.
-    host_impl->dropped_frame_counter()->OnEndFrame(last_args_, true);
+    host_impl->dropped_frame_counter()->OnEndFrame(
+        last_args_, {FrameInfo::FrameFinalState::kDropped,
+                     FrameInfo::SmoothThread::kSmoothBoth});
     host_impl->SetNeedsRedraw();
     --frames_counter_;
   }
@@ -9972,7 +9974,9 @@
     }
 
     // Mark every frame as a dropped frame affecting smoothness.
-    host_impl->dropped_frame_counter()->OnEndFrame(last_args_, true);
+    host_impl->dropped_frame_counter()->OnEndFrame(
+        last_args_, {FrameInfo::FrameFinalState::kDropped,
+                     FrameInfo::SmoothThread::kSmoothBoth});
     host_impl->SetNeedsRedraw();
     --frames_counter_;
   }