summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-29 05:42:22 +0000
committerrtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-29 05:42:22 +0000
commit63a8ba1e1fb71156beff23b4a26828dbc387c734 (patch)
treee69b6e1f24c5d7d1446bc6d5596518f44399bc67
parentdbf00c96e956033c1ea9b7542fa0d9c5ab1e66ac (diff)
downloadchromium_src-63a8ba1e1fb71156beff23b4a26828dbc387c734.zip
chromium_src-63a8ba1e1fb71156beff23b4a26828dbc387c734.tar.gz
chromium_src-63a8ba1e1fb71156beff23b4a26828dbc387c734.tar.bz2
Field trials are currently implemented (commonly) using a static variable that is set
once, the first time it is necessary to decide if there is an experiment by a given name active. With this change the field-test system can "push" a group that is selected for the given field trial (field test) if/when an experiment does arrive. This change implements a simple IPC notification of the result of a FieldTrial setting being sent to any previously started renderers. BUG=16494 TEST=field trial tests R=jar Review URL: http://codereview.chromium.org/6883029 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83488 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/metrics/field_trial.cc88
-rw-r--r--base/metrics/field_trial.h47
-rw-r--r--base/metrics/field_trial_unittest.cc28
-rw-r--r--chrome/browser/browser_main.cc8
-rw-r--r--chrome/browser/metrics/field_trial_synchronizer.cc52
-rw-r--r--chrome/browser/metrics/field_trial_synchronizer.h67
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/common/render_messages.h6
-rw-r--r--chrome/renderer/chrome_render_process_observer.cc9
-rw-r--r--chrome/renderer/chrome_render_process_observer.h4
10 files changed, 288 insertions, 23 deletions
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index ad2e5a0..daeb369 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -25,6 +25,10 @@ const char FieldTrialList::kPersistentStringSeparator('/');
static const char kHistogramFieldTrialSeparator('_');
+// static
+int FieldTrialList::kExpirationYearInFuture = 0;
+
+
//------------------------------------------------------------------------------
// FieldTrial methods and members.
@@ -83,6 +87,7 @@ int FieldTrial::AppendGroup(const std::string& name,
base::StringAppendF(&group_name_, "%d", group_);
else
group_name_ = name;
+ FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_);
}
return next_group_number_++;
}
@@ -92,6 +97,7 @@ int FieldTrial::group() {
accumulated_group_probability_ = divisor_;
group_ = kDefaultGroupNumber;
group_name_ = default_group_name_;
+ FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_);
}
return group_;
}
@@ -136,10 +142,18 @@ FieldTrialList* FieldTrialList::global_ = NULL;
// static
bool FieldTrialList::register_without_global_ = false;
-FieldTrialList::FieldTrialList() : application_start_time_(TimeTicks::Now()) {
+FieldTrialList::FieldTrialList()
+ : application_start_time_(TimeTicks::Now()),
+ observer_list_(ObserverList<Observer>::NOTIFY_EXISTING_ONLY) {
DCHECK(!global_);
DCHECK(!register_without_global_);
global_ = this;
+
+ Time::Exploded exploded;
+ Time two_years_from_now =
+ Time::NowFromSystemTime() + TimeDelta::FromDays(730);
+ two_years_from_now.LocalExplode(&exploded);
+ kExpirationYearInFuture = exploded.year;
}
FieldTrialList::~FieldTrialList() {
@@ -219,12 +233,6 @@ bool FieldTrialList::CreateTrialsInChildProcess(
if (parent_trials.empty() || !global_)
return true;
- Time::Exploded exploded;
- Time two_years_from_now =
- Time::NowFromSystemTime() + TimeDelta::FromDays(730);
- two_years_from_now.LocalExplode(&exploded);
- const int kTwoYearsFromNow = exploded.year;
-
size_t next_item = 0;
while (next_item < parent_trials.length()) {
size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item);
@@ -239,23 +247,65 @@ bool FieldTrialList::CreateTrialsInChildProcess(
group_name_end - name_end - 1);
next_item = group_name_end + 1;
- FieldTrial *field_trial(FieldTrialList::Find(name));
- if (field_trial) {
- // In single process mode, we may have already created the field trial.
- if ((field_trial->group_name_internal() != group_name) &&
- (field_trial->default_group_name() != group_name))
- return false;
- continue;
- }
- const int kTotalProbability = 100;
- field_trial = new FieldTrial(name, kTotalProbability, group_name,
- kTwoYearsFromNow, 1, 1);
- field_trial->AppendGroup(group_name, kTotalProbability);
+ if (!CreateFieldTrial(name, group_name))
+ return false;
}
return true;
}
// static
+FieldTrial* FieldTrialList::CreateFieldTrial(
+ const std::string& name,
+ const std::string& group_name) {
+ DCHECK(global_);
+ DCHECK_GE(name.size(), 0u);
+ DCHECK_GE(group_name.size(), 0u);
+ if (name.empty() || group_name.empty() || !global_)
+ return NULL;
+
+ FieldTrial *field_trial(FieldTrialList::Find(name));
+ if (field_trial) {
+ // In single process mode, we may have already created the field trial.
+ if (field_trial->group_name_internal() != group_name)
+ return NULL;
+ return field_trial;
+ }
+ const int kTotalProbability = 100;
+ field_trial = new FieldTrial(name, kTotalProbability, group_name,
+ kExpirationYearInFuture, 1, 1);
+ field_trial->AppendGroup(group_name, kTotalProbability);
+ return field_trial;
+}
+
+// static
+void FieldTrialList::AddObserver(Observer* observer) {
+ if (!global_)
+ return;
+ DCHECK(global_);
+ global_->observer_list_.AddObserver(observer);
+}
+
+// static
+void FieldTrialList::RemoveObserver(Observer* observer) {
+ if (!global_)
+ return;
+ DCHECK(global_);
+ global_->observer_list_.RemoveObserver(observer);
+}
+
+// static
+void FieldTrialList::NotifyFieldTrialGroupSelection(
+ const std::string& name,
+ const std::string& group_name) {
+ if (!global_)
+ return;
+ DCHECK(global_);
+ FOR_EACH_OBSERVER(Observer,
+ global_->observer_list_,
+ OnFieldTrialGroupFinalized(name, group_name));
+}
+
+// static
size_t FieldTrialList::GetFieldTrialCount() {
if (!global_)
return 0;
diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h
index dcfd391..b8ab0c5 100644
--- a/base/metrics/field_trial.h
+++ b/base/metrics/field_trial.h
@@ -81,6 +81,7 @@
#include "base/base_api.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
#include "base/synchronization/lock.h"
#include "base/time.h"
@@ -218,6 +219,20 @@ class BASE_API FieldTrialList {
// second process to mimic our state (i.e., provide the same group name).
static const char kPersistentStringSeparator; // Currently a slash.
+ // Define expiration year in future. It is initialized to two years from Now.
+ static int kExpirationYearInFuture;
+
+ // Observer is notified when a FieldTrial's group is selected.
+ class Observer {
+ public:
+ // Notify observers when FieldTrials's group is selected.
+ virtual void OnFieldTrialGroupFinalized(const std::string& trial_name,
+ const std::string& group_name) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
// This singleton holds the global list of registered FieldTrials.
FieldTrialList();
// Destructor Release()'s references to all registered FieldTrial instances.
@@ -245,11 +260,34 @@ class BASE_API FieldTrialList {
// Use a previously generated state string (re: StatesToString()) augment the
// current list of field tests to include the supplied tests, and using a 100%
// probability for each test, force them to have the same group string. This
- // is commonly used in a sub-process, to carry randomly selected state in a
- // parent process into this sub-process.
- // Currently only the group_name_ and name_ are restored.
+ // is commonly used in a non-browser process, to carry randomly selected state
+ // in a browser process into this non-browser process. This method calls
+ // CreateFieldTrial to create the FieldTrial in the non-browser process.
+ // Currently only the group_name_ and name_ are restored.
static bool CreateTrialsInChildProcess(const std::string& prior_trials);
+ // Create a FieldTrial with the given |name| and using 100% probability for
+ // the FieldTrial, force FieldTrial to have the same group string as
+ // |group_name|. This is commonly used in a non-browser process, to carry
+ // randomly selected state in a browser process into this non-browser process.
+ // Currently only the group_name_ and name_ are set in the FieldTrial. It
+ // returns NULL if there is a FieldTrial that is already registered with the
+ // same |name| but has different finalized group string (|group_name|).
+ static FieldTrial* CreateFieldTrial(const std::string& name,
+ const std::string& group_name);
+
+ // Add an observer to be notified when a field trial is irrevocably committed
+ // to being part of some specific field_group (and hence the group_name is
+ // also finalized for that field_trial).
+ static void AddObserver(Observer* observer);
+
+ // Remove an observer.
+ static void RemoveObserver(Observer* observer);
+
+ // Notify all observers that a group is finalized for the named Trial.
+ static void NotifyFieldTrialGroupSelection(const std::string& name,
+ const std::string& group_name);
+
// The time of construction of the global map is recorded in a static variable
// and is commonly used by experiments to identify the time since the start
// of the application. In some experiments it may be useful to discount
@@ -288,6 +326,9 @@ class BASE_API FieldTrialList {
base::Lock lock_;
RegistrationList registered_;
+ // List of observers to be notified when a group is selected for a FieldTrial.
+ ObserverList<Observer> observer_list_;
+
DISALLOW_COPY_AND_ASSIGN(FieldTrialList);
};
diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc
index 0af6d60..3357085 100644
--- a/base/metrics/field_trial_unittest.cc
+++ b/base/metrics/field_trial_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -287,6 +287,32 @@ TEST_F(FieldTrialTest, DuplicateRestore) {
EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("Some name/Loser/"));
}
+TEST_F(FieldTrialTest, CreateFieldTrial) {
+ EXPECT_TRUE(FieldTrialList::Find("Some_name") == NULL);
+
+ FieldTrialList::CreateFieldTrial("Some_name", "Winner");
+
+ FieldTrial* trial = FieldTrialList::Find("Some_name");
+ ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ EXPECT_EQ("Winner", trial->group_name());
+ EXPECT_EQ("Some_name", trial->name());
+}
+
+TEST_F(FieldTrialTest, DuplicateFieldTrial) {
+ FieldTrial* trial =
+ new FieldTrial(
+ "Some_name", 10, "Default some name", next_year_, 12, 31);
+ trial->AppendGroup("Winner", 10);
+
+ // It is OK if we redundantly specify a winner.
+ FieldTrial* trial1 = FieldTrialList::CreateFieldTrial("Some_name", "Winner");
+ EXPECT_TRUE(trial1 != NULL);
+
+ // But it is an error to try to change to a different winner.
+ FieldTrial* trial2 = FieldTrialList::CreateFieldTrial("Some_name", "Loser");
+ EXPECT_TRUE(trial2 == NULL);
+}
+
TEST_F(FieldTrialTest, MakeName) {
FieldTrial* trial =
new FieldTrial("Field Trial", 10, "Winner", next_year_, 12, 31);
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index d391bff..9b580b6 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -44,6 +44,7 @@
#include "chrome/browser/first_run/first_run_browser_process.h"
#include "chrome/browser/first_run/upgrade_util.h"
#include "chrome/browser/jankometer.h"
+#include "chrome/browser/metrics/field_trial_synchronizer.h"
#include "chrome/browser/metrics/histogram_synchronizer.h"
#include "chrome/browser/metrics/metrics_log.h"
#include "chrome/browser/metrics/metrics_service.h"
@@ -1330,6 +1331,13 @@ int BrowserMain(const MainFunctionParams& parameters) {
// field trials
parts->SetupFieldTrials();
+ // Initialize FieldTrialSynchronizer system. This is a singleton and is used
+ // for posting tasks via NewRunnableMethod. Its deleted when it goes out of
+ // scope. Even though NewRunnableMethod does AddRef and Release, the object
+ // will not be deleted after the Task is executed.
+ scoped_refptr<FieldTrialSynchronizer> field_trial_synchronizer(
+ new FieldTrialSynchronizer());
+
// Now that all preferences have been registered, set the install date
// for the uninstall metrics if this is our first run. This only actually
// gets used if the user has metrics reporting enabled at uninstall time.
diff --git a/chrome/browser/metrics/field_trial_synchronizer.cc b/chrome/browser/metrics/field_trial_synchronizer.cc
new file mode 100644
index 0000000..0e7484f
--- /dev/null
+++ b/chrome/browser/metrics/field_trial_synchronizer.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/field_trial_synchronizer.h"
+
+#include "base/logging.h"
+#include "base/threading/thread.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/render_messages.h"
+#include "content/browser/browser_thread.h"
+#include "content/browser/renderer_host/render_process_host.h"
+
+FieldTrialSynchronizer::FieldTrialSynchronizer() {
+ DCHECK(field_trial_synchronizer_ == NULL);
+ field_trial_synchronizer_ = this;
+ base::FieldTrialList::AddObserver(this);
+}
+
+FieldTrialSynchronizer::~FieldTrialSynchronizer() {
+ base::FieldTrialList::RemoveObserver(this);
+ field_trial_synchronizer_ = NULL;
+}
+
+void FieldTrialSynchronizer::NotifyAllRenderers(
+ const std::string& field_trial_name,
+ const std::string& group_name) {
+ // To iterate over RenderProcessHosts, or to send messages to the hosts, we
+ // need to be on the UI thread.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ it.GetCurrentValue()->Send(
+ new ViewMsg_SetFieldTrialGroup(field_trial_name, group_name));
+ }
+}
+
+void FieldTrialSynchronizer::OnFieldTrialGroupFinalized(
+ const std::string& field_trial_name,
+ const std::string& group_name) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &FieldTrialSynchronizer::NotifyAllRenderers,
+ field_trial_name,
+ group_name));
+}
+
+// static
+FieldTrialSynchronizer*
+ FieldTrialSynchronizer::field_trial_synchronizer_ = NULL;
diff --git a/chrome/browser/metrics/field_trial_synchronizer.h b/chrome/browser/metrics/field_trial_synchronizer.h
new file mode 100644
index 0000000..d72a75a
--- /dev/null
+++ b/chrome/browser/metrics/field_trial_synchronizer.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_METRICS_FIELD_TRIAL_SYNCHRONIZER_H_
+#define CHROME_BROWSER_METRICS_FIELD_TRIAL_SYNCHRONIZER_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/field_trial.h"
+
+class MessageLoop;
+class Task;
+
+// This class is used by the browser process to communicate FieldTrial setting
+// (field trial name and group) to any previously started renderers.
+//
+// This class registers itself as an observer of FieldTrialList. FieldTrialList
+// notifies this class by calling it's OnFieldTrialGroupFinalized method when a
+// group is selected (finalized) for a FieldTrial and OnFieldTrialGroupFinalized
+// method sends the FieldTrial's name and the group to all renderer processes.
+// Each renderer process creates the FieldTrial, and by using a 100% probability
+// for the FieldTrial, forces the FieldTrial to have the same group string.
+
+class FieldTrialSynchronizer
+ : public base::RefCountedThreadSafe<FieldTrialSynchronizer>,
+ public base::FieldTrialList::Observer {
+ public:
+ // Construction also sets up the global singleton instance. This instance is
+ // used to communicate between the UI and other threads, and is destroyed only
+ // as the main thread (browser_main) terminates, which means all other threads
+ // have completed, and will not need this instance any further. It adds itself
+ // as an observer of FieldTrialList so that it gets notified whenever a group
+ // is finalized in the browser process.
+ FieldTrialSynchronizer();
+
+ virtual ~FieldTrialSynchronizer();
+
+ // Notify all renderer processes about the |group_name| that is finalized for
+ // the given field trail (|field_trial_name|). This is called on UI thread.
+ void NotifyAllRenderers(const std::string& field_trial_name,
+ const std::string& group_name);
+
+ // FieldTrialList::Observer methods:
+
+ // This method is called by the FieldTrialList singleton when a trial's group
+ // is finalized. This method contacts all renderers (by calling
+ // NotifyAllRenderers) to create a FieldTrial that carries the randomly
+ // selected state from the browser process into all the renderer processes.
+ virtual void OnFieldTrialGroupFinalized(const std::string& name,
+ const std::string& group_name);
+
+ private:
+ // This singleton instance should be constructed during the single threaded
+ // portion of main(). It initializes globals to provide support for all future
+ // calls. This object is created on the UI thread, and it is destroyed after
+ // all the other threads have gone away.
+ static FieldTrialSynchronizer* field_trial_synchronizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(FieldTrialSynchronizer);
+};
+
+#endif // CHROME_BROWSER_METRICS_FIELD_TRIAL_SYNCHRONIZER_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 15eed9f..da723a2 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1277,6 +1277,8 @@
'browser/memory_details_win.cc',
'browser/memory_purger.cc',
'browser/memory_purger.h',
+ 'browser/metrics/field_trial_synchronizer.cc',
+ 'browser/metrics/field_trial_synchronizer.h',
'browser/metrics/histogram_synchronizer.cc',
'browser/metrics/histogram_synchronizer.h',
'browser/metrics/metric_event_duration_details.h',
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index b75ccb6..82b4e6a 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -201,6 +201,12 @@ IPC_MESSAGE_CONTROL0(ViewMsg_GetCacheResourceStats)
IPC_MESSAGE_CONTROL1(ViewMsg_GetRendererHistograms,
int /* sequence number of Renderer Histograms. */)
+// Tells the renderer to create a FieldTrial, and by using a 100% probability
+// for the FieldTrial, forces the FieldTrial to have assigned group name.
+IPC_MESSAGE_CONTROL2(ViewMsg_SetFieldTrialGroup,
+ std::string /* field trial name */,
+ std::string /* group name that was assigned. */)
+
#if defined(USE_TCMALLOC)
// Asks the renderer to send back tcmalloc stats.
IPC_MESSAGE_CONTROL0(ViewMsg_GetRendererTcmalloc)
diff --git a/chrome/renderer/chrome_render_process_observer.cc b/chrome/renderer/chrome_render_process_observer.cc
index ac54050..cfb98a7 100644
--- a/chrome/renderer/chrome_render_process_observer.cc
+++ b/chrome/renderer/chrome_render_process_observer.cc
@@ -6,6 +6,7 @@
#include "base/command_line.h"
#include "base/file_util.h"
+#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/native_library.h"
#include "base/path_service.h"
@@ -390,6 +391,7 @@ bool ChromeRenderProcessObserver::OnControlMessageReceived(
OnSetContentSettingsForCurrentURL)
IPC_MESSAGE_HANDLER(ViewMsg_SetCacheCapacities, OnSetCacheCapacities)
IPC_MESSAGE_HANDLER(ViewMsg_ClearCache, OnClearCache)
+ IPC_MESSAGE_HANDLER(ViewMsg_SetFieldTrialGroup, OnSetFieldTrialGroup)
#if defined(USE_TCMALLOC)
IPC_MESSAGE_HANDLER(ViewMsg_GetRendererTcmalloc, OnGetRendererTcmalloc)
#endif
@@ -441,6 +443,12 @@ void ChromeRenderProcessObserver::OnGetRendererTcmalloc() {
}
#endif
+void ChromeRenderProcessObserver::OnSetFieldTrialGroup(
+ const std::string& field_trial_name,
+ const std::string& group_name) {
+ base::FieldTrialList::CreateFieldTrial(field_trial_name, group_name);
+}
+
void ChromeRenderProcessObserver::OnGetV8HeapStats() {
v8::HeapStatistics heap_stats;
v8::V8::GetHeapStatistics(&heap_stats);
@@ -476,3 +484,4 @@ void ChromeRenderProcessObserver::OnPurgeMemory() {
MallocExtension::instance()->ReleaseFreeMemory();
#endif
}
+
diff --git a/chrome/renderer/chrome_render_process_observer.h b/chrome/renderer/chrome_render_process_observer.h
index d40b502..844440b 100644
--- a/chrome/renderer/chrome_render_process_observer.h
+++ b/chrome/renderer/chrome_render_process_observer.h
@@ -6,6 +6,8 @@
#define CHROME_RENDERER_CHROME_RENDER_PROCESS_OBSERVER_H_
#pragma once
+#include <string>
+
#include "base/compiler_specific.h"
#include "content/renderer/render_process_observer.h"
@@ -35,6 +37,8 @@ class ChromeRenderProcessObserver : public RenderProcessObserver {
size_t capacity);
void OnClearCache();
void OnGetCacheResourceStats();
+ void OnSetFieldTrialGroup(const std::string& fiel_trial_name,
+ const std::string& group_name);
void OnGetRendererTcmalloc();
void OnGetV8HeapStats();
void OnPurgeMemory();