blob: 42042c892ee7fd1d443e1c5e7d4c63451ab3c0b8 [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/saved_tab_groups/internal/stats.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/time/time.h"
#include "components/saved_tab_groups/internal/saved_tab_group_model.h"
#include "components/saved_tab_groups/public/saved_tab_group.h"
#include "components/saved_tab_groups/public/saved_tab_group_tab.h"
#include "components/saved_tab_groups/public/types.h"
#include "components/tab_groups/tab_group_visual_data.h"
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(TabGroupLoadedEmptiness)
enum class TabGroupLoadedEmptiness {
kNotEmpty = 0,
kAlreadyEmpty = 1,
kBecameEmptyAfterRemovingDuplicates = 2,
kMaxValue = kBecameEmptyAfterRemovingDuplicates,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/tab/enums.xml:TabGroupLoadedEmptiness)
} // namespace
namespace tab_groups {
namespace stats {
constexpr base::TimeDelta kModifiedThreshold = base::Days(30);
// Only used for desktop code that uses SavedTabGroupKeyedService. Soon to be
// deprecated.
void RecordSavedTabGroupMetrics(SavedTabGroupModel* model) {
base::UmaHistogramCounts10000("TabGroups.SavedTabGroupCount", model->Count());
const base::Time current_time = base::Time::Now();
int pinned_group_count = 0;
int active_group_count = 0;
int open_group_count = 0;
for (const SavedTabGroup& group : model->saved_tab_groups()) {
base::UmaHistogramCounts10000("TabGroups.SavedTabGroupTabCount",
group.saved_tabs().size());
const base::TimeDelta duration_saved = current_time - group.creation_time();
if (!duration_saved.is_negative()) {
base::UmaHistogramCounts1M("TabGroups.SavedTabGroupAge",
duration_saved.InMinutes());
}
const base::TimeDelta duration_since_group_modification =
current_time - group.update_time();
if (!duration_since_group_modification.is_negative()) {
base::UmaHistogramCounts1M("TabGroups.SavedTabGroupTimeSinceModification",
duration_since_group_modification.InMinutes());
if (duration_since_group_modification <= kModifiedThreshold) {
++active_group_count;
}
}
for (const SavedTabGroupTab& tab : group.saved_tabs()) {
const base::TimeDelta duration_since_tab_modification =
current_time - tab.update_time();
if (duration_since_tab_modification.is_negative()) {
continue;
}
base::UmaHistogramCounts1M(
"TabGroups.SavedTabGroupTabTimeSinceModification",
duration_since_tab_modification.InMinutes());
}
if (group.is_pinned()) {
++pinned_group_count;
}
if (group.local_group_id().has_value()) {
++open_group_count;
}
}
base::UmaHistogramCounts10000("TabGroups.SavedTabGroupPinnedCount",
pinned_group_count);
base::UmaHistogramCounts10000("TabGroups.SavedTabGroupUnpinnedCount",
model->Count() - pinned_group_count);
base::UmaHistogramCounts10000("TabGroups.SavedTabGroupActiveCount",
active_group_count);
base::UmaHistogramCounts10000("TabGroups.SavedTabGroupOpenCount",
open_group_count);
}
void RecordTabCountMismatchOnConnect(size_t tabs_in_saved_group,
size_t tabs_in_group) {
if (tabs_in_group > tabs_in_saved_group) {
base::UmaHistogramCounts100(
"TabGroups.SavedTabGroups.TabCountDifference.Positive",
tabs_in_group - tabs_in_saved_group);
} else if (tabs_in_group < tabs_in_saved_group) {
base::UmaHistogramCounts100(
"TabGroups.SavedTabGroups.TabCountDifference.Negative",
tabs_in_saved_group - tabs_in_group);
}
}
void RecordMigrationResult(MigrationResult migration_result) {
base::UmaHistogramEnumeration(
"TabGroups.SavedTabGroupSyncBridge.MigrationResult", migration_result,
MigrationResult::kCount);
}
void RecordSpecificsParseFailureCount(int parse_failure_count,
int total_entries) {
int parse_failure_percentage = 0;
if (total_entries != 0) {
parse_failure_percentage = base::ClampRound(
100.f * (static_cast<float>(parse_failure_count) / total_entries));
}
base::UmaHistogramPercentage(
"TabGroups.SpecificsToDataMigration.ParseFailurePercentage",
parse_failure_percentage);
}
void RecordParsedSavedTabGroupDataCount(int parsed_entries_count,
int total_entries) {
int parse_failure_percentage = 0;
if (total_entries != 0) {
parse_failure_percentage = base::ClampRound(
100.f * (static_cast<float>(total_entries - parsed_entries_count) /
total_entries));
}
base::UmaHistogramPercentage(
"TabGroups.ParseSavedTabGroupDataEntries.ParseFailurePercentage",
parse_failure_percentage);
}
void RecordTabGroupVisualsMetrics(
const tab_groups::TabGroupVisualData* visual_data) {
if (!visual_data) {
return;
}
base::UmaHistogramCounts100("TabGroups.Sync.TabGroupTitleLength",
visual_data->title().length());
}
void RecordSharedTabGroupDataLoadFromDiskResult(
SharedTabGroupDataLoadFromDiskResult result) {
base::UmaHistogramEnumeration(
"TabGroups.Shared.LoadFromDiskResult", result);
}
void RecordEmptyGroupsMetricsOnLoad(
const std::vector<SavedTabGroup>& all_groups,
const std::vector<SavedTabGroupTab>& all_tabs,
const std::unordered_set<base::Uuid, base::UuidHash>&
groups_with_filtered_tabs) {
// Empty groups will sometimes happen because the order of sync messages are
// not guaranteed - and clients might quit while they are missing some out of
// order messages, so groups might be empty on load too. Record metrics so we
// can validate that this is rare as expected, and that we don't create empty
// groups in other ways.
std::unordered_set<base::Uuid, base::UuidHash> empty_groups;
for (const SavedTabGroup& group : all_groups) {
empty_groups.emplace(group.saved_guid());
}
for (const SavedTabGroupTab& tab : all_tabs) {
empty_groups.erase(tab.saved_group_guid());
}
for (const SavedTabGroup& group : all_groups) {
TabGroupLoadedEmptiness metric_value = TabGroupLoadedEmptiness::kNotEmpty;
if (empty_groups.contains(group.saved_guid())) {
metric_value =
groups_with_filtered_tabs.contains(group.saved_guid())
? TabGroupLoadedEmptiness::kBecameEmptyAfterRemovingDuplicates
: TabGroupLoadedEmptiness::kAlreadyEmpty;
}
base::UmaHistogramEnumeration(
"TabGroups.SavedTabGroups.TabGroupLoadedEmptiness", metric_value);
}
}
void RecordEmptyGroupsMetricsOnGroupAddedLocally(const SavedTabGroup& group,
bool model_is_loaded) {
// This method is used to load the model from disk. Avoid recording metrics
// during load so we don't double count with the previous session.
if (!model_is_loaded) {
return;
}
base::UmaHistogramBoolean("TabGroups.Sync.AddedGroupIsEmptyLocally",
group.saved_tabs().empty());
}
void RecordEmptyGroupsMetricsOnGroupAddedFromSync(const SavedTabGroup& group,
bool model_is_loaded) {
// Avoid recording metrics during load so we don't double count with the
// previous session. This method is currently not called during load, but that
// could change.
if (!model_is_loaded) {
return;
}
base::UmaHistogramBoolean("TabGroups.Sync.AddedGroupIsEmptyFromSync",
group.saved_tabs().empty());
}
void RecordEmptyGroupsMetricsOnTabAddedLocally(const SavedTabGroup& group,
const SavedTabGroupTab& tab,
bool model_is_loaded) {
// Avoid recording metrics during load so we don't double count with the
// previous session. This method is currently not called during load, but that
// could change.
if (!model_is_loaded) {
return;
}
// The tab must not be already in the group; otherwise we can't tell if the
// group was empty before (as the tab may have been replaced).
CHECK(!group.ContainsTab(tab.saved_tab_guid()));
base::UmaHistogramBoolean("TabGroups.Sync.TabAddedToEmptyGroupLocally",
group.saved_tabs().empty());
}
void RecordEmptyGroupsMetricsOnTabAddedFromSync(const SavedTabGroup& group,
const SavedTabGroupTab& tab,
bool model_is_loaded) {
// This method is used to load the model from disk. Avoid recording metrics
// during load so we don't double count with the previous session.
if (!model_is_loaded) {
return;
}
// The tab must not be already in the group; otherwise we can't tell if the
// group was empty before (as the tab may have been replaced).
CHECK(!group.ContainsTab(tab.saved_tab_guid()));
base::UmaHistogramBoolean("TabGroups.Sync.TabAddedToEmptyGroupFromSync",
group.saved_tabs().empty());
}
void RecordSharedGroupTitleSanitization(
bool use_url_as_title,
TitleSanitizationType title_sanitization_type) {
std::string histogram;
switch (title_sanitization_type) {
case TitleSanitizationType::kAddTab:
histogram = "TabGroups.Shared.UseUrlForTitle.AddTab";
break;
case TitleSanitizationType::kNavigateTab:
histogram = "TabGroups.Shared.UseUrlForTitle.NavigateTab";
break;
case TitleSanitizationType::kShareTabGroup:
histogram = "TabGroups.Shared.UseUrlForTitle.ShareTabGroup";
break;
}
base::UmaHistogramBoolean(histogram, use_url_as_title);
}
} // namespace stats
} // namespace tab_groups