0

growth: Clear events for debug

When testing on growth framework, we often reach the
impression/dismissal caps and it is not easy to clear the events for
further tests.

This patch adds the support to clear events on the internals page to
make the debug process easier.

Bug: 373388252
Change-Id: I4cccae6e7de986a06cd559d9808f2987e599b3dd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5930303
Reviewed-by: Nasko Oskov <nasko@chromium.org>
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: Li Lin <llin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1372194}
This commit is contained in:
Tao Wu
2024-10-22 18:27:58 +00:00
committed by Chromium LUCI CQ
parent eff45b9a4f
commit 06b50e778d
13 changed files with 186 additions and 29 deletions

@ -31,6 +31,7 @@ static_library("growth_internals") {
"//ash/webui/common:chrome_os_webui_config",
"//base",
"//chromeos/ash/components/growth",
"//chromeos/ash/components/growth:utils",
]
}

@ -22,13 +22,14 @@ found in the LICENSE file. -->
}
</style>
<h2>Growth Framework Internals</h2>
<div id="logs">
<button id="clearAllEventsButton" on-click="onClickClearAllEventsButton_">Clear all events</button>
<div id="logsContainer">
<h4 id="logsLabel">Logs:</h4>
<button id="refreshButton" on-click="onClickRefreshButton_">Load/Refresh</button>
<cr-input id="filterById" type="number" min="0" max="500" label="Filter by Campaign id" value="{{campaignId_}}"
on-change="onFilterByIdChange_">
</cr-input>
<div id="logsContainer">
<div id="logs">
<template is="dom-repeat" items="[[filteredLogs_]]" as="log">
<p>[[log]]</p>
</template>

@ -48,6 +48,11 @@ export class GrowthInternalsAppElement extends PolymerElement {
return await this.browserProxy_!.handler!.getCampaignsLogs();
}
private async onClickClearAllEventsButton_(event: Event) {
event.stopPropagation();
this.browserProxy_!.handler!.clearAllEvents();
}
private async onClickRefreshButton_(event: Event) {
event.stopPropagation();
this.set('logs_', []);

@ -11,4 +11,7 @@ interface PageHandler {
// string, which may contains useful information, such as campaign id, and
// targeting result, for debug.
GetCampaignsLogs() => (array<string> logs);
// Clear all the growth framework events stored in the feature engagement DB.
ClearAllEvents();
};

@ -6,6 +6,7 @@
#include "ash/webui/growth_internals/growth_internals.mojom.h"
#include "chromeos/ash/components/growth/campaigns_logger.h"
#include "chromeos/ash/components/growth/campaigns_manager.h"
namespace ash {
@ -17,14 +18,15 @@ GrowthInternalsPageHandler::~GrowthInternalsPageHandler() = default;
void GrowthInternalsPageHandler::GetCampaignsLogs(
GetCampaignsLogsCallback callback) {
std::vector<std::string> logs;
// `Logger` may not be initialized.
auto* logger = ::growth::CampaignsLogger::Get();
if (logger) {
logs = logger->GetLogs();
}
std::vector<std::string> logs = logger->GetLogs();
std::move(callback).Run(logs);
}
void GrowthInternalsPageHandler::ClearAllEvents() {
auto* campaigns_manager = ::growth::CampaignsManager::Get();
campaigns_manager->ClearAllEvents();
}
} // namespace ash

@ -19,6 +19,7 @@ class GrowthInternalsPageHandler : public growth::mojom::PageHandler {
// mojom::PageHandler:
void GetCampaignsLogs(GetCampaignsLogsCallback callback) override;
void ClearAllEvents() override;
private:
mojo::Receiver<growth::mojom::PageHandler> page_handler_;

@ -13,6 +13,7 @@
#include "ash/constants/ash_switches.h"
#include "base/base64.h"
#include "base/command_line.h"
#include "base/containers/enum_set.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
@ -40,6 +41,9 @@ namespace {
CampaignsManager* g_instance = nullptr;
static constexpr auto kAllSlotsSet =
base::EnumSet<Slot, Slot::kMinValue, Slot::kMaxValue>::All();
inline constexpr char kCampaignFileName[] = "campaigns.json";
inline constexpr char kEventKey[] = "event_to_be_cleared";
@ -315,6 +319,52 @@ void CampaignsManager::ClearEvent(std::string_view event) {
client_->ClearConfig(conditions_params);
}
void CampaignsManager::ClearAllEvents() {
if (!ash::features::IsGrowthInternalsEnabled()) {
return;
}
for (const auto slot : kAllSlotsSet) {
const auto* targeted_campaigns = GetCampaignsBySlot(&campaigns_, slot);
if (!targeted_campaigns) {
continue;
}
for (auto& campaign_value : *targeted_campaigns) {
const auto* campaign = campaign_value.GetIfDict();
if (!campaign) {
continue;
}
const auto campaign_id = GetCampaignId(campaign);
if (!campaign_id) {
continue;
}
const auto* targetings = GetTargetings(campaign);
if (!targetings || targetings->empty()) {
continue;
}
for (const auto& targeting : *targetings) {
const auto* target = targeting.GetIfDict();
if (!target) {
continue;
}
const auto events_targeting =
RuntimeTargeting(target).GetEventsTargeting();
if (!events_targeting) {
continue;
}
ClearEventsByTargeting(*events_targeting, campaign_id.value(),
GetCampaignGroupId(campaign));
}
}
}
}
void CampaignsManager::RecordEvent(const std::string& event,
bool trigger_campaigns) {
const bool should_trigger_campaigns =
@ -542,4 +592,63 @@ void CampaignsManager::RegisterTrialForCampaign(
client_->RegisterSyntheticFieldTrial(trial_name, group_name);
}
void CampaignsManager::ClearEventsByTargeting(
const EventsTargeting& events_targeting,
int campaign_id,
std::optional<int> group_id) {
if (!ash::features::IsGrowthInternalsEnabled()) {
return;
}
std::map<std::string, std::string> conditions_params =
CreateBasicConditionParams();
// Clear group impression and dismissal events.
if (group_id) {
// The cap value can be any number. The string here will be parsed as an
// EventConfig and only the name of the EventConfig is used to clear the
// database.
conditions_params[kEventKey] = CreateConditionParamForCap(
"Group", group_id.value(), "Impression",
events_targeting.GetGroupImpressionCap().value_or(1));
client_->ClearConfig(conditions_params);
conditions_params[kEventKey] = CreateConditionParamForCap(
"Group", group_id.value(), "Dismissed",
events_targeting.GetGroupDismissalCap().value_or(1));
client_->ClearConfig(conditions_params);
}
// Clear campaign impression and dismissal events.
conditions_params[kEventKey] =
CreateConditionParamForCap("Campaign", campaign_id, "Impression",
events_targeting.GetImpressionCap());
client_->ClearConfig(conditions_params);
conditions_params[kEventKey] = CreateConditionParamForCap(
"Campaign", campaign_id, "Dismissed", events_targeting.GetDismissalCap());
client_->ClearConfig(conditions_params);
// Clear events used by the campaign targeting.
const base::Value::List* conditions = events_targeting.GetEventsConditions();
if (!conditions) {
return;
}
for (const auto& condition : *conditions) {
if (!condition.is_list()) {
continue;
}
for (const auto& param : condition.GetList()) {
if (!param.is_string()) {
continue;
}
conditions_params[kEventKey] = param.GetString();
client_->ClearConfig(conditions_params);
}
}
}
} // namespace growth

@ -112,6 +112,10 @@ class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH) CampaignsManager {
void ClearEvent(CampaignEvent event, std::string_view id);
void ClearEvent(std::string_view event);
// Clear all events associated by all the campaigns.
// Only used by the `growth-internals` page.
void ClearAllEvents();
// Record event to the Feature Engagement framework. Event will be stored and
// could be used for targeting.
// If `trigger campaigns` is true, it will try to trigger the campaign if the
@ -169,6 +173,12 @@ class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH) CampaignsManager {
// incomplete, i.e. missing id.
void RegisterTrialForCampaign(const Campaign* campaign) const;
// Clear events used in `events_targeting` from the DB of feature engagement
// framework. Only used by `growth-internals` page.
void ClearEventsByTargeting(const EventsTargeting& events_targeting,
int campaign_id,
std::optional<int> group_id);
raw_ptr<CampaignsManagerClient> client_ = nullptr;
// True if campaigns are loaded.

@ -18,7 +18,6 @@
#include "base/features.h"
#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/time/time.h"
#include "base/version.h"
@ -357,19 +356,6 @@ bool IsCampaignValid(const Campaign* campaign) {
return true;
}
std::map<std::string, std::string> CreateBasicConditionParams() {
std::map<std::string, std::string> conditions_params;
// `event_used` and `event_trigger` are required for feature_engagement
// config, although they are not used in campaign matching.
static constexpr char kTemplate[] =
"name:ChromeOSAshGrowthCampaigns_Event%s;comparator:any;window:1;storage:"
"1";
conditions_params["event_used"] = base::StringPrintf(kTemplate, "Used");
conditions_params["event_trigger"] = base::StringPrintf(kTemplate, "Trigger");
return conditions_params;
}
} // namespace
CampaignsMatcher::CampaignsMatcher(CampaignsManagerClient* client,
@ -803,10 +789,8 @@ bool CampaignsMatcher::ReachCap(base::cstring_view campaign_type,
CreateBasicConditionParams();
// Event can be put in any key starting with `event_`.
// Please see `components/feature_engagement/README.md#featureconfig`.
conditions_params[kEventKey] = base::StringPrintf(
"name:ChromeOSAshGrowthCampaigns_%s%d_%s;comparator:<%d;window:3650;"
"storage:3650",
campaign_type.c_str(), id, event_type.c_str(), cap.value());
conditions_params[kEventKey] =
CreateConditionParamForCap(campaign_type, id, event_type, cap.value());
const bool reach_cap = !client_->WouldTriggerHelpUI(conditions_params);
if (reach_cap) {
@ -1061,7 +1045,8 @@ bool CampaignsMatcher::MatchRuntimeTargeting(
return false;
}
is_matched = MatchEvents(targeting.GetEventsConfig(), campaign_id, group_id);
is_matched =
MatchEvents(targeting.GetEventsTargeting(), campaign_id, group_id);
if (!is_matched) {
return false;
}

@ -719,7 +719,7 @@ const std::vector<std::string> RuntimeTargeting::GetActiveUrlRegexes() const {
return active_urls_regexs;
}
std::unique_ptr<EventsTargeting> RuntimeTargeting::GetEventsConfig() const {
std::unique_ptr<EventsTargeting> RuntimeTargeting::GetEventsTargeting() const {
auto* config = GetDictCriteria(kEventsTargetings);
if (!config) {
return nullptr;

@ -34,7 +34,8 @@ namespace growth {
// as it is used for logging metrics as well. Please keep in sync with
// "CampaignSlot" in tools/metrics/histograms/metadata/ash_growth/enums.xml.
enum class Slot {
kDemoModeApp = 0,
kMinValue = 0,
kDemoModeApp = kMinValue,
kDemoModeFreePlayApps = 1,
kNudge = 2,
kNotification = 3,
@ -436,7 +437,7 @@ class RuntimeTargeting : public TargetingBase {
const std::vector<std::string> GetActiveUrlRegexes() const;
std::unique_ptr<EventsTargeting> GetEventsConfig() const;
std::unique_ptr<EventsTargeting> GetEventsTargeting() const;
// Returns a list of triggers against the current trigger, e.g. `kAppOpened`.
const std::vector<std::unique_ptr<TriggerTargeting>> GetTriggers() const;

@ -5,12 +5,15 @@
#include "chromeos/ash/components/growth/campaigns_utils.h"
#include <algorithm>
#include <map>
#include <string>
#include <string_view>
#include "base/containers/fixed_flat_map.h"
#include "base/notreached.h"
#include "base/strings/cstring_view.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "chromeos/ash/components/growth/campaigns_constants.h"
#include "url/gurl.h"
@ -115,6 +118,29 @@ std::string_view GetAppGroupId(const GURL& url) {
return (it == map.end()) ? std::string_view() : it->second;
}
std::map<std::string, std::string> CreateBasicConditionParams() {
std::map<std::string, std::string> conditions_params;
// `event_used` and `event_trigger` are required for feature_engagement
// config, although they are not used in campaign matching.
static constexpr char kTemplate[] =
"name:ChromeOSAshGrowthCampaigns_Event%s;comparator:any;window:1;storage:"
"1";
conditions_params["event_used"] = base::StringPrintf(kTemplate, "Used");
conditions_params["event_trigger"] = base::StringPrintf(kTemplate, "Trigger");
return conditions_params;
}
std::string CreateConditionParamForCap(base::cstring_view campaign_type,
int id,
base::cstring_view event_type,
int cap) {
return base::StringPrintf(
"name:ChromeOSAshGrowthCampaigns_%s%d_%s;comparator:<%d;window:3650;"
"storage:3650",
campaign_type.c_str(), id, event_type.c_str(), cap);
}
std::string ToString(bool value) {
return value ? "true" : "false";
}

@ -5,10 +5,12 @@
#ifndef CHROMEOS_ASH_COMPONENTS_GROWTH_CAMPAIGNS_UTILS_H_
#define CHROMEOS_ASH_COMPONENTS_GROWTH_CAMPAIGNS_UTILS_H_
#include <map>
#include <string>
#include <string_view>
#include "base/component_export.h"
#include "base/strings/cstring_view.h"
#include "chromeos/ash/components/growth/campaigns_constants.h"
class GURL;
@ -36,6 +38,17 @@ std::string_view GetAppGroupId(std::string_view app_id);
COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH_UTILS)
std::string_view GetAppGroupId(const GURL& url);
// Returns the base conditions to query feature engagement framework.
COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH_UTILS)
std::map<std::string, std::string> CreateBasicConditionParams();
// Returns the query string to check the impression/dismissal cap.
COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH_UTILS)
std::string CreateConditionParamForCap(base::cstring_view campaign_type,
int id,
base::cstring_view event_type,
int cap);
COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GROWTH_UTILS)
std::string ToString(bool value);