cc: Add UKM for checkerboarding from compositor input handling.

When handling a gesture on the compositor thread, we may checkerboard
tiles if no rasterized resource is available for them. This is currently
captured in Compositing.RenderPass.AppendQuadData UMA. This change adds
an equivalent UKM to capture this metric per site.

Since checkerboarding should only occur from input handled on the
compositor thread, the stats are accumulated only when we are actively
running an animation resulting from user input. In order to limit the
frequency with which the metric is recorded, the stats are accumulated
over the gesture duration and emitted when exiting it.

[email protected], [email protected]

Bug: 728273
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I1c47215963966f32f3c7d0f69661c87b21558ebb
Reviewed-on: https://chromium-review.googlesource.com/734134
Reviewed-by: Robert Kaplow <[email protected]>
Reviewed-by: Nasko Oskov <[email protected]>
Reviewed-by: Ali Juma <[email protected]>
Reviewed-by: Ken Buchanan <[email protected]>
Reviewed-by: vmpstr <[email protected]>
Commit-Queue: Khushal <[email protected]>
Cr-Commit-Position: refs/heads/master@{#512667}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index fb611f3..725c089 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -335,6 +335,8 @@
     "trees/transform_node.h",
     "trees/tree_synchronizer.cc",
     "trees/tree_synchronizer.h",
+    "trees/ukm_manager.cc",
+    "trees/ukm_manager.h",
   ]
 
   if (current_cpu == "x86" || current_cpu == "x64") {
@@ -356,6 +358,7 @@
     "//base",
     "//base/third_party/dynamic_annotations",
     "//cc/paint",
+    "//components/ukm",
     "//components/viz/common",
     "//gpu",
     "//gpu/command_buffer/client:gles2_implementation",
@@ -365,6 +368,7 @@
     "//gpu/vulkan:features",
     "//media",
     "//mojo/public/cpp/bindings:struct_traits",
+    "//services/metrics/public/cpp:ukm_builders",
     "//third_party/libyuv",
     "//ui/events:events_base",
     "//ui/gfx",
@@ -512,6 +516,8 @@
     "test/test_tile_priorities.h",
     "test/test_tile_task_runner.cc",
     "test/test_tile_task_runner.h",
+    "test/test_ukm_recorder_factory.cc",
+    "test/test_ukm_recorder_factory.h",
     "test/test_web_graphics_context_3d.cc",
     "test/test_web_graphics_context_3d.h",
   ]
@@ -526,6 +532,8 @@
     "//base/test:test_support",
     "//base/third_party/dynamic_annotations",
     "//cc/paint",
+    "//components/ukm",
+    "//components/ukm:test_support",
     "//components/viz/common",
     "//components/viz/service",
     "//components/viz/test:test_support",
@@ -671,6 +679,7 @@
     "trees/property_tree_unittest.cc",
     "trees/swap_promise_manager_unittest.cc",
     "trees/tree_synchronizer_unittest.cc",
+    "trees/ukm_manager_unittest.cc",
 
     # Animation test files.
     "animation/animation_host_unittest.cc",
@@ -704,6 +713,7 @@
     "//base/test:test_support",
     "//cc/ipc",
     "//cc/paint",
+    "//components/ukm:test_support",
     "//components/viz/common",
     "//components/viz/service",
     "//components/viz/test:test_support",
diff --git a/cc/DEPS b/cc/DEPS
index 6899c79..0e483c8 100644
--- a/cc/DEPS
+++ b/cc/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/ukm/test_ukm_recorder.h",
   "+components/viz/common",
   "+gpu/GLES2",
   "+gpu/command_buffer/client/context_support.h",
@@ -15,6 +16,7 @@
   "+gpu/vulkan",
   "+media",
   "+skia/ext",
+  "+services/metrics/public/cpp",
   "+third_party/khronos/GLES2/gl2.h",
   "+third_party/khronos/GLES2/gl2ext.h",
   "+third_party/libyuv",
diff --git a/cc/test/fake_proxy.h b/cc/test/fake_proxy.h
index daf017f..24e4523 100644
--- a/cc/test/fake_proxy.h
+++ b/cc/test/fake_proxy.h
@@ -8,6 +8,7 @@
 #include "base/single_thread_task_runner.h"
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/proxy.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 
 namespace cc {
 
@@ -41,6 +42,7 @@
                                   BrowserControlsState current,
                                   bool animate) override {}
   void RequestBeginMainFrameNotExpected(bool new_state) override {}
+  void SetURLForUkm(const GURL& url) override {}
 
  private:
   LayerTreeHost* layer_tree_host_;
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 90403793..e22d27e 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -24,6 +24,7 @@
 #include "cc/test/fake_output_surface.h"
 #include "cc/test/test_context_provider.h"
 #include "cc/test/test_shared_bitmap_manager.h"
+#include "cc/test/test_ukm_recorder_factory.h"
 #include "cc/trees/layer_tree_host_client.h"
 #include "cc/trees/layer_tree_host_impl.h"
 #include "cc/trees/layer_tree_host_single_thread_client.h"
@@ -31,6 +32,7 @@
 #include "cc/trees/proxy_impl.h"
 #include "cc/trees/proxy_main.h"
 #include "cc/trees/single_thread_proxy.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "components/viz/common/resources/buffer_to_texture_target_map.h"
 #include "components/viz/test/begin_frame_args_test.h"
 #include "components/viz/test/test_layer_tree_frame_sink.h"
@@ -406,6 +408,7 @@
     params.settings = &settings;
     params.mutator_host = mutator_host;
     params.image_worker_task_runner = std::move(image_worker_task_runner);
+    params.ukm_recorder_factory = std::make_unique<TestUkmRecorderFactory>();
 
     std::unique_ptr<LayerTreeHostForTesting> layer_tree_host(
         new LayerTreeHostForTesting(test_hooks, &params, mode));
@@ -436,6 +439,8 @@
             test_hooks_, GetSettings(), host_impl_client,
             GetTaskRunnerProvider(), task_graph_runner(),
             rendering_stats_instrumentation(), image_worker_task_runner_);
+
+    host_impl->InitializeUkm(ukm_recorder_factory_->CreateRecorder());
     input_handler_weak_ptr_ = host_impl->AsWeakPtr();
     return host_impl;
   }
diff --git a/cc/test/test_ukm_recorder_factory.cc b/cc/test/test_ukm_recorder_factory.cc
new file mode 100644
index 0000000..156072b
--- /dev/null
+++ b/cc/test/test_ukm_recorder_factory.cc
@@ -0,0 +1,17 @@
+// Copyright 2017 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/test/test_ukm_recorder_factory.h"
+
+#include "components/ukm/test_ukm_recorder.h"
+
+namespace cc {
+
+TestUkmRecorderFactory::~TestUkmRecorderFactory() = default;
+
+std::unique_ptr<ukm::UkmRecorder> TestUkmRecorderFactory::CreateRecorder() {
+  return std::make_unique<ukm::TestUkmRecorder>();
+}
+
+}  // namespace cc
diff --git a/cc/test/test_ukm_recorder_factory.h b/cc/test/test_ukm_recorder_factory.h
new file mode 100644
index 0000000..113a61f
--- /dev/null
+++ b/cc/test/test_ukm_recorder_factory.h
@@ -0,0 +1,21 @@
+// Copyright 2017 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_TEST_TEST_UKM_RECORDER_FACTORY_H_
+#define CC_TEST_TEST_UKM_RECORDER_FACTORY_H_
+
+#include "cc/trees/ukm_manager.h"
+
+namespace cc {
+
+class TestUkmRecorderFactory : public UkmRecorderFactory {
+ public:
+  ~TestUkmRecorderFactory() override;
+
+  std::unique_ptr<ukm::UkmRecorder> CreateRecorder() override;
+};
+
+}  // namespace cc
+
+#endif  // CC_TEST_TEST_UKM_RECORDER_FACTORY_H_
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 90058b8..a93bc46 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -55,6 +55,8 @@
 #include "cc/trees/swap_promise_manager.h"
 #include "cc/trees/transform_node.h"
 #include "cc/trees/tree_synchronizer.h"
+#include "cc/trees/ukm_manager.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/geometry/vector2d_conversions.h"
 
@@ -75,6 +77,7 @@
   DCHECK(params->main_task_runner.get());
   DCHECK(impl_task_runner.get());
   DCHECK(params->settings);
+  DCHECK(params->ukm_recorder_factory);
   std::unique_ptr<LayerTreeHost> layer_tree_host(
       new LayerTreeHost(params, CompositorMode::THREADED));
   layer_tree_host->InitializeThreaded(params->main_task_runner,
@@ -97,6 +100,7 @@
 LayerTreeHost::LayerTreeHost(InitParams* params, CompositorMode mode)
     : micro_benchmark_controller_(this),
       image_worker_task_runner_(params->image_worker_task_runner),
+      ukm_recorder_factory_(std::move(params->ukm_recorder_factory)),
       compositor_mode_(mode),
       ui_resource_manager_(std::make_unique<UIResourceManager>()),
       client_(params->client),
@@ -485,6 +489,11 @@
       settings_, client, task_runner_provider_.get(),
       rendering_stats_instrumentation_.get(), task_graph_runner_,
       std::move(mutator_host_impl), id_, std::move(image_worker_task_runner_));
+  if (ukm_recorder_factory_) {
+    host_impl->InitializeUkm(ukm_recorder_factory_->CreateRecorder());
+    ukm_recorder_factory_.reset();
+  }
+
   host_impl->SetHasGpuRasterizationTrigger(has_gpu_rasterization_trigger_);
   host_impl->SetContentHasSlowPaths(content_has_slow_paths_);
   host_impl->SetContentHasNonAAPaint(content_has_non_aa_paint_);
@@ -1515,4 +1524,8 @@
   proxy_->RequestBeginMainFrameNotExpected(new_state);
 }
 
+void LayerTreeHost::SetURLForUkm(const GURL& url) {
+  proxy_->SetURLForUkm(url);
+}
+
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 6e38ab76..561f95c6 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -45,6 +45,7 @@
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/common/surfaces/surface_reference_owner.h"
 #include "components/viz/common/surfaces/surface_sequence_generator.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -63,6 +64,7 @@
 struct ScrollBoundaryBehavior;
 class TaskGraphRunner;
 class UIResourceManager;
+class UkmRecorderFactory;
 struct RenderingStats;
 struct ScrollAndScaleSet;
 
@@ -81,6 +83,8 @@
     // raster worker threads.
     scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner;
 
+    std::unique_ptr<UkmRecorderFactory> ukm_recorder_factory;
+
     InitParams();
     ~InitParams();
   };
@@ -502,6 +506,8 @@
 
   float recording_scale_factor() const { return recording_scale_factor_; }
 
+  void SetURLForUkm(const GURL& url);
+
  protected:
   LayerTreeHost(InitParams* params, CompositorMode mode);
 
@@ -532,6 +538,7 @@
   base::WeakPtr<InputHandler> input_handler_weak_ptr_;
 
   scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner_;
+  std::unique_ptr<UkmRecorderFactory> ukm_recorder_factory_;
 
  private:
   friend class LayerTreeHostSerializationTest;
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index f20a8a9..105b5d7 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -87,6 +87,7 @@
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -917,6 +918,7 @@
   int num_incomplete_tiles = 0;
   int64_t checkerboarded_no_recording_content_area = 0;
   int64_t checkerboarded_needs_raster_content_area = 0;
+  int64_t total_visible_area = 0;
   bool have_copy_request =
       active_tree()->property_trees()->effect_tree.HasCopyRequests();
   bool have_missing_animated_tiles = false;
@@ -980,6 +982,7 @@
           append_quads_data.checkerboarded_no_recording_content_area;
       checkerboarded_needs_raster_content_area +=
           append_quads_data.checkerboarded_needs_raster_content_area;
+      total_visible_area += append_quads_data.visible_layer_area;
       if (append_quads_data.num_missing_tiles > 0) {
         have_missing_animated_tiles |=
             layer->screen_space_transform_is_animating();
@@ -1044,6 +1047,13 @@
     active_tree()->set_needs_update_draw_properties();
   }
 
+  if (ukm_manager_) {
+    ukm_manager_->AddCheckerboardStatsForFrame(
+        checkerboarded_no_recording_content_area +
+            checkerboarded_needs_raster_content_area,
+        num_missing_tiles, total_visible_area);
+  }
+
   if (active_tree_->has_ever_been_drawn()) {
     UMA_HISTOGRAM_COUNTS_100(
         "Compositing.RenderPass.AppendQuadData.NumMissingTiles",
@@ -4574,4 +4584,10 @@
   client_->NeedsImplSideInvalidation(needs_first_draw_on_activation);
 }
 
+void LayerTreeHostImpl::InitializeUkm(
+    std::unique_ptr<ukm::UkmRecorder> recorder) {
+  DCHECK(!ukm_manager_);
+  ukm_manager_ = std::make_unique<UkmManager>(std::move(recorder));
+}
+
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 996698bc..ed2c071 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -40,6 +40,7 @@
 #include "cc/trees/managed_memory_policy.h"
 #include "cc/trees/mutator_host_client.h"
 #include "cc/trees/task_runner_provider.h"
+#include "cc/trees/ukm_manager.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/gpu/context_cache_controller.h"
 #include "components/viz/common/quads/render_pass.h"
@@ -636,6 +637,11 @@
       base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
           decoding_mode_map);
 
+  void InitializeUkm(std::unique_ptr<ukm::UkmRecorder> recorder);
+  UkmManager* ukm_manager() { return ukm_manager_.get(); }
+
+  void RenewTreePriorityForTesting() { client_->RenewTreePriority(); }
+
  protected:
   LayerTreeHostImpl(
       const LayerTreeSettings& settings,
@@ -928,6 +934,8 @@
 
   base::Optional<ImageAnimationController> image_animation_controller_;
 
+  std::unique_ptr<UkmManager> ukm_manager_;
+
   DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImpl);
 };
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 33045f4..2ddb18a0 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -57,6 +57,7 @@
 #include "cc/trees/swap_promise.h"
 #include "cc/trees/swap_promise_manager.h"
 #include "cc/trees/transform_node.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
@@ -83,6 +84,10 @@
 
 namespace cc {
 namespace {
+const char kUserInteraction[] = "Compositor.UserInteraction";
+const char kCheckerboardArea[] = "CheckerboardedContentArea";
+const char kCheckerboardAreaRatio[] = "CheckerboardedContentAreaRatio";
+const char kMissingTiles[] = "NumMissingTiles";
 
 class LayerTreeHostTest : public LayerTreeTest {};
 
@@ -8184,5 +8189,70 @@
 };
 
 MULTI_THREAD_TEST_F(LayerTreeHostTestImageDecodingHints);
+
+class LayerTreeHostTestCheckerboardUkm : public LayerTreeHostTest {
+ public:
+  LayerTreeHostTestCheckerboardUkm() : url_(GURL("chrome://test")) {}
+
+  void BeginTest() override {
+    PostSetNeedsCommitToMainThread();
+    layer_tree_host()->SetURLForUkm(url_);
+  }
+
+  void SetupTree() override {
+    gfx::Size layer_size(100, 100);
+    content_layer_client_.set_bounds(layer_size);
+    content_layer_client_.set_fill_with_nonsolid_color(true);
+    layer_tree_host()->SetRootLayer(
+        FakePictureLayer::Create(&content_layer_client_));
+    layer_tree_host()->root_layer()->SetBounds(layer_size);
+    LayerTreeTest::SetupTree();
+  }
+
+  void DidActivateTreeOnThread(LayerTreeHostImpl* impl) override {
+    if (impl->active_tree()->source_frame_number() != 0)
+      return;
+
+    // We have an active tree. Start a pinch gesture so we start recording
+    // stats.
+    impl->PinchGestureBegin();
+  }
+
+  void DrawLayersOnThread(LayerTreeHostImpl* impl) override {
+    if (!impl->pinch_gesture_active())
+      return;
+
+    // We just drew a frame, stats for it should have been recorded. End the
+    // gesture so they are flushed to the recorder.
+    impl->PinchGestureEnd();
+
+    // RenewTreePriority will run when the smoothness expiration timer fires.
+    // Synthetically do it here so the UkmManager is notified.
+    impl->RenewTreePriorityForTesting();
+
+    auto* recorder = static_cast<ukm::TestUkmRecorder*>(
+        impl->ukm_manager()->recorder_for_testing());
+    auto* source = recorder->GetSourceForUrl(url_);
+    ASSERT_TRUE(source);
+
+    EXPECT_EQ(recorder->entries_count(), 1u);
+    recorder->ExpectMetric(*source, kUserInteraction, kCheckerboardArea, 0);
+    recorder->ExpectMetric(*source, kUserInteraction, kMissingTiles, 0);
+    recorder->ExpectMetric(*source, kUserInteraction, kCheckerboardAreaRatio,
+                           0);
+
+    EndTest();
+  }
+
+  void AfterTest() override {}
+
+ private:
+  const GURL url_;
+  FakeContentLayerClient content_layer_client_;
+};
+
+// Only multi-thread mode needs to record UKMs.
+MULTI_THREAD_TEST_F(LayerTreeHostTestCheckerboardUkm);
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_unittest_scroll.cc b/cc/trees/layer_tree_host_unittest_scroll.cc
index 409d991..8298948 100644
--- a/cc/trees/layer_tree_host_unittest_scroll.cc
+++ b/cc/trees/layer_tree_host_unittest_scroll.cc
@@ -23,6 +23,7 @@
 #include "cc/test/geometry_test_utils.h"
 #include "cc/test/layer_tree_test.h"
 #include "cc/test/test_task_graph_runner.h"
+#include "cc/test/test_ukm_recorder_factory.h"
 #include "cc/trees/layer_tree_host_common.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/scroll_node.h"
@@ -1361,6 +1362,7 @@
   params.settings = &settings;
   params.main_task_runner = base::ThreadTaskRunnerHandle::Get();
   params.mutator_host = animation_host.get();
+  params.ukm_recorder_factory = std::make_unique<TestUkmRecorderFactory>();
   std::unique_ptr<LayerTreeHost> layer_tree_host =
       LayerTreeHost::CreateThreaded(impl_thread.task_runner(), &params);
 
diff --git a/cc/trees/proxy.h b/cc/trees/proxy.h
index 35a6399..c68bdb56 100644
--- a/cc/trees/proxy.h
+++ b/cc/trees/proxy.h
@@ -17,6 +17,8 @@
 #include "cc/input/browser_controls_state.h"
 #include "cc/trees/task_runner_provider.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "url/gurl.h"
 
 namespace gfx {
 class Rect;
@@ -74,6 +76,8 @@
 
   // Testing hooks
   virtual bool MainFrameWillHappenForTesting() = 0;
+
+  virtual void SetURLForUkm(const GURL& url) = 0;
 };
 
 }  // namespace cc
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index f143117501..bf3e8b3 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -28,6 +28,7 @@
 #include "components/viz/common/frame_sinks/delay_based_time_source.h"
 #include "components/viz/common/gpu/context_provider.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 
 namespace cc {
 
@@ -402,12 +403,15 @@
 
 void ProxyImpl::RenewTreePriority() {
   DCHECK(IsImplThread());
-  bool smoothness_takes_priority = host_impl_->pinch_gesture_active() ||
-                                   host_impl_->page_scale_animation_active() ||
-                                   host_impl_->IsActivelyScrolling();
+  const bool user_interaction_in_progress =
+      host_impl_->pinch_gesture_active() ||
+      host_impl_->page_scale_animation_active() ||
+      host_impl_->IsActivelyScrolling();
+  host_impl_->ukm_manager()->SetUserInteractionInProgress(
+      user_interaction_in_progress);
 
   // Schedule expiration if smoothness currently takes priority.
-  if (smoothness_takes_priority)
+  if (user_interaction_in_progress)
     smoothness_priority_expiration_notifier_.Schedule();
 
   // We use the same priority for both trees by default.
@@ -718,4 +722,16 @@
   return task_runner_provider_->MainThreadTaskRunner();
 }
 
+void ProxyImpl::SetURLForUkm(const GURL& url) {
+  DCHECK(IsImplThread());
+  DCHECK(host_impl_->ukm_manager());
+  // The active tree might still be from content for the previous page when the
+  // recorder is updated here, since new content will be pushed with the next
+  // main frame. But we should only get a few impl frames wrong here in that
+  // case. Also, since checkerboard stats are only recorded with user
+  // interaction, it must be in progress when the navigation commits for this
+  // case to occur.
+  host_impl_->ukm_manager()->SetSourceURL(url);
+}
+
 }  // namespace cc
diff --git a/cc/trees/proxy_impl.h b/cc/trees/proxy_impl.h
index 916446b..40700e5 100644
--- a/cc/trees/proxy_impl.h
+++ b/cc/trees/proxy_impl.h
@@ -53,6 +53,7 @@
                                  LayerTreeHost* layer_tree_host,
                                  base::TimeTicks main_thread_start_time,
                                  bool hold_commit_for_activation);
+  void SetURLForUkm(const GURL& url);
 
   void MainFrameWillHappenOnImplForTesting(CompletionEvent* completion,
                                            bool* main_frame_will_happen);
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index 73bc8fb..8245573 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -20,6 +20,7 @@
 #include "cc/trees/proxy_impl.h"
 #include "cc/trees/scoped_abort_remaining_swap_promises.h"
 #include "cc/trees/swap_promise.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 
 namespace cc {
 
@@ -539,4 +540,11 @@
   return task_runner_provider_->ImplThreadTaskRunner();
 }
 
+void ProxyMain::SetURLForUkm(const GURL& url) {
+  DCHECK(IsMainThread());
+  ImplThreadTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&ProxyImpl::SetURLForUkm,
+                                base::Unretained(proxy_impl_.get()), url));
+}
+
 }  // namespace cc
diff --git a/cc/trees/proxy_main.h b/cc/trees/proxy_main.h
index 5007bb29..df55511 100644
--- a/cc/trees/proxy_main.h
+++ b/cc/trees/proxy_main.h
@@ -88,6 +88,7 @@
                                   BrowserControlsState current,
                                   bool animate) override;
   void RequestBeginMainFrameNotExpected(bool new_state) override;
+  void SetURLForUkm(const GURL& url) override;
 
   // Returns |true| if the request was actually sent, |false| if one was
   // already outstanding.
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index 12f988ba..cf14f11 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -57,6 +57,11 @@
   void SetMutator(std::unique_ptr<LayerTreeMutator> mutator) override;
   bool SupportsImplScrolling() const override;
   bool MainFrameWillHappenForTesting() override;
+  void SetURLForUkm(const GURL& url) override {
+    // Single-threaded mode is only for browser compositing and for renderers in
+    // layout tests. This will still get called in the latter case, but we don't
+    // need to record UKM in that case.
+  }
 
   // Blink layout tests might call into this even though an unthreaded CC
   // doesn't have BrowserControls itself.
diff --git a/cc/trees/ukm_manager.cc b/cc/trees/ukm_manager.cc
new file mode 100644
index 0000000..8a817d5
--- /dev/null
+++ b/cc/trees/ukm_manager.cc
@@ -0,0 +1,69 @@
+// Copyright 2017 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/trees/ukm_manager.h"
+
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+
+namespace cc {
+
+UkmManager::UkmManager(std::unique_ptr<ukm::UkmRecorder> recorder)
+    : recorder_(std::move(recorder)) {
+  DCHECK(recorder_);
+}
+
+UkmManager::~UkmManager() = default;
+
+void UkmManager::SetSourceURL(const GURL& url) {
+  // If we accumulating any metrics, record them before reseting the source.
+  RecordCheckerboardUkm();
+
+  source_id_ = recorder_->GetNewSourceID();
+  recorder_->UpdateSourceURL(source_id_, url);
+}
+
+void UkmManager::SetUserInteractionInProgress(bool in_progress) {
+  if (user_interaction_in_progress_ == in_progress)
+    return;
+
+  user_interaction_in_progress_ = in_progress;
+  if (!user_interaction_in_progress_)
+    RecordCheckerboardUkm();
+}
+
+void UkmManager::AddCheckerboardStatsForFrame(int64_t checkerboard_area,
+                                              int64_t num_missing_tiles,
+                                              int64_t total_visible_area) {
+  DCHECK_GE(total_visible_area, checkerboard_area);
+  if (source_id_ == ukm::kInvalidSourceId || !user_interaction_in_progress_)
+    return;
+
+  checkerboarded_content_area_ += checkerboard_area;
+  num_missing_tiles_ += num_missing_tiles;
+  total_visible_area_ += total_visible_area;
+  num_of_frames_++;
+}
+
+void UkmManager::RecordCheckerboardUkm() {
+  // Only make a recording if there was any visible area from PictureLayers,
+  // which can be checkerboarded.
+  if (num_of_frames_ > 0 && total_visible_area_ > 0) {
+    DCHECK_NE(source_id_, ukm::kInvalidSourceId);
+    ukm::builders::Compositor_UserInteraction(source_id_)
+        .SetCheckerboardedContentArea(checkerboarded_content_area_ /
+                                      num_of_frames_)
+        .SetNumMissingTiles(num_missing_tiles_ / num_of_frames_)
+        .SetCheckerboardedContentAreaRatio(
+            (checkerboarded_content_area_ * 100) / total_visible_area_)
+        .Record(recorder_.get());
+  }
+
+  checkerboarded_content_area_ = 0;
+  num_missing_tiles_ = 0;
+  num_of_frames_ = 0;
+  total_visible_area_ = 0;
+}
+
+}  // namespace cc
diff --git a/cc/trees/ukm_manager.h b/cc/trees/ukm_manager.h
new file mode 100644
index 0000000..7521ccc
--- /dev/null
+++ b/cc/trees/ukm_manager.h
@@ -0,0 +1,54 @@
+// Copyright 2017 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_TREES_UKM_MANAGER_H_
+#define CC_TREES_UKM_MANAGER_H_
+
+#include "cc/cc_export.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "url/gurl.h"
+
+namespace ukm {
+class UkmRecorder;
+}  // namespace ukm
+
+namespace cc {
+
+class CC_EXPORT UkmRecorderFactory {
+ public:
+  virtual ~UkmRecorderFactory() {}
+
+  virtual std::unique_ptr<ukm::UkmRecorder> CreateRecorder() = 0;
+};
+
+class CC_EXPORT UkmManager {
+ public:
+  explicit UkmManager(std::unique_ptr<ukm::UkmRecorder> recorder);
+  ~UkmManager();
+
+  void SetSourceURL(const GURL& url);
+
+  void SetUserInteractionInProgress(bool in_progress);
+  void AddCheckerboardStatsForFrame(int64_t checkerboard_area,
+                                    int64_t num_missing_tiles,
+                                    int64_t total_visible_area);
+
+  ukm::UkmRecorder* recorder_for_testing() { return recorder_.get(); }
+
+ private:
+  void RecordCheckerboardUkm();
+
+  bool user_interaction_in_progress_ = false;
+  int64_t checkerboarded_content_area_ = 0;
+  int64_t num_missing_tiles_ = 0;
+  int64_t total_visible_area_ = 0;
+  int64_t num_of_frames_ = 0;
+
+  ukm::SourceId source_id_ = ukm::kInvalidSourceId;
+  std::unique_ptr<ukm::UkmRecorder> recorder_;
+};
+
+}  // namespace cc
+
+#endif  // CC_TREES_UKM_MANAGER_H_
diff --git a/cc/trees/ukm_manager_unittest.cc b/cc/trees/ukm_manager_unittest.cc
new file mode 100644
index 0000000..d59604d
--- /dev/null
+++ b/cc/trees/ukm_manager_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2017 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/trees/ukm_manager.h"
+
+#include "components/ukm/test_ukm_recorder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+const char kUserInteraction[] = "Compositor.UserInteraction";
+const char kCheckerboardArea[] = "CheckerboardedContentArea";
+const char kCheckerboardAreaRatio[] = "CheckerboardedContentAreaRatio";
+const char kMissingTiles[] = "NumMissingTiles";
+
+class UkmRecorderForTest : public ukm::TestUkmRecorder {
+ public:
+  UkmRecorderForTest() = default;
+  ~UkmRecorderForTest() override {
+    ExpectMetrics(*source_, kUserInteraction, kCheckerboardArea,
+                  expected_final_checkerboard_);
+    ExpectMetrics(*source_, kUserInteraction, kMissingTiles,
+                  expected_final_missing_tiles_);
+    ExpectMetrics(*source_, kUserInteraction, kCheckerboardAreaRatio,
+                  expected_final_checkerboard_ratio_);
+  }
+
+  std::vector<int64_t> expected_final_checkerboard_;
+  std::vector<int64_t> expected_final_missing_tiles_;
+  std::vector<int64_t> expected_final_checkerboard_ratio_;
+  ukm::UkmSource* source_ = nullptr;
+};
+
+class UkmManagerTest : public testing::Test {
+ public:
+  UkmManagerTest() : manager_(std::make_unique<UkmRecorderForTest>()) {
+    UpdateURL(GURL("chrome://test"));
+  }
+
+  void TearDown() override { ukm_source_ = nullptr; }
+
+  UkmRecorderForTest* recorder() {
+    return static_cast<UkmRecorderForTest*>(manager_.recorder_for_testing());
+  }
+
+ protected:
+  void UpdateURL(const GURL& url) {
+    manager_.SetSourceURL(url);
+    ukm_source_ = const_cast<ukm::UkmSource*>(recorder()->GetSourceForUrl(url));
+  }
+
+  UkmManager manager_;
+  ukm::UkmSource* ukm_source_ = nullptr;
+};
+
+TEST_F(UkmManagerTest, Basic) {
+  manager_.SetUserInteractionInProgress(true);
+  manager_.AddCheckerboardStatsForFrame(5, 1, 10);
+  manager_.AddCheckerboardStatsForFrame(15, 3, 30);
+  manager_.SetUserInteractionInProgress(false);
+
+  // We should see a single entry for the interaction above.
+  EXPECT_EQ(recorder()->entries_count(), 1u);
+  EXPECT_TRUE(recorder()->HasEntry(*ukm_source_, kUserInteraction));
+  recorder()->ExpectMetric(*ukm_source_, kUserInteraction, kCheckerboardArea,
+                           10);
+  recorder()->ExpectMetric(*ukm_source_, kUserInteraction, kMissingTiles, 2);
+  recorder()->ExpectMetric(*ukm_source_, kUserInteraction,
+                           kCheckerboardAreaRatio, 50);
+
+  // Try pushing some stats while no user interaction is happening. No entries
+  // should be pushed.
+  manager_.AddCheckerboardStatsForFrame(6, 1, 10);
+  manager_.AddCheckerboardStatsForFrame(99, 3, 100);
+  EXPECT_EQ(recorder()->entries_count(), 1u);
+  manager_.SetUserInteractionInProgress(true);
+  EXPECT_EQ(recorder()->entries_count(), 1u);
+
+  // Record a few entries and change the source before the interaction ends. The
+  // stats collected up till this point should be recorded before the source is
+  // swapped.
+  manager_.AddCheckerboardStatsForFrame(10, 1, 100);
+  manager_.AddCheckerboardStatsForFrame(30, 5, 100);
+  recorder()->expected_final_checkerboard_ = {10, 20};
+  recorder()->expected_final_missing_tiles_ = {2, 3};
+  recorder()->expected_final_checkerboard_ratio_ = {50, 20};
+  recorder()->source_ = ukm_source_;
+
+  UpdateURL(GURL("chrome://test2"));
+}
+
+}  // namespace
+}  // namespace cc
diff --git a/content/renderer/gpu/compositor_dependencies.h b/content/renderer/gpu/compositor_dependencies.h
index 712e2cb..91f629e 100644
--- a/content/renderer/gpu/compositor_dependencies.h
+++ b/content/renderer/gpu/compositor_dependencies.h
@@ -17,6 +17,7 @@
 
 namespace cc {
 class TaskGraphRunner;
+class UkmRecorderFactory;
 }
 
 namespace blink {
@@ -50,6 +51,8 @@
   virtual cc::TaskGraphRunner* GetTaskGraphRunner() = 0;
   virtual bool IsThreadedAnimationEnabled() = 0;
   virtual bool IsScrollAnimatorEnabled() = 0;
+  virtual std::unique_ptr<cc::UkmRecorderFactory>
+  CreateUkmRecorderFactory() = 0;
 
   virtual ~CompositorDependencies() {}
 };
diff --git a/content/renderer/gpu/render_widget_compositor.cc b/content/renderer/gpu/render_widget_compositor.cc
index e376ae8d..0642ab0 100644
--- a/content/renderer/gpu/render_widget_compositor.cc
+++ b/content/renderer/gpu/render_widget_compositor.cc
@@ -43,6 +43,7 @@
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/layer_tree_mutator.h"
 #include "cc/trees/swap_promise.h"
+#include "cc/trees/ukm_manager.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
@@ -320,6 +321,7 @@
   params.task_graph_runner = deps->GetTaskGraphRunner();
   params.main_task_runner = deps->GetCompositorMainThreadTaskRunner();
   params.mutator_host = mutator_host;
+  params.ukm_recorder_factory = deps->CreateUkmRecorderFactory();
   if (base::TaskScheduler::GetInstance()) {
     // The image worker thread needs to allow waiting since it makes discardable
     // shared memory allocations which need to make synchronous calls to the
@@ -1312,4 +1314,8 @@
   layer_tree_host_->RequestBeginMainFrameNotExpected(new_state);
 }
 
+void RenderWidgetCompositor::SetURLForUkm(const GURL& url) {
+  layer_tree_host_->SetURLForUkm(url);
+}
+
 }  // namespace content
diff --git a/content/renderer/gpu/render_widget_compositor.h b/content/renderer/gpu/render_widget_compositor.h
index 375541d..f15dc000 100644
--- a/content/renderer/gpu/render_widget_compositor.h
+++ b/content/renderer/gpu/render_widget_compositor.h
@@ -20,6 +20,7 @@
 #include "cc/trees/swap_promise_monitor.h"
 #include "content/common/content_export.h"
 #include "content/renderer/gpu/compositor_dependencies.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/WebKit/public/platform/WebLayerTreeView.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -124,6 +125,7 @@
   void SetContentSourceId(uint32_t source_id);
   void SetViewportSize(const gfx::Size& device_viewport_size,
                        const viz::LocalSurfaceId& local_surface_id);
+  void SetURLForUkm(const GURL& url);
 
   // WebLayerTreeView implementation.
   viz::FrameSinkId GetFrameSinkId() override;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 6e2a043aa..8a3113d 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -103,6 +103,7 @@
 #include "content/renderer/external_popup_menu.h"
 #include "content/renderer/frame_owner_properties.h"
 #include "content/renderer/gpu/gpu_benchmarking_extension.h"
+#include "content/renderer/gpu/render_widget_compositor.h"
 #include "content/renderer/history_entry.h"
 #include "content/renderer/history_serialization.h"
 #include "content/renderer/image_downloader/image_downloader_impl.h"
@@ -3889,9 +3890,18 @@
   // Navigations that change the document represent a new content source.  Keep
   // track of that on the widget to help the browser process detect when stale
   // compositor frames are being shown after a commit.
-  if (is_main_frame_ && !navigation_state->WasWithinSameDocument())
+  if (is_main_frame_ && !navigation_state->WasWithinSameDocument()) {
     GetRenderWidget()->IncrementContentSourceId();
 
+    // Update the URL used to key Ukm metrics in the compositor if the
+    // navigation is not in the same document, which represents a new source
+    // URL.
+    // Note that this is only done for the main frame since the metrics for all
+    // frames are keyed to the main frame's URL.
+    if (GetRenderWidget()->compositor())
+      GetRenderWidget()->compositor()->SetURLForUkm(GetLoadingUrl());
+  }
+
   // When we perform a new navigation, we need to update the last committed
   // session history entry with state for the page we are leaving. Do this
   // before updating the current history item.
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index d9282e0..997e8c0 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -161,6 +161,7 @@
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/base/url_util.h"
 #include "ppapi/features/features.h"
+#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
@@ -441,6 +442,28 @@
   RenderWidgetSurfaceProperties surface_properties_;
 };
 
+// This factory is used to defer binding of the InterfacePtr to the compositor
+// thread.
+class UkmRecorderFactoryImpl : public cc::UkmRecorderFactory {
+ public:
+  UkmRecorderFactoryImpl(ukm::mojom::UkmRecorderInterfacePtrInfo info)
+      : info_(std::move(info)) {
+    DCHECK(info_.is_valid());
+  }
+  ~UkmRecorderFactoryImpl() override = default;
+
+  std::unique_ptr<ukm::UkmRecorder> CreateRecorder() override {
+    DCHECK(info_.is_valid());
+
+    ukm::mojom::UkmRecorderInterfacePtr recorder;
+    recorder.Bind(std::move(info_));
+    return std::make_unique<ukm::MojoUkmRecorder>(std::move(recorder));
+  }
+
+ private:
+  ukm::mojom::UkmRecorderInterfacePtrInfo info_;
+};
+
 }  // namespace
 
 RenderThreadImpl::HistogramCustomizer::HistogramCustomizer() {
@@ -1694,6 +1717,13 @@
   return is_scroll_animator_enabled_;
 }
 
+std::unique_ptr<cc::UkmRecorderFactory>
+RenderThreadImpl::CreateUkmRecorderFactory() {
+  ukm::mojom::UkmRecorderInterfacePtrInfo info;
+  mojo::MakeRequest(&info);
+  return std::make_unique<UkmRecorderFactoryImpl>(std::move(info));
+}
+
 void RenderThreadImpl::OnRAILModeChanged(v8::RAILMode rail_mode) {
   blink::MainThreadIsolate()->SetRAILMode(rail_mode);
   blink::SetRAILModeOnWorkerThreadIsolates(rail_mode);
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 6903418..a2487d4 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -261,6 +261,7 @@
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
   bool IsThreadedAnimationEnabled() override;
   bool IsScrollAnimatorEnabled() override;
+  std::unique_ptr<cc::UkmRecorderFactory> CreateUkmRecorderFactory() override;
 
   // blink::scheduler::RendererScheduler::RAILModeObserver implementation.
   void OnRAILModeChanged(v8::RAILMode rail_mode) override;
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 992233b..6bd8444 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -42,6 +42,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/context_menu_params.h"
 #include "content/public/common/drop_data.h"
+#include "content/public/common/service_names.mojom.h"
 #include "content/public/renderer/content_renderer_client.h"
 #include "content/renderer/browser_plugin/browser_plugin_manager.h"
 #include "content/renderer/cursor_utils.h"
@@ -1430,6 +1431,8 @@
     }
   }
 
+  UpdateURLForCompositorUkm();
+
   return compositor_.get();
 }
 
@@ -2510,6 +2513,20 @@
   need_update_rect_for_auto_resize_ = false;
 }
 
+void RenderWidget::UpdateURLForCompositorUkm() {
+  DCHECK(compositor_);
+
+  if (!GetWebWidget() || !GetWebWidget()->IsWebFrameWidget())
+    return;
+
+  auto* render_frame = RenderFrameImpl::FromWebFrame(
+      static_cast<blink::WebFrameWidget*>(GetWebWidget())->LocalRoot());
+  if (!render_frame->IsMainFrame())
+    return;
+
+  compositor_->SetURLForUkm(render_frame->GetWebFrame()->GetDocument().Url());
+}
+
 #if BUILDFLAG(ENABLE_PLUGINS)
 PepperPluginInstanceImpl* RenderWidget::GetFocusedPepperPluginInsideWidget() {
   if (!GetWebWidget() || !GetWebWidget()->IsWebFrameWidget())
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index b2eb5258..f3bf93f 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -867,6 +867,12 @@
 #endif
   void RecordTimeToFirstActivePaint();
 
+  // Updates the URL used by the compositor for keying UKM metrics.
+  // Note that this uses the main frame's URL and only if its available in the
+  // current process. In the case where it is not available, no metrics will be
+  // recorded.
+  void UpdateURLForCompositorUkm();
+
   // Indicates whether this widget has focus.
   bool has_focus_;
 
diff --git a/content/test/fake_compositor_dependencies.cc b/content/test/fake_compositor_dependencies.cc
index 4187c49d..ad987db 100644
--- a/content/test/fake_compositor_dependencies.cc
+++ b/content/test/fake_compositor_dependencies.cc
@@ -9,6 +9,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "cc/test/test_ukm_recorder_factory.h"
 #include "third_party/khronos/GLES2/gl2.h"
 #include "ui/gfx/buffer_types.h"
 
@@ -88,4 +89,9 @@
   return false;
 }
 
+std::unique_ptr<cc::UkmRecorderFactory>
+FakeCompositorDependencies::CreateUkmRecorderFactory() {
+  return std::make_unique<cc::TestUkmRecorderFactory>();
+}
+
 }  // namespace content
diff --git a/content/test/fake_compositor_dependencies.h b/content/test/fake_compositor_dependencies.h
index 23483de..f22d4db 100644
--- a/content/test/fake_compositor_dependencies.h
+++ b/content/test/fake_compositor_dependencies.h
@@ -38,6 +38,7 @@
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
   bool IsThreadedAnimationEnabled() override;
   bool IsScrollAnimatorEnabled() override;
+  std::unique_ptr<cc::UkmRecorderFactory> CreateUkmRecorderFactory() override;
 
  private:
   cc::TestTaskGraphRunner task_graph_runner_;
diff --git a/services/metrics/public/cpp/mojo_ukm_recorder.h b/services/metrics/public/cpp/mojo_ukm_recorder.h
index 032a105f..08f0c03c 100644
--- a/services/metrics/public/cpp/mojo_ukm_recorder.h
+++ b/services/metrics/public/cpp/mojo_ukm_recorder.h
@@ -27,7 +27,7 @@
  */
 class METRICS_EXPORT MojoUkmRecorder : public UkmRecorder {
  public:
-  explicit MojoUkmRecorder(mojom::UkmRecorderInterfacePtr interface);
+  explicit MojoUkmRecorder(mojom::UkmRecorderInterfacePtr recorder_interface);
   ~MojoUkmRecorder() override;
 
   // UkmRecorder:
diff --git a/services/metrics/public/cpp/ukm_recorder.h b/services/metrics/public/cpp/ukm_recorder.h
index 09fc22f..d244d9a 100644
--- a/services/metrics/public/cpp/ukm_recorder.h
+++ b/services/metrics/public/cpp/ukm_recorder.h
@@ -30,6 +30,10 @@
 class AutoplayUmaHelper;
 }
 
+namespace cc {
+class UkmManager;
+}
+
 namespace content {
 class RenderWidgetHostLatencyTracker;
 }  // namespace content
@@ -78,6 +82,7 @@
 
  private:
   friend blink::AutoplayUmaHelper;
+  friend cc::UkmManager;
   friend ContextualSearchRankerLoggerImpl;
   friend PluginInfoMessageFilter;
   friend UkmPageLoadMetricsObserver;
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 3ac0fab..25da3e5 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -279,6 +279,35 @@
   </metric>
 </event>
 
+<event name="Compositor.UserInteraction">
+  <owner>[email protected]</owner>
+  <summary>
+    Metrics related to user interaction handled by the compositor. This includes
+    user gestures like scrolling, pinch-zoom and page-scale animations triggered
+    in response to a double-tap event. The length of the user interaction is
+    defined as the time the compositor is running an animation resulting from
+    user input.
+  </summary>
+  <metric name="CheckerboardedContentArea">
+    <summary>
+      The number of visible pixels per frame checkerboarded during this
+      interaction.
+    </summary>
+  </metric>
+  <metric name="CheckerboardedContentAreaRatio">
+    <summary>
+      The percentage of visible pixels per frame checkerboarded during this
+      interaction. This value should be between [0, 100].
+    </summary>
+  </metric>
+  <metric name="NumMissingTiles">
+    <summary>
+      The number of visible tiles per frame checkerboarded during this
+      interaction.
+    </summary>
+  </metric>
+</event>
+
 <event name="ContextualSearch">
   <owner>[email protected]</owner>
   <summary>