Add custom attribute support for metrics (#35418)

* Add custom attribute support for metrics

* Rename `EventReceiver` to `EventRelay`

* Address PR feedback
This commit is contained in:
Darnell Andries
2026-04-14 15:47:18 -07:00
committed by GitHub
parent 40e05127bf
commit cbe59d4cd2
18 changed files with 476 additions and 4 deletions
+5 -1
View File
@@ -60,6 +60,8 @@ static_library("p3a") {
"utils.h", "utils.h",
] ]
public_deps = [ "//brave/components/p3a_utils" ]
deps = [ deps = [
"constellation/rs/cxx:rust_lib", "constellation/rs/cxx:rust_lib",
"//base", "//base",
@@ -68,7 +70,6 @@ static_library("p3a") {
"//brave/components/brave_origin/buildflags", "//brave/components/brave_origin/buildflags",
"//brave/components/brave_stats/browser", "//brave/components/brave_stats/browser",
"//brave/components/l10n/common", "//brave/components/l10n/common",
"//brave/components/p3a_utils",
"//brave/components/version_info", "//brave/components/version_info",
"//brave/vendor/brave_base", "//brave/vendor/brave_base",
"//components/cbor", "//components/cbor",
@@ -99,10 +100,12 @@ source_set("unit_tests") {
"constellation_helper_unittest.cc", "constellation_helper_unittest.cc",
"constellation_log_store_unittest.cc", "constellation_log_store_unittest.cc",
"message_manager_unittest.cc", "message_manager_unittest.cc",
"metric_config_unittest.cc",
"metric_log_store_unittest.cc", "metric_log_store_unittest.cc",
"metric_names_unittest.cc", "metric_names_unittest.cc",
"nitro_utils/attestation_unittest.cc", "nitro_utils/attestation_unittest.cc",
"nitro_utils/cose_unittest.cc", "nitro_utils/cose_unittest.cc",
"p3a_message_unittest.cc",
"p3a_service_unittest.cc", "p3a_service_unittest.cc",
"region_unittest.cc", "region_unittest.cc",
"remote_config_manager_unittest.cc", "remote_config_manager_unittest.cc",
@@ -117,6 +120,7 @@ source_set("unit_tests") {
"//brave/components/brave_stats/browser", "//brave/components/brave_stats/browser",
"//brave/components/constants", "//brave/components/constants",
"//brave/components/p3a", "//brave/components/p3a",
"//brave/components/p3a_utils",
"//components/cbor", "//components/cbor",
"//components/prefs:test_support", "//components/prefs:test_support",
"//net", "//net",
+25
View File
@@ -30,6 +30,7 @@ constexpr auto kMetricAttributeMap =
{"dtoa", MetricAttribute::kDateOfActivation}, {"dtoa", MetricAttribute::kDateOfActivation},
{"woa", MetricAttribute::kWeekOfActivation}, {"woa", MetricAttribute::kWeekOfActivation},
{"is_browser_default", MetricAttribute::kIsBrowserDefault}, {"is_browser_default", MetricAttribute::kIsBrowserDefault},
{"custom_attribute", MetricAttribute::kCustomAttribute},
}); });
bool GetMetricAttribute(const base::Value* value, bool GetMetricAttribute(const base::Value* value,
@@ -120,6 +121,27 @@ bool GetOptionalBool(const base::Value* value, std::optional<bool>* field) {
return true; return true;
} }
bool GetCustomAttributes(const base::Value* value,
std::optional<RemoteCustomAttributes>* field) {
if (!value || !value->is_list()) {
return false;
}
const auto& list = value->GetList();
RemoteCustomAttributes attributes;
for (size_t i = 0; i < list.size() && i < attributes.size(); i++) {
if (!list[i].is_string()) {
return false;
}
attributes[i] = list[i].GetString();
}
*field = attributes;
return true;
}
} // namespace } // namespace
RemoteMetricConfig::RemoteMetricConfig() = default; RemoteMetricConfig::RemoteMetricConfig() = default;
@@ -151,6 +173,9 @@ void RemoteMetricConfig::RegisterJSONConverter(
&GetOptionalString); &GetOptionalString);
converter->RegisterCustomValueField("cadence", &RemoteMetricConfig::cadence, converter->RegisterCustomValueField("cadence", &RemoteMetricConfig::cadence,
&GetMetricLogType); &GetMetricLogType);
converter->RegisterCustomValueField("custom_attributes",
&RemoteMetricConfig::custom_attributes,
&GetCustomAttributes);
} }
} // namespace p3a } // namespace p3a
+10 -1
View File
@@ -35,7 +35,8 @@ enum class MetricAttribute {
kWeekOfActivation, kWeekOfActivation,
kDateOfActivation, kDateOfActivation,
kIsBrowserDefault, kIsBrowserDefault,
kMaxValue = kIsBrowserDefault, kCustomAttribute,
kMaxValue = kCustomAttribute,
}; };
inline constexpr MetricAttribute kDefaultMetricAttributes[] = { inline constexpr MetricAttribute kDefaultMetricAttributes[] = {
@@ -47,6 +48,8 @@ inline constexpr MetricAttribute kDefaultMetricAttributes[] = {
using MetricAttributes = std::array<std::optional<MetricAttribute>, 8>; using MetricAttributes = std::array<std::optional<MetricAttribute>, 8>;
using MetricAttributesToAppend = std::array<std::optional<MetricAttribute>, 2>; using MetricAttributesToAppend = std::array<std::optional<MetricAttribute>, 2>;
using CustomAttributes = std::array<std::optional<std::string_view>, 2>;
using RemoteCustomAttributes = std::array<std::optional<std::string>, 2>;
struct MetricConfig { struct MetricConfig {
// Once the metric value has been sent, the value will be removed from the log // Once the metric value has been sent, the value will be removed from the log
@@ -72,6 +75,11 @@ struct MetricConfig {
// If specified in a remote configuration, the cadence of the metric will be // If specified in a remote configuration, the cadence of the metric will be
// overridden. // overridden.
std::optional<MetricLogType> cadence; std::optional<MetricLogType> cadence;
// Custom attribute key names to include with the metric. Each
// kCustomAttribute in the attributes list is replaced in order with the next
// key from this array.
CustomAttributes custom_attributes;
}; };
// This struct is used to store the remote configuration for a metric. // This struct is used to store the remote configuration for a metric.
@@ -91,6 +99,7 @@ struct RemoteMetricConfig {
std::optional<bool> record_activation_date; std::optional<bool> record_activation_date;
std::optional<std::string> activation_metric_name; std::optional<std::string> activation_metric_name;
std::optional<MetricLogType> cadence; std::optional<MetricLogType> cadence;
std::optional<RemoteCustomAttributes> custom_attributes;
static void RegisterJSONConverter( static void RegisterJSONConverter(
base::JSONValueConverter<RemoteMetricConfig>* converter); base::JSONValueConverter<RemoteMetricConfig>* converter);
+51
View File
@@ -0,0 +1,51 @@
/* Copyright (c) 2026 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/metric_config.h"
#include "base/json/json_value_converter.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace p3a {
namespace {
RemoteMetricConfig ConvertFromDict(base::DictValue dict) {
base::JSONValueConverter<RemoteMetricConfig> converter;
RemoteMetricConfig config;
converter.Convert(base::Value(std::move(dict)), &config);
return config;
}
} // namespace
TEST(P3AMetricConfigTest, CustomAttributesValidList) {
auto dict = base::DictValue().Set(
"custom_attributes", base::ListValue().Append("attr_a").Append("attr_b"));
auto config = ConvertFromDict(std::move(dict));
ASSERT_TRUE(config.custom_attributes.has_value());
EXPECT_EQ((*config.custom_attributes)[0], "attr_a");
EXPECT_EQ((*config.custom_attributes)[1], "attr_b");
}
TEST(P3AMetricConfigTest, CustomAttributesNonList) {
// custom_attributes must be a list; a non-list value should leave the field
// unset.
auto dict = base::DictValue().Set("custom_attributes", "not_a_list");
auto config = ConvertFromDict(std::move(dict));
EXPECT_FALSE(config.custom_attributes.has_value());
}
TEST(P3AMetricConfigTest, CustomAttributesListWithNonStringElement) {
// A list element that is not a string should cause parsing to fail and leave
// the field unset.
auto dict = base::DictValue().Set(
"custom_attributes", base::ListValue().Append("valid_attr").Append(42));
auto config = ConvertFromDict(std::move(dict));
EXPECT_FALSE(config.custom_attributes.has_value());
}
} // namespace p3a
+38
View File
@@ -56,6 +56,7 @@ constexpr char kSubregionAttributeName[] = "subregion";
constexpr char kRefAttributeName[] = "ref"; constexpr char kRefAttributeName[] = "ref";
constexpr char kIsBrowserDefaultAttributeName[] = "is_default"; constexpr char kIsBrowserDefaultAttributeName[] = "is_default";
constexpr char kCustomAttributeKeyPrefix[] = "custom_";
constexpr char kOrganicRefPrefix[] = "BRV"; constexpr char kOrganicRefPrefix[] = "BRV";
constexpr char kNone[] = "none"; constexpr char kNone[] = "none";
constexpr char kRefOther[] = "other"; constexpr char kRefOther[] = "other";
@@ -105,6 +106,26 @@ std::string InferActivationDate(const MessageMetainfo& meta,
return FormatUTCDateFromTime(*activation_date); return FormatUTCDateFromTime(*activation_date);
} }
std::optional<std::array<std::string, 2>> FetchCustomAttribute(
const MessageMetainfo& meta,
const MetricConfig* metric_config,
size_t custom_attr_index) {
if (!metric_config ||
custom_attr_index >= metric_config->custom_attributes.size()) {
return std::nullopt;
}
const auto& key_opt = metric_config->custom_attributes[custom_attr_index];
if (!key_opt) {
return std::nullopt;
}
auto value = meta.GetCustomAttribute(*key_opt);
if (!value) {
return std::nullopt;
}
return std::array<std::string, 2>{
base::StrCat({kCustomAttributeKeyPrefix, *key_opt}), std::move(*value)};
}
std::vector<std::array<std::string, 2>> PopulateConstellationAttributes( std::vector<std::array<std::string, 2>> PopulateConstellationAttributes(
const std::string_view metric_name, const std::string_view metric_name,
const uint64_t metric_value, const uint64_t metric_value,
@@ -126,6 +147,7 @@ std::vector<std::array<std::string, 2>> PopulateConstellationAttributes(
attributes = {{kMetricNameAttributeName, std::string(metric_name)}}; attributes = {{kMetricNameAttributeName, std::string(metric_name)}};
} }
std::string attribute_value; std::string attribute_value;
size_t custom_attr_index = 0;
for (const auto& attribute : attributes_to_load) { for (const auto& attribute : attributes_to_load) {
switch (attribute) { switch (attribute) {
@@ -214,6 +236,12 @@ std::vector<std::array<std::string, 2>> PopulateConstellationAttributes(
{kIsBrowserDefaultAttributeName, {kIsBrowserDefaultAttributeName,
base::ToString(meta.is_browser_default().value_or(false))}); base::ToString(meta.is_browser_default().value_or(false))});
break; break;
case MetricAttribute::kCustomAttribute:
if (auto attr = FetchCustomAttribute(meta, metric_config,
custom_attr_index++)) {
attributes.push_back(std::move(*attr));
}
break;
} }
} }
return attributes; return attributes;
@@ -390,4 +418,14 @@ std::optional<base::Time> MessageMetainfo::GetActivationDate(
return base::ValueToTime(*time_val); return base::ValueToTime(*time_val);
} }
std::optional<std::string> MessageMetainfo::GetCustomAttribute(
std::string_view attribute_name) const {
const auto* value = local_state_->GetDict(kCustomAttributesDictPref)
.FindString(attribute_name);
if (!value || value->empty()) {
return std::nullopt;
}
return *value;
}
} // namespace p3a } // namespace p3a
+3
View File
@@ -43,6 +43,9 @@ class MessageMetainfo {
std::optional<base::Time> GetActivationDate( std::optional<base::Time> GetActivationDate(
std::string_view histogram_name) const; std::string_view histogram_name) const;
std::optional<std::string> GetCustomAttribute(
std::string_view attribute_name) const;
const std::string& platform() const { return platform_; } const std::string& platform() const { return platform_; }
const std::string& general_platform() const { return general_platform_; } const std::string& general_platform() const { return general_platform_; }
const std::string& channel() const { return channel_; } const std::string& channel() const { return channel_; }
+146
View File
@@ -0,0 +1,146 @@
// Copyright (c) 2026 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_message.h"
#include "base/time/time.h"
#include "brave/components/p3a/metric_config.h"
#include "brave/components/p3a/pref_names.h"
#include "brave/components/p3a/uploader.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !BUILDFLAG(IS_IOS)
#include "brave/components/brave_referrals/common/pref_names.h"
#endif // !BUILDFLAG(IS_IOS)
namespace p3a {
namespace {
constexpr char kTestMetricName[] = "Brave.Test.Message";
constexpr uint64_t kTestMetricValue = 3;
} // namespace
class P3AMessageTest : public testing::Test {
protected:
void SetUp() override {
local_state_.registry()->RegisterDictionaryPref(kActivationDatesDictPref);
local_state_.registry()->RegisterDictionaryPref(kCustomAttributesDictPref);
#if !BUILDFLAG(IS_IOS)
local_state_.registry()->RegisterStringPref(kReferralPromoCode, {});
#endif
base::Time install_time;
ASSERT_TRUE(base::Time::FromString("2020-01-01", &install_time));
meta_.Init(&local_state_, "release", install_time);
}
TestingPrefServiceSimple local_state_;
MessageMetainfo meta_;
};
// Verifies GenerateP3AConstellationMessage serializes metric name, value, and
// channel correctly using fully deterministic attributes.
TEST_F(P3AMessageTest, Basic) {
constexpr MetricConfig kConfig = {
.attributes = MetricAttributes{MetricAttribute::kAnswerIndex,
MetricAttribute::kChannel,
MetricAttribute::kDateOfInstall},
};
std::string message = GenerateP3AConstellationMessage(
kTestMetricName, kTestMetricValue, meta_, kP3AUploadType, &kConfig);
EXPECT_EQ(message,
"metric_name|Brave.Test.Message"
";metric_value|3"
";channel|release"
";dtoi|none");
constexpr MetricConfig kNullAttrConfig = {
.attributes = MetricAttributes{MetricAttribute::kAnswerIndex,
std::nullopt, MetricAttribute::kChannel,
MetricAttribute::kDateOfInstall},
};
std::string message_null_attr =
GenerateP3AConstellationMessage(kTestMetricName, kTestMetricValue, meta_,
kP3AUploadType, &kNullAttrConfig);
EXPECT_EQ(message_null_attr,
"metric_name|Brave.Test.Message"
";metric_value|3"
";channel|release"
";dtoi|none");
}
// Verifies that custom attributes are included with the "custom_" prefix, and
// that missing attributes are not included in the message.
TEST_F(P3AMessageTest, CustomAttributes) {
{
ScopedDictPrefUpdate update(&local_state_, kCustomAttributesDictPref);
update->Set("color", "blue");
update->Set("size", "large");
}
constexpr MetricConfig kConfig = {
.attributes = MetricAttributes{MetricAttribute::kAnswerIndex,
MetricAttribute::kCustomAttribute,
MetricAttribute::kCustomAttribute,
MetricAttribute::kDateOfInstall},
.custom_attributes = CustomAttributes{"color", "size"},
};
std::string message = GenerateP3AConstellationMessage(
kTestMetricName, kTestMetricValue, meta_, kP3AUploadType, &kConfig);
EXPECT_EQ(message,
"metric_name|Brave.Test.Message"
";metric_value|3"
";custom_color|blue"
";custom_size|large"
";dtoi|none");
constexpr MetricConfig kMissingFirstConfig = {
.attributes = MetricAttributes{MetricAttribute::kAnswerIndex,
MetricAttribute::kCustomAttribute,
MetricAttribute::kCustomAttribute,
MetricAttribute::kDateOfInstall},
.custom_attributes = CustomAttributes{"nonexistent", "color"},
};
std::string message_missing_first =
GenerateP3AConstellationMessage(kTestMetricName, kTestMetricValue, meta_,
kP3AUploadType, &kMissingFirstConfig);
EXPECT_EQ(message_missing_first,
"metric_name|Brave.Test.Message"
";metric_value|3"
";custom_color|blue"
";dtoi|none");
constexpr MetricConfig kAllMissingConfig = {
.attributes = MetricAttributes{MetricAttribute::kAnswerIndex,
MetricAttribute::kCustomAttribute,
MetricAttribute::kDateOfInstall},
.custom_attributes = CustomAttributes{"nonexistent"},
};
std::string message_all_missing =
GenerateP3AConstellationMessage(kTestMetricName, kTestMetricValue, meta_,
kP3AUploadType, &kAllMissingConfig);
EXPECT_EQ(message_all_missing,
"metric_name|Brave.Test.Message"
";metric_value|3"
";dtoi|none");
}
} // namespace p3a
+15
View File
@@ -22,6 +22,7 @@
#include "brave/components/p3a/metric_names.h" #include "brave/components/p3a/metric_names.h"
#include "brave/components/p3a/p3a_config.h" #include "brave/components/p3a/p3a_config.h"
#include "brave/components/p3a/pref_names.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_registry_simple.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h" #include "components/prefs/scoped_user_pref_update.h"
@@ -84,6 +85,8 @@ P3AService::P3AService(PrefService& local_state,
message_manager_ = std::make_unique<MessageManager>( message_manager_ = std::make_unique<MessageManager>(
local_state, &config_, *this, channel, first_run_time); local_state, &config_, *this, channel, first_run_time);
event_relay_observation_.Observe(p3a_utils::EventRelay::GetInstance());
} }
P3AService::~P3AService() = default; P3AService::~P3AService() = default;
@@ -95,6 +98,7 @@ void P3AService::RegisterPrefs(PrefRegistrySimple* registry, bool first_run) {
// New users are shown the P3A notice via the welcome page. // New users are shown the P3A notice via the welcome page.
registry->RegisterBooleanPref(kP3ANoticeAcknowledged, first_run); registry->RegisterBooleanPref(kP3ANoticeAcknowledged, first_run);
registry->RegisterDictionaryPref(kCustomAttributesDictPref);
registry->RegisterDictionaryPref(kDynamicMetricsDictPref); registry->RegisterDictionaryPref(kDynamicMetricsDictPref);
registry->RegisterDictionaryPref(kActivationDatesDictPref); registry->RegisterDictionaryPref(kActivationDatesDictPref);
} }
@@ -342,6 +346,17 @@ void P3AService::OnDefaultBrowserStatusChanged(bool is_default) {
} }
#endif // !BUILDFLAG(IS_IOS) #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, void P3AService::HandleHistogramChange(std::string_view histogram_name,
size_t bucket) { size_t bucket) {
VLOG(2) << "P3AService::OnHistogramChanged: histogram_name = " VLOG(2) << "P3AService::OnHistogramChanged: histogram_name = "
+11
View File
@@ -24,6 +24,7 @@
#include "brave/components/p3a/metric_log_type.h" #include "brave/components/p3a/metric_log_type.h"
#include "brave/components/p3a/p3a_config.h" #include "brave/components/p3a/p3a_config.h"
#include "brave/components/p3a/remote_config_manager.h" #include "brave/components/p3a/remote_config_manager.h"
#include "brave/components/p3a_utils/event_relay.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/prefs/pref_change_registrar.h" #include "components/prefs/pref_change_registrar.h"
@@ -57,6 +58,7 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
#if !BUILDFLAG(IS_IOS) #if !BUILDFLAG(IS_IOS)
public misc_metrics::DefaultBrowserMonitor::Observer, public misc_metrics::DefaultBrowserMonitor::Observer,
#endif // !BUILDFLAG(IS_IOS) #endif // !BUILDFLAG(IS_IOS)
public p3a_utils::EventRelay::Observer,
public RemoteConfigManager::Delegate { public RemoteConfigManager::Delegate {
public: public:
P3AService(PrefService& local_state, P3AService(PrefService& local_state,
@@ -128,6 +130,11 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
// RemoteConfigManager::Delegate // RemoteConfigManager::Delegate
void OnRemoteConfigLoaded() override; void OnRemoteConfigLoaded() override;
// p3a_utils::EventRelay::Observer
void OnCustomAttributeSet(
std::string_view attribute_name,
std::optional<std::string_view> attribute_value) override;
#if !BUILDFLAG(IS_IOS) #if !BUILDFLAG(IS_IOS)
// misc_metrics::DefaultBrowserMonitor::Observer // misc_metrics::DefaultBrowserMonitor::Observer
void OnDefaultBrowserStatusChanged(bool is_default) override; void OnDefaultBrowserStatusChanged(bool is_default) override;
@@ -191,6 +198,10 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
base::RepeatingCallbackList<void(const std::string& histogram_name)> base::RepeatingCallbackList<void(const std::string& histogram_name)>
metric_cycled_callbacks_; metric_cycled_callbacks_;
base::ScopedObservation<p3a_utils::EventRelay,
p3a_utils::EventRelay::Observer>
event_relay_observation_{this};
#if !BUILDFLAG(IS_IOS) #if !BUILDFLAG(IS_IOS)
base::ScopedObservation<misc_metrics::DefaultBrowserMonitor, base::ScopedObservation<misc_metrics::DefaultBrowserMonitor,
misc_metrics::DefaultBrowserMonitor::Observer> misc_metrics::DefaultBrowserMonitor::Observer>
+16
View File
@@ -11,6 +11,7 @@
#include "base/test/values_test_util.h" #include "base/test/values_test_util.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "brave/components/p3a/pref_names.h" #include "brave/components/p3a/pref_names.h"
#include "brave/components/p3a_utils/custom_attributes.h"
#include "components/prefs/testing_pref_service.h" #include "components/prefs/testing_pref_service.h"
#include "content/public/test/browser_task_environment.h" #include "content/public/test/browser_task_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -139,4 +140,19 @@ TEST_F(P3AServiceTest, MetricValueStored) {
EXPECT_TRUE(stored_log != nullptr); EXPECT_TRUE(stored_log != nullptr);
} }
TEST_F(P3AServiceTest, CustomAttributeStored) {
CreateP3AService();
p3a_utils::SetCustomAttribute("foo", "bar");
const auto* value =
local_state_.GetDict(kCustomAttributesDictPref).FindString("foo");
ASSERT_TRUE(value != nullptr);
EXPECT_EQ(*value, "bar");
p3a_utils::SetCustomAttribute("foo", std::nullopt);
EXPECT_FALSE(
local_state_.GetDict(kCustomAttributesDictPref).FindString("foo"));
}
} // namespace p3a } // namespace p3a
+1
View File
@@ -40,6 +40,7 @@ inline constexpr char kLastExpressConstellationRotationTimeStampPref[] =
"p3a.last_express_constellation_rotation_timestamp"; "p3a.last_express_constellation_rotation_timestamp";
// P3A service preference names // P3A service preference names
inline constexpr char kCustomAttributesDictPref[] = "p3a.custom_attributes";
inline constexpr char kDynamicMetricsDictPref[] = "p3a.dynamic_metrics"; inline constexpr char kDynamicMetricsDictPref[] = "p3a.dynamic_metrics";
// Star randomness meta preference names // Star randomness meta preference names
+25 -2
View File
@@ -107,13 +107,24 @@ void RemoteConfigManager::SetMetricConfigs(
metric_configs_.clear(); metric_configs_.clear();
activation_metric_names_.clear(); activation_metric_names_.clear();
custom_attribute_names_.clear();
for (const auto& entry : *result) { for (const auto& entry : *result) {
if (!entry.second.activation_metric_name || if ((!entry.second.activation_metric_name &&
!entry.second.custom_attributes) ||
!delegate_->GetLogTypeForHistogram(entry.first)) { !delegate_->GetLogTypeForHistogram(entry.first)) {
continue; continue;
} }
activation_metric_names_.insert(*entry.second.activation_metric_name); if (entry.second.activation_metric_name) {
activation_metric_names_.insert(*entry.second.activation_metric_name);
}
if (entry.second.custom_attributes) {
for (const auto& attr : *entry.second.custom_attributes) {
if (attr) {
custom_attribute_names_.insert(*attr);
}
}
}
} }
for (const auto& entry : *result) { for (const auto& entry : *result) {
@@ -151,6 +162,18 @@ void RemoteConfigManager::SetMetricConfigs(
if (remote_config.cadence) { if (remote_config.cadence) {
metric_config.cadence = remote_config.cadence; metric_config.cadence = remote_config.cadence;
} }
if (remote_config.custom_attributes) {
for (size_t i = 0; i < remote_config.custom_attributes->size(); i++) {
const auto& attr = (*remote_config.custom_attributes)[i];
if (!attr) {
continue;
}
auto it = custom_attribute_names_.find(*attr);
if (it != custom_attribute_names_.end()) {
metric_config.custom_attributes[i] = *it;
}
}
}
metric_configs_.emplace(entry.first, metric_config); metric_configs_.emplace(entry.first, metric_config);
} }
+1
View File
@@ -57,6 +57,7 @@ class RemoteConfigManager {
base::flat_map<std::string, MetricConfig> metric_configs_; base::flat_map<std::string, MetricConfig> metric_configs_;
base::flat_set<std::string> activation_metric_names_; base::flat_set<std::string> activation_metric_names_;
base::flat_set<std::string> custom_attribute_names_;
bool is_loaded_ = false; bool is_loaded_ = false;
+4
View File
@@ -1,6 +1,10 @@
static_library("p3a_utils") { static_library("p3a_utils") {
sources = [ sources = [
"bucket.h", "bucket.h",
"custom_attributes.cc",
"custom_attributes.h",
"event_relay.cc",
"event_relay.h",
"feature_usage.cc", "feature_usage.cc",
"feature_usage.h", "feature_usage.h",
] ]
+18
View File
@@ -0,0 +1,18 @@
/* Copyright (c) 2026 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_utils/custom_attributes.h"
#include "brave/components/p3a_utils/event_relay.h"
namespace p3a_utils {
void SetCustomAttribute(std::string_view attribute_name,
std::optional<std::string_view> attribute_value) {
EventRelay::GetInstance()->NotifyCustomAttributeSet(attribute_name,
attribute_value);
}
} // namespace p3a_utils
+23
View File
@@ -0,0 +1,23 @@
/* Copyright (c) 2026 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_P3A_UTILS_CUSTOM_ATTRIBUTES_H_
#define BRAVE_COMPONENTS_P3A_UTILS_CUSTOM_ATTRIBUTES_H_
#include <optional>
#include <string_view>
namespace p3a_utils {
// Sets a custom attribute value, which can be included
// in any metric report, depending on metric configuration.
// If nullopt is provided for attribute_value, the attribute
// will be cleared.
void SetCustomAttribute(std::string_view attribute_name,
std::optional<std::string_view> attribute_value);
} // namespace p3a_utils
#endif // BRAVE_COMPONENTS_P3A_UTILS_CUSTOM_ATTRIBUTES_H_
+34
View File
@@ -0,0 +1,34 @@
/* Copyright (c) 2026 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_utils/event_relay.h"
namespace p3a_utils {
// static
EventRelay* EventRelay::GetInstance() {
static base::NoDestructor<EventRelay> instance;
return instance.get();
}
EventRelay::EventRelay() = default;
EventRelay::~EventRelay() = default;
void EventRelay::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void EventRelay::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void EventRelay::NotifyCustomAttributeSet(
std::string_view attribute_name,
std::optional<std::string_view> attribute_value) {
observers_.Notify(&Observer::OnCustomAttributeSet, attribute_name,
attribute_value);
}
} // namespace p3a_utils
+50
View File
@@ -0,0 +1,50 @@
/* Copyright (c) 2026 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_P3A_UTILS_EVENT_RELAY_H_
#define BRAVE_COMPONENTS_P3A_UTILS_EVENT_RELAY_H_
#include <optional>
#include <string_view>
#include "base/no_destructor.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
namespace p3a_utils {
class EventRelay {
public:
class Observer : public base::CheckedObserver {
public:
virtual void OnCustomAttributeSet(
std::string_view attribute_name,
std::optional<std::string_view> attribute_value) = 0;
};
static EventRelay* GetInstance();
EventRelay(const EventRelay&) = delete;
EventRelay& operator=(const EventRelay&) = delete;
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
void NotifyCustomAttributeSet(
std::string_view attribute_name,
std::optional<std::string_view> attribute_value);
private:
friend class base::NoDestructor<EventRelay>;
EventRelay();
~EventRelay();
base::ObserverList<Observer> observers_;
};
} // namespace p3a_utils
#endif // BRAVE_COMPONENTS_P3A_UTILS_EVENT_RELAY_H_