blob: 38fc82aac27ba143c033b66aaccf9023f72b33a8 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/lens/lens_overlay_metrics.h"
#include <cstddef>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/time/time.h"
#include "components/lens/lens_features.h"
#include "components/lens/lens_overlay_mime_type.h"
#include "services/metrics/public/cpp/ukm_builders.h"
namespace lens {
std::string InvocationSourceToString(
LensOverlayInvocationSource invocation_source) {
switch (invocation_source) {
case LensOverlayInvocationSource::kAppMenu:
return "AppMenu";
case LensOverlayInvocationSource::kContentAreaContextMenuPage:
return "ContentAreaContextMenuPage";
case LensOverlayInvocationSource::kContentAreaContextMenuImage:
return "ContentAreaContextMenuImage";
case LensOverlayInvocationSource::kToolbar:
return "Toolbar";
case LensOverlayInvocationSource::kFindInPage:
return "FindInPage";
case LensOverlayInvocationSource::kOmnibox:
return "Omnibox";
case LensOverlayInvocationSource::kLVFShutterButton:
return "LVFShutterButton";
case LensOverlayInvocationSource::kLVFGallery:
return "LVFGallery";
case LensOverlayInvocationSource::kContextMenu:
return "ContextMenu";
case LensOverlayInvocationSource::kOmniboxPageAction:
return "OmniboxPageAction";
case LensOverlayInvocationSource::kOmniboxContextualSuggestion:
return "OmniboxContextualSuggestion";
case LensOverlayInvocationSource::kHomeworkActionChip:
return "HomeworkActionChip";
case LensOverlayInvocationSource::kAIHub:
return "AIHub";
case LensOverlayInvocationSource::kFREPromo:
return "FREPromo";
}
}
std::string FirstInteractionTypeToString(
LensOverlayFirstInteractionType interaction_type) {
switch (interaction_type) {
case LensOverlayFirstInteractionType::kPermissionDialog:
return "Permission";
case LensOverlayFirstInteractionType::kLensMenu:
return "LensMenu";
case LensOverlayFirstInteractionType::kRegionSelect:
return "RegionSelect";
case LensOverlayFirstInteractionType::kTextSelect:
return "TextSelect";
case LensOverlayFirstInteractionType::kSearchbox:
return "Searchbox";
}
}
std::string MimeTypeToMetricString(lens::MimeType mime_type) {
switch (mime_type) {
case lens::MimeType::kPdf:
return "Pdf";
case lens::MimeType::kHtml:
return "Html";
case lens::MimeType::kPlainText:
return "PlainText";
case lens::MimeType::kAnnotatedPageContent:
return "AnnotatedPageContent";
case lens::MimeType::kImage:
return "Image";
case lens::MimeType::kVideo:
return "Video";
case lens::MimeType::kAudio:
return "Audio";
case lens::MimeType::kJson:
return "Json";
default:
return "Unknown";
}
}
void RecordPermissionRequestedToBeShown(
bool shown,
LensOverlayInvocationSource invocation_source) {
base::UmaHistogramBoolean("Lens.Overlay.PermissionBubble.Shown", shown);
const auto histogram_name =
"Lens.Overlay.PermissionBubble.ByInvocationSource." +
InvocationSourceToString(invocation_source) + ".Shown";
base::UmaHistogramBoolean(histogram_name, shown);
}
void RecordPermissionUserAction(LensPermissionUserAction user_action,
LensOverlayInvocationSource invocation_source) {
base::UmaHistogramEnumeration("Lens.Overlay.PermissionBubble.UserAction",
user_action);
const auto histogram_name =
"Lens.Overlay.PermissionBubble.ByInvocationSource." +
InvocationSourceToString(invocation_source) + ".UserAction";
base::UmaHistogramEnumeration(histogram_name, user_action);
}
void RecordInvocation(LensOverlayInvocationSource invocation_source,
lens::MimeType document_content_type) {
base::UmaHistogramEnumeration("Lens.Overlay.Invoked", invocation_source);
// UMA Invocation sliced by document type.
const auto sliced_invoked_histogram_name =
"Lens.Overlay.ByDocumentType." +
MimeTypeToMetricString(document_content_type) + ".Invoked";
base::UmaHistogramBoolean(sliced_invoked_histogram_name, true);
}
void RecordDismissal(LensOverlayDismissalSource dismissal_source) {
base::UmaHistogramEnumeration("Lens.Overlay.Dismissed", dismissal_source);
}
void RecordInvocationResultedInSearch(
LensOverlayInvocationSource invocation_source,
bool search_performed_in_session) {
// UMA unsliced InvocationResultedInSearch.
base::UmaHistogramBoolean("Lens.Overlay.InvocationResultedInSearch",
search_performed_in_session);
// UMA InvocationResultedInSearch sliced by entry point.
const auto sliced_search_performed_histogram_name =
"Lens.Overlay.ByInvocationSource." +
InvocationSourceToString(invocation_source) +
".InvocationResultedInSearch";
base::UmaHistogramBoolean(sliced_search_performed_histogram_name,
search_performed_in_session);
}
void RecordSessionDuration(LensOverlayInvocationSource invocation_source,
base::TimeDelta duration) {
// UMA unsliced session duration.
base::UmaHistogramCustomTimes("Lens.Overlay.SessionDuration", duration,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
// UMA session duration sliced by entry point.
const auto sliced_session_duration_histogram_name =
"Lens.Overlay.ByInvocationSource." +
InvocationSourceToString(invocation_source) + ".SessionDuration";
base::UmaHistogramCustomTimes(sliced_session_duration_histogram_name,
duration,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
}
void RecordContextualSearchboxSessionEndMetrics(
ukm::SourceId source_id,
ContextualSearchboxSessionEndMetrics session_end_metrics,
lens::MimeType page_content_type,
lens::MimeType document_content_type) {
// Only record if the contextual search box feature is enabled.
if (!lens::features::IsLensOverlayContextualSearchboxEnabled()) {
return;
}
// UMA contextual searchbox shown in session.
base::UmaHistogramBoolean("Lens.Overlay.ContextualSearchBox.ShownInSession",
session_end_metrics.searchbox_shown_);
// UMA contextual searchbox shown in session sliced by page content type.
const auto sliced_page_content_invoked_histogram_name =
"Lens.Overlay.ContextualSearchBox.ByPageContentType." +
MimeTypeToMetricString(page_content_type) + ".ShownInSession";
base::UmaHistogramBoolean(sliced_page_content_invoked_histogram_name,
session_end_metrics.searchbox_shown_);
// UMA contextual searchbox shown in session sliced by document type.
const auto sliced_document_invoked_histogram_name =
"Lens.Overlay.ContextualSearchBox.ByDocumentType." +
MimeTypeToMetricString(document_content_type) + ".ShownInSession";
base::UmaHistogramBoolean(sliced_document_invoked_histogram_name,
session_end_metrics.searchbox_shown_);
// Record UKM for contextual search box shown in session.
if (source_id != ukm::kInvalidSourceId) {
ukm::builders::Lens_Overlay_ContextualSearchBox_ShownInSession event(
source_id);
event.SetWasShown(session_end_metrics.searchbox_shown_)
.SetPageContentType(static_cast<int64_t>(page_content_type))
.Record(ukm::UkmRecorder::Get());
}
// Don't record the rest of the contextual searchbox metrics if it was not
// shown in the session.
if (!session_end_metrics.searchbox_shown_) {
return;
}
// UMA contextual searchbox focused in session.
base::UmaHistogramBoolean("Lens.Overlay.ContextualSearchBox.FocusedInSession",
session_end_metrics.searchbox_focused_);
// UMA contextual searchbox focused in session sliced by document type.
const auto sliced_focused_histogram_name =
"Lens.Overlay.ContextualSearchBox.ByPageContentType." +
MimeTypeToMetricString(page_content_type) + ".FocusedInSession";
base::UmaHistogramBoolean(sliced_focused_histogram_name,
session_end_metrics.searchbox_focused_);
bool zps_shown_in_session =
session_end_metrics.zps_shown_on_initial_query_ ||
session_end_metrics.zps_shown_on_follow_up_query_;
// UMA contextual zps shown in session.
base::UmaHistogramBoolean("Lens.Overlay.ContextualSuggest.ZPS.ShownInSession",
zps_shown_in_session);
// UMA contextual zps shown in session sliced by document type.
const auto sliced_contextual_zps_histogram_name =
"Lens.Overlay.ContextualSuggest.ZPS.ByPageContentType." +
MimeTypeToMetricString(page_content_type) + ".ShownInSession";
base::UmaHistogramBoolean(sliced_contextual_zps_histogram_name,
zps_shown_in_session);
// UMA contextual zps used in session.
base::UmaHistogramBoolean(
"Lens.Overlay.ContextualSuggest.ZPS.SuggestionUsedInSession",
session_end_metrics.zps_used_);
// UMA contextual zps used in session sliced by document type.
const auto sliced_contextual_zps_used_histogram_name =
"Lens.Overlay.ContextualSuggest.ZPS.ByPageContentType." +
MimeTypeToMetricString(page_content_type) + ".SuggestionUsedInSession";
base::UmaHistogramBoolean(sliced_contextual_zps_used_histogram_name,
session_end_metrics.zps_used_);
// UMA contextual query issued in session.
base::UmaHistogramBoolean(
"Lens.Overlay.ContextualSuggest.QueryIssuedInSession",
session_end_metrics.query_issued_);
// UMA contextual query issued in session sliced by document type.
const auto sliced_contextual_query_issued_histogram_name =
"Lens.Overlay.ContextualSuggest.ByPageContentType." +
MimeTypeToMetricString(page_content_type) + ".QueryIssuedInSession";
base::UmaHistogramBoolean(sliced_contextual_query_issued_histogram_name,
session_end_metrics.query_issued_);
// Only record the contextual query issued before zps shown metrics if a
// contextual query was issued in the session.
if (session_end_metrics.query_issued_) {
// UMA initial contextual query issued before zps shown in session.
base::UmaHistogramBoolean(
"Lens.Overlay.ContextualSuggest.InitialQuery."
"QueryIssuedInSessionBeforeSuggestShown",
session_end_metrics.initial_query_issued_before_zps_shown_);
// UMA initial contextual query issued before zps shown in session sliced by
// page content type.
const auto
sliced_contextual_query_issued_before_zps_shown_initial_histogram_name =
"Lens.Overlay.ContextualSuggest.InitialQuery.ByPageContentType." +
MimeTypeToMetricString(page_content_type) +
".QueryIssuedInSessionBeforeSuggestShown";
base::UmaHistogramBoolean(
sliced_contextual_query_issued_before_zps_shown_initial_histogram_name,
session_end_metrics.initial_query_issued_before_zps_shown_);
}
if (session_end_metrics.follow_up_query_issued_) {
// UMA follow up contextual query issued before zps shown in session.
base::UmaHistogramBoolean(
"Lens.Overlay.ContextualSuggest.FollowUpQuery."
"QueryIssuedInSessionBeforeSuggestShown",
session_end_metrics.follow_up_query_issued_before_zps_shown_);
// UMA initial contextual query issued before zps shown in session sliced by
// page content type.
const auto
sliced_contextual_query_issued_before_zps_shown_follow_up_histogram_name =
"Lens.Overlay.ContextualSuggest.FollowUpQuery.ByPageContentType." +
MimeTypeToMetricString(page_content_type) +
".QueryIssuedInSessionBeforeSuggestShown";
base::UmaHistogramBoolean(
sliced_contextual_query_issued_before_zps_shown_follow_up_histogram_name,
session_end_metrics.follow_up_query_issued_before_zps_shown_);
}
if (source_id == ukm::kInvalidSourceId) {
return;
}
// UKM contextual searchbox focused in session.
ukm::builders::Lens_Overlay_ContextualSearchbox_FocusedInSession(source_id)
.SetFocusedInSession(session_end_metrics.searchbox_focused_)
.SetPageContentType(static_cast<int64_t>(page_content_type))
.Record(ukm::UkmRecorder::Get());
// UKM contextual zps shown in session.
ukm::builders::Lens_Overlay_ContextualSuggest_ZPS_ShownInSession(source_id)
.SetShownInSession(zps_shown_in_session)
.SetPageContentType(static_cast<int64_t>(page_content_type))
.Record(ukm::UkmRecorder::Get());
// UKM contextual zps used in session.
ukm::builders::Lens_Overlay_ContextualSuggest_ZPS_SuggestionUsedInSession(
source_id)
.SetSuggestionUsedInSession(session_end_metrics.zps_used_)
.SetPageContentType(static_cast<int64_t>(page_content_type))
.Record(ukm::UkmRecorder::Get());
// UKM contextual query issued in session.
ukm::builders::Lens_Overlay_ContextualSuggest_QueryIssuedInSession(source_id)
.SetQueryIssuedInSession(session_end_metrics.query_issued_)
.SetPageContentType(static_cast<int64_t>(page_content_type))
.Record(ukm::UkmRecorder::Get());
}
void RecordSessionForegroundDuration(
LensOverlayInvocationSource invocation_source,
base::TimeDelta duration) {
// UMA unsliced session duration.
base::UmaHistogramCustomTimes("Lens.Overlay.SessionForegroundDuration",
duration,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
// UMA session duration sliced by entry point.
const auto sliced_session_duration_histogram_name =
"Lens.Overlay.ByInvocationSource." +
InvocationSourceToString(invocation_source) +
".SessionForegroundDuration";
base::UmaHistogramCustomTimes(sliced_session_duration_histogram_name,
duration,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
}
void RecordTimeToFirstInteraction(
LensOverlayInvocationSource invocation_source,
base::TimeDelta time_to_first_interaction,
LensOverlayFirstInteractionType first_interaction_type,
ukm::SourceId source_id) {
// UMA unsliced TimeToFirstInteraction.
base::UmaHistogramCustomTimes("Lens.Overlay.TimeToFirstInteraction",
time_to_first_interaction,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
// UMA TimeToFirstInteraction sliced by entry point.
const auto sliced_time_to_first_interaction_histogram_name =
"Lens.Overlay.ByInvocationSource." +
InvocationSourceToString(invocation_source) + ".TimeToFirstInteraction";
base::UmaHistogramCustomTimes(sliced_time_to_first_interaction_histogram_name,
time_to_first_interaction,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
// UMA TimeToFirstInteraction sliced by interaction type.
const auto interaction_type_histogram_name =
"Lens.Overlay.TimeToFirstInteraction." +
FirstInteractionTypeToString(first_interaction_type);
base::UmaHistogramCustomTimes(interaction_type_histogram_name,
time_to_first_interaction,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
if (source_id == ukm::kInvalidSourceId) {
return;
}
// UKM unsliced TimeToFirstInteraction.
ukm::builders::Lens_Overlay_TimeToFirstInteraction(source_id)
.SetAllEntryPoints(time_to_first_interaction.InMilliseconds())
.SetFirstInteractionType(static_cast<int64_t>(first_interaction_type))
.Record(ukm::UkmRecorder::Get());
// UKM TimeToFirstInteraction sliced by entry point.
ukm::builders::Lens_Overlay_TimeToFirstInteraction event(source_id);
switch (invocation_source) {
case lens::LensOverlayInvocationSource::kAppMenu:
event.SetAppMenu(time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kContentAreaContextMenuPage:
event.SetContentAreaContextMenuPage(
time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kContentAreaContextMenuImage:
// Not recorded since the image menu entry point results in a search
// without the user having to interact with the overlay. Time to first
// interaction in this case is essentially zero.
break;
case LensOverlayInvocationSource::kLVFShutterButton:
case LensOverlayInvocationSource::kLVFGallery:
case LensOverlayInvocationSource::kContextMenu:
// Not recorded since for LVF and context menu invocation the first
// interaction is done automatically by autoselection.
break;
case lens::LensOverlayInvocationSource::kToolbar:
event.SetToolbar(time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kFindInPage:
event.SetFindInPage(time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kOmnibox:
case lens::LensOverlayInvocationSource::kAIHub:
event.SetOmnibox(time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kOmniboxPageAction:
event.SetOmniboxPageAction(time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kOmniboxContextualSuggestion:
event.SetOmniboxContextualSuggestion(
time_to_first_interaction.InMilliseconds());
break;
case lens::LensOverlayInvocationSource::kFREPromo:
// First interaction for Lens Overlay is already recorded and sliced by invocation
// source.
break;
case lens::LensOverlayInvocationSource::kHomeworkActionChip:
event.SetHomeworkActionChip(time_to_first_interaction.InMilliseconds());
break;
}
event.SetFirstInteractionType(static_cast<int64_t>(first_interaction_type))
.Record(ukm::UkmRecorder::Get());
}
void RecordNewTabGenerated(LensOverlayNewTabSource tab_source) {
base::UmaHistogramEnumeration("Lens.Overlay.GeneratedTab", tab_source);
}
void RecordGeneratedTabCount(int generated_tab_count) {
base::UmaHistogramCounts100("Lens.Overlay.GeneratedTab.SessionCount",
generated_tab_count);
}
void RecordUKMSessionEndMetrics(
ukm::SourceId source_id,
LensOverlayInvocationSource invocation_source,
bool search_performed_in_session,
base::TimeDelta session_duration,
lens::MimeType document_content_type,
std::optional<base::TimeDelta> session_foreground_duration,
std::optional<int> generated_tab_count) {
if (source_id == ukm::kInvalidSourceId) {
return;
}
ukm::builders::Lens_Overlay_SessionEnd event(source_id);
if (session_foreground_duration.has_value()) {
event.SetSessionForegroundDuration(
session_foreground_duration->InMilliseconds());
}
if (generated_tab_count.has_value()) {
event.SetGeneratedTabCount(*generated_tab_count);
}
event.SetInvocationSource(static_cast<int64_t>(invocation_source))
.SetInvocationResultedInSearch(search_performed_in_session)
.SetSessionDuration(session_duration.InMilliseconds())
.SetInvocationDocumentType(static_cast<int64_t>(document_content_type))
.Record(ukm::UkmRecorder::Get());
}
void RecordLensResponseTime(base::TimeDelta response_time) {
base::UmaHistogramTimes("Lens.Overlay.LensResponseTime", response_time);
}
void RecordContextualSearchboxTimeToFirstFocus(
base::TimeDelta time_to_focus,
lens::MimeType page_content_type) {
const auto sliced_time_to_interaction_histogram_name =
"Lens.Overlay.ContextualSearchBox.ByPageContentType." +
MimeTypeToMetricString(page_content_type) +
".TimeFromInvocationToFirstFocus";
base::UmaHistogramCustomTimes(sliced_time_to_interaction_histogram_name,
time_to_focus,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(5), /*buckets=*/50);
}
void RecordContextualSearchboxTimeToFocusAfterNavigation(
base::TimeDelta time_to_focus,
lens::MimeType page_content_type) {
const auto sliced_time_to_interaction_histogram_name =
"Lens.Overlay.ContextualSearchBox.ByPageContentType." +
MimeTypeToMetricString(page_content_type) +
".TimeFromNavigationToFirstFocus";
base::UmaHistogramCustomTimes(sliced_time_to_interaction_histogram_name,
time_to_focus,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
}
void RecordContextualSearchboxTimeToInteractionAfterNavigation(
base::TimeDelta time_to_interaction,
lens::MimeType page_content_type) {
const auto sliced_time_to_interaction_histogram_name =
"Lens.Overlay.ContextualSearchBox.ByPageContentType." +
MimeTypeToMetricString(page_content_type) +
".TimeFromNavigationToFirstInteraction";
base::UmaHistogramCustomTimes(sliced_time_to_interaction_histogram_name,
time_to_interaction,
/*min=*/base::Milliseconds(1),
/*max=*/base::Minutes(10), /*buckets=*/50);
}
void RecordDocumentSizeBytes(lens::MimeType page_content_type,
size_t document_size_bytes) {
const auto sliced_invoked_histogram_name =
"Lens.Overlay.ByPageContentType." +
MimeTypeToMetricString(page_content_type) + ".DocumentSize2";
base::UmaHistogramCustomCounts(sliced_invoked_histogram_name,
document_size_bytes / 1000, 1, 100000, 100);
}
void RecordPdfPageCount(uint32_t page_count) {
base::UmaHistogramCounts10000("Lens.Overlay.ByPageContentType.Pdf.PageCount",
page_count);
}
void RecordOcrDomSimilarity(double similarity) {
base::UmaHistogramPercentage("Lens.Overlay.OcrDomSimilarity",
similarity * 100);
}
void RecordSidePanelResultStatus(SidePanelResultStatus status) {
base::UmaHistogramEnumeration("Lens.Overlay.SidePanelResultStatus", status);
}
void RecordSidePanelMenuOptionSelected(
lens::LensOverlaySidePanelMenuOption menu_option) {
base::UmaHistogramEnumeration(
"Lens.Overlay.SidePanel.SelectedMoreInfoMenuOption", menu_option);
}
void RecordHandleTextDirectiveResult(
lens::LensOverlayTextDirectiveResult result) {
base::UmaHistogramEnumeration("Lens.Overlay.TextDirectiveResult", result);
}
} // namespace lens