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:
@@ -60,6 +60,8 @@ static_library("p3a") {
|
||||
"utils.h",
|
||||
]
|
||||
|
||||
public_deps = [ "//brave/components/p3a_utils" ]
|
||||
|
||||
deps = [
|
||||
"constellation/rs/cxx:rust_lib",
|
||||
"//base",
|
||||
@@ -68,7 +70,6 @@ static_library("p3a") {
|
||||
"//brave/components/brave_origin/buildflags",
|
||||
"//brave/components/brave_stats/browser",
|
||||
"//brave/components/l10n/common",
|
||||
"//brave/components/p3a_utils",
|
||||
"//brave/components/version_info",
|
||||
"//brave/vendor/brave_base",
|
||||
"//components/cbor",
|
||||
@@ -99,10 +100,12 @@ source_set("unit_tests") {
|
||||
"constellation_helper_unittest.cc",
|
||||
"constellation_log_store_unittest.cc",
|
||||
"message_manager_unittest.cc",
|
||||
"metric_config_unittest.cc",
|
||||
"metric_log_store_unittest.cc",
|
||||
"metric_names_unittest.cc",
|
||||
"nitro_utils/attestation_unittest.cc",
|
||||
"nitro_utils/cose_unittest.cc",
|
||||
"p3a_message_unittest.cc",
|
||||
"p3a_service_unittest.cc",
|
||||
"region_unittest.cc",
|
||||
"remote_config_manager_unittest.cc",
|
||||
@@ -117,6 +120,7 @@ source_set("unit_tests") {
|
||||
"//brave/components/brave_stats/browser",
|
||||
"//brave/components/constants",
|
||||
"//brave/components/p3a",
|
||||
"//brave/components/p3a_utils",
|
||||
"//components/cbor",
|
||||
"//components/prefs:test_support",
|
||||
"//net",
|
||||
|
||||
@@ -30,6 +30,7 @@ constexpr auto kMetricAttributeMap =
|
||||
{"dtoa", MetricAttribute::kDateOfActivation},
|
||||
{"woa", MetricAttribute::kWeekOfActivation},
|
||||
{"is_browser_default", MetricAttribute::kIsBrowserDefault},
|
||||
{"custom_attribute", MetricAttribute::kCustomAttribute},
|
||||
});
|
||||
|
||||
bool GetMetricAttribute(const base::Value* value,
|
||||
@@ -120,6 +121,27 @@ bool GetOptionalBool(const base::Value* value, std::optional<bool>* field) {
|
||||
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
|
||||
|
||||
RemoteMetricConfig::RemoteMetricConfig() = default;
|
||||
@@ -151,6 +173,9 @@ void RemoteMetricConfig::RegisterJSONConverter(
|
||||
&GetOptionalString);
|
||||
converter->RegisterCustomValueField("cadence", &RemoteMetricConfig::cadence,
|
||||
&GetMetricLogType);
|
||||
converter->RegisterCustomValueField("custom_attributes",
|
||||
&RemoteMetricConfig::custom_attributes,
|
||||
&GetCustomAttributes);
|
||||
}
|
||||
|
||||
} // namespace p3a
|
||||
|
||||
@@ -35,7 +35,8 @@ enum class MetricAttribute {
|
||||
kWeekOfActivation,
|
||||
kDateOfActivation,
|
||||
kIsBrowserDefault,
|
||||
kMaxValue = kIsBrowserDefault,
|
||||
kCustomAttribute,
|
||||
kMaxValue = kCustomAttribute,
|
||||
};
|
||||
|
||||
inline constexpr MetricAttribute kDefaultMetricAttributes[] = {
|
||||
@@ -47,6 +48,8 @@ inline constexpr MetricAttribute kDefaultMetricAttributes[] = {
|
||||
|
||||
using MetricAttributes = std::array<std::optional<MetricAttribute>, 8>;
|
||||
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 {
|
||||
// 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
|
||||
// overridden.
|
||||
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.
|
||||
@@ -91,6 +99,7 @@ struct RemoteMetricConfig {
|
||||
std::optional<bool> record_activation_date;
|
||||
std::optional<std::string> activation_metric_name;
|
||||
std::optional<MetricLogType> cadence;
|
||||
std::optional<RemoteCustomAttributes> custom_attributes;
|
||||
|
||||
static void RegisterJSONConverter(
|
||||
base::JSONValueConverter<RemoteMetricConfig>* converter);
|
||||
|
||||
@@ -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
|
||||
@@ -56,6 +56,7 @@ constexpr char kSubregionAttributeName[] = "subregion";
|
||||
constexpr char kRefAttributeName[] = "ref";
|
||||
constexpr char kIsBrowserDefaultAttributeName[] = "is_default";
|
||||
|
||||
constexpr char kCustomAttributeKeyPrefix[] = "custom_";
|
||||
constexpr char kOrganicRefPrefix[] = "BRV";
|
||||
constexpr char kNone[] = "none";
|
||||
constexpr char kRefOther[] = "other";
|
||||
@@ -105,6 +106,26 @@ std::string InferActivationDate(const MessageMetainfo& meta,
|
||||
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(
|
||||
const std::string_view metric_name,
|
||||
const uint64_t metric_value,
|
||||
@@ -126,6 +147,7 @@ std::vector<std::array<std::string, 2>> PopulateConstellationAttributes(
|
||||
attributes = {{kMetricNameAttributeName, std::string(metric_name)}};
|
||||
}
|
||||
std::string attribute_value;
|
||||
size_t custom_attr_index = 0;
|
||||
|
||||
for (const auto& attribute : attributes_to_load) {
|
||||
switch (attribute) {
|
||||
@@ -214,6 +236,12 @@ std::vector<std::array<std::string, 2>> PopulateConstellationAttributes(
|
||||
{kIsBrowserDefaultAttributeName,
|
||||
base::ToString(meta.is_browser_default().value_or(false))});
|
||||
break;
|
||||
case MetricAttribute::kCustomAttribute:
|
||||
if (auto attr = FetchCustomAttribute(meta, metric_config,
|
||||
custom_attr_index++)) {
|
||||
attributes.push_back(std::move(*attr));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
@@ -390,4 +418,14 @@ std::optional<base::Time> MessageMetainfo::GetActivationDate(
|
||||
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
|
||||
|
||||
@@ -43,6 +43,9 @@ class MessageMetainfo {
|
||||
std::optional<base::Time> GetActivationDate(
|
||||
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& general_platform() const { return general_platform_; }
|
||||
const std::string& channel() const { return channel_; }
|
||||
|
||||
@@ -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
|
||||
@@ -22,6 +22,7 @@
|
||||
#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"
|
||||
@@ -84,6 +85,8 @@ P3AService::P3AService(PrefService& local_state,
|
||||
|
||||
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;
|
||||
@@ -95,6 +98,7 @@ void P3AService::RegisterPrefs(PrefRegistrySimple* registry, bool first_run) {
|
||||
// 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);
|
||||
}
|
||||
@@ -342,6 +346,17 @@ void P3AService::OnDefaultBrowserStatusChanged(bool 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 = "
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "brave/components/p3a/metric_log_type.h"
|
||||
#include "brave/components/p3a/p3a_config.h"
|
||||
#include "brave/components/p3a/remote_config_manager.h"
|
||||
#include "brave/components/p3a_utils/event_relay.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/prefs/pref_change_registrar.h"
|
||||
|
||||
@@ -57,6 +58,7 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
|
||||
#if !BUILDFLAG(IS_IOS)
|
||||
public misc_metrics::DefaultBrowserMonitor::Observer,
|
||||
#endif // !BUILDFLAG(IS_IOS)
|
||||
public p3a_utils::EventRelay::Observer,
|
||||
public RemoteConfigManager::Delegate {
|
||||
public:
|
||||
P3AService(PrefService& local_state,
|
||||
@@ -128,6 +130,11 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
|
||||
// RemoteConfigManager::Delegate
|
||||
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)
|
||||
// misc_metrics::DefaultBrowserMonitor::Observer
|
||||
void OnDefaultBrowserStatusChanged(bool is_default) override;
|
||||
@@ -191,6 +198,10 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
|
||||
base::RepeatingCallbackList<void(const std::string& histogram_name)>
|
||||
metric_cycled_callbacks_;
|
||||
|
||||
base::ScopedObservation<p3a_utils::EventRelay,
|
||||
p3a_utils::EventRelay::Observer>
|
||||
event_relay_observation_{this};
|
||||
|
||||
#if !BUILDFLAG(IS_IOS)
|
||||
base::ScopedObservation<misc_metrics::DefaultBrowserMonitor,
|
||||
misc_metrics::DefaultBrowserMonitor::Observer>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "base/test/values_test_util.h"
|
||||
#include "base/time/time.h"
|
||||
#include "brave/components/p3a/pref_names.h"
|
||||
#include "brave/components/p3a_utils/custom_attributes.h"
|
||||
#include "components/prefs/testing_pref_service.h"
|
||||
#include "content/public/test/browser_task_environment.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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -40,6 +40,7 @@ inline constexpr char kLastExpressConstellationRotationTimeStampPref[] =
|
||||
"p3a.last_express_constellation_rotation_timestamp";
|
||||
|
||||
// P3A service preference names
|
||||
inline constexpr char kCustomAttributesDictPref[] = "p3a.custom_attributes";
|
||||
inline constexpr char kDynamicMetricsDictPref[] = "p3a.dynamic_metrics";
|
||||
|
||||
// Star randomness meta preference names
|
||||
|
||||
@@ -107,13 +107,24 @@ void RemoteConfigManager::SetMetricConfigs(
|
||||
|
||||
metric_configs_.clear();
|
||||
activation_metric_names_.clear();
|
||||
custom_attribute_names_.clear();
|
||||
|
||||
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)) {
|
||||
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) {
|
||||
@@ -151,6 +162,18 @@ void RemoteConfigManager::SetMetricConfigs(
|
||||
if (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);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ class RemoteConfigManager {
|
||||
|
||||
base::flat_map<std::string, MetricConfig> metric_configs_;
|
||||
base::flat_set<std::string> activation_metric_names_;
|
||||
base::flat_set<std::string> custom_attribute_names_;
|
||||
|
||||
bool is_loaded_ = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
static_library("p3a_utils") {
|
||||
sources = [
|
||||
"bucket.h",
|
||||
"custom_attributes.cc",
|
||||
"custom_attributes.h",
|
||||
"event_relay.cc",
|
||||
"event_relay.h",
|
||||
"feature_usage.cc",
|
||||
"feature_usage.h",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user