* Add custom attribute support for metrics * Rename `EventReceiver` to `EventRelay` * Address PR feedback
382 lines
12 KiB
C++
382 lines
12 KiB
C++
/* Copyright (c) 2019 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/p3a/p3a_service.h"
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include "base/check.h"
|
|
#include "base/logging.h"
|
|
#include "base/metrics/histogram_samples.h"
|
|
#include "base/metrics/statistics_recorder.h"
|
|
#include "base/trace_event/trace_event.h"
|
|
#include "brave/components/brave_origin/buildflags/buildflags.h"
|
|
#include "brave/components/p3a/component_installer.h"
|
|
#include "brave/components/p3a/message_manager.h"
|
|
#include "brave/components/p3a/metric_config_utils.h"
|
|
#include "brave/components/p3a/metric_names.h"
|
|
#include "brave/components/p3a/p3a_config.h"
|
|
#include "brave/components/p3a/pref_names.h"
|
|
#include "brave/components/p3a_utils/event_relay.h"
|
|
#include "components/prefs/pref_registry_simple.h"
|
|
#include "components/prefs/pref_service.h"
|
|
#include "components/prefs/scoped_user_pref_update.h"
|
|
#include "services/network/public/cpp/shared_url_loader_factory.h"
|
|
|
|
#if BUILDFLAG(IS_IOS)
|
|
#include "ios/web/public/thread/web_task_traits.h"
|
|
#include "ios/web/public/thread/web_thread.h"
|
|
#else
|
|
#include "content/public/browser/browser_task_traits.h"
|
|
#include "content/public/browser/browser_thread.h"
|
|
#endif // BUILDFLAG(IS_IOS)
|
|
|
|
namespace p3a {
|
|
|
|
namespace {
|
|
|
|
// Receiving this value will effectively prevent the metric from transmission
|
|
// to the backend. For now we consider this as a hack for p3a metrics, which
|
|
// should be refactored in better times.
|
|
const uint64_t kSuspendedMetricBucket = INT_MAX - 1;
|
|
|
|
#if BUILDFLAG(IS_IOS)
|
|
constexpr std::string_view kIsLikelyDefaultHistogramName =
|
|
"Brave.IOS.IsLikelyDefault";
|
|
#endif // BUILDFLAG(IS_IOS)
|
|
|
|
bool IsSuspendedMetric(std::string_view metric_name, uint64_t value_or_bucket) {
|
|
return value_or_bucket == kSuspendedMetricBucket;
|
|
}
|
|
|
|
inline scoped_refptr<base::SingleThreadTaskRunner> GetUIThreadTaskRunner() {
|
|
#if BUILDFLAG(IS_IOS)
|
|
return web::GetUIThreadTaskRunner({});
|
|
#else
|
|
return content::GetUIThreadTaskRunner({});
|
|
#endif
|
|
}
|
|
|
|
inline void DCheckCurrentlyOnUIThread() {
|
|
#if BUILDFLAG(IS_IOS)
|
|
DCHECK_CURRENTLY_ON(web::WebThread::UI);
|
|
#else
|
|
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
P3AService::P3AService(PrefService& local_state,
|
|
std::string channel,
|
|
base::Time first_run_time,
|
|
P3AConfig config)
|
|
: local_state_(local_state), config_(std::move(config)) {
|
|
LoadDynamicMetrics();
|
|
if (first_run_time.is_null()) {
|
|
first_run_time = base::Time::Now();
|
|
}
|
|
remote_config_manager_ = std::make_unique<RemoteConfigManager>(this);
|
|
|
|
message_manager_ = std::make_unique<MessageManager>(
|
|
local_state, &config_, *this, channel, first_run_time);
|
|
|
|
event_relay_observation_.Observe(p3a_utils::EventRelay::GetInstance());
|
|
}
|
|
|
|
P3AService::~P3AService() = default;
|
|
|
|
void P3AService::RegisterPrefs(PrefRegistrySimple* registry, bool first_run) {
|
|
MessageManager::RegisterPrefs(registry);
|
|
registry->RegisterBooleanPref(kP3AEnabled,
|
|
!BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED));
|
|
// New users are shown the P3A notice via the welcome page.
|
|
registry->RegisterBooleanPref(kP3ANoticeAcknowledged, first_run);
|
|
|
|
registry->RegisterDictionaryPref(kCustomAttributesDictPref);
|
|
registry->RegisterDictionaryPref(kDynamicMetricsDictPref);
|
|
registry->RegisterDictionaryPref(kActivationDatesDictPref);
|
|
}
|
|
|
|
void P3AService::InitCallback(std::string_view histogram_name) {
|
|
histogram_sample_callbacks_.push_back(
|
|
std::make_unique<base::StatisticsRecorder::ScopedHistogramSampleObserver>(
|
|
histogram_name, base::BindRepeating(&P3AService::OnHistogramChanged,
|
|
base::Unretained(this))));
|
|
}
|
|
|
|
void P3AService::InitCallbacks() {
|
|
for (const auto& [histogram_name, _] : kCollectedTypicalHistograms) {
|
|
InitCallback(histogram_name);
|
|
}
|
|
for (const auto& [histogram_name, _] : kCollectedExpressHistograms) {
|
|
InitCallback(histogram_name);
|
|
}
|
|
for (const auto& [histogram_name, _] : kCollectedSlowHistograms) {
|
|
InitCallback(histogram_name);
|
|
}
|
|
for (const auto& [histogram_name, log_type] : dynamic_metric_log_types_) {
|
|
RegisterDynamicMetric(histogram_name, log_type, false);
|
|
}
|
|
}
|
|
|
|
void P3AService::StartTeardown() {
|
|
#if !BUILDFLAG(IS_IOS)
|
|
default_browser_observation_.Reset();
|
|
#endif // !BUILDFLAG(IS_IOS)
|
|
dynamic_metric_sample_callbacks_.clear();
|
|
pref_change_registrar_.RemoveAll();
|
|
}
|
|
|
|
void P3AService::RegisterDynamicMetric(const std::string& histogram_name,
|
|
MetricLogType log_type,
|
|
bool should_be_on_ui_thread) {
|
|
if (should_be_on_ui_thread) {
|
|
DCheckCurrentlyOnUIThread();
|
|
}
|
|
if (dynamic_metric_sample_callbacks_.contains(histogram_name)) {
|
|
return;
|
|
}
|
|
dynamic_metric_log_types_[histogram_name] = log_type;
|
|
dynamic_metric_sample_callbacks_[histogram_name] =
|
|
std::make_unique<base::StatisticsRecorder::ScopedHistogramSampleObserver>(
|
|
histogram_name,
|
|
base::BindRepeating(&P3AService::OnHistogramChanged, this));
|
|
|
|
ScopedDictPrefUpdate update(&*local_state_, kDynamicMetricsDictPref);
|
|
update->Set(histogram_name, static_cast<int>(log_type));
|
|
}
|
|
|
|
void P3AService::RemoveDynamicMetric(const std::string& histogram_name) {
|
|
DCheckCurrentlyOnUIThread();
|
|
if (!dynamic_metric_log_types_.contains(histogram_name)) {
|
|
return;
|
|
}
|
|
message_manager_->RemoveMetricValue(histogram_name);
|
|
dynamic_metric_sample_callbacks_.erase(histogram_name);
|
|
dynamic_metric_log_types_.erase(histogram_name);
|
|
|
|
ScopedDictPrefUpdate update(&*local_state_, kDynamicMetricsDictPref);
|
|
update->Remove(histogram_name);
|
|
}
|
|
|
|
base::CallbackListSubscription P3AService::RegisterRotationCallback(
|
|
base::RepeatingCallback<void(MetricLogType log_type)> callback) {
|
|
DCheckCurrentlyOnUIThread();
|
|
return rotation_callbacks_.Add(std::move(callback));
|
|
}
|
|
|
|
base::CallbackListSubscription P3AService::RegisterMetricCycledCallback(
|
|
base::RepeatingCallback<void(const std::string& histogram_name)> callback) {
|
|
DCheckCurrentlyOnUIThread();
|
|
return metric_cycled_callbacks_.Add(std::move(callback));
|
|
}
|
|
|
|
bool P3AService::IsP3AEnabled() const {
|
|
return local_state_->GetBoolean(kP3AEnabled);
|
|
}
|
|
|
|
void P3AService::Init(
|
|
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
|
|
component_updater::ComponentUpdateService* cus) {
|
|
if (url_loader_factory) {
|
|
url_loader_factory_ = url_loader_factory;
|
|
}
|
|
|
|
if (cus) {
|
|
component_update_service_ = cus;
|
|
}
|
|
|
|
if (pref_change_registrar_.IsEmpty()) {
|
|
pref_change_registrar_.Init(&*local_state_);
|
|
auto callback = base::BindRepeating(&P3AService::OnP3AEnabledChanged,
|
|
base::Unretained(this));
|
|
pref_change_registrar_.Add(kP3AEnabled, callback);
|
|
}
|
|
|
|
if (initialized_ || !url_loader_factory_ ||
|
|
!remote_config_manager_->is_loaded()) {
|
|
return;
|
|
}
|
|
|
|
initialized_ = true;
|
|
|
|
// Store values that were recorded between calling constructor and |Init()|.
|
|
for (const auto& entry : histogram_values_) {
|
|
HandleHistogramChange(std::string(entry.first), entry.second);
|
|
}
|
|
histogram_values_.clear();
|
|
|
|
if (IsP3AEnabled()) {
|
|
message_manager_->Start(url_loader_factory_);
|
|
}
|
|
}
|
|
|
|
void P3AService::OnRotation(MetricLogType log_type) {
|
|
rotation_callbacks_.Notify(log_type);
|
|
}
|
|
|
|
void P3AService::OnMetricCycled(const std::string& histogram_name) {
|
|
metric_cycled_callbacks_.Notify(histogram_name);
|
|
}
|
|
|
|
std::optional<MetricLogType> P3AService::GetDynamicMetricLogType(
|
|
std::string_view histogram_name) const {
|
|
auto log_type_it = dynamic_metric_log_types_.find(histogram_name);
|
|
return log_type_it != dynamic_metric_log_types_.end()
|
|
? log_type_it->second
|
|
: std::optional<MetricLogType>();
|
|
}
|
|
|
|
const MetricConfig* P3AService::GetMetricConfig(
|
|
std::string_view histogram_name) const {
|
|
// First check if there's a remote config for this metric
|
|
if (remote_config_manager_) {
|
|
const auto* remote_config =
|
|
remote_config_manager_->GetRemoteMetricConfig(histogram_name);
|
|
if (remote_config) {
|
|
return remote_config;
|
|
}
|
|
}
|
|
|
|
// Fall back to the base config if no remote config exists
|
|
return GetBaseMetricConfig(histogram_name);
|
|
}
|
|
|
|
void P3AService::OnRemoteConfigLoaded() {
|
|
if (!initialized_) {
|
|
Init(nullptr, nullptr);
|
|
} else {
|
|
message_manager_->RemoveObsoleteLogs();
|
|
}
|
|
}
|
|
|
|
std::optional<MetricLogType> P3AService::GetLogTypeForHistogram(
|
|
std::string_view histogram_name) const {
|
|
if (remote_config_manager_) {
|
|
const auto* remote_config =
|
|
remote_config_manager_->GetRemoteMetricConfig(histogram_name);
|
|
if (remote_config && remote_config->cadence) {
|
|
return *remote_config->cadence;
|
|
}
|
|
}
|
|
|
|
auto dynamic_metric_log_type = GetDynamicMetricLogType(histogram_name);
|
|
if (dynamic_metric_log_type) {
|
|
return dynamic_metric_log_type;
|
|
}
|
|
|
|
return GetBaseLogTypeForHistogram(histogram_name);
|
|
}
|
|
|
|
void P3AService::LoadDynamicMetrics() {
|
|
const auto& dict = local_state_->GetDict(kDynamicMetricsDictPref);
|
|
|
|
for (const auto [histogram_name, log_type_ordinal] : dict) {
|
|
DCHECK(log_type_ordinal.is_int());
|
|
const MetricLogType log_type =
|
|
static_cast<MetricLogType>(log_type_ordinal.GetInt());
|
|
|
|
dynamic_metric_log_types_[histogram_name] = log_type;
|
|
}
|
|
}
|
|
|
|
void P3AService::OnP3AEnabledChanged() {
|
|
if (initialized_) {
|
|
if (IsP3AEnabled()) {
|
|
message_manager_->Start(url_loader_factory_);
|
|
} else {
|
|
message_manager_->Stop();
|
|
}
|
|
}
|
|
|
|
MaybeToggleP3AComponent(component_update_service_, this);
|
|
}
|
|
|
|
void P3AService::OnHistogramChanged(std::string_view histogram_name,
|
|
uint64_t name_hash,
|
|
base::HistogramBase::Sample32 sample) {
|
|
std::unique_ptr<base::HistogramSamples> samples =
|
|
base::StatisticsRecorder::FindHistogram(histogram_name)->SnapshotDelta();
|
|
|
|
// Stop now if there's nothing to do.
|
|
if (samples->Iterator()->Done()) {
|
|
return;
|
|
}
|
|
|
|
// Shortcut for the special values, see |kSuspendedMetricValue|
|
|
// description for details.
|
|
if (IsSuspendedMetric(histogram_name, sample)) {
|
|
GetUIThreadTaskRunner()->PostTask(
|
|
FROM_HERE, base::BindOnce(&P3AService::HandleHistogramChange, this,
|
|
histogram_name, kSuspendedMetricBucket));
|
|
return;
|
|
}
|
|
|
|
// Note that we store only buckets, not actual values.
|
|
size_t bucket = 0u;
|
|
const bool ok = samples->Iterator()->GetBucketIndex(&bucket);
|
|
if (!ok) {
|
|
LOG(ERROR) << "Only linear histograms are supported at the moment!";
|
|
return;
|
|
}
|
|
|
|
GetUIThreadTaskRunner()->PostTask(
|
|
FROM_HERE, base::BindOnce(&P3AService::HandleHistogramChange, this,
|
|
histogram_name, bucket));
|
|
}
|
|
|
|
#if !BUILDFLAG(IS_IOS)
|
|
void P3AService::SetDefaultBrowserMonitor(
|
|
misc_metrics::DefaultBrowserMonitor* monitor) {
|
|
default_browser_observation_.Observe(monitor);
|
|
auto cached_status = monitor->GetCachedDefaultStatus();
|
|
if (cached_status.has_value()) {
|
|
message_manager_->SetIsBrowserDefault(*cached_status);
|
|
}
|
|
}
|
|
|
|
void P3AService::OnDefaultBrowserStatusChanged(bool is_default) {
|
|
message_manager_->SetIsBrowserDefault(is_default);
|
|
}
|
|
#endif // !BUILDFLAG(IS_IOS)
|
|
|
|
void P3AService::OnCustomAttributeSet(
|
|
std::string_view attribute_name,
|
|
std::optional<std::string_view> attribute_value) {
|
|
ScopedDictPrefUpdate update(&*local_state_, kCustomAttributesDictPref);
|
|
if (attribute_value) {
|
|
update->Set(attribute_name, *attribute_value);
|
|
} else {
|
|
update->Remove(attribute_name);
|
|
}
|
|
}
|
|
|
|
void P3AService::HandleHistogramChange(std::string_view histogram_name,
|
|
size_t bucket) {
|
|
VLOG(2) << "P3AService::OnHistogramChanged: histogram_name = "
|
|
<< histogram_name << " Sample = " << bucket;
|
|
#if BUILDFLAG(IS_IOS)
|
|
if (histogram_name == kIsLikelyDefaultHistogramName) {
|
|
message_manager_->SetIsBrowserDefault(bucket == 2);
|
|
}
|
|
#endif // BUILDFLAG(IS_IOS)
|
|
if (!initialized_) {
|
|
// Will handle it later when ready.
|
|
histogram_values_[histogram_name] = bucket;
|
|
return;
|
|
}
|
|
if (IsSuspendedMetric(histogram_name, bucket)) {
|
|
message_manager_->RemoveMetricValue(histogram_name);
|
|
return;
|
|
}
|
|
message_manager_->UpdateMetricValue(histogram_name, bucket);
|
|
}
|
|
|
|
} // namespace p3a
|