Include synthetic field trials in crash reports for non-browser processes
The docs: https://docs.google.com/document/d/1SrgNGL59FAHs6gxQ50QzAHlmfZoLUxBiGvh0Khks6sE/edit?usp=sharing Bug: 1472291 Change-Id: I966ff171daba3d5c06746782bd3ac28435241562 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4917143 Reviewed-by: Colin Blundell <blundell@chromium.org> Commit-Queue: Takashi Sakamoto <tasak@google.com> Reviewed-by: Alex Moshchuk <alexmos@chromium.org> Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org> Reviewed-by: Alexei Svitkine <asvitkine@chromium.org> Cr-Commit-Position: refs/heads/main@{#1229211}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a5303fc64f
commit
f9daf0ecfc
chrome/browser
components/variations
content
@ -176,6 +176,7 @@
|
||||
#include "content/public/browser/first_party_sets_handler.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/site_instance.h"
|
||||
#include "content/public/browser/synthetic_trial_syncer.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "content/public/common/content_features.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
@ -677,6 +678,8 @@ void ChromeBrowserMainParts::SetupMetrics() {
|
||||
variations::VariationsIdsProvider::GetInstance());
|
||||
metrics->GetSyntheticTrialRegistry()->AddObserver(
|
||||
variations::SyntheticTrialsActiveGroupIdProvider::GetInstance());
|
||||
synthetic_trial_syncer_ = content::SyntheticTrialSyncer::Create(
|
||||
metrics->GetSyntheticTrialRegistry());
|
||||
// Now that field trials have been created, initializes metrics recording.
|
||||
metrics->InitializeMetricsRecordingState();
|
||||
|
||||
@ -1897,6 +1900,11 @@ void ChromeBrowserMainParts::PostMainMessageLoopRun() {
|
||||
|
||||
browser_process_->metrics_service()->Stop();
|
||||
|
||||
// BrowserProcessImpl::StartTearDown() makes SyntheticTrialRegistry
|
||||
// unavailable. Since SyntheticTrialSyncer depends on SyntheticTrialRegistry,
|
||||
// destroy before the tear-down.
|
||||
synthetic_trial_syncer_.reset();
|
||||
|
||||
restart_last_session_ = browser_shutdown::ShutdownPreThreadsStop();
|
||||
browser_process_->StartTearDown();
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
@ -36,6 +36,10 @@ class CommandLine;
|
||||
class RunLoop;
|
||||
}
|
||||
|
||||
namespace content {
|
||||
class SyntheticTrialSyncer;
|
||||
}
|
||||
|
||||
namespace tracing {
|
||||
class TraceEventSystemStatsMonitor;
|
||||
}
|
||||
@ -175,6 +179,8 @@ class ChromeBrowserMainParts : public content::BrowserMainParts {
|
||||
std::unique_ptr<tracing::TraceEventSystemStatsMonitor>
|
||||
trace_event_system_stats_monitor_;
|
||||
|
||||
std::unique_ptr<content::SyntheticTrialSyncer> synthetic_trial_syncer_;
|
||||
|
||||
// Members initialized after / released before main_message_loop_ ------------
|
||||
|
||||
std::unique_ptr<BrowserProcessImpl> browser_process_;
|
||||
|
@ -18,6 +18,10 @@ namespace metrics {
|
||||
class MetricsServiceAccessor;
|
||||
} // namespace metrics
|
||||
|
||||
namespace content {
|
||||
class SyntheticTrialSyncer;
|
||||
} // namespace content
|
||||
|
||||
namespace tpcd::experiment {
|
||||
class ExperimentManagerImplBrowserTest;
|
||||
} // namespace tpcd::experiment
|
||||
@ -87,6 +91,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialRegistry {
|
||||
friend FieldTrialsProviderTest;
|
||||
friend SyntheticTrialRegistryTest;
|
||||
friend ::tpcd::experiment::ExperimentManagerImplBrowserTest;
|
||||
friend content::SyntheticTrialSyncer;
|
||||
FRIEND_TEST_ALL_PREFIXES(SyntheticTrialRegistryTest, RegisterSyntheticTrial);
|
||||
FRIEND_TEST_ALL_PREFIXES(SyntheticTrialRegistryTest,
|
||||
GetSyntheticFieldTrialsOlderThanSuffix);
|
||||
@ -132,6 +137,12 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialRegistry {
|
||||
std::vector<ActiveGroupId>* synthetic_trials,
|
||||
base::StringPiece suffix = "") const;
|
||||
|
||||
// SyntheticTrialSyncer needs to know all current synthetic trial
|
||||
// groups after launching new child processes.
|
||||
const std::vector<SyntheticTrialGroup>& GetSyntheticTrialGroups() const {
|
||||
return synthetic_trial_groups_;
|
||||
}
|
||||
|
||||
// Notifies observers on a synthetic trial list change.
|
||||
void NotifySyntheticTrialObservers(
|
||||
const std::vector<SyntheticTrialGroup>& trials_updated,
|
||||
|
@ -59,10 +59,6 @@ crash_reporter::CrashKeyString<kVariationsKeySize> g_variations_crash_key(
|
||||
crash_reporter::CrashKeyString<64> g_variations_seed_version_crash_key(
|
||||
kVariationsSeedVersionKey);
|
||||
|
||||
std::string ActiveGroupToString(const ActiveGroupId& active_group) {
|
||||
return base::StringPrintf("%x-%x,", active_group.name, active_group.group);
|
||||
}
|
||||
|
||||
std::string GetVariationsSeedVersion() {
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
// kVariationsSeedVersion should be set by the browser process in
|
||||
@ -300,4 +296,9 @@ ExperimentListInfo GetExperimentListInfo() {
|
||||
DCHECK(g_variations_crash_keys);
|
||||
return g_variations_crash_keys->GetExperimentListInfo();
|
||||
}
|
||||
|
||||
std::string ActiveGroupToString(const ActiveGroupId& active_group) {
|
||||
return base::StringPrintf("%x-%x,", active_group.name, active_group.group);
|
||||
}
|
||||
|
||||
} // namespace variations
|
||||
|
@ -13,6 +13,7 @@
|
||||
namespace variations {
|
||||
|
||||
class SyntheticTrialGroup;
|
||||
struct ActiveGroupId;
|
||||
|
||||
// The key used in crash reports to indicate the number of active experiments.
|
||||
// Should match the number of entries in kExperimentListKey.
|
||||
@ -55,6 +56,10 @@ struct COMPONENT_EXPORT(VARIATIONS) ExperimentListInfo {
|
||||
// in that list in |num_experiments|. Must be called on the UI thread.
|
||||
COMPONENT_EXPORT(VARIATIONS) ExperimentListInfo GetExperimentListInfo();
|
||||
|
||||
// Gets the hash code of the experiment.
|
||||
COMPONENT_EXPORT(VARIATIONS)
|
||||
std::string ActiveGroupToString(const ActiveGroupId& active_group);
|
||||
|
||||
} // namespace variations
|
||||
|
||||
#endif // COMPONENTS_VARIATIONS_VARIATIONS_CRASH_KEYS_H_
|
||||
|
@ -2195,6 +2195,7 @@ source_set("browser") {
|
||||
"storage_partition_impl.h",
|
||||
"storage_partition_impl_map.cc",
|
||||
"storage_partition_impl_map.h",
|
||||
"synthetic_trial_syncer.cc",
|
||||
"theme_helper.cc",
|
||||
"theme_helper.h",
|
||||
"tracing/background_startup_tracing_observer.cc",
|
||||
|
188
content/browser/synthetic_trial_syncer.cc
Normal file
188
content/browser/synthetic_trial_syncer.cc
Normal file
@ -0,0 +1,188 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/public/browser/synthetic_trial_syncer.h"
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "components/variations/synthetic_trial_registry.h"
|
||||
#include "content/common/synthetic_trial_configuration.mojom.h"
|
||||
#include "content/public/browser/browser_child_process_host_iterator.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/child_process_data.h"
|
||||
#include "content/public/browser/child_process_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "mojo/public/cpp/bindings/remote.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> ConvertTrialGroupsToMojo(
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials) {
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> groups;
|
||||
for (const auto& trial : trials) {
|
||||
std::string trial_name(trial.trial_name());
|
||||
std::string group_name(trial.group_name());
|
||||
groups.push_back(mojom::SyntheticTrialGroup::New(trial_name, group_name));
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
void NotifyChildProcess(
|
||||
mojo::Remote<mojom::SyntheticTrialConfiguration>&
|
||||
synthetic_trial_configuration,
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_removed) {
|
||||
synthetic_trial_configuration->AddOrUpdateSyntheticTrialGroups(
|
||||
ConvertTrialGroupsToMojo(trials_updated));
|
||||
synthetic_trial_configuration->RemoveSyntheticTrialGroups(
|
||||
ConvertTrialGroupsToMojo(trials_removed));
|
||||
}
|
||||
|
||||
class RenderProcessIterator {
|
||||
public:
|
||||
using HostType = RenderProcessHost;
|
||||
|
||||
RenderProcessIterator() : iter_(RenderProcessHost::AllHostsIterator()) {}
|
||||
|
||||
bool IsAtEnd() { return iter_.IsAtEnd(); }
|
||||
|
||||
void Advance() { iter_.Advance(); }
|
||||
|
||||
const base::Process& GetProcess() {
|
||||
return iter_.GetCurrentValue()->GetProcess();
|
||||
}
|
||||
|
||||
HostType* GetHost() { return iter_.GetCurrentValue(); }
|
||||
|
||||
private:
|
||||
RenderProcessHost::iterator iter_;
|
||||
};
|
||||
|
||||
class NonRenderProcessIterator {
|
||||
public:
|
||||
using HostType = ChildProcessHost;
|
||||
|
||||
NonRenderProcessIterator() = default;
|
||||
|
||||
bool IsAtEnd() { return iter_.Done(); }
|
||||
|
||||
void Advance() { ++iter_; }
|
||||
|
||||
const base::Process& GetProcess() { return iter_.GetData().GetProcess(); }
|
||||
|
||||
HostType* GetHost() { return iter_.GetHost(); }
|
||||
|
||||
private:
|
||||
BrowserChildProcessHostIterator iter_;
|
||||
};
|
||||
|
||||
template <typename Iterator>
|
||||
void NotifySyntheticTrialsChange(
|
||||
base::ProcessId process_id,
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_removed) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
|
||||
base::ProcessId current_pid = base::Process::Current().Pid();
|
||||
for (Iterator iter; !iter.IsAtEnd(); iter.Advance()) {
|
||||
const base::Process& process = iter.GetProcess();
|
||||
if (!process.IsValid() ||
|
||||
(process_id != base::kNullProcessId && process_id != process.Pid())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if in-browser process mode, because the browser process
|
||||
// manages synthetic trial groups properly.
|
||||
if (process.Pid() == current_pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mojo::Remote<mojom::SyntheticTrialConfiguration>
|
||||
synthetic_trial_configuration;
|
||||
iter.GetHost()->BindReceiver(
|
||||
synthetic_trial_configuration.BindNewPipeAndPassReceiver());
|
||||
NotifyChildProcess(synthetic_trial_configuration, trials_updated,
|
||||
trials_removed);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<SyntheticTrialSyncer> SyntheticTrialSyncer::Create(
|
||||
variations::SyntheticTrialRegistry* registry) {
|
||||
// Only 1 instance is allowed for the browser process.
|
||||
static bool s_called = false;
|
||||
CHECK(!s_called);
|
||||
std::unique_ptr<SyntheticTrialSyncer> instance =
|
||||
std::make_unique<SyntheticTrialSyncer>(registry);
|
||||
registry->AddObserver(instance.get());
|
||||
BrowserChildProcessObserver::Add(instance.get());
|
||||
s_called = true;
|
||||
return instance;
|
||||
}
|
||||
|
||||
SyntheticTrialSyncer::SyntheticTrialSyncer(
|
||||
variations::SyntheticTrialRegistry* registry)
|
||||
: registry_(registry) {}
|
||||
|
||||
SyntheticTrialSyncer::~SyntheticTrialSyncer() {
|
||||
registry_->RemoveObserver(this);
|
||||
BrowserChildProcessObserver::Remove(this);
|
||||
|
||||
for (RenderProcessIterator it; !it.IsAtEnd(); it.Advance()) {
|
||||
if (it.GetHost()) {
|
||||
it.GetHost()->RemoveObserver(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyntheticTrialSyncer::OnSyntheticTrialsChanged(
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_removed,
|
||||
const std::vector<variations::SyntheticTrialGroup>& groups) {
|
||||
NotifySyntheticTrialsChange<RenderProcessIterator>(
|
||||
base::kNullProcessId, trials_updated, trials_removed);
|
||||
NotifySyntheticTrialsChange<NonRenderProcessIterator>(
|
||||
base::kNullProcessId, trials_updated, trials_removed);
|
||||
}
|
||||
|
||||
void SyntheticTrialSyncer::BrowserChildProcessLaunchedAndConnected(
|
||||
const ChildProcessData& data) {
|
||||
if (!data.GetProcess().IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotifySyntheticTrialsChange<NonRenderProcessIterator>(
|
||||
data.GetProcess().Pid(), registry_->GetSyntheticTrialGroups(), {});
|
||||
}
|
||||
|
||||
void SyntheticTrialSyncer::OnRenderProcessHostCreated(RenderProcessHost* host) {
|
||||
host->AddObserver(this);
|
||||
}
|
||||
|
||||
void SyntheticTrialSyncer::RenderProcessReady(RenderProcessHost* host) {
|
||||
const base::Process& process = host->GetProcess();
|
||||
if (!process.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotifySyntheticTrialsChange<RenderProcessIterator>(
|
||||
process.Pid(), registry_->GetSyntheticTrialGroups(), {});
|
||||
}
|
||||
|
||||
void SyntheticTrialSyncer::RenderProcessExited(
|
||||
RenderProcessHost* host,
|
||||
const ChildProcessTerminationInfo& info) {
|
||||
host->RemoveObserver(this);
|
||||
}
|
||||
|
||||
void SyntheticTrialSyncer::RenderProcessHostDestroyed(RenderProcessHost* host) {
|
||||
// To ensure this is removed from the observer list, call RemoveObserver()
|
||||
// again.
|
||||
host->RemoveObserver(this);
|
||||
}
|
||||
|
||||
} // namespace content
|
@ -45,6 +45,8 @@ target(link_target_type, "child") {
|
||||
"child_histogram_fetcher_impl.h",
|
||||
"child_process.cc",
|
||||
"child_process.h",
|
||||
"child_process_synthetic_trial_syncer.cc",
|
||||
"child_process_synthetic_trial_syncer.h",
|
||||
"child_thread_impl.cc",
|
||||
"child_thread_impl.h",
|
||||
"field_trial.cc",
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "content/child/child_histogram_fetcher_impl.h"
|
||||
#include "content/child/child_process_synthetic_trial_syncer.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
#include "mojo/public/cpp/bindings/binder_map.h"
|
||||
#include "services/tracing/public/cpp/traced_process.h"
|
||||
@ -16,6 +17,7 @@ namespace content {
|
||||
|
||||
void ExposeChildInterfacesToBrowser(
|
||||
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
|
||||
const bool in_browser_process,
|
||||
mojo::BinderMap* binders) {
|
||||
binders->Add<mojom::ChildHistogramFetcherFactory>(
|
||||
base::BindRepeating(&ChildHistogramFetcherFactoryImpl::Create),
|
||||
@ -24,6 +26,12 @@ void ExposeChildInterfacesToBrowser(
|
||||
base::BindRepeating(&tracing::TracedProcess::OnTracedProcessRequest),
|
||||
base::SequencedTaskRunner::GetCurrentDefault());
|
||||
|
||||
if (!in_browser_process) {
|
||||
binders->Add<mojom::SyntheticTrialConfiguration>(
|
||||
base::BindRepeating(&ChildProcessSyntheticTrialSyncer::Create),
|
||||
base::SequencedTaskRunner::GetCurrentDefault());
|
||||
}
|
||||
|
||||
GetContentClient()->ExposeInterfacesToBrowser(io_task_runner, binders);
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,17 @@ namespace content {
|
||||
// process from all child processes (including renderers, GPU, service
|
||||
// processes, etc.). Interfaces exposed here can be acquired in the browser via
|
||||
// |RenderProcessHost::BindReceiver()| or |ChildProcessHost::BindReceiver()|.
|
||||
//
|
||||
// |in_browser_process| is true if the child process is running in the browser
|
||||
// process. For example, single-process mode, or in-process gpu mode (forced
|
||||
// by low-end device mode) makes all child processes or gpu process run in
|
||||
// the browser process. If the services depend on whether the child process
|
||||
// is running in the browser process or not (e.g. if using a process-wide
|
||||
// global variables and |in_browser_process| is true, the browser process and
|
||||
// the child process will use the same global variables.
|
||||
void ExposeChildInterfacesToBrowser(
|
||||
scoped_refptr<base::SequencedTaskRunner> io_task_runner,
|
||||
bool in_browser_process,
|
||||
mojo::BinderMap* binders);
|
||||
|
||||
} // namespace content
|
||||
|
70
content/child/child_process_synthetic_trial_syncer.cc
Normal file
70
content/child/child_process_synthetic_trial_syncer.cc
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/child/child_process_synthetic_trial_syncer.h"
|
||||
|
||||
#include "base/no_destructor.h"
|
||||
#include "components/variations/variations_crash_keys.h"
|
||||
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
ChildProcessSyntheticTrialSyncer::ChildProcessSyntheticTrialSyncer() = default;
|
||||
ChildProcessSyntheticTrialSyncer::~ChildProcessSyntheticTrialSyncer() = default;
|
||||
|
||||
void ChildProcessSyntheticTrialSyncer::Create(
|
||||
mojo::PendingReceiver<mojom::SyntheticTrialConfiguration> receiver) {
|
||||
mojo::MakeSelfOwnedReceiver(
|
||||
std::make_unique<ChildProcessSyntheticTrialSyncer>(),
|
||||
std::move(receiver));
|
||||
}
|
||||
|
||||
std::unique_ptr<ChildProcessSyntheticTrialSyncer>
|
||||
ChildProcessSyntheticTrialSyncer::CreateInstanceForTesting() {
|
||||
return std::make_unique<ChildProcessSyntheticTrialSyncer>();
|
||||
}
|
||||
|
||||
void ChildProcessSyntheticTrialSyncer::AddOrUpdateSyntheticTrialGroups(
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) {
|
||||
for (const auto& it : trial_groups) {
|
||||
AddOrUpdateTrialGroupInternal(it->trial_name, it->group_name);
|
||||
}
|
||||
variations::UpdateCrashKeysWithSyntheticTrials(trials_);
|
||||
}
|
||||
|
||||
void ChildProcessSyntheticTrialSyncer::RemoveSyntheticTrialGroups(
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) {
|
||||
std::vector<variations::SyntheticTrialGroup> new_trials;
|
||||
|
||||
for (auto& trial : trials_) {
|
||||
auto find_it =
|
||||
std::find_if(trial_groups.begin(), trial_groups.end(),
|
||||
[&trial](mojom::SyntheticTrialGroupPtr& ptr) {
|
||||
return ptr->trial_name == trial.trial_name() &&
|
||||
ptr->group_name == trial.group_name();
|
||||
});
|
||||
if (find_it != trial_groups.end()) {
|
||||
continue;
|
||||
}
|
||||
new_trials.push_back(trial);
|
||||
}
|
||||
trials_.swap(new_trials);
|
||||
variations::UpdateCrashKeysWithSyntheticTrials(trials_);
|
||||
}
|
||||
|
||||
void ChildProcessSyntheticTrialSyncer::AddOrUpdateTrialGroupInternal(
|
||||
const std::string& trial_name,
|
||||
const std::string& group_name) {
|
||||
for (auto& trial : trials_) {
|
||||
if (trial.trial_name() == trial_name) {
|
||||
trial.SetGroupName(group_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
trials_.emplace_back(trial_name, group_name,
|
||||
variations::SyntheticTrialAnnotationMode::kCurrentLog);
|
||||
}
|
||||
|
||||
} // namespace content
|
61
content/child/child_process_synthetic_trial_syncer.h
Normal file
61
content/child/child_process_synthetic_trial_syncer.h
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_CHILD_CHILD_PROCESS_SYNTHETIC_TRIAL_SYNCER_H_
|
||||
#define CONTENT_CHILD_CHILD_PROCESS_SYNTHETIC_TRIAL_SYNCER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "components/variations/synthetic_trials.h"
|
||||
#include "content/common/content_export.h"
|
||||
#include "content/common/synthetic_trial_configuration.mojom.h"
|
||||
#include "mojo/public/cpp/bindings/binder_map.h"
|
||||
#include "mojo/public/cpp/bindings/receiver.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
// This class works in child processes and receives synthetic trial groups
|
||||
// from SyntheticTrialSyncer running in the browser process via mojo.
|
||||
//
|
||||
// When receiving any message from SyntheticTrialSyncer, this class updates
|
||||
// synthetic trial groups and updates crash keys with synthetic trials.
|
||||
// This makes crash dumps from non-browser processes have synthetic trial
|
||||
// information.
|
||||
class CONTENT_EXPORT ChildProcessSyntheticTrialSyncer
|
||||
: public mojom::SyntheticTrialConfiguration {
|
||||
public:
|
||||
static void Create(
|
||||
mojo::PendingReceiver<mojom::SyntheticTrialConfiguration> receiver);
|
||||
|
||||
ChildProcessSyntheticTrialSyncer();
|
||||
~ChildProcessSyntheticTrialSyncer() override;
|
||||
|
||||
ChildProcessSyntheticTrialSyncer(const ChildProcessSyntheticTrialSyncer&) =
|
||||
delete;
|
||||
ChildProcessSyntheticTrialSyncer& operator=(
|
||||
const ChildProcessSyntheticTrialSyncer&) = delete;
|
||||
ChildProcessSyntheticTrialSyncer(ChildProcessSyntheticTrialSyncer&&) = delete;
|
||||
|
||||
private:
|
||||
friend class ChildProcessSyntheticTrialSyncerTest;
|
||||
|
||||
static std::unique_ptr<ChildProcessSyntheticTrialSyncer>
|
||||
CreateInstanceForTesting();
|
||||
|
||||
// mojom::SyntheticTrialConfiguration:
|
||||
void AddOrUpdateSyntheticTrialGroups(
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) override;
|
||||
void RemoveSyntheticTrialGroups(
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> trial_groups) override;
|
||||
|
||||
void AddOrUpdateTrialGroupInternal(const std::string& trial_name,
|
||||
const std::string& group_name);
|
||||
|
||||
std::vector<variations::SyntheticTrialGroup> trials_;
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_CHILD_CHILD_PROCESS_SYNTHETIC_TRIAL_SYNCER_H_
|
119
content/child/child_process_synthetic_trial_syncer_unittest.cc
Normal file
119
content/child/child_process_synthetic_trial_syncer_unittest.cc
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/child/child_process_synthetic_trial_syncer.h"
|
||||
|
||||
#include "base/test/task_environment.h"
|
||||
#include "components/variations/active_field_trials.h"
|
||||
#include "components/variations/variations_crash_keys.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> Create(
|
||||
std::vector<std::pair<std::string, std::string>> trial_names) {
|
||||
std::vector<mojom::SyntheticTrialGroupPtr> groups;
|
||||
for (auto& it : trial_names) {
|
||||
groups.push_back(mojom::SyntheticTrialGroup::New(it.first, it.second));
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ChildProcessSyntheticTrialSyncerTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
variations::InitCrashKeys();
|
||||
syncer_ = ChildProcessSyntheticTrialSyncer::CreateInstanceForTesting();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
syncer_.reset();
|
||||
variations::ClearCrashKeysInstanceForTesting();
|
||||
}
|
||||
|
||||
void AddOrUpdateSyntheticTrialGroups(
|
||||
std::vector<std::pair<std::string, std::string>> groups) {
|
||||
syncer_->AddOrUpdateSyntheticTrialGroups(Create(groups));
|
||||
}
|
||||
|
||||
void RemoveSyntheticTrialGroups(
|
||||
std::vector<std::pair<std::string, std::string>> groups) {
|
||||
syncer_->RemoveSyntheticTrialGroups(Create(groups));
|
||||
}
|
||||
|
||||
std::string GetExperimentHash(std::string trial_name,
|
||||
std::string group_name) {
|
||||
return variations::ActiveGroupToString(
|
||||
variations::MakeActiveGroupId(trial_name, group_name));
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ChildProcessSyntheticTrialSyncer> syncer_;
|
||||
base::test::TaskEnvironment task_environment_;
|
||||
};
|
||||
|
||||
TEST_F(ChildProcessSyntheticTrialSyncerTest, Basic) {
|
||||
AddOrUpdateSyntheticTrialGroups({{"A", "G1"}, {"B", "G2"}});
|
||||
|
||||
auto info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2"),
|
||||
info.experiment_list);
|
||||
|
||||
AddOrUpdateSyntheticTrialGroups({{"C", "G3"}});
|
||||
info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(3, info.num_experiments);
|
||||
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2") +
|
||||
GetExperimentHash("C", "G3"),
|
||||
info.experiment_list);
|
||||
|
||||
AddOrUpdateSyntheticTrialGroups({{"A", "G4"}});
|
||||
info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(3, info.num_experiments);
|
||||
EXPECT_EQ(GetExperimentHash("A", "G4") + GetExperimentHash("B", "G2") +
|
||||
GetExperimentHash("C", "G3"),
|
||||
info.experiment_list);
|
||||
|
||||
RemoveSyntheticTrialGroups({{"B", "G2"}});
|
||||
info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(2, info.num_experiments);
|
||||
EXPECT_EQ(GetExperimentHash("A", "G4") + GetExperimentHash("C", "G3"),
|
||||
info.experiment_list);
|
||||
|
||||
RemoveSyntheticTrialGroups({{"C", "G3"}, {"A", "G4"}});
|
||||
info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(0, info.num_experiments);
|
||||
}
|
||||
|
||||
TEST_F(ChildProcessSyntheticTrialSyncerTest, AddSameTrial) {
|
||||
AddOrUpdateSyntheticTrialGroups({{"A", "G1"}, {"A", "G2"}, {"A", "G3"}});
|
||||
auto info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(1, info.num_experiments);
|
||||
EXPECT_EQ(GetExperimentHash("A", "G3"), info.experiment_list);
|
||||
}
|
||||
|
||||
TEST_F(ChildProcessSyntheticTrialSyncerTest, RemoveFromEmpty) {
|
||||
RemoveSyntheticTrialGroups({{"B", "G2"}, {"C", "G1"}});
|
||||
auto info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(0, info.num_experiments);
|
||||
}
|
||||
|
||||
TEST_F(ChildProcessSyntheticTrialSyncerTest, RemoveWrongTrialGroup) {
|
||||
AddOrUpdateSyntheticTrialGroups({{"A", "G1"}, {"B", "G2"}});
|
||||
|
||||
auto info = variations::GetExperimentListInfo();
|
||||
EXPECT_EQ(2, info.num_experiments);
|
||||
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2"),
|
||||
info.experiment_list);
|
||||
|
||||
RemoveSyntheticTrialGroups({{"B", "G4"}});
|
||||
EXPECT_EQ(2, info.num_experiments);
|
||||
EXPECT_EQ(GetExperimentHash("A", "G1") + GetExperimentHash("B", "G2"),
|
||||
info.experiment_list);
|
||||
}
|
||||
|
||||
} // namespace content
|
@ -42,6 +42,7 @@
|
||||
#include "build/build_config.h"
|
||||
#include "content/child/browser_exposed_child_interfaces.h"
|
||||
#include "content/child/child_process.h"
|
||||
#include "content/child/child_process_synthetic_trial_syncer.h"
|
||||
#include "content/common/child_process.mojom.h"
|
||||
#include "content/common/content_constants_internal.h"
|
||||
#include "content/common/features.h"
|
||||
@ -876,7 +877,8 @@ void ChildThreadImpl::ExposeInterfacesToBrowser(mojo::BinderMap binders) {
|
||||
// NOTE: Do not add new binders directly within this method. Instead, modify
|
||||
// the definition of |ExposeChildInterfacesToBrowser()|, ensuring security
|
||||
// review coverage.
|
||||
ExposeChildInterfacesToBrowser(GetIOTaskRunner(), &binders);
|
||||
ExposeChildInterfacesToBrowser(GetIOTaskRunner(), IsInBrowserProcess(),
|
||||
&binders);
|
||||
|
||||
ChildThreadImpl::GetIOTaskRunner()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&IOThreadState::ExposeInterfacesToBrowser,
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "base/threading/thread.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/variations/child_process_field_trial_syncer.h"
|
||||
#include "content/child/child_process_synthetic_trial_syncer.h"
|
||||
#include "content/common/associated_interfaces.mojom.h"
|
||||
#include "content/common/child_process.mojom.h"
|
||||
#include "content/public/child/child_thread.h"
|
||||
|
@ -483,6 +483,7 @@ mojom("mojo_bindings") {
|
||||
"renderer.mojom",
|
||||
"renderer_host.mojom",
|
||||
"renderer_variations_configuration.mojom",
|
||||
"synthetic_trial_configuration.mojom",
|
||||
"web_ui.mojom",
|
||||
]
|
||||
|
||||
|
21
content/common/synthetic_trial_configuration.mojom
Normal file
21
content/common/synthetic_trial_configuration.mojom
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
module content.mojom;
|
||||
|
||||
struct SyntheticTrialGroup {
|
||||
string trial_name;
|
||||
string group_name;
|
||||
};
|
||||
|
||||
// Interface used by the browser process to tell the child processes about
|
||||
// the current active synthetic trial groups.
|
||||
interface SyntheticTrialConfiguration {
|
||||
// Tells the child process to add new synthetic trial groups or update the
|
||||
// existing synthetic trial groups.
|
||||
AddOrUpdateSyntheticTrialGroups(array<SyntheticTrialGroup> groups);
|
||||
|
||||
// Tells the child process to remove the existing synthetic trial groups.
|
||||
RemoveSyntheticTrialGroups(array<SyntheticTrialGroup> groups);
|
||||
};
|
@ -418,6 +418,7 @@ source_set("browser_sources") {
|
||||
"supported_delegations.cc",
|
||||
"supported_delegations.h",
|
||||
"swap_metrics_driver.h",
|
||||
"synthetic_trial_syncer.h",
|
||||
"touch_selection_controller_client_manager.h",
|
||||
"tracing_controller.h",
|
||||
"tracing_delegate.cc",
|
||||
@ -544,6 +545,7 @@ source_set("browser_sources") {
|
||||
"//build:chromeos_buildflags",
|
||||
"//cc",
|
||||
"//components/services/storage/public/cpp",
|
||||
"//components/variations:variations",
|
||||
"//components/viz/host",
|
||||
"//content/browser", # Must not be public_deps!
|
||||
"//device/fido",
|
||||
|
74
content/public/browser/synthetic_trial_syncer.h
Normal file
74
content/public/browser/synthetic_trial_syncer.h
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_PUBLIC_BROWSER_SYNTHETIC_TRIAL_SYNCER_H_
|
||||
#define CONTENT_PUBLIC_BROWSER_SYNTHETIC_TRIAL_SYNCER_H_
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "components/variations/synthetic_trials.h"
|
||||
#include "content/common/content_export.h"
|
||||
#include "content/public/browser/browser_child_process_observer.h"
|
||||
#include "content/public/browser/render_process_host_creation_observer.h"
|
||||
#include "content/public/browser/render_process_host_observer.h"
|
||||
|
||||
namespace variations {
|
||||
class SyntheticTrialRegistry;
|
||||
} // namespace variations
|
||||
|
||||
namespace content {
|
||||
|
||||
// This class is used by the browser process to tell child processes
|
||||
// what synthetic trial groups the browser process joins in.
|
||||
//
|
||||
// This class registers itself as an observer of SyntheticTrialObserver.
|
||||
// SyntheticTrialRegistry notifies this class when a synthetic trial group
|
||||
// is updated.
|
||||
//
|
||||
// This class also registers itself as BrowserChildProcessObserver,
|
||||
// RenderProcessHostCreationObserver and RenderProcessHostObserver to
|
||||
// tell the synthetic trial groups just after a child process is created.
|
||||
// At that time, this class gets all joined synthetic groups by calling
|
||||
// SyntheticTrialRegistry::GetSyntheticTrialGroups().
|
||||
class CONTENT_EXPORT SyntheticTrialSyncer
|
||||
: public variations::SyntheticTrialObserver,
|
||||
public BrowserChildProcessObserver,
|
||||
public RenderProcessHostCreationObserver,
|
||||
public RenderProcessHostObserver {
|
||||
public:
|
||||
static std::unique_ptr<SyntheticTrialSyncer> Create(
|
||||
variations::SyntheticTrialRegistry* registry);
|
||||
|
||||
explicit SyntheticTrialSyncer(variations::SyntheticTrialRegistry* registry);
|
||||
~SyntheticTrialSyncer() override;
|
||||
|
||||
SyntheticTrialSyncer(const SyntheticTrialSyncer&) = delete;
|
||||
SyntheticTrialSyncer(SyntheticTrialSyncer&&) = delete;
|
||||
SyntheticTrialSyncer& operator=(const SyntheticTrialSyncer&) = delete;
|
||||
|
||||
private:
|
||||
// variations::SyntheticTrialObserver:
|
||||
void OnSyntheticTrialsChanged(
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_updated,
|
||||
const std::vector<variations::SyntheticTrialGroup>& trials_removed,
|
||||
const std::vector<variations::SyntheticTrialGroup>& groups) override;
|
||||
|
||||
// BrowserChildProcessObserver:
|
||||
void BrowserChildProcessLaunchedAndConnected(
|
||||
const ChildProcessData& data) override;
|
||||
|
||||
// RenderProcessHostCreationObserver:
|
||||
void OnRenderProcessHostCreated(RenderProcessHost* host) override;
|
||||
|
||||
// RenderProcessHostObserver:
|
||||
void RenderProcessReady(RenderProcessHost* host) override;
|
||||
void RenderProcessHostDestroyed(RenderProcessHost* host) override;
|
||||
void RenderProcessExited(RenderProcessHost* host,
|
||||
const ChildProcessTerminationInfo& info) override;
|
||||
|
||||
const raw_ptr<variations::SyntheticTrialRegistry> registry_;
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_PUBLIC_BROWSER_SYNTHETIC_TRIAL_SYNCER_H_
|
@ -2765,6 +2765,7 @@ test("content_unittests") {
|
||||
"../browser/worker_host/worker_script_loader_factory_unittest.cc",
|
||||
"../browser/xr/metrics/session_tracker_unittest.cc",
|
||||
"../child/blink_platform_impl_unittest.cc",
|
||||
"../child/child_process_synthetic_trial_syncer_unittest.cc",
|
||||
"../common/background_fetch/background_fetch_mojom_traits_unittest.cc",
|
||||
"../common/color_parser_unittest.cc",
|
||||
"../common/common_param_traits_unittest.cc",
|
||||
@ -2944,6 +2945,7 @@ test("content_unittests") {
|
||||
"//components/system_media_controls:test_support",
|
||||
"//components/ukm:test_support",
|
||||
"//components/user_prefs/test:test_support",
|
||||
"//components/variations:variations",
|
||||
"//components/viz/client",
|
||||
"//components/viz/common",
|
||||
"//components/viz/host",
|
||||
|
Reference in New Issue
Block a user