From 4924ecbd94026cfc8b59d347d653401e029515d2 Mon Sep 17 00:00:00 2001
From: Gary Klassen <gklassen@chromium.org>
Date: Fri, 28 Mar 2025 16:54:08 -0700
Subject: [PATCH] Add Node Depth UKM metric for AIPageContent.

UKM Collection Review Doc is https://docs.google.com/document/d/1wf99leudueP4WJOnuYFd5RDuPSeg64tKDpvGvGXCiPI/edit?usp=sharing&resourcekey=0-buhzwIk_KKrANyLCXvxcPg

Bug: 404239036
Change-Id: Iedb3d8e2eb923dc2e246843d8170d00654aa4573
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6371458
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Commit-Queue: Gary Klassen <gklassen@chromium.org>
Reviewed-by: Khushal Sagar <khushalsagar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1439787}
---
 .../optimization_guide/content/browser/DEPS   |  2 ++
 ...page_content_proto_provider_browsertest.cc | 33 ++++++++++++++++++-
 .../modules/content_extraction/BUILD.gn       |  2 ++
 .../renderer/modules/content_extraction/DEPS  |  5 +++
 .../ai_page_content_agent.cc                  | 10 +++++-
 .../ai_page_content_agent.h                   |  3 ++
 tools/metrics/ukm/ukm.xml                     | 17 ++++++++++
 7 files changed, 70 insertions(+), 2 deletions(-)
 create mode 100644 third_party/blink/renderer/modules/content_extraction/DEPS

diff --git a/components/optimization_guide/content/browser/DEPS b/components/optimization_guide/content/browser/DEPS
index 20e88291da48c..896cc7870474b 100644
--- a/components/optimization_guide/content/browser/DEPS
+++ b/components/optimization_guide/content/browser/DEPS
@@ -15,6 +15,8 @@ specific_include_rules = {
   "page_content_proto_provider_browsertest\.cc": [
     "+content/shell",
     "+components/network_session_configurator/common/network_switches.h",
+    "+components/ukm/test_ukm_recorder.h",
+    "+services/metrics/public/cpp/ukm_builders.h",
   ],
   "page_content_proto_util_unittest\.cc": [
     "+third_party/blink/renderer/platform/graphics",
diff --git a/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc b/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
index 2c83328ee89c6..a20580dc1d2a4 100644
--- a/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
+++ b/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/test/test_future.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
@@ -18,6 +19,7 @@
 #include "content/shell/browser/shell.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "ui/display/display_switches.h"
 
 namespace optimization_guide {
@@ -733,15 +735,39 @@ int TreeDepth(const optimization_guide::proto::ContentNode& node) {
 
 IN_PROC_BROWSER_TEST_P(PageContentProtoProviderBrowserTestMultiProcess,
                        MAYBE_DeepTree) {
+  // Listen for ukm metrics.
+  base::test::TestFuture<void> future;
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+  ukm_recorder.SetOnAddEntryCallback(
+      ukm::builders::OptimizationGuide_AIPageContentAgent::kEntryName,
+      future.GetRepeatingCallback());
+
   LoadPage(https_server()->GetURL("/deep.html"));
 
   // deep.html has a tree depth of 300.  Expect mojo encoding to trim to less
   // than mojo's kMaxRecursionDepth of 200.
   EXPECT_LT(TreeDepth(page_content().root_node()), 200);
+
+  // Ensure a ukm metric was recorded.
+  auto entries = ukm_recorder.GetEntriesByName(
+      ukm::builders::OptimizationGuide_AIPageContentAgent::kEntryName);
+  EXPECT_EQ(1u, entries.size());
+  auto* entry = entries[0].get();
+  EXPECT_EQ(
+      1, *ukm_recorder.GetEntryMetric(
+             entry, ukm::builders::OptimizationGuide_AIPageContentAgent::
+                        kNodeDepthLimitExceededName));
 }
 
 IN_PROC_BROWSER_TEST_P(PageContentProtoProviderBrowserTestMultiProcess,
-                       MAYBE_DeepSparseTree) {
+                       DeepSparseTree) {
+  // Listen for ukm metrics.
+  base::test::TestFuture<void> future;
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+  ukm_recorder.SetOnAddEntryCallback(
+      ukm::builders::OptimizationGuide_AIPageContentAgent::kEntryName,
+      future.GetRepeatingCallback());
+
   LoadPage(https_server()->GetURL("/deep_sparse.html"));
 
   // deep_sparse.html has a dom tree depth of 300. Every other DIV is one that
@@ -749,6 +775,11 @@ IN_PROC_BROWSER_TEST_P(PageContentProtoProviderBrowserTestMultiProcess,
   // is working properly, the limit should not be reached and the encoded depth
   // should be 152 (one for each unskipped div plus root and attributes).
   EXPECT_EQ(TreeDepth(page_content().root_node()), 152);
+
+  // Ensure that no ukm metric was recorded.
+  auto entries = ukm_recorder.GetEntriesByName(
+      ukm::builders::OptimizationGuide_AIPageContentAgent::kEntryName);
+  EXPECT_EQ(0u, entries.size());
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/modules/content_extraction/BUILD.gn b/third_party/blink/renderer/modules/content_extraction/BUILD.gn
index f863caa79e3c3..c6bdefc1efa58 100644
--- a/third_party/blink/renderer/modules/content_extraction/BUILD.gn
+++ b/third_party/blink/renderer/modules/content_extraction/BUILD.gn
@@ -20,5 +20,7 @@ blink_modules_sources("content_extraction") {
     "inner_text_builder.h",
   ]
 
+  deps = [ "//services/metrics/public/cpp:ukm_builders" ]
+
   public_deps = [ "//third_party/blink/public/mojom:mojom_modules_blink" ]
 }
diff --git a/third_party/blink/renderer/modules/content_extraction/DEPS b/third_party/blink/renderer/modules/content_extraction/DEPS
new file mode 100644
index 0000000000000..b763177197bee
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_extraction/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+    "ai_page_content_agent.cc": [
+      "+services/metrics/public/cpp/ukm_builders.h",
+    ]
+}
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
index e53e666ebf0bd..7ce157957dbe3 100644
--- a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
+++ b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.h"
 
 #include "base/time/time.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-blink.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
@@ -683,6 +684,13 @@ mojom::blink::AIPageContentPtr AIPageContentAgent::ContentBuilder::Build(
   WalkChildren(*layout_view, *root_node, *document_style);
   page_content->root_node = std::move(root_node);
 
+  if (stack_depth_exceeded_) {
+    ukm::builders::OptimizationGuide_AIPageContentAgent(
+        document.UkmSourceID())
+        .SetNodeDepthLimitExceeded(true)
+        .Record(document.UkmRecorder());
+  }
+
   return page_content;
 }
 
@@ -740,7 +748,7 @@ bool AIPageContentAgent::ContentBuilder::WalkChildren(
   // used in message creation.
   static const int kMaxTreeDepth = kMaxRecursionDepth - 8;
   if (stack_depth_ > kMaxTreeDepth) {
-    // TODO(gklassen): Add a metric for this.
+    stack_depth_exceeded_ = true;
     return false;
   }
 
diff --git a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.h b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.h
index 0dfffad65f664..642bce9c879be 100644
--- a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.h
+++ b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.h
@@ -111,6 +111,9 @@ class MODULES_EXPORT AIPageContentAgent final
 
     // The current depth of the tree being walked.
     int stack_depth_ = 0;
+
+    // Whether the stack depth has exceeded the max tree depth.
+    bool stack_depth_exceeded_ = false;
   };
 
   void Bind(mojo::PendingReceiver<mojom::blink::AIPageContentAgent> receiver);
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 4dff168719ff9..54aa264e3c570 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -16468,6 +16468,23 @@ be describing additional metrics about the same event.
   </metric>
 </event>
 
+<event name="OptimizationGuide.AIPageContentAgent">
+  <owner>chrome-intelligence-core@google.com</owner>
+  <owner>khushalsagar@chromium.org</owner>
+  <owner>abigailklein@chromium.org</owner>
+  <owner>gklassen@chromium.org</owner>
+  <summary>
+    Metrics related to page content extraction. Only recorded when depth limit
+    is exceeded.
+  </summary>
+  <metric name="NodeDepthLimitExceeded">
+    <summary>
+      Indicates that the node depth limit was exceeded while building
+      AnnotatedPageContent.
+    </summary>
+  </metric>
+</event>
+
 <event name="OptimizationGuide.AnnotatedPageContent">
   <owner>chrome-intelligence-core@google.com</owner>
   <owner>rajendrant@chromium.org</owner>