Add Synthetic Field Trial TS API
Bug: 394958637 Change-Id: I63dba2ef13866721e93b0a55c9eef96dda990184 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6303005 Reviewed-by: Erik Chen <erikchen@chromium.org> Commit-Queue: Nick Birnie <birnie@google.com> Cr-Commit-Position: refs/heads/main@{#1427373}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
ea777fea6d
commit
4d9cf6063c
chrome
browser
glic
BUILD.gnglic.mojomglic_keyed_service.hglic_page_handler.ccglic_synthetic_trial_manager.ccglic_synthetic_trial_manager.h
metrics
resources
glic
test
data
webui
glic
test_client
tools/metrics/histograms/metadata/glic
@ -25,6 +25,7 @@ source_set("glic") {
|
||||
"glic_pref_names.h",
|
||||
"glic_profile_manager.h",
|
||||
"glic_settings_util.h",
|
||||
"glic_synthetic_trial_manager.h",
|
||||
"glic_tab_data.h",
|
||||
"glic_tab_indicator_helper.h",
|
||||
"glic_ui.h",
|
||||
@ -47,9 +48,11 @@ source_set("glic") {
|
||||
"//chrome/browser/ui/browser_window:browser_window",
|
||||
"//chrome/browser/ui/tabs:tab_strip_model_observer",
|
||||
"//components/favicon/core:core",
|
||||
"//components/metrics:metrics",
|
||||
"//components/optimization_guide/content/browser:browser",
|
||||
"//components/prefs",
|
||||
"//components/signin/public/identity_manager:identity_manager",
|
||||
"//components/variations:variations",
|
||||
"//content/public/browser",
|
||||
"//pdf/mojom:mojom",
|
||||
"//ui/views:views",
|
||||
@ -96,6 +99,7 @@ source_set("impl") {
|
||||
"glic_screenshot_capturer.cc",
|
||||
"glic_screenshot_capturer.h",
|
||||
"glic_settings_util.cc",
|
||||
"glic_synthetic_trial_manager.cc",
|
||||
"glic_tab_data.cc",
|
||||
"glic_tab_indicator_helper.cc",
|
||||
"glic_ui.cc",
|
||||
|
@ -361,6 +361,13 @@ interface WebClientHandler {
|
||||
// Scrolls to and (optionally) highlights content specified by `params`.
|
||||
// Returns an error reason if the scroll fails, or null otherwise.
|
||||
ScrollTo(ScrollToParams params) => (ScrollToErrorReason? error_reason);
|
||||
|
||||
// Enrolls Chrome in the synthetic experiment group specified by
|
||||
// trial_name.group_name. Enrollment will start when the API is called and end
|
||||
// when Chrome closes.
|
||||
SetSyntheticExperimentState(string trial_name,
|
||||
string group_name);
|
||||
|
||||
};
|
||||
|
||||
// State of the glic panel.
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "chrome/browser/glic/glic_enums.h"
|
||||
#include "chrome/browser/glic/glic_focused_tab_manager.h"
|
||||
#include "chrome/browser/glic/glic_page_handler.h"
|
||||
#include "chrome/browser/glic/glic_synthetic_trial_manager.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
|
||||
class BrowserWindowInterface;
|
||||
@ -95,6 +96,11 @@ class GlicKeyedService : public KeyedService {
|
||||
void SetContextAccessIndicator(bool show);
|
||||
void NotifyWindowIntentToShow();
|
||||
|
||||
// Accessor for the GlicSyntheticTrialManager singleton. This exists as a
|
||||
// singleton so that it may be shared across multiple profiles.
|
||||
raw_ptr<GlicSyntheticTrialManager> synthetic_trial_manager() {
|
||||
return GlicSyntheticTrialManager::GetInstance();
|
||||
}
|
||||
// Callback for changes to focused tab data.
|
||||
using FocusedTabChangedCallback =
|
||||
base::RepeatingCallback<void(FocusedTabData)>;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "chrome/browser/glic/glic_keyed_service_factory.h"
|
||||
#include "chrome/browser/glic/glic_metrics.h"
|
||||
#include "chrome/browser/glic/glic_pref_names.h"
|
||||
#include "chrome/browser/glic/glic_synthetic_trial_manager.h"
|
||||
#include "chrome/browser/glic/glic_tab_data.h"
|
||||
#include "chrome/browser/glic/glic_web_client_access.h"
|
||||
#include "chrome/browser/glic/glic_window_controller.h"
|
||||
@ -407,6 +408,12 @@ class GlicWebClientHandler : public glic::mojom::WebClientHandler,
|
||||
annotation_manager_->ScrollTo(std::move(params), std::move(callback));
|
||||
}
|
||||
|
||||
void SetSyntheticExperimentState(const std::string& trial_name,
|
||||
const std::string& group_name) override {
|
||||
glic_service_->synthetic_trial_manager()->SetSyntheticExperimentState(
|
||||
trial_name, group_name);
|
||||
}
|
||||
|
||||
// GlicWindowController::StateObserver implementation.
|
||||
void PanelStateChanged(const glic::mojom::PanelState& panel_state,
|
||||
Browser* attached_browser) override {
|
||||
|
96
chrome/browser/glic/glic_synthetic_trial_manager.cc
Normal file
96
chrome/browser/glic/glic_synthetic_trial_manager.cc
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "chrome/browser/glic/glic_synthetic_trial_manager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
|
||||
#include "components/metrics/metrics_service.h"
|
||||
#include "components/variations/synthetic_trial_registry.h"
|
||||
|
||||
namespace glic {
|
||||
|
||||
// static
|
||||
GlicSyntheticTrialManager* GlicSyntheticTrialManager::GetInstance() {
|
||||
return base::Singleton<GlicSyntheticTrialManager>::get();
|
||||
}
|
||||
|
||||
GlicSyntheticTrialManager::GlicSyntheticTrialManager() {
|
||||
metrics::MetricsService* metrics_service =
|
||||
g_browser_process->metrics_service();
|
||||
if (metrics_service) {
|
||||
metrics_service->AddLogsObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
GlicSyntheticTrialManager::~GlicSyntheticTrialManager() {
|
||||
metrics::MetricsService* metrics_service =
|
||||
g_browser_process->metrics_service();
|
||||
if (metrics_service) {
|
||||
metrics_service->RemoveLogsObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GlicSyntheticTrialManager::SetSyntheticExperimentState(
|
||||
const std::string& trial_name,
|
||||
const std::string& group_name) {
|
||||
// If already registered discard the logs if in a different group. This
|
||||
// avoids combining logs from multiple groups from different profiles.
|
||||
bool conflicting_group_registered =
|
||||
synthetic_field_trial_groups_.count(trial_name) > 0 &&
|
||||
synthetic_field_trial_groups_[trial_name] != group_name;
|
||||
|
||||
if (conflicting_group_registered) {
|
||||
if (synthetic_field_trial_groups_[trial_name] != "MultiProfileDetected") {
|
||||
base::UmaHistogramBoolean(
|
||||
"Glic.ChromeClient.MultiProfileSyntheticTrialConflictDetected",
|
||||
conflicting_group_registered);
|
||||
}
|
||||
ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
|
||||
trial_name, "MultiProfileDetected",
|
||||
variations::SyntheticTrialAnnotationMode::kCurrentLog);
|
||||
synthetic_field_trial_groups_[trial_name] = "MultiProfileDetected";
|
||||
// After contaminated logfile is uploaded we store the the most recently
|
||||
// requested group to register the next created log file. If TrialA.Group1
|
||||
// is reported then TrialA.Group2, then TrialA.Group3, then the next log cut
|
||||
// will register TrialA.Group3.
|
||||
staged_synthetic_field_trial_groups_[trial_name] = group_name;
|
||||
} else {
|
||||
ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
|
||||
trial_name, group_name,
|
||||
variations::SyntheticTrialAnnotationMode::kCurrentLog);
|
||||
synthetic_field_trial_groups_[trial_name] = group_name;
|
||||
}
|
||||
}
|
||||
|
||||
void GlicSyntheticTrialManager::OnLogEvent(
|
||||
metrics::MetricsLogsEventManager::LogEvent event,
|
||||
std::string_view log_hash,
|
||||
std::string_view message) {
|
||||
if (event == metrics::MetricsLogsEventManager::LogEvent::kLogCreated &&
|
||||
!staged_synthetic_field_trial_groups_.empty()) {
|
||||
// Remove all elements from synthetic_field_trial_groups_ that have
|
||||
// entries in staged_synthetic_field_trial_groups_ with the same
|
||||
// trial_name.
|
||||
for (const auto& [trial, group] : staged_synthetic_field_trial_groups_) {
|
||||
ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
|
||||
trial, group, variations::SyntheticTrialAnnotationMode::kCurrentLog);
|
||||
synthetic_field_trial_groups_[trial] = group;
|
||||
}
|
||||
staged_synthetic_field_trial_groups_ = {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace glic
|
57
chrome/browser/glic/glic_synthetic_trial_manager.h
Normal file
57
chrome/browser/glic/glic_synthetic_trial_manager.h
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_BROWSER_GLIC_GLIC_SYNTHETIC_TRIAL_MANAGER_H_
|
||||
#define CHROME_BROWSER_GLIC_GLIC_SYNTHETIC_TRIAL_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "components/metrics/metrics_logs_event_manager.h"
|
||||
#include "components/metrics/metrics_service.h"
|
||||
#include "components/variations/synthetic_trial_registry.h"
|
||||
|
||||
namespace glic {
|
||||
|
||||
class GlicSyntheticTrialManager : metrics::MetricsLogsEventManager::Observer {
|
||||
public:
|
||||
static GlicSyntheticTrialManager* GetInstance();
|
||||
GlicSyntheticTrialManager();
|
||||
|
||||
GlicSyntheticTrialManager(const GlicSyntheticTrialManager&) = delete;
|
||||
GlicSyntheticTrialManager& operator=(const GlicSyntheticTrialManager&) =
|
||||
delete;
|
||||
|
||||
~GlicSyntheticTrialManager() override;
|
||||
|
||||
// Used by the web client to enroll Chrome in the specified synthetic trial
|
||||
// group. If a conflicting group is already registered by another profile
|
||||
// than we instead register into a new group called `MultiProfileDetected` to
|
||||
// indicate the log file is corrupted. At this point we stage the requested
|
||||
// group for enrollment when the next log file is created.
|
||||
void SetSyntheticExperimentState(const std::string& trial_name,
|
||||
const std::string& group_name);
|
||||
|
||||
// metrics::MetricsLogsEventManager::Observer:
|
||||
void OnLogEvent(metrics::MetricsLogsEventManager::LogEvent event,
|
||||
std::string_view log_hash,
|
||||
std::string_view message) override;
|
||||
void OnLogCreated(
|
||||
std::string_view log_hash,
|
||||
std::string_view log_data,
|
||||
std::string_view log_timestamp,
|
||||
metrics::MetricsLogsEventManager::CreateReason reason) override {}
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> synthetic_field_trial_groups_;
|
||||
std::map<std::string, std::string> staged_synthetic_field_trial_groups_;
|
||||
|
||||
base::WeakPtrFactory<GlicSyntheticTrialManager> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace glic
|
||||
|
||||
#endif // CHROME_BROWSER_GLIC_GLIC_SYNTHETIC_TRIAL_MANAGER_H_
|
@ -12,10 +12,15 @@
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "build/build_config.h"
|
||||
#include "chrome/browser/metrics/metrics_reporting_state.h"
|
||||
#include "chrome/common/buildflags.h"
|
||||
#include "components/metrics/metrics_service_accessor.h"
|
||||
#include "components/variations/synthetic_trials.h"
|
||||
#include "ppapi/buildflags/buildflags.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_GLIC)
|
||||
#include "chrome/browser/glic/glic_synthetic_trial_manager.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PPAPI)
|
||||
#include "chrome/common/ppapi_metrics.mojom.h"
|
||||
#endif
|
||||
@ -175,6 +180,9 @@ class ChromeMetricsServiceAccessor : public metrics::MetricsServiceAccessor {
|
||||
friend class ChromeBrowserMainExtraPartsGpu;
|
||||
friend class Browser;
|
||||
friend class BrowserProcessImpl;
|
||||
#if BUILDFLAG(ENABLE_GLIC)
|
||||
friend class glic::GlicSyntheticTrialManager;
|
||||
#endif
|
||||
friend class OptimizationGuideKeyedService;
|
||||
friend class WebUITabStripFieldTrial;
|
||||
friend class feed::FeedServiceDelegateImpl;
|
||||
|
@ -334,6 +334,13 @@ export declare interface GlicBrowserHost {
|
||||
* @throws {ScrollToError} on failure.
|
||||
*/
|
||||
scrollTo?(params: ScrollToParams): Promise<void>;
|
||||
|
||||
/**
|
||||
* Enrolls the Chrome client in the synthetic experiment group specified by
|
||||
* trial_name.group_name. Enrollment will only start when the API is called
|
||||
* and end when Chrome closes.
|
||||
*/
|
||||
setSyntheticExperimentState?(trialName: string, groupName: string): void;
|
||||
}
|
||||
|
||||
/** Holds optional parameters for `GlicBrowserHost#resizeWindow`. */
|
||||
|
@ -372,6 +372,11 @@ class GlicBrowserHostImpl implements GlicBrowserHost {
|
||||
scrollTo(params: ScrollToParams): Promise<void> {
|
||||
return this.sender.requestWithResponse('glicBrowserScrollTo', {params});
|
||||
}
|
||||
|
||||
setSyntheticExperimentState(trialName: string, groupName: string): void {
|
||||
this.sender.requestNoResponse(
|
||||
'glicBrowserSetSyntheticExperimentState', {trialName, groupName});
|
||||
}
|
||||
}
|
||||
|
||||
class GlicBrowserHostMetricsImpl implements GlicBrowserHostMetrics {
|
||||
|
@ -477,6 +477,14 @@ class HostMessageHandler implements HostMessageHandlerInterface {
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
glicBrowserSetSyntheticExperimentState(request: {
|
||||
trialName: string,
|
||||
groupName: string,
|
||||
}) {
|
||||
return this.handler.setSyntheticExperimentState(
|
||||
request.trialName, request.groupName);
|
||||
}
|
||||
}
|
||||
|
||||
export class GlicApiHost implements PostMessageRequestHandler {
|
||||
|
@ -142,6 +142,12 @@ export declare interface HostRequestTypes {
|
||||
glicBrowserScrollTo: {
|
||||
request: {params: ScrollToParams},
|
||||
};
|
||||
glicBrowserSetSyntheticExperimentState: {
|
||||
request: {
|
||||
trialName: string,
|
||||
groupName: string,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Types of requests to the GlicWebClient.
|
||||
|
@ -277,6 +277,15 @@ found in the LICENSE file.
|
||||
</div>
|
||||
<button id="scrollToBn" style="margin-top: 12px;">Scroll!</button>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h1>Set Synthetic Experiment</h1>
|
||||
<label for="trialName">Trial Name:</label>
|
||||
<input type="text" id="trialName" name="trialName"><br><br>
|
||||
<label for="groupName">Group Name:</label>
|
||||
<input type="text" id="groupName" name="groupName"><br><br>
|
||||
<button id="setExperiment">Set Experiment</button>
|
||||
<span id="setExperimentStatus"></span>
|
||||
</div>
|
||||
<br />
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
|
@ -70,6 +70,10 @@ interface PageElementTypes {
|
||||
fileDropList: HTMLDivElement;
|
||||
showDirectoryPicker: HTMLButtonElement;
|
||||
failInitializationCheckbox: HTMLInputElement;
|
||||
setExperiment: HTMLButtonElement;
|
||||
trialName: HTMLInputElement;
|
||||
groupName: HTMLInputElement;
|
||||
setExperimentStatus: HTMLSpanElement;
|
||||
}
|
||||
|
||||
const $: PageElementTypes = new Proxy({}, {
|
||||
@ -638,6 +642,14 @@ window.addEventListener('load', () => {
|
||||
$.desktopScreenshotErrorReason!.innerText = `Caught error: ${error}`;
|
||||
}
|
||||
});
|
||||
$.setExperiment.addEventListener('click', async () => {
|
||||
const trialName = $.trialName.value;
|
||||
const groupName = $.groupName.value;
|
||||
$.setExperimentStatus!.innerText +=
|
||||
`\nSetting experiment: ${trialName} ${groupName}`;
|
||||
await getBrowser()!.setSyntheticExperimentState!(trialName, groupName);
|
||||
$.setExperimentStatus!.innerText += '\nExperiment State Set.';
|
||||
});
|
||||
});
|
||||
|
||||
function readStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {
|
||||
|
@ -32,6 +32,20 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Glic.ChromeClient.MultiProfileSyntheticTrialConflictDetected"
|
||||
enum="BooleanEnabled" expires_after="2026-01-15">
|
||||
<owner>birnie@chromium.org</owner>
|
||||
<owner>carlosk@chromium.org</owner>
|
||||
<summary>
|
||||
Recorded when different Chrome profiles using glic attempt to register
|
||||
conflicting synthetic field trial groups in the same log file. This should
|
||||
only happen on multi profile installs that use glic on different profiles in
|
||||
quick succession. When analyzing metrics from a synthetic field trial this
|
||||
histogram will allow us to create custom queries to filter out users who may
|
||||
have contaminated logs from multiple profiles.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Glic.EntryPoint.Impression" enum="GlicEntryPointImpression"
|
||||
expires_after="2026-01-15">
|
||||
<owner>erikchen@chromium.org</owner>
|
||||
|
Reference in New Issue
Block a user