diff --git a/browser/misc_metrics/misc_android_metrics.cc b/browser/misc_metrics/misc_android_metrics.cc index 5b7d86a47deb..57cabdc24102 100644 --- a/browser/misc_metrics/misc_android_metrics.cc +++ b/browser/misc_metrics/misc_android_metrics.cc @@ -18,6 +18,8 @@ MiscAndroidMetrics::MiscAndroidMetrics( : misc_metrics_(misc_metrics), search_engine_tracker_(search_engine_tracker) {} +MiscAndroidMetrics::~MiscAndroidMetrics() = default; + mojo::PendingRemote MiscAndroidMetrics::MakeRemote() { mojo::PendingRemote remote; diff --git a/browser/misc_metrics/misc_android_metrics.h b/browser/misc_metrics/misc_android_metrics.h index deaee1ef0c48..c8c64588d2ae 100644 --- a/browser/misc_metrics/misc_android_metrics.h +++ b/browser/misc_metrics/misc_android_metrics.h @@ -21,6 +21,7 @@ class MiscAndroidMetrics : public mojom::MiscAndroidMetrics { public: MiscAndroidMetrics(ProcessMiscMetrics* misc_metrics, SearchEngineTracker* search_engine_tracker); + ~MiscAndroidMetrics() override; MiscAndroidMetrics(const MiscAndroidMetrics&) = delete; MiscAndroidMetrics& operator=(const MiscAndroidMetrics&) = delete; diff --git a/browser/misc_metrics/profile_misc_metrics_service.cc b/browser/misc_metrics/profile_misc_metrics_service.cc index 0e4fc9244eb1..dc20bbc2cb1d 100644 --- a/browser/misc_metrics/profile_misc_metrics_service.cc +++ b/browser/misc_metrics/profile_misc_metrics_service.cc @@ -6,18 +6,37 @@ #include "brave/browser/misc_metrics/profile_misc_metrics_service.h" #include "brave/browser/brave_browser_process.h" -#include "brave/browser/misc_metrics/extension_metrics.h" +#include "brave/components/misc_metrics/language_metrics.h" #include "brave/components/misc_metrics/page_metrics.h" #include "chrome/browser/browser_process.h" +#if BUILDFLAG(IS_ANDROID) +#include "brave/browser/misc_metrics/misc_android_metrics.h" +#else +#include "brave/browser/misc_metrics/extension_metrics.h" +#endif + namespace misc_metrics { +#if BUILDFLAG(IS_ANDROID) ProfileMiscMetricsService::ProfileMiscMetricsService( + PrefService* profile_prefs, + history::HistoryService* history_service, + SearchEngineTracker* search_engine_tracker) { +#else +ProfileMiscMetricsService::ProfileMiscMetricsService( + PrefService* profile_prefs, extensions::ExtensionRegistry* extension_registry, history::HistoryService* history_service, SearchEngineTracker* search_engine_tracker) { +#endif auto* local_state = g_browser_process->local_state(); - page_metrics_ = std::make_unique(local_state, history_service); + if (profile_prefs) { + language_metrics_ = std::make_unique(profile_prefs); + } + if (history_service) { + page_metrics_ = std::make_unique(local_state, history_service); + } #if BUILDFLAG(IS_ANDROID) misc_android_metrics_ = std::make_unique( g_brave_browser_process->process_misc_metrics(), search_engine_tracker); diff --git a/browser/misc_metrics/profile_misc_metrics_service.h b/browser/misc_metrics/profile_misc_metrics_service.h index 842c60537231..541f98bdd2eb 100644 --- a/browser/misc_metrics/profile_misc_metrics_service.h +++ b/browser/misc_metrics/profile_misc_metrics_service.h @@ -11,11 +11,14 @@ #include "build/build_config.h" #include "components/keyed_service/core/keyed_service.h" +class PrefService; class SearchEngineTracker; +#if !BUILDFLAG(IS_ANDROID) namespace extensions { class ExtensionRegistry; } // namespace extensions +#endif namespace history { class HistoryService; @@ -28,14 +31,21 @@ class MiscAndroidMetrics; #else class ExtensionMetrics; #endif +class LanguageMetrics; class PageMetrics; -class ProcessMiscMetrics; class ProfileMiscMetricsService : public KeyedService { public: - ProfileMiscMetricsService(extensions::ExtensionRegistry* extension_registry, +#if BUILDFLAG(IS_ANDROID) + ProfileMiscMetricsService(PrefService* profile_prefs, + history::HistoryService* history_service, + SearchEngineTracker* search_engine_tracker); +#else + ProfileMiscMetricsService(PrefService* profile_prefs, + extensions::ExtensionRegistry* extension_registry, history::HistoryService* history_service, SearchEngineTracker* search_engine_tracker); +#endif ~ProfileMiscMetricsService() override; ProfileMiscMetricsService(const ProfileMiscMetricsService&) = delete; @@ -50,6 +60,7 @@ class ProfileMiscMetricsService : public KeyedService { #endif private: + std::unique_ptr language_metrics_ = nullptr; std::unique_ptr page_metrics_ = nullptr; #if BUILDFLAG(IS_ANDROID) std::unique_ptr misc_android_metrics_ = nullptr; diff --git a/browser/misc_metrics/profile_misc_metrics_service_factory.cc b/browser/misc_metrics/profile_misc_metrics_service_factory.cc index 1e4f4efa4434..87aa2d1d7c5e 100644 --- a/browser/misc_metrics/profile_misc_metrics_service_factory.cc +++ b/browser/misc_metrics/profile_misc_metrics_service_factory.cc @@ -12,7 +12,12 @@ #include "chrome/browser/profiles/profile.h" #include "components/history/core/browser/history_service.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/user_prefs/user_prefs.h" + +#if !BUILDFLAG(IS_ANDROID) +#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry_factory.h" +#endif namespace misc_metrics { @@ -35,7 +40,9 @@ ProfileMiscMetricsServiceFactory::ProfileMiscMetricsServiceFactory() : BrowserContextKeyedServiceFactory( "ProfileMiscMetricsService", BrowserContextDependencyManager::GetInstance()) { +#if !BUILDFLAG(IS_ANDROID) DependsOn(extensions::ExtensionRegistryFactory::GetInstance()); +#endif DependsOn(HistoryServiceFactory::GetInstance()); DependsOn(SearchEngineTrackerFactory::GetInstance()); } @@ -44,14 +51,22 @@ ProfileMiscMetricsServiceFactory::~ProfileMiscMetricsServiceFactory() = default; KeyedService* ProfileMiscMetricsServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { - auto* extension_registry = +#if !BUILDFLAG(IS_ANDROID) + extensions::ExtensionRegistry* extension_registry = extensions::ExtensionRegistryFactory::GetForBrowserContext(context); +#endif auto* history_service = HistoryServiceFactory::GetForProfile( Profile::FromBrowserContext(context), ServiceAccessType::EXPLICIT_ACCESS); auto* search_engine_tracker = SearchEngineTrackerFactory::GetInstance()->GetForBrowserContext(context); - return new ProfileMiscMetricsService(extension_registry, history_service, +#if BUILDFLAG(IS_ANDROID) + return new ProfileMiscMetricsService(user_prefs::UserPrefs::Get(context), + history_service, search_engine_tracker); +#else + return new ProfileMiscMetricsService(user_prefs::UserPrefs::Get(context), + extension_registry, history_service, search_engine_tracker); +#endif } content::BrowserContext* diff --git a/components/misc_metrics/BUILD.gn b/components/misc_metrics/BUILD.gn index 19670bebe71b..1ea765758fcb 100644 --- a/components/misc_metrics/BUILD.gn +++ b/components/misc_metrics/BUILD.gn @@ -7,6 +7,8 @@ static_library("misc_metrics") { sources = [ "general_browser_usage.cc", "general_browser_usage.h", + "language_metrics.cc", + "language_metrics.h", "menu_metrics.cc", "menu_metrics.h", "page_metrics.cc", @@ -24,6 +26,8 @@ static_library("misc_metrics") { "//brave/components/time_period_storage", "//components/history/core/browser", "//components/keyed_service/core", + "//components/language/core/browser", + "//components/language/core/common", "//components/prefs", "//url", ] @@ -36,6 +40,7 @@ source_set("unit_tests") { sources = [ "general_browser_usage_unittest.cc", + "language_metrics_unittest.cc", "menu_metrics_unittest.cc", "privacy_hub_metrics_unittest.cc", "tab_metrics_unittest.cc", @@ -45,6 +50,7 @@ source_set("unit_tests") { "//base", "//base/test:test_support", "//brave/components/misc_metrics", + "//components/language/core/browser", "//components/prefs:test_support", "//content/test:test_support", "//testing/gtest", diff --git a/components/misc_metrics/language_metrics.cc b/components/misc_metrics/language_metrics.cc new file mode 100644 index 000000000000..165afd2099cc --- /dev/null +++ b/components/misc_metrics/language_metrics.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/misc_metrics/language_metrics.h" + +#include "base/containers/fixed_flat_set.h" +#include "base/functional/bind.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_split.h" +#include "components/language/core/browser/pref_names.h" +#include "components/language/core/common/locale_util.h" +#include "components/prefs/pref_service.h" + +namespace misc_metrics { + +struct LanguageSynonymPair { + const char* deprecated_code; + const char* active_code; +}; + +constexpr char kPrimaryLanguageHistogramName[] = "Brave.Core.PrimaryLang"; + +constexpr auto kOrderedLangCodes = + base::MakeFixedFlatSetSorted({ + "aa", // Afar + "ab", // Abkhazian + "ae", // Avestan + "af", // Afrikaans + "ak", // Akan + "am", // Amharic + "an", // Aragonese + "ar", // Arabic + "as", // Assamese + "av", // Avaric + "ay", // Aymara + "az", // Azerbaijani + "ba", // Bashkir + "be", // Belarusian + "bg", // Bulgarian + "bh", // Bihari languages + "bi", // Bislama + "bm", // Bambara + "bn", // Bengali + "bo", // Tibetan + "br", // Breton + "bs", // Bosnian + "ca", // Catalan; Valencian + "ce", // Chechen + "ch", // Chamorro + "co", // Corsican + "cr", // Cree + "cs", // Czech + "cu", // Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; + // Old Church Slavonic + "cv", // Chuvash + "cy", // Welsh + "da", // Danish + "de", // German + "dv", // Divehi; Dhivehi; Maldivian + "dz", // Dzongkha + "ee", // Ewe + "el", // "Greek, Modern (1453-)" + "en", // English + "eo", // Esperanto + "es", // Spanish; Castilian + "et", // Estonian + "eu", // Basque + "fa", // Persian + "ff", // Fulah + "fi", // Finnish + "fj", // Fijian + "fo", // Faroese + "fr", // French + "fy", // Western Frisian + "ga", // Irish + "gd", // Gaelic; Scottish Gaelic + "gl", // Galician + "gn", // Guarani + "gu", // Gujarati + "gv", // Manx + "ha", // Hausa + "he", // Hebrew + "hi", // Hindi + "ho", // Hiri Motu + "hr", // Croatian + "ht", // Haitian; Haitian Creole + "hu", // Hungarian + "hy", // Armenian + "hz", // Herero + "ia", // Interlingua (International Auxiliary Language Association) + "id", // Indonesian + "ie", // Interlingue; Occidental + "ig", // Igbo + "ii", // Sichuan Yi; Nuosu + "ik", // Inupiaq + "io", // Ido + "is", // Icelandic + "it", // Italian + "iu", // Inuktitut + "ja", // Japanese + "jv", // Javanese + "ka", // Georgian + "kg", // Kongo + "ki", // Kikuyu; Gikuyu + "kj", // Kuanyama; Kwanyama + "kk", // Kazakh + "kl", // Kalaallisut; Greenlandic + "km", // Central Khmer + "kn", // Kannada + "ko", // Korean + "kr", // Kanuri + "ks", // Kashmiri + "ku", // Kurdish + "kv", // Komi + "kw", // Cornish + "ky", // Kirghiz; Kyrgyz + "la", // Latin + "lb", // Luxembourgish; Letzeburgesch + "lg", // Ganda + "li", // Limburgan; Limburger; Limburgish + "ln", // Lingala + "lo", // Lao + "lt", // Lithuanian + "lu", // Luba-Katanga + "lv", // Latvian + "mg", // Malagasy + "mh", // Marshallese + "mi", // Maori + "mk", // Macedonian + "ml", // Malayalam + "mn", // Mongolian + "mr", // Marathi + "ms", // Malay + "mt", // Maltese + "my", // Burmese + "na", // Nauru + "nb", // "Bokmål, Norwegian; Norwegian Bokmål" + "nd", // "Ndebele, North; North Ndebele" + "ne", // Nepali + "ng", // Ndonga + "nl", // Dutch; Flemish + "nn", // "Norwegian Nynorsk; Nynorsk, Norwegian" + "no", // Norwegian + "nr", // "Ndebele, South; South Ndebele" + "nv", // Navajo; Navaho + "ny", // Chichewa; Chewa; Nyanja + "oc", // Occitan (post 1500) + "oj", // Ojibwa + "om", // Oromo + "or", // Oriya + "os", // Ossetian; Ossetic + "pa", // Panjabi; Punjabi + "pi", // Pali + "pl", // Polish + "ps", // Pushto; Pashto + "pt", // Portuguese + "qu", // Quechua + "rm", // Romansh + "rn", // Rundi + "ro", // Romanian; Moldavian; Moldovan + "ru", // Russian + "rw", // Kinyarwanda + "sa", // Sanskrit + "sc", // Sardinian + "sd", // Sindhi + "se", // Northern Sami + "sg", // Sango + "si", // Sinhala; Sinhalese + "sk", // Slovak + "sl", // Slovenian + "sm", // Samoan + "sn", // Shona + "so", // Somali + "sq", // Albanian + "sr", // Serbian + "ss", // Swati + "st", // "Sotho, Southern" + "su", // Sundanese + "sv", // Swedish + "sw", // Swahili + "ta", // Tamil + "te", // Telugu + "tg", // Tajik + "th", // Thai + "ti", // Tigrinya + "tk", // Turkmen + "tl", // Tagalog + "tn", // Tswana + "to", // Tonga (Tonga Islands) + "tr", // Turkish + "ts", // Tsonga + "tt", // Tatar + "tw", // Twi + "ty", // Tahitian + "ug", // Uighur; Uyghur + "uk", // Ukrainian + "ur", // Urdu + "uz", // Uzbek + "ve", // Venda + "vi", // Vietnamese + "vo", // Volapük + "wa", // Walloon + "wo", // Wolof + "xh", // Xhosa + "yi", // Yiddish + "yo", // Yoruba + "za", // Zhuang; Chuang + "zh", // Chinese + "zu", // Zulu + }); + +constexpr LanguageSynonymPair kLanguageSynonyms[] = { + {"in", "id"}, {"iw", "he"}, {"ji", "yi"}, {"jw", "jv"}, {"mo", "ro"}, +}; + +LanguageMetrics::LanguageMetrics(PrefService* profile_prefs) + : profile_prefs_(profile_prefs) { + pref_change_registrar_.Init(profile_prefs); + pref_change_registrar_.Add( + language::prefs::kAcceptLanguages, + base::BindRepeating(&LanguageMetrics::RecordLanguageMetric, + base::Unretained(this))); + + RecordLanguageMetric(); +} + +LanguageMetrics::~LanguageMetrics() = default; + +void LanguageMetrics::RecordLanguageMetric() { + auto languages = profile_prefs_->GetString(language::prefs::kAcceptLanguages); + auto languages_split = base::SplitString( + languages, ",", base::WhitespaceHandling::TRIM_WHITESPACE, + base::SplitResult::SPLIT_WANT_NONEMPTY); + if (languages_split.empty()) { + // Suspend metric if primary language not detected + UMA_HISTOGRAM_EXACT_LINEAR(kPrimaryLanguageHistogramName, INT_MAX - 1, + kOrderedLangCodes.size()); + return; + } + + auto primary_language = language::ExtractBaseLanguage(languages_split[0]); + for (const auto& synonym_pair : kLanguageSynonyms) { + if (primary_language == synonym_pair.deprecated_code) { + primary_language = synonym_pair.active_code; + } + } + + auto* language_it = kOrderedLangCodes.find(primary_language); + if (language_it == kOrderedLangCodes.end()) { + // language is not in list, suspend metric + UMA_HISTOGRAM_EXACT_LINEAR(kPrimaryLanguageHistogramName, INT_MAX - 1, + kOrderedLangCodes.size()); + return; + } + + int index = std::distance(kOrderedLangCodes.begin(), language_it); + UMA_HISTOGRAM_EXACT_LINEAR(kPrimaryLanguageHistogramName, index, + kOrderedLangCodes.size()); +} + +} // namespace misc_metrics diff --git a/components/misc_metrics/language_metrics.h b/components/misc_metrics/language_metrics.h new file mode 100644 index 000000000000..f529bd359398 --- /dev/null +++ b/components/misc_metrics/language_metrics.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_MISC_METRICS_LANGUAGE_METRICS_H_ +#define BRAVE_COMPONENTS_MISC_METRICS_LANGUAGE_METRICS_H_ + +#include "components/prefs/pref_change_registrar.h" + +class PrefService; + +namespace misc_metrics { + +extern const char kPrimaryLanguageHistogramName[]; + +class LanguageMetrics { + public: + explicit LanguageMetrics(PrefService* profile_prefs); + ~LanguageMetrics(); + + LanguageMetrics(const LanguageMetrics&) = delete; + LanguageMetrics& operator=(const LanguageMetrics&) = delete; + + private: + void RecordLanguageMetric(); + + PrefChangeRegistrar pref_change_registrar_; + raw_ptr profile_prefs_; +}; + +} // namespace misc_metrics + +#endif // BRAVE_COMPONENTS_MISC_METRICS_LANGUAGE_METRICS_H_ diff --git a/components/misc_metrics/language_metrics_unittest.cc b/components/misc_metrics/language_metrics_unittest.cc new file mode 100644 index 000000000000..0258d1297485 --- /dev/null +++ b/components/misc_metrics/language_metrics_unittest.cc @@ -0,0 +1,69 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include + +#include "base/test/metrics/histogram_tester.h" +#include "brave/components/misc_metrics/language_metrics.h" +#include "components/language/core/browser/pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "content/public/test/browser_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace misc_metrics { + +class LanguageMetricsUnitTest : public testing::Test { + public: + LanguageMetricsUnitTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} + + void SetUp() override { + profile_prefs_.registry()->RegisterStringPref( + language::prefs::kAcceptLanguages, ""); + language_metrics_ = std::make_unique(&profile_prefs_); + } + + protected: + content::BrowserTaskEnvironment task_environment_; + TestingPrefServiceSimple profile_prefs_; + base::HistogramTester histogram_tester_; + std::unique_ptr language_metrics_; +}; + +TEST_F(LanguageMetricsUnitTest, Basic) { + histogram_tester_.ExpectUniqueSample(kPrimaryLanguageHistogramName, + INT_MAX - 1, 1); + + // bad language code + profile_prefs_.SetString(language::prefs::kAcceptLanguages, "zz"); + histogram_tester_.ExpectUniqueSample(kPrimaryLanguageHistogramName, + INT_MAX - 1, 2); + + profile_prefs_.SetString(language::prefs::kAcceptLanguages, "en-US,en"); + + histogram_tester_.ExpectBucketCount(kPrimaryLanguageHistogramName, 37, 1); + + profile_prefs_.SetString(language::prefs::kAcceptLanguages, "fr-CA,en-US,en"); + + histogram_tester_.ExpectBucketCount(kPrimaryLanguageHistogramName, 47, 1); + histogram_tester_.ExpectTotalCount(kPrimaryLanguageHistogramName, 4); +} + +TEST_F(LanguageMetricsUnitTest, DeprecatedCodes) { + histogram_tester_.ExpectUniqueSample(kPrimaryLanguageHistogramName, + INT_MAX - 1, 1); + + // should convert to 'he' + profile_prefs_.SetString(language::prefs::kAcceptLanguages, "iw"); + histogram_tester_.ExpectBucketCount(kPrimaryLanguageHistogramName, 56, 1); + + // should convert to 'ro' + profile_prefs_.SetString(language::prefs::kAcceptLanguages, "mo"); + histogram_tester_.ExpectBucketCount(kPrimaryLanguageHistogramName, 134, 1); + histogram_tester_.ExpectTotalCount(kPrimaryLanguageHistogramName, 3); +} + +} // namespace misc_metrics diff --git a/components/p3a/metric_names.h b/components/p3a/metric_names.h index 3063246300bb..bf028cd5ee28 100644 --- a/components/p3a/metric_names.h +++ b/components/p3a/metric_names.h @@ -208,6 +208,7 @@ inline constexpr auto kCollectedSlowHistograms = base::MakeFixedFlatSetSorted({ "Brave.Accessibility.DisplayZoomEnabled", "Brave.Core.DocumentsDirectorySizeMB", + "Brave.Core.PrimaryLang", "Brave.Core.ProfileCount", "Brave.Core.UsageMonthly", "Brave.General.BottomBarLocation",