Streamline frame timings recording

`FlutterFrameTimingsRecorder` is passed through the lifecycle of the
frame and records the events that happen through it. This also serves as
a builder for the `FrameTiming` object that is later passed to the
framework.

This change also aims to lay the foundation to capture a unique frame
identifier which will later be added to `FlutterFrameTimingsRecorder` to
identify the trace events corresponding to each frame.

x-ref: https://github.com/flutter/engine/pull/25662
x-ref: https://github.com/flutter/flutter/issues/80735
diff --git a/flow/BUILD.gn b/flow/BUILD.gn
index e112594..a2f1d8a 100644
--- a/flow/BUILD.gn
+++ b/flow/BUILD.gn
@@ -14,6 +14,8 @@
     "diff_context.h",
     "embedded_views.cc",
     "embedded_views.h",
+    "frame_timings.cc",
+    "frame_timings.h",
     "instrumentation.cc",
     "instrumentation.h",
     "layers/backdrop_filter_layer.cc",
diff --git a/flow/frame_timings.cc b/flow/frame_timings.cc
new file mode 100644
index 0000000..2f0ccf8
--- /dev/null
+++ b/flow/frame_timings.cc
@@ -0,0 +1,102 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is FrameTimingsRecorder::Governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "flutter/flow/frame_timings.h"
+#include <mutex>
+#include "flutter/common/settings.h"
+#include "flutter/fml/logging.h"
+
+namespace flutter {
+
+FrameTimingsRecorder::FrameTimingsRecorder() = default;
+
+FrameTimingsRecorder::~FrameTimingsRecorder() = default;
+
+fml::TimePoint FrameTimingsRecorder::GetVsyncStartTime() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kVsync);
+  return vsync_start_;
+}
+
+fml::TimePoint FrameTimingsRecorder::GetVsyncTargetTime() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kVsync);
+  return vsync_target_;
+}
+
+fml::TimePoint FrameTimingsRecorder::GetBuildStartTime() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kBuildStart);
+  return build_start_;
+}
+
+fml::TimePoint FrameTimingsRecorder::GetBuildEndTime() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kBuildEnd);
+  return build_end_;
+}
+
+fml::TimePoint FrameTimingsRecorder::GetRasterStartTime() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kRasterStart);
+  return raster_start_;
+}
+
+fml::TimePoint FrameTimingsRecorder::GetRasterEndTime() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kRasterEnd);
+  return raster_end_;
+}
+
+fml::TimeDelta FrameTimingsRecorder::GetBuildDuration() const {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ >= State::kBuildEnd);
+  return build_end_ - build_start_;
+}
+
+void FrameTimingsRecorder::RecordVsync(fml::TimePoint vsync_start,
+                                       fml::TimePoint vsync_target) {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ == State::kUnitialized);
+  state_ = State::kVsync;
+  vsync_start_ = vsync_start;
+  vsync_target_ = vsync_target;
+}
+
+void FrameTimingsRecorder::RecordBuildStart(fml::TimePoint build_start) {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ == State::kVsync);
+  state_ = State::kBuildStart;
+  build_start_ = build_start;
+}
+
+void FrameTimingsRecorder::RecordBuildEnd(fml::TimePoint build_end) {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ == State::kBuildStart);
+  state_ = State::kBuildEnd;
+  build_end_ = build_end;
+}
+
+void FrameTimingsRecorder::RecordRasterStart(fml::TimePoint raster_start) {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ == State::kBuildEnd);
+  state_ = State::kRasterStart;
+  raster_start_ = raster_start;
+}
+
+FrameTiming FrameTimingsRecorder::RecordRasterEnd(fml::TimePoint raster_end) {
+  std::scoped_lock state_lock(state_mutex_);
+  FML_CHECK(state_ == State::kRasterStart);
+  state_ = State::kRasterEnd;
+  raster_end_ = raster_end;
+  FrameTiming timing;
+  timing.Set(FrameTiming::kVsyncStart, vsync_start_);
+  timing.Set(FrameTiming::kBuildStart, build_start_);
+  timing.Set(FrameTiming::kBuildFinish, build_end_);
+  timing.Set(FrameTiming::kRasterStart, raster_start_);
+  timing.Set(FrameTiming::kRasterFinish, raster_end_);
+  return timing;
+}
+
+}  // namespace flutter
diff --git a/flow/frame_timings.h b/flow/frame_timings.h
new file mode 100644
index 0000000..4d7d9d4
--- /dev/null
+++ b/flow/frame_timings.h
@@ -0,0 +1,72 @@
+// Copyright 2013 The Flutter 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 FLUTTER_FLOW_FRAME_TIMINGS_H_
+#define FLUTTER_FLOW_FRAME_TIMINGS_H_
+
+#include <mutex>
+
+#include "flutter/common/settings.h"
+#include "flutter/fml/macros.h"
+#include "flutter/fml/time/time_delta.h"
+#include "flutter/fml/time/time_point.h"
+
+namespace flutter {
+
+class FrameTimingsRecorder {
+ public:
+  FrameTimingsRecorder();
+
+  ~FrameTimingsRecorder();
+
+  fml::TimePoint GetVsyncStartTime() const;
+
+  fml::TimePoint GetVsyncTargetTime() const;
+
+  fml::TimePoint GetBuildStartTime() const;
+
+  fml::TimePoint GetBuildEndTime() const;
+
+  fml::TimePoint GetRasterStartTime() const;
+
+  fml::TimePoint GetRasterEndTime() const;
+
+  fml::TimeDelta GetBuildDuration() const;
+
+  void RecordVsync(fml::TimePoint vsync_start, fml::TimePoint vsync_target);
+
+  void RecordBuildStart(fml::TimePoint build_start);
+
+  void RecordBuildEnd(fml::TimePoint build_end);
+
+  void RecordRasterStart(fml::TimePoint raster_start);
+
+  FrameTiming RecordRasterEnd(fml::TimePoint raster_end);
+
+ private:
+  enum class State : uint32_t {
+    kUnitialized = 1,
+    kVsync = 2,
+    kBuildStart = 3,
+    kBuildEnd = 4,
+    kRasterStart = 5,
+    kRasterEnd = 6,
+  };
+
+  mutable std::mutex state_mutex_;
+  State state_ = State::kUnitialized;
+
+  fml::TimePoint vsync_start_ = fml::TimePoint::Min();
+  fml::TimePoint vsync_target_ = fml::TimePoint::Min();
+  fml::TimePoint build_start_ = fml::TimePoint::Min();
+  fml::TimePoint build_end_ = fml::TimePoint::Min();
+  fml::TimePoint raster_start_ = fml::TimePoint::Min();
+  fml::TimePoint raster_end_ = fml::TimePoint::Min();
+
+  FML_DISALLOW_COPY_ASSIGN_AND_MOVE(FrameTimingsRecorder);
+};
+
+}  // namespace flutter
+
+#endif  // FLUTTER_FLOW_FRAME_TIMINGS_H_
diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc
index eb58bc1..2fa755f 100644
--- a/flow/layers/layer_tree.cc
+++ b/flow/layers/layer_tree.cc
@@ -4,7 +4,9 @@
 
 #include "flutter/flow/layers/layer_tree.h"
 
+#include "flutter/flow/frame_timings.h"
 #include "flutter/flow/layers/layer.h"
+#include "flutter/fml/time/time_point.h"
 #include "flutter/fml/trace_event.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
 #include "third_party/skia/include/utils/SkNWayCanvas.h"
@@ -20,15 +22,6 @@
   FML_CHECK(device_pixel_ratio_ != 0.0f);
 }
 
-void LayerTree::RecordBuildTime(fml::TimePoint vsync_start,
-                                fml::TimePoint build_start,
-                                fml::TimePoint target_time) {
-  vsync_start_ = vsync_start;
-  build_start_ = build_start;
-  target_time_ = target_time;
-  build_finish_ = fml::TimePoint::Now();
-}
-
 bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
                         bool ignore_raster_cache) {
   TRACE_EVENT0("flutter", "LayerTree::Preroll");
diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h
index db8a33b..67a32ca 100644
--- a/flow/layers/layer_tree.h
+++ b/flow/layers/layer_tree.h
@@ -56,16 +56,6 @@
 
 #endif  // FLUTTER_ENABLE_DIFF_CONTEXT
 
-  void RecordBuildTime(fml::TimePoint vsync_start,
-                       fml::TimePoint build_start,
-                       fml::TimePoint target_time);
-  fml::TimePoint vsync_start() const { return vsync_start_; }
-  fml::TimeDelta vsync_overhead() const { return build_start_ - vsync_start_; }
-  fml::TimePoint build_start() const { return build_start_; }
-  fml::TimePoint build_finish() const { return build_finish_; }
-  fml::TimeDelta build_time() const { return build_finish_ - build_start_; }
-  fml::TimePoint target_time() const { return target_time_; }
-
   // The number of frame intervals missed after which the compositor must
   // trace the rasterized picture to a trace file. Specify 0 to disable all
   // tracing
@@ -87,10 +77,6 @@
 
  private:
   std::shared_ptr<Layer> root_layer_;
-  fml::TimePoint vsync_start_;
-  fml::TimePoint build_start_;
-  fml::TimePoint build_finish_;
-  fml::TimePoint target_time_;
   SkISize frame_size_ = SkISize::MakeEmpty();  // Physical pixels.
   const float device_pixel_ratio_;  // Logical / Physical pixels ratio.
   uint32_t rasterizer_tracing_threshold_;
diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc
index 6e74df5..7b787b0 100644
--- a/lib/ui/painting/image_decoder_unittests.cc
+++ b/lib/ui/painting/image_decoder_unittests.cc
@@ -427,7 +427,8 @@
   latch.Wait();
 }
 
-// TODO(https://github.com/flutter/flutter/issues/81232) - disabled due to flakiness
+// TODO(https://github.com/flutter/flutter/issues/81232) - disabled due to
+// flakiness
 TEST_F(ImageDecoderFixtureTest, DISABLED_CanResizeWithoutDecode) {
   SkImageInfo info = {};
   size_t row_bytes;
diff --git a/shell/common/animator.cc b/shell/common/animator.cc
index c2fc83e..4e5fff1 100644
--- a/shell/common/animator.cc
+++ b/shell/common/animator.cc
@@ -4,6 +4,8 @@
 
 #include "flutter/shell/common/animator.h"
 
+#include "flutter/flow/frame_timings.h"
+#include "flutter/fml/time/time_point.h"
 #include "flutter/fml/trace_event.h"
 #include "third_party/dart/runtime/include/dart_tools_api.h"
 
@@ -25,9 +27,6 @@
     : delegate_(delegate),
       task_runners_(std::move(task_runners)),
       waiter_(std::move(waiter)),
-      last_frame_begin_time_(),
-      last_vsync_start_time_(),
-      last_frame_target_time_(),
       dart_frame_deadline_(0),
 #if SHELL_ENABLE_METAL
       layer_tree_pipeline_(fml::MakeRefCounted<LayerTreePipeline>(2)),
@@ -96,8 +95,8 @@
   return (time - fxl_now).ToMicroseconds() + dart_now;
 }
 
-void Animator::BeginFrame(fml::TimePoint vsync_start_time,
-                          fml::TimePoint frame_target_time) {
+void Animator::BeginFrame(
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
   TRACE_EVENT_ASYNC_END0("flutter", "Frame Request Pending", frame_number_++);
 
   TRACE_EVENT0("flutter", "Animator::BeginFrame");
@@ -131,12 +130,14 @@
   // to service potential frame.
   FML_DCHECK(producer_continuation_);
 
-  last_frame_begin_time_ = fml::TimePoint::Now();
-  last_vsync_start_time_ = vsync_start_time;
-  fml::tracing::TraceEventAsyncComplete("flutter", "VsyncSchedulingOverhead",
-                                        last_vsync_start_time_,
-                                        last_frame_begin_time_);
-  last_frame_target_time_ = frame_target_time;
+  frame_timings_recorder_ = std::move(frame_timings_recorder);
+  frame_timings_recorder_->RecordBuildStart(fml::TimePoint::Now());
+  fml::tracing::TraceEventAsyncComplete(
+      "flutter", "VsyncSchedulingOverhead",
+      frame_timings_recorder_->GetVsyncStartTime(),
+      frame_timings_recorder_->GetBuildStartTime());
+  const fml::TimePoint frame_target_time =
+      frame_timings_recorder_->GetVsyncTargetTime();
   dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time);
   {
     TRACE_EVENT2("flutter", "Framework Workload", "mode", "basic", "frame",
@@ -180,9 +181,7 @@
   }
   last_layer_tree_size_ = layer_tree->frame_size();
 
-  // Note the frame time for instrumentation.
-  layer_tree->RecordBuildTime(last_vsync_start_time_, last_frame_begin_time_,
-                              last_frame_target_time_);
+  frame_timings_recorder_->RecordBuildEnd(fml::TimePoint::Now());
 
   // Commit the pending continuation.
   bool result = producer_continuation_.Complete(std::move(layer_tree));
@@ -190,16 +189,25 @@
     FML_DLOG(INFO) << "No pending continuation to commit";
   }
 
-  delegate_.OnAnimatorDraw(layer_tree_pipeline_, last_frame_target_time_);
+  delegate_.OnAnimatorDraw(layer_tree_pipeline_,
+                           std::move(frame_timings_recorder_));
 }
 
 bool Animator::CanReuseLastLayerTree() {
   return !regenerate_layer_tree_;
 }
 
-void Animator::DrawLastLayerTree() {
+void Animator::DrawLastLayerTree(
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
   pending_frame_semaphore_.Signal();
-  delegate_.OnAnimatorDrawLastLayerTree();
+  // In this case BeginFrame doesn't get called, we need to
+  // adjust frame timings to update build start and end times,
+  // given that the frame doesn't get built in this case, we
+  // will use Now() for both start and end times as an indication.
+  const auto now = fml::TimePoint::Now();
+  frame_timings_recorder->RecordBuildStart(now);
+  frame_timings_recorder->RecordBuildEnd(now);
+  delegate_.OnAnimatorDrawLastLayerTree(std::move(frame_timings_recorder));
 }
 
 void Animator::RequestFrame(bool regenerate_layer_tree) {
@@ -236,13 +244,13 @@
 
 void Animator::AwaitVSync() {
   waiter_->AsyncWaitForVsync(
-      [self = weak_factory_.GetWeakPtr()](fml::TimePoint vsync_start_time,
-                                          fml::TimePoint frame_target_time) {
+      [self = weak_factory_.GetWeakPtr()](
+          std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
         if (self) {
           if (self->CanReuseLastLayerTree()) {
-            self->DrawLastLayerTree();
+            self->DrawLastLayerTree(std::move(frame_timings_recorder));
           } else {
-            self->BeginFrame(vsync_start_time, frame_target_time);
+            self->BeginFrame(std::move(frame_timings_recorder));
           }
         }
       });
diff --git a/shell/common/animator.h b/shell/common/animator.h
index 6927e85..c8efaff 100644
--- a/shell/common/animator.h
+++ b/shell/common/animator.h
@@ -8,6 +8,7 @@
 #include <deque>
 
 #include "flutter/common/task_runners.h"
+#include "flutter/flow/frame_timings.h"
 #include "flutter/fml/memory/ref_ptr.h"
 #include "flutter/fml/memory/weak_ptr.h"
 #include "flutter/fml/synchronization/semaphore.h"
@@ -36,9 +37,10 @@
 
     virtual void OnAnimatorDraw(
         fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
-        fml::TimePoint frame_target_time) = 0;
+        std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) = 0;
 
-    virtual void OnAnimatorDrawLastLayerTree() = 0;
+    virtual void OnAnimatorDrawLastLayerTree(
+        std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) = 0;
   };
 
   Animator(Delegate& delegate,
@@ -83,11 +85,12 @@
  private:
   using LayerTreePipeline = Pipeline<flutter::LayerTree>;
 
-  void BeginFrame(fml::TimePoint frame_start_time,
-                  fml::TimePoint frame_target_time);
+  void BeginFrame(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder);
 
   bool CanReuseLastLayerTree();
-  void DrawLastLayerTree();
+
+  void DrawLastLayerTree(
+      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder);
 
   void AwaitVSync();
 
@@ -100,9 +103,7 @@
   TaskRunners task_runners_;
   std::shared_ptr<VsyncWaiter> waiter_;
 
-  fml::TimePoint last_frame_begin_time_;
-  fml::TimePoint last_vsync_start_time_;
-  fml::TimePoint last_frame_target_time_;
+  std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder_;
   int64_t dart_frame_deadline_;
   fml::RefPtr<LayerTreePipeline> layer_tree_pipeline_;
   fml::Semaphore pending_frame_semaphore_;
diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc
index acd3397..40ec095 100644
--- a/shell/common/rasterizer.cc
+++ b/shell/common/rasterizer.cc
@@ -7,10 +7,12 @@
 #include <algorithm>
 #include <utility>
 
+#include "flow/frame_timings.h"
 #include "flutter/common/graphics/persistent_cache.h"
 #include "flutter/fml/time/time_delta.h"
 #include "flutter/fml/time/time_point.h"
 #include "flutter/shell/common/serialization_callbacks.h"
+#include "fml/make_copyable.h"
 #include "third_party/skia/include/core/SkEncodedImageFormat.h"
 #include "third_party/skia/include/core/SkImageEncoder.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
@@ -145,15 +147,18 @@
   return last_layer_tree_.get();
 }
 
-void Rasterizer::DrawLastLayerTree() {
+void Rasterizer::DrawLastLayerTree(
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
   if (!last_layer_tree_ || !surface_) {
     return;
   }
-  DrawToSurface(*last_layer_tree_);
+  DrawToSurface(frame_timings_recorder->GetBuildDuration(), *last_layer_tree_);
 }
 
-void Rasterizer::Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
-                      LayerTreeDiscardCallback discardCallback) {
+void Rasterizer::Draw(
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,
+    fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
+    LayerTreeDiscardCallback discardCallback) {
   TRACE_EVENT0("flutter", "GPURasterizer::Draw");
   if (raster_thread_merger_ &&
       !raster_thread_merger_->IsOnRasterizingThread()) {
@@ -170,7 +175,8 @@
         if (discardCallback(*layer_tree.get())) {
           raster_status = RasterStatus::kDiscarded;
         } else {
-          raster_status = DoDraw(std::move(layer_tree));
+          raster_status =
+              DoDraw(std::move(frame_timings_recorder), std::move(layer_tree));
         }
       };
 
@@ -196,22 +202,6 @@
     external_view_embedder_->EndFrame(should_resubmit_frame,
                                       raster_thread_merger_);
   }
-
-  // Consume as many pipeline items as possible. But yield the event loop
-  // between successive tries.
-  switch (consume_result) {
-    case PipelineConsumeResult::MoreAvailable: {
-      delegate_.GetTaskRunners().GetRasterTaskRunner()->PostTask(
-          [weak_this = weak_factory_.GetWeakPtr(), pipeline]() {
-            if (weak_this) {
-              weak_this->Draw(pipeline);
-            }
-          });
-      break;
-    }
-    default:
-      break;
-  }
 }
 
 namespace {
@@ -332,6 +322,7 @@
 }
 
 RasterStatus Rasterizer::DoDraw(
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,
     std::unique_ptr<flutter::LayerTree> layer_tree) {
   FML_DCHECK(delegate_.GetTaskRunners()
                  .GetRasterTaskRunner()
@@ -341,19 +332,13 @@
     return RasterStatus::kFailed;
   }
 
-  FrameTiming timing;
-#if !defined(OS_FUCHSIA)
-  const fml::TimePoint frame_target_time = layer_tree->target_time();
-#endif
-  timing.Set(FrameTiming::kVsyncStart, layer_tree->vsync_start());
-  timing.Set(FrameTiming::kBuildStart, layer_tree->build_start());
-  timing.Set(FrameTiming::kBuildFinish, layer_tree->build_finish());
-  timing.Set(FrameTiming::kRasterStart, fml::TimePoint::Now());
+  frame_timings_recorder->RecordRasterStart(fml::TimePoint::Now());
 
   PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess();
   persistent_cache->ResetStoredNewShaders();
 
-  RasterStatus raster_status = DrawToSurface(*layer_tree);
+  RasterStatus raster_status =
+      DrawToSurface(frame_timings_recorder->GetBuildDuration(), *layer_tree);
   if (raster_status == RasterStatus::kSuccess) {
     last_layer_tree_ = std::move(layer_tree);
   } else if (raster_status == RasterStatus::kResubmit ||
@@ -373,12 +358,14 @@
   // Rasterizer::DoDraw finishes. Future work is needed to adapt the timestamp
   // for Fuchsia to capture SceneUpdateContext::ExecutePaintTasks.
   const auto raster_finish_time = fml::TimePoint::Now();
-  timing.Set(FrameTiming::kRasterFinish, raster_finish_time);
-  delegate_.OnFrameRasterized(timing);
+  delegate_.OnFrameRasterized(
+      frame_timings_recorder->RecordRasterEnd(raster_finish_time));
 
 // SceneDisplayLag events are disabled on Fuchsia.
 // see: https://github.com/flutter/flutter/issues/56598
 #if !defined(OS_FUCHSIA)
+  fml::TimePoint frame_target_time =
+      frame_timings_recorder->GetVsyncTargetTime();
   if (raster_finish_time > frame_target_time) {
     fml::TimePoint latest_frame_target_time =
         delegate_.GetLatestFrameTargetTime();
@@ -432,14 +419,13 @@
   return raster_status;
 }
 
-RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
+RasterStatus Rasterizer::DrawToSurface(
+    const fml::TimeDelta frame_build_duration,
+    flutter::LayerTree& layer_tree) {
   TRACE_EVENT0("flutter", "Rasterizer::DrawToSurface");
   FML_DCHECK(surface_);
 
-  // There is no way for the compositor to know how long the layer tree
-  // construction took. Fortunately, the layer tree does. Grab that time
-  // for instrumentation.
-  compositor_context_->ui_time().SetLapTime(layer_tree.build_time());
+  compositor_context_->ui_time().SetLapTime(frame_build_duration);
 
   SkCanvas* embedder_root_canvas = nullptr;
   if (external_view_embedder_) {
diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h
index 9258224..1f1559c 100644
--- a/shell/common/rasterizer.h
+++ b/shell/common/rasterizer.h
@@ -8,10 +8,11 @@
 #include <memory>
 #include <optional>
 
-#include "flow/embedded_views.h"
 #include "flutter/common/settings.h"
 #include "flutter/common/task_runners.h"
 #include "flutter/flow/compositor_context.h"
+#include "flutter/flow/embedded_views.h"
+#include "flutter/flow/frame_timings.h"
 #include "flutter/flow/layers/layer_tree.h"
 #include "flutter/flow/surface.h"
 #include "flutter/fml/closure.h"
@@ -195,7 +196,8 @@
   ///             textures instead of waiting for the framework to do the work
   ///             to generate the layer tree describing the same contents.
   ///
-  void DrawLastLayerTree();
+  void DrawLastLayerTree(
+      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder);
 
   //----------------------------------------------------------------------------
   /// @brief      Gets the registry of external textures currently in use by the
@@ -241,7 +243,8 @@
   /// @param[in]  discardCallback if specified and returns true, the layer tree
   ///                             is discarded instead of being rendered
   ///
-  void Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
+  void Draw(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,
+            fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
             LayerTreeDiscardCallback discardCallback = NoDiscard);
 
   //----------------------------------------------------------------------------
@@ -478,9 +481,12 @@
       SkISize size,
       std::function<void(SkCanvas*)> draw_callback);
 
-  RasterStatus DoDraw(std::unique_ptr<flutter::LayerTree> layer_tree);
+  RasterStatus DoDraw(
+      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,
+      std::unique_ptr<flutter::LayerTree> layer_tree);
 
-  RasterStatus DrawToSurface(flutter::LayerTree& layer_tree);
+  RasterStatus DrawToSurface(const fml::TimeDelta frame_build_duration,
+                             flutter::LayerTree& layer_tree);
 
   void FireNextFrameCallbackIfPresent();
 
diff --git a/shell/common/rasterizer_unittests.cc b/shell/common/rasterizer_unittests.cc
index 96321df..b95212f 100644
--- a/shell/common/rasterizer_unittests.cc
+++ b/shell/common/rasterizer_unittests.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+#include "flow/frame_timings.h"
 #define FML_USED_ON_EMBEDDER
 
 #include "flutter/shell/common/rasterizer.h"
@@ -91,7 +93,8 @@
   fml::AutoResetWaitableEvent latch;
   thread_host.raster_thread->GetTaskRunner()->PostTask([&] {
     auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
-    rasterizer->Draw(pipeline, nullptr);
+    rasterizer->Draw(std::make_unique<FrameTimingsRecorder>(), pipeline,
+                     nullptr);
     latch.Signal();
   });
   latch.Wait();
@@ -148,7 +151,8 @@
     bool result = pipeline->Produce().Complete(std::move(layer_tree));
     EXPECT_TRUE(result);
     auto no_discard = [](LayerTree&) { return false; };
-    rasterizer->Draw(pipeline, no_discard);
+    rasterizer->Draw(std::make_unique<FrameTimingsRecorder>(), pipeline,
+                     no_discard);
     latch.Signal();
   });
   latch.Wait();
@@ -202,7 +206,8 @@
     bool result = pipeline->Produce().Complete(std::move(layer_tree));
     EXPECT_TRUE(result);
     auto no_discard = [](LayerTree&) { return false; };
-    rasterizer->Draw(pipeline, no_discard);
+    rasterizer->Draw(std::make_unique<FrameTimingsRecorder>(), pipeline,
+                     no_discard);
     latch.Signal();
   });
   latch.Wait();
@@ -261,7 +266,8 @@
   bool result = pipeline->Produce().Complete(std::move(layer_tree));
   EXPECT_TRUE(result);
   auto no_discard = [](LayerTree&) { return false; };
-  rasterizer->Draw(pipeline, no_discard);
+  rasterizer->Draw(std::make_unique<FrameTimingsRecorder>(), pipeline,
+                   no_discard);
 }
 
 TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNoSurfaceIsSet) {
@@ -294,7 +300,8 @@
   thread_host.raster_thread->GetTaskRunner()->PostTask([&] {
     auto pipeline = fml::AdoptRef(new Pipeline<LayerTree>(/*depth=*/10));
     auto no_discard = [](LayerTree&) { return false; };
-    rasterizer->Draw(pipeline, no_discard);
+    rasterizer->Draw(std::make_unique<FrameTimingsRecorder>(), pipeline,
+                     no_discard);
     latch.Signal();
   });
   latch.Wait();
diff --git a/shell/common/shell.cc b/shell/common/shell.cc
index a2f71f6..c9541ba 100644
--- a/shell/common/shell.cc
+++ b/shell/common/shell.cc
@@ -1138,13 +1138,16 @@
 }
 
 // |Animator::Delegate|
-void Shell::OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
-                           fml::TimePoint frame_target_time) {
+void Shell::OnAnimatorDraw(
+    fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
   FML_DCHECK(is_setup_);
 
   // record the target time for use by rasterizer.
   {
     std::scoped_lock time_recorder_lock(time_recorder_mutex_);
+    const fml::TimePoint frame_target_time =
+        frame_timings_recorder->GetVsyncTargetTime();
     if (!latest_frame_target_time_) {
       latest_frame_target_time_ = frame_target_time;
     } else if (latest_frame_target_time_ < frame_target_time) {
@@ -1158,32 +1161,38 @@
            tree.frame_size() != expected_frame_size_;
   };
 
-  task_runners_.GetRasterTaskRunner()->PostTask(
+  task_runners_.GetRasterTaskRunner()->PostTask(fml::MakeCopyable(
       [&waiting_for_first_frame = waiting_for_first_frame_,
        &waiting_for_first_frame_condition = waiting_for_first_frame_condition_,
        rasterizer = rasterizer_->GetWeakPtr(), pipeline = std::move(pipeline),
-       discard_callback = std::move(discard_callback)]() {
+       discard_callback = std::move(discard_callback),
+       frame_timings_recorder = std::move(frame_timings_recorder)]() mutable {
         if (rasterizer) {
-          rasterizer->Draw(pipeline, std::move(discard_callback));
+          rasterizer->Draw(std::move(frame_timings_recorder), pipeline,
+                           std::move(discard_callback));
 
           if (waiting_for_first_frame.load()) {
             waiting_for_first_frame.store(false);
             waiting_for_first_frame_condition.notify_all();
           }
         }
-      });
+      }));
 }
 
 // |Animator::Delegate|
-void Shell::OnAnimatorDrawLastLayerTree() {
+void Shell::OnAnimatorDrawLastLayerTree(
+    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
   FML_DCHECK(is_setup_);
 
-  task_runners_.GetRasterTaskRunner()->PostTask(
-      [rasterizer = rasterizer_->GetWeakPtr()]() {
+  auto task = fml::MakeCopyable(
+      [rasterizer = rasterizer_->GetWeakPtr(),
+       frame_timings_recorder = std::move(frame_timings_recorder)]() mutable {
         if (rasterizer) {
-          rasterizer->DrawLastLayerTree();
+          rasterizer->DrawLastLayerTree(std::move(frame_timings_recorder));
         }
       });
+
+  task_runners_.GetRasterTaskRunner()->PostTask(task);
 }
 
 // |Engine::Delegate|
diff --git a/shell/common/shell.h b/shell/common/shell.h
index de24a19..53e1b83 100644
--- a/shell/common/shell.h
+++ b/shell/common/shell.h
@@ -539,11 +539,13 @@
   void OnAnimatorNotifyIdle(int64_t deadline) override;
 
   // |Animator::Delegate|
-  void OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
-                      fml::TimePoint frame_target_time) override;
+  void OnAnimatorDraw(
+      fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline,
+      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) override;
 
   // |Animator::Delegate|
-  void OnAnimatorDrawLastLayerTree() override;
+  void OnAnimatorDrawLastLayerTree(
+      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) override;
 
   // |Engine::Delegate|
   void OnEngineUpdateSemantics(
diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc
index 00c12f0..5a4803e 100644
--- a/shell/common/shell_test.cc
+++ b/shell/common/shell_test.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+#include "flow/frame_timings.h"
 #define FML_USED_ON_EMBEDDER
 
 #include "flutter/shell/common/shell_test.h"
@@ -136,7 +138,10 @@
           const auto frame_begin_time = fml::TimePoint::Now();
           const auto frame_end_time =
               frame_begin_time + fml::TimeDelta::FromSecondsF(1.0 / 60.0);
-          engine->animator_->BeginFrame(frame_begin_time, frame_end_time);
+          std::unique_ptr<FrameTimingsRecorder> recorder =
+              std::make_unique<FrameTimingsRecorder>();
+          recorder->RecordVsync(frame_begin_time, frame_end_time);
+          engine->animator_->BeginFrame(std::move(recorder));
         }
         latch.Signal();
       });
@@ -175,7 +180,10 @@
         const auto frame_begin_time = fml::TimePoint::Now();
         const auto frame_end_time =
             frame_begin_time + fml::TimeDelta::FromSecondsF(1.0 / 60.0);
-        engine->animator_->BeginFrame(frame_begin_time, frame_end_time);
+        std::unique_ptr<FrameTimingsRecorder> recorder =
+            std::make_unique<FrameTimingsRecorder>();
+        recorder->RecordVsync(frame_begin_time, frame_end_time);
+        engine->animator_->BeginFrame(std::move(recorder));
         latch.Signal();
       });
   latch.Wait();
diff --git a/shell/common/vsync_waiter.cc b/shell/common/vsync_waiter.cc
index 40ced72..ede589f 100644
--- a/shell/common/vsync_waiter.cc
+++ b/shell/common/vsync_waiter.cc
@@ -4,6 +4,7 @@
 
 #include "flutter/shell/common/vsync_waiter.h"
 
+#include "flow/frame_timings.h"
 #include "flutter/fml/task_runner.h"
 #include "flutter/fml/trace_event.h"
 #include "fml/message_loop_task_queues.h"
@@ -132,7 +133,11 @@
          pause_secondary_tasks]() {
           FML_TRACE_EVENT("flutter", kVsyncTraceName, "StartTime",
                           frame_start_time, "TargetTime", frame_target_time);
-          callback(frame_start_time, frame_target_time);
+          std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder =
+              std::make_unique<FrameTimingsRecorder>();
+          frame_timings_recorder->RecordVsync(frame_start_time,
+                                              frame_target_time);
+          callback(std::move(frame_timings_recorder));
           TRACE_FLOW_END("flutter", kVsyncFlowName, flow_identifier);
           if (pause_secondary_tasks) {
             ResumeDartMicroTasks();
diff --git a/shell/common/vsync_waiter.h b/shell/common/vsync_waiter.h
index d593c70..6af0b8d 100644
--- a/shell/common/vsync_waiter.h
+++ b/shell/common/vsync_waiter.h
@@ -11,6 +11,7 @@
 #include <unordered_map>
 
 #include "flutter/common/task_runners.h"
+#include "flutter/flow/frame_timings.h"
 #include "flutter/fml/time/time_point.h"
 
 namespace flutter {
@@ -19,8 +20,7 @@
 /// getting callbacks when a vsync event happens.
 class VsyncWaiter : public std::enable_shared_from_this<VsyncWaiter> {
  public:
-  using Callback = std::function<void(fml::TimePoint frame_start_time,
-                                      fml::TimePoint frame_target_time)>;
+  using Callback = std::function<void(std::unique_ptr<FrameTimingsRecorder>)>;
 
   virtual ~VsyncWaiter();
 
@@ -40,7 +40,7 @@
 
   const TaskRunners task_runners_;
 
-  VsyncWaiter(TaskRunners task_runners);
+  explicit VsyncWaiter(TaskRunners task_runners);
 
   // Implementations are meant to override this method and arm their vsync
   // latches when in response to this invocation. On vsync, they are meant to