blob: 451cec4bdb80bd7c5d7957ef55a576da9f06728a [file] [log] [blame]
// Copyright 2025 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/omnibox/composebox/composebox_metrics_recorder.h"
#include "base/metrics/histogram_functions.h"
#include "components/lens/lens_overlay_mime_type.h"
#include "components/omnibox/composebox/composebox_query.mojom.h"
#include "components/omnibox/composebox/composebox_query_controller.h"
using composebox::SessionMetrics;
using composebox::SessionState;
namespace {
const char kComposeboxFileDeleted[] = "Composebox.Session.File.DeletedCount";
const char kComposeboxSessionDuration[] = "Composebox.Session.Duration.Total";
const char kComposeboxSessionDurationQuerySubmitted[] =
"Composebox.Session.Duration.QuerySubmitted";
const char kComposeboxSessionAbandonedDuration[] =
"Composebox.Session.Duration.Abandoned";
const char kComposeboxQuerySubmissionTime[] =
"Composebox.Query.Time.ToSubmission";
const char kComposeboxFileUploadAttemptPerFileType[] =
"Composebox.Session.File.Browser.UploadAttemptCount.";
const char kComposeboxFileUploadSuccessPerFileType[] =
"Composebox.Session.File.Browser.UploadSuccessCount.";
const char kComposeboxFileUploadFailure[] =
"Composebox.Session.File.Browser.UploadFailureCount.";
const char kComposeboxFileValidationErrorTypes[] =
"Composebox.Session.File.Browser.ValidationFailureCount.";
const char kComposeboxQueryTextLength[] = "Composebox.Query.TextLength";
const char kComposeboxQueryFileCount[] = "Composebox.Query.FileCount";
const char kComposeboxQueryModality[] = "Composebox.Query.Modality";
const char kComposeboxQueryCount[] = "Composebox.Session.QueryCount";
const char kComposeboxFileSizePerType[] = "Composebox.File.Size.";
std::string UploadStatusToString(FileUploadStatus status) {
switch (status) {
case FileUploadStatus::kNotUploaded:
return "NotUploaded";
case FileUploadStatus::kProcessing:
return "Processing";
case FileUploadStatus::kValidationFailed:
return "ValidationFailed";
case FileUploadStatus::kUploadStarted:
return "UploadStarted";
case FileUploadStatus::kUploadSuccessful:
return "UploadSuccessful";
case FileUploadStatus::kUploadFailed:
return "UploadFailed";
default:
return "Unknown";
}
}
} // namespace
SessionMetrics::SessionMetrics() = default;
SessionMetrics::~SessionMetrics() = default;
ComposeboxMetricsRecorder::ComposeboxMetricsRecorder(
std::string metric_category_name)
: metric_category_name_(metric_category_name),
session_metrics_{std::make_unique<SessionMetrics>()} {}
ComposeboxMetricsRecorder::~ComposeboxMetricsRecorder() {
// Record session abandonments and completions.
if (session_state_ == SessionState::kSessionStarted) {
RecordSessionAbandonedMetrics();
} else if (session_state_ == SessionState::kNavigationOccurred) {
RecordSessionCompletedMetrics();
}
}
void ComposeboxMetricsRecorder::NotifySessionStateChanged(
SessionState session_state) {
session_state_ = session_state;
switch (session_state) {
case SessionState::kSessionStarted:
NotifySessionStarted();
break;
case SessionState::kQuerySubmitted:
NotifyQuerySubmitted();
break;
case SessionState::kSessionAbandoned:
RecordSessionAbandonedMetrics();
break;
// On navigation occurrences, keep track of the session state, but do not
// record any metrics until the end of the session, as multiple queries can
// be submitted, such as in the case were the AIM page is opened in a new
// tab and the composebox remains open.
case SessionState::kNavigationOccurred:
break;
default:
DCHECK(session_state_ != SessionState::kNone);
}
}
void ComposeboxMetricsRecorder::OnFileUploadStatusChanged(
lens::MimeType file_mime_type,
FileUploadStatus file_upload_status,
const std::optional<FileUploadErrorType>& error_type) {
switch (file_upload_status) {
case FileUploadStatus::kProcessing:
session_metrics_->file_upload_attempt_count_per_type[file_mime_type]++;
break;
case FileUploadStatus::kUploadSuccessful:
session_metrics_->file_upload_success_count_per_type[file_mime_type]++;
break;
// Every validation error will have an error type, but not every file status
// has an error, hence safeguarding the error value.
case FileUploadStatus::kValidationFailed:
if (error_type.has_value()) {
session_metrics_
->file_validation_failure_count_per_type[file_mime_type]
[error_type.value()]++;
}
break;
case FileUploadStatus::kUploadFailed:
session_metrics_->file_upload_failure_count_per_type[file_mime_type]++;
break;
// The following are not file upload success or failure statuses.
case FileUploadStatus::kNotUploaded:
case FileUploadStatus::kUploadStarted:
case FileUploadStatus::kUploadExpired:
break;
}
}
void ComposeboxMetricsRecorder::RecordQueryMetrics(int text_length,
int file_count) {
base::UmaHistogramCounts1M(metric_category_name_ + kComposeboxQueryTextLength,
text_length);
bool has_text = text_length != 0;
bool has_files = file_count != 0;
// Submission requests will always have either 1) both text and files 2) text
// only or 3) files only.
NtpComposeboxMultimodalState multimodal_state =
has_text ? (has_files ? NtpComposeboxMultimodalState::kTextAndFile
: NtpComposeboxMultimodalState::kTextOnly)
: NtpComposeboxMultimodalState::kFileOnly;
base::UmaHistogramEnumeration(
metric_category_name_ + kComposeboxQueryModality, multimodal_state);
base::UmaHistogramCounts100(metric_category_name_ + kComposeboxQueryFileCount,
file_count);
}
void ComposeboxMetricsRecorder::RecordFileSizeMetric(lens::MimeType mime_type,
uint64_t file_size_bytes) {
base::UmaHistogramCounts10M(metric_category_name_ +
kComposeboxFileSizePerType +
MimeTypeToString(mime_type),
file_size_bytes);
}
void ComposeboxMetricsRecorder::RecordFileDeletedMetrics(
bool success,
lens::MimeType file_type,
FileUploadStatus file_status) {
base::UmaHistogramBoolean(metric_category_name_ + kComposeboxFileDeleted +
"." + MimeTypeToString(file_type) + "." +
UploadStatusToString(file_status),
success);
}
void ComposeboxMetricsRecorder::NotifySessionStarted() {
session_metrics_->session_elapsed_timer =
std::make_unique<base::ElapsedTimer>();
}
void ComposeboxMetricsRecorder::NotifyQuerySubmitted() {
base::TimeDelta time_to_query_submission =
session_metrics_->session_elapsed_timer->Elapsed();
base::UmaHistogramMediumTimes(
metric_category_name_ + kComposeboxQuerySubmissionTime,
time_to_query_submission);
session_metrics_->num_query_submissions++;
}
void ComposeboxMetricsRecorder::RecordSessionAbandonedMetrics() {
// In the case that the user has submitted a query in a new tab and abandons
// the composebox session record the session as completed.
if (session_metrics_->num_query_submissions > 0) {
RecordSessionCompletedMetrics();
return;
}
base::TimeDelta session_duration =
session_metrics_->session_elapsed_timer->Elapsed();
base::UmaHistogramMediumTimes(
metric_category_name_ + kComposeboxSessionAbandonedDuration,
session_duration);
RecordTotalSessionDuration(session_duration);
FinalizeSessionMetrics();
}
void ComposeboxMetricsRecorder::RecordSessionCompletedMetrics() {
base::TimeDelta session_duration =
session_metrics_->session_elapsed_timer->Elapsed();
base::UmaHistogramMediumTimes(
metric_category_name_ + kComposeboxSessionDurationQuerySubmitted,
session_duration);
base::UmaHistogramCounts100(metric_category_name_ + kComposeboxQueryCount,
session_metrics_->num_query_submissions);
RecordTotalSessionDuration(session_duration);
FinalizeSessionMetrics();
}
void ComposeboxMetricsRecorder::RecordTotalSessionDuration(
base::TimeDelta session_duration) {
base::UmaHistogramMediumTimes(
metric_category_name_ + kComposeboxSessionDuration, session_duration);
}
void ComposeboxMetricsRecorder::FinalizeSessionMetrics() {
// Log upload attempt metrics.
for (const auto& file_info :
session_metrics_->file_upload_attempt_count_per_type) {
std::string file_type = MimeTypeToString(file_info.first);
std::string histogram_name = metric_category_name_ +
kComposeboxFileUploadAttemptPerFileType +
file_type;
base::UmaHistogramCounts100(histogram_name, file_info.second);
}
// Log successful uploads.
for (const auto& file_info :
session_metrics_->file_upload_success_count_per_type) {
std::string file_type = MimeTypeToString(file_info.first);
std::string histogram_name = metric_category_name_ +
kComposeboxFileUploadSuccessPerFileType +
file_type;
base::UmaHistogramCounts100(histogram_name, file_info.second);
}
// Log file upload failures.
for (const auto& file_info :
session_metrics_->file_upload_failure_count_per_type) {
std::string file_type = MimeTypeToString(file_info.first);
std::string histogram_name =
metric_category_name_ + kComposeboxFileUploadFailure + file_type;
base::UmaHistogramCounts100(histogram_name, file_info.second);
}
// Log file validation errors.
for (const auto& file_info :
session_metrics_->file_validation_failure_count_per_type) {
for (const auto& error_info : file_info.second) {
std::string file_type = MimeTypeToString(file_info.first);
std::string error_type = FileErrorToString(error_info.first);
std::string histogram_name = metric_category_name_ +
kComposeboxFileValidationErrorTypes +
file_type + "." + error_type;
base::UmaHistogramCounts100(histogram_name, error_info.second);
}
}
ResetSessionMetrics();
}
void ComposeboxMetricsRecorder::ResetSessionMetrics() {
session_metrics_->session_elapsed_timer.reset();
session_metrics_->file_upload_attempt_count_per_type.clear();
session_metrics_->file_upload_success_count_per_type.clear();
session_metrics_->file_upload_failure_count_per_type.clear();
session_metrics_->file_validation_failure_count_per_type.clear();
session_metrics_->num_query_submissions = 0;
}
std::string ComposeboxMetricsRecorder::FileErrorToString(
FileUploadErrorType error) {
switch (error) {
case FileUploadErrorType::kUnknown:
return "Unknown";
case FileUploadErrorType::kBrowserProcessingError:
return "BrowserProcessingError";
case FileUploadErrorType::kNetworkError:
return "NetworkError";
case FileUploadErrorType::kServerError:
return "ServerError";
case FileUploadErrorType::kServerSizeLimitExceeded:
return "ServerLimitExceededError";
case FileUploadErrorType::kAborted:
return "AbortedError";
case FileUploadErrorType::kImageProcessingError:
return "ImageProcessingError";
}
}
std::string ComposeboxMetricsRecorder::MimeTypeToString(
lens::MimeType mime_type) {
switch (mime_type) {
case lens::MimeType::kPdf:
return "Pdf";
case lens::MimeType::kImage:
return "Image";
default:
return "Other";
}
}