diff options
author | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-29 05:42:22 +0000 |
---|---|---|
committer | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-29 05:42:22 +0000 |
commit | 63a8ba1e1fb71156beff23b4a26828dbc387c734 (patch) | |
tree | e69b6e1f24c5d7d1446bc6d5596518f44399bc67 | |
parent | dbf00c96e956033c1ea9b7542fa0d9c5ab1e66ac (diff) | |
download | chromium_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.cc | 88 | ||||
-rw-r--r-- | base/metrics/field_trial.h | 47 | ||||
-rw-r--r-- | base/metrics/field_trial_unittest.cc | 28 | ||||
-rw-r--r-- | chrome/browser/browser_main.cc | 8 | ||||
-rw-r--r-- | chrome/browser/metrics/field_trial_synchronizer.cc | 52 | ||||
-rw-r--r-- | chrome/browser/metrics/field_trial_synchronizer.h | 67 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/common/render_messages.h | 6 | ||||
-rw-r--r-- | chrome/renderer/chrome_render_process_observer.cc | 9 | ||||
-rw-r--r-- | chrome/renderer/chrome_render_process_observer.h | 4 |
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(); |