blob: 0c244bbbfd099deefb77e38ef92d28fe93f2c752 [file] [log] [blame]
[email protected]a502bbe72011-01-07 18:06:451// Copyright (c) 2011 The Chromium Authors. All rights reserved.
[email protected]ac262c9f2008-10-19 17:45:212// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// FieldTrial is a class for handling details of statistical experiments
6// performed by actual users in the field (i.e., in a shipped or beta product).
7// All code is called exclusively on the UI thread currently.
8//
[email protected]9660b972009-03-02 19:02:569// The simplest example is an experiment to see whether one of two options
10// produces "better" results across our user population. In that scenario, UMA
11// data is uploaded to aggregate the test results, and this FieldTrial class
12// manages the state of each such experiment (state == which option was
13// pseudo-randomly selected).
14//
[email protected]ac262c9f2008-10-19 17:45:2115// States are typically generated randomly, either based on a one time
[email protected]edafd4c2011-05-10 17:18:5316// randomization (which will yield the same results, in terms of selecting
17// the client for a field trial or not, for every run of the program on a
18// given machine), or by a startup randomization (generated each time the
19// application starts up, but held constant during the duration of the
20// process), or by continuous randomization across a run (where the state
21// can be recalculated again and again, many times during a process).
22// Continuous randomization is not yet implemented.
[email protected]9660b972009-03-02 19:02:5623
24//------------------------------------------------------------------------------
25// Example: Suppose we have an experiment involving memory, such as determining
[email protected]57a336a2009-09-30 20:42:2726// the impact of some pruning algorithm.
[email protected]9660b972009-03-02 19:02:5627// We assume that we already have a histogram of memory usage, such as:
28
29// HISTOGRAM_COUNTS("Memory.RendererTotal", count);
30
31// Somewhere in main thread initialization code, we'd probably define an
32// instance of a FieldTrial, with code such as:
33
[email protected]f1d16d4a2011-03-21 14:04:0134// // FieldTrials are reference counted, and persist automagically until
[email protected]9660b972009-03-02 19:02:5635// // process teardown, courtesy of their automatic registration in
36// // FieldTrialList.
[email protected]f1d16d4a2011-03-21 14:04:0137// // Note: This field trial will run in Chrome instances compiled through
38// // 8 July, 2015, and after that all instances will be in "StandardMem".
39// scoped_refptr<FieldTrial> trial = new FieldTrial("MemoryExperiment", 1000,
40// "StandardMem", 2015, 7, 8);
41// const int kHighMemGroup =
42// trial->AppendGroup("HighMem", 20); // 2% in HighMem group.
43// const int kLowMemGroup =
44// trial->AppendGroup("LowMem", 20); // 2% in LowMem group.
[email protected]9660b972009-03-02 19:02:5645// // Take action depending of which group we randomly land in.
[email protected]f1d16d4a2011-03-21 14:04:0146// if (trial->group() == kHighMemGroup)
[email protected]57a336a2009-09-30 20:42:2747// SetPruningAlgorithm(kType1); // Sample setting of browser state.
[email protected]f1d16d4a2011-03-21 14:04:0148// else if (trial->group() == kLowMemGroup)
[email protected]57a336a2009-09-30 20:42:2749// SetPruningAlgorithm(kType2); // Sample alternate setting.
[email protected]9660b972009-03-02 19:02:5650
[email protected]f1d16d4a2011-03-21 14:04:0151// We then, in addition to our original histogram, output histograms which have
52// slightly different names depending on what group the trial instance happened
53// to randomly be assigned:
[email protected]9660b972009-03-02 19:02:5654
[email protected]f1d16d4a2011-03-21 14:04:0155// HISTOGRAM_COUNTS("Memory.RendererTotal", count); // The original histogram.
[email protected]edafd4c2011-05-10 17:18:5356// static const bool memory_renderer_total_trial_exists =
[email protected]a140efe2011-09-15 23:15:0257// FieldTrialList::TrialExists("MemoryExperiment");
[email protected]edafd4c2011-05-10 17:18:5358// if (memory_renderer_total_trial_exists) {
[email protected]f1d16d4a2011-03-21 14:04:0159// HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal",
60// "MemoryExperiment"), count);
61// }
[email protected]9660b972009-03-02 19:02:5662
[email protected]f1d16d4a2011-03-21 14:04:0163// The above code will create four distinct histograms, with each run of the
[email protected]b37fdaa2009-07-01 01:14:5664// application being assigned to of of the three groups, and for each group, the
[email protected]9660b972009-03-02 19:02:5665// correspondingly named histogram will be populated:
66
[email protected]f1d16d4a2011-03-21 14:04:0167// Memory.RendererTotal // 100% of users still fill this histogram.
68// Memory.RendererTotal_HighMem // 2% of users will fill this histogram.
69// Memory.RendererTotal_LowMem // 2% of users will fill this histogram.
70// Memory.RendererTotal_StandardMem // 96% of users will fill this histogram.
[email protected]9660b972009-03-02 19:02:5671
72//------------------------------------------------------------------------------
[email protected]ac262c9f2008-10-19 17:45:2173
[email protected]835d7c82010-10-14 04:38:3874#ifndef BASE_METRICS_FIELD_TRIAL_H_
75#define BASE_METRICS_FIELD_TRIAL_H_
[email protected]32b76ef2010-07-26 23:08:2476#pragma once
[email protected]ac262c9f2008-10-19 17:45:2177
78#include <map>
79#include <string>
80
[email protected]0bea7252011-08-05 15:34:0081#include "base/base_export.h"
[email protected]933729bc2011-01-19 18:52:3282#include "base/gtest_prod_util.h"
[email protected]3b63f8f42011-03-28 01:54:1583#include "base/memory/ref_counted.h"
[email protected]63a8ba12011-04-29 05:42:2284#include "base/observer_list.h"
[email protected]20305ec2011-01-21 04:55:5285#include "base/synchronization/lock.h"
[email protected]ac262c9f2008-10-19 17:45:2186#include "base/time.h"
87
[email protected]835d7c82010-10-14 04:38:3888namespace base {
[email protected]9660b972009-03-02 19:02:5689
[email protected]933729bc2011-01-19 18:52:3290class FieldTrialList;
91
[email protected]0bea7252011-08-05 15:34:0092class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
[email protected]ac262c9f2008-10-19 17:45:2193 public:
[email protected]e695fbd62009-06-30 16:31:5494 typedef int Probability; // Probability type for being selected in a trial.
95
96 // A return value to indicate that a given instance has not yet had a group
97 // assignment (and hence is not yet participating in the trial).
[email protected]933729bc2011-01-19 18:52:3298 static const int kNotFinalized;
[email protected]9660b972009-03-02 19:02:5699
[email protected]933729bc2011-01-19 18:52:32100 // This is the group number of the 'default' group. This provides an easy way
101 // to assign all the remaining probability to a group ('default').
102 static const int kDefaultGroupNumber;
[email protected]9660b972009-03-02 19:02:56103
[email protected]ac262c9f2008-10-19 17:45:21104 // The name is used to register the instance with the FieldTrialList class,
105 // and can be used to find the trial (only one trial can be present for each
[email protected]edafd4c2011-05-10 17:18:53106 // name). |name| and |default_group_name| may not be empty.
107 //
[email protected]9660b972009-03-02 19:02:56108 // Group probabilities that are later supplied must sum to less than or equal
[email protected]933729bc2011-01-19 18:52:32109 // to the total_probability. Arguments year, month and day_of_month specify
110 // the expiration time. If the build time is after the expiration time then
111 // the field trial reverts to the 'default' group.
[email protected]edafd4c2011-05-10 17:18:53112 //
113 // Using this constructor creates a startup-randomized FieldTrial. If you
114 // want a one-time randomized trial, call UseOneTimeRandomization() right
115 // after construction.
[email protected]933729bc2011-01-19 18:52:32116 FieldTrial(const std::string& name, Probability total_probability,
117 const std::string& default_group_name, const int year,
118 const int month, const int day_of_month);
[email protected]ac262c9f2008-10-19 17:45:21119
[email protected]edafd4c2011-05-10 17:18:53120 // Changes the field trial to use one-time randomization, i.e. produce the
121 // same result for the current trial on every run of this client. Must be
122 // called right after construction.
[email protected]edafd4c2011-05-10 17:18:53123 void UseOneTimeRandomization();
124
125 // Disables this trial, meaning it always determines the default group
126 // has been selected. May be called immediately after construction, or
127 // at any time after initialization (should not be interleaved with
128 // AppendGroup calls). Once disabled, there is no way to re-enable a
129 // trial.
130 void Disable();
131
[email protected]9660b972009-03-02 19:02:56132 // Establish the name and probability of the next group in this trial.
[email protected]933729bc2011-01-19 18:52:32133 // Sometimes, based on construction randomization, this call may cause the
[email protected]9660b972009-03-02 19:02:56134 // provided group to be *THE* group selected for use in this instance.
[email protected]9238ab92011-02-25 17:22:46135 // The return value is the group number of the new group.
[email protected]9660b972009-03-02 19:02:56136 int AppendGroup(const std::string& name, Probability group_probability);
137
138 // Return the name of the FieldTrial (excluding the group name).
139 std::string name() const { return name_; }
140
141 // Return the randomly selected group number that was assigned.
[email protected]933729bc2011-01-19 18:52:32142 // Return kDefaultGroupNumber if the instance is in the 'default' group.
143 // Note that this will force an instance to participate, and make it illegal
[email protected]edafd4c2011-05-10 17:18:53144 // to attempt to probabilistically add any other groups to the trial.
[email protected]933729bc2011-01-19 18:52:32145 int group();
[email protected]9660b972009-03-02 19:02:56146
[email protected]edafd4c2011-05-10 17:18:53147 // If the group's name is empty, a string version containing the group
[email protected]9660b972009-03-02 19:02:56148 // number is used as the group name.
[email protected]933729bc2011-01-19 18:52:32149 std::string group_name();
150
151 // Return the default group name of the FieldTrial.
152 std::string default_group_name() const { return default_group_name_; }
[email protected]9660b972009-03-02 19:02:56153
[email protected]edafd4c2011-05-10 17:18:53154 // Helper function for the most common use: as an argument to specify the
[email protected]9660b972009-03-02 19:02:56155 // name of a HISTOGRAM. Use the original histogram name as the name_prefix.
156 static std::string MakeName(const std::string& name_prefix,
157 const std::string& trial_name);
[email protected]ac262c9f2008-10-19 17:45:21158
[email protected]e8d82c612010-12-07 22:54:27159 // Enable benchmarking sets field trials to a common setting.
160 static void EnableBenchmarking();
161
[email protected]ac262c9f2008-10-19 17:45:21162 private:
[email protected]933729bc2011-01-19 18:52:32163 // Allow tests to access our innards for testing purposes.
164 FRIEND_TEST(FieldTrialTest, Registration);
165 FRIEND_TEST(FieldTrialTest, AbsoluteProbabilities);
166 FRIEND_TEST(FieldTrialTest, RemainingProbability);
167 FRIEND_TEST(FieldTrialTest, FiftyFiftyProbability);
168 FRIEND_TEST(FieldTrialTest, MiddleProbabilities);
169 FRIEND_TEST(FieldTrialTest, OneWinner);
170 FRIEND_TEST(FieldTrialTest, DisableProbability);
171 FRIEND_TEST(FieldTrialTest, Save);
172 FRIEND_TEST(FieldTrialTest, DuplicateRestore);
173 FRIEND_TEST(FieldTrialTest, MakeName);
[email protected]edafd4c2011-05-10 17:18:53174 FRIEND_TEST(FieldTrialTest, HashClientId);
175 FRIEND_TEST(FieldTrialTest, HashClientIdIsUniform);
176 FRIEND_TEST(FieldTrialTest, UseOneTimeRandomization);
[email protected]933729bc2011-01-19 18:52:32177
178 friend class base::FieldTrialList;
179
[email protected]835d7c82010-10-14 04:38:38180 friend class RefCounted<FieldTrial>;
[email protected]877d55d2009-11-05 21:53:08181
[email protected]9b2331d92010-10-04 23:11:19182 virtual ~FieldTrial();
[email protected]877d55d2009-11-05 21:53:08183
[email protected]933729bc2011-01-19 18:52:32184 // Returns the group_name. A winner need not have been chosen.
185 std::string group_name_internal() const { return group_name_; }
186
187 // Get build time.
188 static Time GetBuildTime();
189
[email protected]edafd4c2011-05-10 17:18:53190 // Calculates a uniformly-distributed double between [0.0, 1.0) given
191 // a |client_id| and a |trial_name| (the latter is used as salt to avoid
192 // separate one-time randomized trials from all having the same results).
193 static double HashClientId(const std::string& client_id,
194 const std::string& trial_name);
195
[email protected]9660b972009-03-02 19:02:56196 // The name of the field trial, as can be found via the FieldTrialList.
[email protected]9660b972009-03-02 19:02:56197 const std::string name_;
198
[email protected]58d2d2d2010-08-05 22:46:33199 // The maximum sum of all probabilities supplied, which corresponds to 100%.
[email protected]9660b972009-03-02 19:02:56200 // This is the scaling factor used to adjust supplied probabilities.
[email protected]933729bc2011-01-19 18:52:32201 const Probability divisor_;
202
203 // The name of the default group.
204 const std::string default_group_name_;
[email protected]9660b972009-03-02 19:02:56205
206 // The randomly selected probability that is used to select a group (or have
207 // the instance not participate). It is the product of divisor_ and a random
208 // number between [0, 1).
[email protected]edafd4c2011-05-10 17:18:53209 Probability random_;
[email protected]9660b972009-03-02 19:02:56210
211 // Sum of the probabilities of all appended groups.
212 Probability accumulated_group_probability_;
213
214 int next_group_number_;
215
216 // The pseudo-randomly assigned group number.
[email protected]933729bc2011-01-19 18:52:32217 // This is kNotFinalized if no group has been assigned.
[email protected]9660b972009-03-02 19:02:56218 int group_;
219
[email protected]edafd4c2011-05-10 17:18:53220 // A textual name for the randomly selected group. Valid after |group()|
221 // has been called.
[email protected]9660b972009-03-02 19:02:56222 std::string group_name_;
[email protected]ac262c9f2008-10-19 17:45:21223
[email protected]edafd4c2011-05-10 17:18:53224 // When enable_field_trial_ is false, field trial reverts to the 'default'
[email protected]933729bc2011-01-19 18:52:32225 // group.
[email protected]edafd4c2011-05-10 17:18:53226 bool enable_field_trial_;
[email protected]933729bc2011-01-19 18:52:32227
[email protected]e8d82c612010-12-07 22:54:27228 // When benchmarking is enabled, field trials all revert to the 'default'
[email protected]933729bc2011-01-19 18:52:32229 // group.
[email protected]e8d82c612010-12-07 22:54:27230 static bool enable_benchmarking_;
231
[email protected]ac262c9f2008-10-19 17:45:21232 DISALLOW_COPY_AND_ASSIGN(FieldTrial);
233};
234
[email protected]9660b972009-03-02 19:02:56235//------------------------------------------------------------------------------
[email protected]ac262c9f2008-10-19 17:45:21236// Class with a list of all active field trials. A trial is active if it has
237// been registered, which includes evaluating its state based on its probaility.
238// Only one instance of this class exists.
[email protected]0bea7252011-08-05 15:34:00239class BASE_EXPORT FieldTrialList {
[email protected]ac262c9f2008-10-19 17:45:21240 public:
[email protected]e695fbd62009-06-30 16:31:54241 // Define a separator charactor to use when creating a persistent form of an
242 // instance. This is intended for use as a command line argument, passed to a
243 // second process to mimic our state (i.e., provide the same group name).
244 static const char kPersistentStringSeparator; // Currently a slash.
245
[email protected]63a8ba12011-04-29 05:42:22246 // Define expiration year in future. It is initialized to two years from Now.
247 static int kExpirationYearInFuture;
248
249 // Observer is notified when a FieldTrial's group is selected.
250 class Observer {
251 public:
252 // Notify observers when FieldTrials's group is selected.
253 virtual void OnFieldTrialGroupFinalized(const std::string& trial_name,
254 const std::string& group_name) = 0;
255
256 protected:
257 virtual ~Observer() {}
258 };
259
[email protected]ac262c9f2008-10-19 17:45:21260 // This singleton holds the global list of registered FieldTrials.
[email protected]edafd4c2011-05-10 17:18:53261 //
262 // |client_id| should be an opaque, diverse ID for this client that does not
263 // change between sessions, to enable one-time randomized trials. The empty
264 // string may be provided, in which case one-time randomized trials will
265 // not be available.
266 explicit FieldTrialList(const std::string& client_id);
[email protected]ac262c9f2008-10-19 17:45:21267 // Destructor Release()'s references to all registered FieldTrial instances.
268 ~FieldTrialList();
269
270 // Register() stores a pointer to the given trial in a global map.
271 // This method also AddRef's the indicated trial.
272 static void Register(FieldTrial* trial);
273
274 // The Find() method can be used to test to see if a named Trial was already
275 // registered, or to retrieve a pointer to it from the global map.
[email protected]9660b972009-03-02 19:02:56276 static FieldTrial* Find(const std::string& name);
277
[email protected]edafd4c2011-05-10 17:18:53278 // Returns the group number chosen for the named trial, or
279 // FieldTrial::kNotFinalized if the trial does not exist.
[email protected]9660b972009-03-02 19:02:56280 static int FindValue(const std::string& name);
281
[email protected]edafd4c2011-05-10 17:18:53282 // Returns the group name chosen for the named trial, or the
283 // empty string if the trial does not exist.
[email protected]9660b972009-03-02 19:02:56284 static std::string FindFullName(const std::string& name);
[email protected]ac262c9f2008-10-19 17:45:21285
[email protected]edafd4c2011-05-10 17:18:53286 // Returns true if the named trial has been registered.
287 static bool TrialExists(const std::string& name);
288
289 // Create a persistent representation of all FieldTrial instances and the
290 // |client_id()| state for resurrection in another process. This allows
291 // randomization to be done in one process, and secondary processes can by
292 // synchronized on the result. The resulting string contains the
293 // |client_id()|, the names, the trial name, and a "/" separator.
[email protected]e695fbd62009-06-30 16:31:54294 static void StatesToString(std::string* output);
295
296 // Use a previously generated state string (re: StatesToString()) augment the
297 // current list of field tests to include the supplied tests, and using a 100%
298 // probability for each test, force them to have the same group string. This
[email protected]63a8ba12011-04-29 05:42:22299 // is commonly used in a non-browser process, to carry randomly selected state
300 // in a browser process into this non-browser process. This method calls
301 // CreateFieldTrial to create the FieldTrial in the non-browser process.
[email protected]edafd4c2011-05-10 17:18:53302 // Currently only the group_name_ and name_ are restored, as well as the
303 // |client_id()| state, that could be used for one-time randomized trials
304 // set up only in child processes.
[email protected]933729bc2011-01-19 18:52:32305 static bool CreateTrialsInChildProcess(const std::string& prior_trials);
[email protected]e695fbd62009-06-30 16:31:54306
[email protected]63a8ba12011-04-29 05:42:22307 // Create a FieldTrial with the given |name| and using 100% probability for
308 // the FieldTrial, force FieldTrial to have the same group string as
309 // |group_name|. This is commonly used in a non-browser process, to carry
310 // randomly selected state in a browser process into this non-browser process.
311 // Currently only the group_name_ and name_ are set in the FieldTrial. It
312 // returns NULL if there is a FieldTrial that is already registered with the
313 // same |name| but has different finalized group string (|group_name|).
314 static FieldTrial* CreateFieldTrial(const std::string& name,
315 const std::string& group_name);
316
317 // Add an observer to be notified when a field trial is irrevocably committed
318 // to being part of some specific field_group (and hence the group_name is
319 // also finalized for that field_trial).
320 static void AddObserver(Observer* observer);
321
322 // Remove an observer.
323 static void RemoveObserver(Observer* observer);
324
325 // Notify all observers that a group is finalized for the named Trial.
326 static void NotifyFieldTrialGroupSelection(const std::string& name,
327 const std::string& group_name);
328
[email protected]ac262c9f2008-10-19 17:45:21329 // The time of construction of the global map is recorded in a static variable
330 // and is commonly used by experiments to identify the time since the start
331 // of the application. In some experiments it may be useful to discount
[email protected]9660b972009-03-02 19:02:56332 // data that is gathered before the application has reached sufficient
[email protected]ac262c9f2008-10-19 17:45:21333 // stability (example: most DLL have loaded, etc.)
[email protected]835d7c82010-10-14 04:38:38334 static TimeTicks application_start_time() {
[email protected]423041b2008-10-27 17:39:28335 if (global_)
336 return global_->application_start_time_;
337 // For testing purposes only, or when we don't yet have a start time.
[email protected]835d7c82010-10-14 04:38:38338 return TimeTicks::Now();
[email protected]ac262c9f2008-10-19 17:45:21339 }
340
[email protected]e8d82c612010-12-07 22:54:27341 // Return the number of active field trials.
342 static size_t GetFieldTrialCount();
343
[email protected]edafd4c2011-05-10 17:18:53344 // Returns true if you can call |FieldTrial::UseOneTimeRandomization()|
345 // without error, i.e. if a non-empty string was provided as the client_id
346 // when constructing the FieldTrialList singleton.
347 static bool IsOneTimeRandomizationEnabled();
348
349 // Returns an opaque, diverse ID for this client that does not change
350 // between sessions.
351 //
352 // Returns the empty string if one-time randomization is not enabled.
353 static const std::string& client_id();
354
[email protected]ac262c9f2008-10-19 17:45:21355 private:
[email protected]e695fbd62009-06-30 16:31:54356 // A map from FieldTrial names to the actual instances.
[email protected]9660b972009-03-02 19:02:56357 typedef std::map<std::string, FieldTrial*> RegistrationList;
[email protected]ac262c9f2008-10-19 17:45:21358
[email protected]a502bbe72011-01-07 18:06:45359 // Helper function should be called only while holding lock_.
360 FieldTrial* PreLockedFind(const std::string& name);
361
[email protected]ac262c9f2008-10-19 17:45:21362 static FieldTrialList* global_; // The singleton of this class.
[email protected]ac262c9f2008-10-19 17:45:21363
[email protected]0f1373a2011-06-22 23:51:24364 // This will tell us if there is an attempt to register a field
365 // trial or check if one-time randomization is enabled without
366 // creating the FieldTrialList. This is not an error, unless a
367 // FieldTrialList is created after that.
368 static bool used_without_global_;
[email protected]7e05f6c42009-07-11 01:50:48369
[email protected]edafd4c2011-05-10 17:18:53370 // A helper value made available to users, that shows when the FieldTrialList
[email protected]e695fbd62009-06-30 16:31:54371 // was initialized. Note that this is a singleton instance, and hence is a
372 // good approximation to the start of the process.
[email protected]835d7c82010-10-14 04:38:38373 TimeTicks application_start_time_;
[email protected]0b48db42009-03-23 02:45:11374
375 // Lock for access to registered_.
[email protected]20305ec2011-01-21 04:55:52376 base::Lock lock_;
[email protected]ac262c9f2008-10-19 17:45:21377 RegistrationList registered_;
378
[email protected]edafd4c2011-05-10 17:18:53379 // An opaque, diverse ID for this client that does not change
380 // between sessions, or the empty string if not initialized.
381 std::string client_id_;
382
[email protected]63a8ba12011-04-29 05:42:22383 // List of observers to be notified when a group is selected for a FieldTrial.
384 ObserverList<Observer> observer_list_;
385
[email protected]ac262c9f2008-10-19 17:45:21386 DISALLOW_COPY_AND_ASSIGN(FieldTrialList);
387};
388
[email protected]835d7c82010-10-14 04:38:38389} // namespace base
390
391#endif // BASE_METRICS_FIELD_TRIAL_H_