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",
|
"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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 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
|
||||||
|
|||||||
@@ -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_; }
|
||||||
|
|||||||
@@ -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/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 = "
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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