| // Copyright (c) 2012 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. |
| |
| // FieldTrial is a class for handling details of statistical experiments |
| // performed by actual users in the field (i.e., in a shipped or beta product). |
| // All code is called exclusively on the UI thread currently. |
| // |
| // The simplest example is an experiment to see whether one of two options |
| // produces "better" results across our user population. In that scenario, UMA |
| // data is uploaded to aggregate the test results, and this FieldTrial class |
| // manages the state of each such experiment (state == which option was |
| // pseudo-randomly selected). |
| // |
| // States are typically generated randomly, either based on a one time |
| // randomization (which will yield the same results, in terms of selecting |
| // the client for a field trial or not, for every run of the program on a |
| // given machine), or by a session randomization (generated each time the |
| // application starts up, but held constant during the duration of the |
| // process). |
| |
| //------------------------------------------------------------------------------ |
| // Example: Suppose we have an experiment involving memory, such as determining |
| // the impact of some pruning algorithm. |
| // We assume that we already have a histogram of memory usage, such as: |
| |
| // UMA_HISTOGRAM_COUNTS("Memory.RendererTotal", count); |
| |
| // Somewhere in main thread initialization code, we'd probably define an |
| // instance of a FieldTrial, with code such as: |
| |
| // // FieldTrials are reference counted, and persist automagically until |
| // // process teardown, courtesy of their automatic registration in |
| // // FieldTrialList. |
| // // Note: This field trial will run in Chrome instances compiled through |
| // // 8 July, 2015, and after that all instances will be in "StandardMem". |
| // scoped_refptr<base::FieldTrial> trial( |
| // base::FieldTrialList::FactoryGetFieldTrial( |
| // "MemoryExperiment", 1000, "StandardMem", 2015, 7, 8, |
| // base::FieldTrial::ONE_TIME_RANDOMIZED, NULL)); |
| // |
| // const int high_mem_group = |
| // trial->AppendGroup("HighMem", 20); // 2% in HighMem group. |
| // const int low_mem_group = |
| // trial->AppendGroup("LowMem", 20); // 2% in LowMem group. |
| // // Take action depending of which group we randomly land in. |
| // if (trial->group() == high_mem_group) |
| // SetPruningAlgorithm(kType1); // Sample setting of browser state. |
| // else if (trial->group() == low_mem_group) |
| // SetPruningAlgorithm(kType2); // Sample alternate setting. |
| |
| //------------------------------------------------------------------------------ |
| |
| #ifndef BASE_METRICS_FIELD_TRIAL_H_ |
| #define BASE_METRICS_FIELD_TRIAL_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/atomicops.h" |
| #include "base/base_export.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/files/file.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/shared_memory.h" |
| #include "base/memory/shared_memory_handle.h" |
| #include "base/metrics/persistent_memory_allocator.h" |
| #include "base/observer_list_threadsafe.h" |
| #include "base/pickle.h" |
| #include "base/process/launch.h" |
| #include "base/strings/string_piece.h" |
| #include "base/synchronization/lock.h" |
| #include "base/time/time.h" |
| #include "build_config.h" |
| |
| namespace base { |
| |
| class FieldTrialList; |
| |
| class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { |
| public: |
| typedef int Probability; // Probability type for being selected in a trial. |
| |
| // TODO(665129): Make private again after crash has been resolved. |
| typedef SharedPersistentMemoryAllocator::Reference FieldTrialRef; |
| |
| // Specifies the persistence of the field trial group choice. |
| enum RandomizationType { |
| // One time randomized trials will persist the group choice between |
| // restarts, which is recommended for most trials, especially those that |
| // change user visible behavior. |
| ONE_TIME_RANDOMIZED, |
| // Session randomized trials will roll the dice to select a group on every |
| // process restart. |
| SESSION_RANDOMIZED, |
| }; |
| |
| // EntropyProvider is an interface for providing entropy for one-time |
| // randomized (persistent) field trials. |
| class BASE_EXPORT EntropyProvider { |
| public: |
| virtual ~EntropyProvider(); |
| |
| // Returns a double in the range of [0, 1) to be used for the dice roll for |
| // the specified field trial. If |randomization_seed| is not 0, it will be |
| // used in preference to |trial_name| for generating the entropy by entropy |
| // providers that support it. A given instance should always return the same |
| // value given the same input |trial_name| and |randomization_seed| values. |
| virtual double GetEntropyForTrial(const std::string& trial_name, |
| uint32_t randomization_seed) const = 0; |
| }; |
| |
| // A pair representing a Field Trial and its selected group. |
| struct ActiveGroup { |
| std::string trial_name; |
| std::string group_name; |
| }; |
| |
| // A triplet representing a FieldTrial, its selected group and whether it's |
| // active. String members are pointers to the underlying strings owned by the |
| // FieldTrial object. Does not use StringPiece to avoid conversions back to |
| // std::string. |
| struct BASE_EXPORT State { |
| const std::string* trial_name = nullptr; |
| const std::string* group_name = nullptr; |
| bool activated = false; |
| |
| State(); |
| State(const State& other); |
| ~State(); |
| }; |
| |
| // We create one FieldTrialEntry per field trial in shared memory, via |
| // AddToAllocatorWhileLocked. The FieldTrialEntry is followed by a |
| // base::Pickle object that we unpickle and read from. |
| struct BASE_EXPORT FieldTrialEntry { |
| // SHA1(FieldTrialEntry): Increment this if structure changes! |
| static constexpr uint32_t kPersistentTypeId = 0xABA17E13 + 2; |
| |
| // Expected size for 32/64-bit check. |
| static constexpr size_t kExpectedInstanceSize = 8; |
| |
| // Whether or not this field trial is activated. This is really just a |
| // boolean but using a 32 bit value for portability reasons. It should be |
| // accessed via NoBarrier_Load()/NoBarrier_Store() to prevent the compiler |
| // from doing unexpected optimizations because it thinks that only one |
| // thread is accessing the memory location. |
| subtle::Atomic32 activated; |
| |
| // Size of the pickled structure, NOT the total size of this entry. |
| uint32_t pickle_size; |
| |
| // Calling this is only valid when the entry is initialized. That is, it |
| // resides in shared memory and has a pickle containing the trial name and |
| // group name following it. |
| bool GetTrialAndGroupName(StringPiece* trial_name, |
| StringPiece* group_name) const; |
| |
| // Calling this is only valid when the entry is initialized as well. Reads |
| // the parameters following the trial and group name and stores them as |
| // key-value mappings in |params|. |
| bool GetParams(std::map<std::string, std::string>* params) const; |
| |
| private: |
| // Returns an iterator over the data containing names and params. |
| PickleIterator GetPickleIterator() const; |
| |
| // Takes the iterator and writes out the first two items into |trial_name| |
| // and |group_name|. |
| bool ReadStringPair(PickleIterator* iter, |
| StringPiece* trial_name, |
| StringPiece* group_name) const; |
| }; |
| |
| typedef std::vector<ActiveGroup> ActiveGroups; |
| |
| // A return value to indicate that a given instance has not yet had a group |
| // assignment (and hence is not yet participating in the trial). |
| static const int kNotFinalized; |
| |
| // Disables this trial, meaning it always determines the default group |
| // has been selected. May be called immediately after construction, or |
| // at any time after initialization (should not be interleaved with |
| // AppendGroup calls). Once disabled, there is no way to re-enable a |
| // trial. |
| // TODO(mad): http://code.google.com/p/chromium/issues/detail?id=121446 |
| // This doesn't properly reset to Default when a group was forced. |
| void Disable(); |
| |
| // Establish the name and probability of the next group in this trial. |
| // Sometimes, based on construction randomization, this call may cause the |
| // provided group to be *THE* group selected for use in this instance. |
| // The return value is the group number of the new group. |
| int AppendGroup(const std::string& name, Probability group_probability); |
| |
| // Return the name of the FieldTrial (excluding the group name). |
| const std::string& trial_name() const { return trial_name_; } |
| |
| // Return the randomly selected group number that was assigned, and notify |
| // any/all observers that this finalized group number has presumably been used |
| // (queried), and will never change. Note that this will force an instance to |
| // participate, and make it illegal to attempt to probabilistically add any |
| // other groups to the trial. |
| int group(); |
| |
| // If the group's name is empty, a string version containing the group number |
| // is used as the group name. This causes a winner to be chosen if none was. |
| const std::string& group_name(); |
| |
| // Finalizes the group choice and returns the chosen group, but does not mark |
| // the trial as active - so its state will not be reported until group_name() |
| // or similar is called. |
| const std::string& GetGroupNameWithoutActivation(); |
| |
| // Set the field trial as forced, meaning that it was setup earlier than |
| // the hard coded registration of the field trial to override it. |
| // This allows the code that was hard coded to register the field trial to |
| // still succeed even though the field trial has already been registered. |
| // This must be called after appending all the groups, since we will make |
| // the group choice here. Note that this is a NOOP for already forced trials. |
| // And, as the rest of the FieldTrial code, this is not thread safe and must |
| // be done from the UI thread. |
| void SetForced(); |
| |
| // Enable benchmarking sets field trials to a common setting. |
| static void EnableBenchmarking(); |
| |
| // Creates a FieldTrial object with the specified parameters, to be used for |
| // simulation of group assignment without actually affecting global field |
| // trial state in the running process. Group assignment will be done based on |
| // |entropy_value|, which must have a range of [0, 1). |
| // |
| // Note: Using this function will not register the field trial globally in the |
| // running process - for that, use FieldTrialList::FactoryGetFieldTrial(). |
| // |
| // The ownership of the returned FieldTrial is transfered to the caller which |
| // is responsible for deref'ing it (e.g. by using scoped_refptr<FieldTrial>). |
| static FieldTrial* CreateSimulatedFieldTrial( |
| const std::string& trial_name, |
| Probability total_probability, |
| const std::string& default_group_name, |
| double entropy_value); |
| |
| private: |
| // Allow tests to access our innards for testing purposes. |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, AbsoluteProbabilities); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, RemainingProbability); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, FiftyFiftyProbability); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MiddleProbabilities); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, OneWinner); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DisableProbability); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroups); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, AllGroups); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroupsNotFinalized); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Save); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SaveAll); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DuplicateRestore); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOff); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOn); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedChangeDefault_Default); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedChangeDefault_NonDefault); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, FloatBoundariesGiveEqualGroupSizes); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DoesNotSurpassTotalProbability); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, |
| DoNotAddSimulatedFieldTrialsToAllocator); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, ClearParamsFromSharedMemory); |
| |
| friend class base::FieldTrialList; |
| |
| friend class RefCounted<FieldTrial>; |
| |
| // This is the group number of the 'default' group when a choice wasn't forced |
| // by a call to FieldTrialList::CreateFieldTrial. It is kept private so that |
| // consumers don't use it by mistake in cases where the group was forced. |
| static const int kDefaultGroupNumber; |
| |
| // Creates a field trial with the specified parameters. Group assignment will |
| // be done based on |entropy_value|, which must have a range of [0, 1). |
| FieldTrial(const std::string& trial_name, |
| Probability total_probability, |
| const std::string& default_group_name, |
| double entropy_value); |
| virtual ~FieldTrial(); |
| |
| // Return the default group name of the FieldTrial. |
| std::string default_group_name() const { return default_group_name_; } |
| |
| // Marks this trial as having been registered with the FieldTrialList. Must be |
| // called no more than once and before any |group()| calls have occurred. |
| void SetTrialRegistered(); |
| |
| // Sets the chosen group name and number. |
| void SetGroupChoice(const std::string& group_name, int number); |
| |
| // Ensures that a group is chosen, if it hasn't yet been. The field trial |
| // might yet be disabled, so this call will *not* notify observers of the |
| // status. |
| void FinalizeGroupChoice(); |
| |
| // Implements FinalizeGroupChoice() with the added flexibility of being |
| // deadlock-free if |is_locked| is true and the caller is holding a lock. |
| void FinalizeGroupChoiceImpl(bool is_locked); |
| |
| // Returns the trial name and selected group name for this field trial via |
| // the output parameter |active_group|, but only if the group has already |
| // been chosen and has been externally observed via |group()| and the trial |
| // has not been disabled. In that case, true is returned and |active_group| |
| // is filled in; otherwise, the result is false and |active_group| is left |
| // untouched. |
| bool GetActiveGroup(ActiveGroup* active_group) const; |
| |
| // Returns the trial name and selected group name for this field trial via |
| // the output parameter |field_trial_state| for all the studies when |
| // |bool include_expired| is true. In case when |bool include_expired| is |
| // false, if the trial has not been disabled true is returned and |
| // |field_trial_state| is filled in; otherwise, the result is false and |
| // |field_trial_state| is left untouched. |
| // This function is deadlock-free if the caller is holding a lock. |
| bool GetStateWhileLocked(State* field_trial_state, bool include_expired); |
| |
| // Returns the group_name. A winner need not have been chosen. |
| std::string group_name_internal() const { return group_name_; } |
| |
| // The name of the field trial, as can be found via the FieldTrialList. |
| const std::string trial_name_; |
| |
| // The maximum sum of all probabilities supplied, which corresponds to 100%. |
| // This is the scaling factor used to adjust supplied probabilities. |
| const Probability divisor_; |
| |
| // The name of the default group. |
| const std::string default_group_name_; |
| |
| // The randomly selected probability that is used to select a group (or have |
| // the instance not participate). It is the product of divisor_ and a random |
| // number between [0, 1). |
| Probability random_; |
| |
| // Sum of the probabilities of all appended groups. |
| Probability accumulated_group_probability_; |
| |
| // The number that will be returned by the next AppendGroup() call. |
| int next_group_number_; |
| |
| // The pseudo-randomly assigned group number. |
| // This is kNotFinalized if no group has been assigned. |
| int group_; |
| |
| // A textual name for the randomly selected group. Valid after |group()| |
| // has been called. |
| std::string group_name_; |
| |
| // When enable_field_trial_ is false, field trial reverts to the 'default' |
| // group. |
| bool enable_field_trial_; |
| |
| // When forced_ is true, we return the chosen group from AppendGroup when |
| // appropriate. |
| bool forced_; |
| |
| // Specifies whether the group choice has been reported to observers. |
| bool group_reported_; |
| |
| // Whether this trial is registered with the global FieldTrialList and thus |
| // should notify it when its group is queried. |
| bool trial_registered_; |
| |
| // Reference to related field trial struct and data in shared memory. |
| FieldTrialRef ref_; |
| |
| // When benchmarking is enabled, field trials all revert to the 'default' |
| // group. |
| static bool enable_benchmarking_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FieldTrial); |
| }; |
| |
| //------------------------------------------------------------------------------ |
| // Class with a list of all active field trials. A trial is active if it has |
| // been registered, which includes evaluating its state based on its probaility. |
| // Only one instance of this class exists and outside of testing, will live for |
| // the entire life time of the process. |
| class BASE_EXPORT FieldTrialList { |
| public: |
| typedef SharedPersistentMemoryAllocator FieldTrialAllocator; |
| |
| // Type for function pointer passed to |AllParamsToString| used to escape |
| // special characters from |input|. |
| typedef std::string (*EscapeDataFunc)(const std::string& input); |
| |
| // Year that is guaranteed to not be expired when instantiating a field trial |
| // via |FactoryGetFieldTrial()|. Set to two years from the build date. |
| static int kNoExpirationYear; |
| |
| // Observer is notified when a FieldTrial's group is selected. |
| class BASE_EXPORT 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. |
| // |
| // To support one-time randomized field trials, specify a non-null |
| // |entropy_provider| which should be a source of uniformly distributed |
| // entropy values. If one time randomization is not desired, pass in null for |
| // |entropy_provider|. |
| explicit FieldTrialList( |
| std::unique_ptr<const FieldTrial::EntropyProvider> entropy_provider); |
| |
| // Destructor Release()'s references to all registered FieldTrial instances. |
| ~FieldTrialList(); |
| |
| // Get a FieldTrial instance from the factory. |
| // |
| // |name| is used to register the instance with the FieldTrialList class, |
| // and can be used to find the trial (only one trial can be present for each |
| // name). |default_group_name| is the name of the default group which will |
| // be chosen if none of the subsequent appended groups get to be chosen. |
| // |default_group_number| can receive the group number of the default group as |
| // AppendGroup returns the number of the subsequence groups. |trial_name| and |
| // |default_group_name| may not be empty but |default_group_number| can be |
| // NULL if the value is not needed. |
| // |
| // Group probabilities that are later supplied must sum to less than or equal |
| // to the |total_probability|. Arguments |year|, |month| and |day_of_month| |
| // specify the expiration time. If the build time is after the expiration time |
| // then the field trial reverts to the 'default' group. |
| // |
| // Use this static method to get a startup-randomized FieldTrial or a |
| // previously created forced FieldTrial. |
| static FieldTrial* FactoryGetFieldTrial( |
| const std::string& trial_name, |
| FieldTrial::Probability total_probability, |
| const std::string& default_group_name, |
| const int year, |
| const int month, |
| const int day_of_month, |
| FieldTrial::RandomizationType randomization_type, |
| int* default_group_number); |
| |
| // Same as FactoryGetFieldTrial(), but allows specifying a custom seed to be |
| // used on one-time randomized field trials (instead of a hash of the trial |
| // name, which is used otherwise or if |randomization_seed| has value 0). The |
| // |randomization_seed| value (other than 0) should never be the same for two |
| // trials, else this would result in correlated group assignments. Note: |
| // Using a custom randomization seed is only supported by the |
| // PermutedEntropyProvider (which is used when UMA is not enabled). If |
| // |override_entropy_provider| is not null, then it will be used for |
| // randomization instead of the provider given when the FieldTrialList was |
| // instantiated. |
| static FieldTrial* FactoryGetFieldTrialWithRandomizationSeed( |
| const std::string& trial_name, |
| FieldTrial::Probability total_probability, |
| const std::string& default_group_name, |
| const int year, |
| const int month, |
| const int day_of_month, |
| FieldTrial::RandomizationType randomization_type, |
| uint32_t randomization_seed, |
| int* default_group_number, |
| const FieldTrial::EntropyProvider* override_entropy_provider); |
| |
| // The Find() method can be used to test to see if a named trial was already |
| // registered, or to retrieve a pointer to it from the global map. |
| static FieldTrial* Find(const std::string& trial_name); |
| |
| // Returns the group number chosen for the named trial, or |
| // FieldTrial::kNotFinalized if the trial does not exist. |
| static int FindValue(const std::string& trial_name); |
| |
| // Returns the group name chosen for the named trial, or the empty string if |
| // the trial does not exist. The first call of this function on a given field |
| // trial will mark it as active, so that its state will be reported with usage |
| // metrics, crashes, etc. |
| static std::string FindFullName(const std::string& trial_name); |
| |
| // Returns true if the named trial has been registered. |
| static bool TrialExists(const std::string& trial_name); |
| |
| // Returns true if the named trial exists and has been activated. |
| static bool IsTrialActive(const std::string& trial_name); |
| |
| // Creates a persistent representation of active FieldTrial instances for |
| // resurrection in another process. This allows randomization to be done in |
| // one process, and secondary processes can be synchronized on the result. |
| // The resulting string contains the name and group name pairs of all |
| // registered FieldTrials for which the group has been chosen and externally |
| // observed (via |group()|) and which have not been disabled, with "/" used |
| // to separate all names and to terminate the string. This string is parsed |
| // by |CreateTrialsFromString()|. |
| static void StatesToString(std::string* output); |
| |
| // Creates a persistent representation of all FieldTrial instances for |
| // resurrection in another process. This allows randomization to be done in |
| // one process, and secondary processes can be synchronized on the result. |
| // The resulting string contains the name and group name pairs of all |
| // registered FieldTrials including disabled based on |include_expired|, |
| // with "/" used to separate all names and to terminate the string. All |
| // activated trials have their name prefixed with "*". This string is parsed |
| // by |CreateTrialsFromString()|. |
| static void AllStatesToString(std::string* output, bool include_expired); |
| |
| // Creates a persistent representation of all FieldTrial params for |
| // resurrection in another process. The returned string contains the trial |
| // name and group name pairs of all registered FieldTrials including disabled |
| // based on |include_expired| separated by '.'. The pair is followed by ':' |
| // separator and list of param name and values separated by '/'. It also takes |
| // |encode_data_func| function pointer for encodeing special charactors. |
| // This string is parsed by |AssociateParamsFromString()|. |
| static std::string AllParamsToString(bool include_expired, |
| EscapeDataFunc encode_data_func); |
| |
| // Fills in the supplied vector |active_groups| (which must be empty when |
| // called) with a snapshot of all registered FieldTrials for which the group |
| // has been chosen and externally observed (via |group()|) and which have |
| // not been disabled. |
| static void GetActiveFieldTrialGroups( |
| FieldTrial::ActiveGroups* active_groups); |
| |
| // Returns the field trials that are marked active in |trials_string|. |
| static void GetActiveFieldTrialGroupsFromString( |
| const std::string& trials_string, |
| FieldTrial::ActiveGroups* active_groups); |
| |
| // Returns the field trials that were active when the process was |
| // created. Either parses the field trial string or the shared memory |
| // holding field trial information. |
| // Must be called only after a call to CreateTrialsFromCommandLine(). |
| static void GetInitiallyActiveFieldTrials( |
| const base::CommandLine& command_line, |
| FieldTrial::ActiveGroups* active_groups); |
| |
| // Use a state string (re: StatesToString()) to augment the current list of |
| // field trials to include the supplied trials, and using a 100% probability |
| // for each trial, force them to have the same group string. This is commonly |
| // used in a non-browser process, to carry randomly selected state in a |
| // browser process into this non-browser process, but could also be invoked |
| // through a command line argument to the browser process. Created field |
| // trials will be marked "used" for the purposes of active trial reporting |
| // if they are prefixed with |kActivationMarker|. Trial names in |
| // |ignored_trial_names| are ignored when parsing |trials_string|. |
| static bool CreateTrialsFromString( |
| const std::string& trials_string, |
| const std::set<std::string>& ignored_trial_names); |
| |
| // Achieves the same thing as CreateTrialsFromString, except wraps the logic |
| // by taking in the trials from the command line, either via shared memory |
| // handle or command line argument. A bit of a misnomer since on POSIX we |
| // simply get the trials from opening |fd_key| if using shared memory. On |
| // Windows, we expect the |cmd_line| switch for |field_trial_handle_switch| to |
| // contain the shared memory handle that contains the field trial allocator. |
| // We need the |field_trial_handle_switch| and |fd_key| arguments to be passed |
| // in since base/ can't depend on content/. |
| static void CreateTrialsFromCommandLine(const base::CommandLine& cmd_line, |
| const char* field_trial_handle_switch, |
| int fd_key); |
| |
| // Creates base::Feature overrides from the command line by first trying to |
| // use shared memory and then falling back to the command line if it fails. |
| static void CreateFeaturesFromCommandLine( |
| const base::CommandLine& command_line, |
| const char* enable_features_switch, |
| const char* disable_features_switch, |
| FeatureList* feature_list); |
| |
| #if defined(OS_WIN) |
| // On Windows, we need to explicitly pass down any handles to be inherited. |
| // This function adds the shared memory handle to field trial state to the |
| // list of handles to be inherited. |
| static void AppendFieldTrialHandleIfNeeded( |
| base::HandlesToInheritVector* handles); |
| #elif defined(OS_FUCHSIA) |
| // TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368). |
| #elif defined(OS_POSIX) && !defined(OS_NACL) |
| // On POSIX, we also need to explicitly pass down this file descriptor that |
| // should be shared with the child process. Returns an invalid handle if it |
| // was not initialized properly. |
| static base::SharedMemoryHandle GetFieldTrialHandle(); |
| #endif |
| |
| // Adds a switch to the command line containing the field trial state as a |
| // string (if not using shared memory to share field trial state), or the |
| // shared memory handle + length. |
| // Needs the |field_trial_handle_switch| argument to be passed in since base/ |
| // can't depend on content/. |
| static void CopyFieldTrialStateToFlags(const char* field_trial_handle_switch, |
| const char* enable_features_switch, |
| const char* disable_features_switch, |
| base::CommandLine* cmd_line); |
| |
| // 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. |
| // 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). Returns false and does nothing if |
| // there is no FieldTrialList singleton. |
| static bool AddObserver(Observer* observer); |
| |
| // Remove an observer. |
| static void RemoveObserver(Observer* observer); |
| |
| // Similar to AddObserver(), but the passed observer will be notified |
| // synchronously when a field trial is activated and its group selected. It |
| // will be notified synchronously on the same thread where the activation and |
| // group selection happened. It is the responsibility of the observer to make |
| // sure that this is a safe operation and the operation must be fast, as this |
| // work is done synchronously as part of group() or related APIs. Only a |
| // single such observer is supported, exposed specifically for crash |
| // reporting. Must be called on the main thread before any other threads |
| // have been started. |
| static void SetSynchronousObserver(Observer* observer); |
| |
| // Removes the single synchronous observer. |
| static void RemoveSynchronousObserver(Observer* observer); |
| |
| // Grabs the lock if necessary and adds the field trial to the allocator. This |
| // should only be called from FinalizeGroupChoice(). |
| static void OnGroupFinalized(bool is_locked, FieldTrial* field_trial); |
| |
| // Notify all observers that a group has been finalized for |field_trial|. |
| static void NotifyFieldTrialGroupSelection(FieldTrial* field_trial); |
| |
| // Return the number of active field trials. |
| static size_t GetFieldTrialCount(); |
| |
| // Gets the parameters for |field_trial| from shared memory and stores them in |
| // |params|. This is only exposed for use by FieldTrialParamAssociator and |
| // shouldn't be used by anything else. |
| static bool GetParamsFromSharedMemory( |
| FieldTrial* field_trial, |
| std::map<std::string, std::string>* params); |
| |
| // Clears all the params in the allocator. |
| static void ClearParamsFromSharedMemoryForTesting(); |
| |
| // Dumps field trial state to an allocator so that it can be analyzed after a |
| // crash. |
| static void DumpAllFieldTrialsToPersistentAllocator( |
| PersistentMemoryAllocator* allocator); |
| |
| // Retrieves field trial state from an allocator so that it can be analyzed |
| // after a crash. The pointers in the returned vector are into the persistent |
| // memory segment and so are only valid as long as the allocator is valid. |
| static std::vector<const FieldTrial::FieldTrialEntry*> |
| GetAllFieldTrialsFromPersistentAllocator( |
| PersistentMemoryAllocator const& allocator); |
| |
| // Returns true if a global field trial list is set. Only used for testing. |
| static bool IsGlobalSetForTesting(); |
| |
| private: |
| // Allow tests to access our innards for testing purposes. |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, InstantiateAllocator); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, AddTrialsToAllocator); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, |
| DoNotAddSimulatedFieldTrialsToAllocator); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, AssociateFieldTrialParams); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, ClearParamsFromSharedMemory); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, |
| SerializeSharedMemoryHandleMetadata); |
| FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, CheckReadOnlySharedMemoryHandle); |
| |
| // Serialization is used to pass information about the handle to child |
| // processes. It passes a reference to the relevant OS resource, and it passes |
| // a GUID. Serialization and deserialization doesn't actually transport the |
| // underlying OS resource - that must be done by the Process launcher. |
| static std::string SerializeSharedMemoryHandleMetadata( |
| const SharedMemoryHandle& shm); |
| #if defined(OS_WIN) || defined(OS_FUCHSIA) |
| static SharedMemoryHandle DeserializeSharedMemoryHandleMetadata( |
| const std::string& switch_value); |
| #elif defined(OS_POSIX) && !defined(OS_NACL) |
| static SharedMemoryHandle DeserializeSharedMemoryHandleMetadata( |
| int fd, |
| const std::string& switch_value); |
| #endif |
| |
| #if defined(OS_WIN) || defined(OS_FUCHSIA) |
| // Takes in |handle_switch| from the command line which represents the shared |
| // memory handle for field trials, parses it, and creates the field trials. |
| // Returns true on success, false on failure. |
| // |switch_value| also contains the serialized GUID. |
| static bool CreateTrialsFromSwitchValue(const std::string& switch_value); |
| #elif defined(OS_POSIX) && !defined(OS_NACL) |
| // On POSIX systems that use the zygote, we look up the correct fd that backs |
| // the shared memory segment containing the field trials by looking it up via |
| // an fd key in GlobalDescriptors. Returns true on success, false on failure. |
| // |switch_value| also contains the serialized GUID. |
| static bool CreateTrialsFromDescriptor(int fd_key, |
| const std::string& switch_value); |
| #endif |
| |
| // Takes an unmapped SharedMemoryHandle, creates a SharedMemory object from it |
| // and maps it with the correct size. |
| static bool CreateTrialsFromSharedMemoryHandle(SharedMemoryHandle shm_handle); |
| |
| // Expects a mapped piece of shared memory |shm| that was created from the |
| // browser process's field_trial_allocator and shared via the command line. |
| // This function recreates the allocator, iterates through all the field |
| // trials in it, and creates them via CreateFieldTrial(). Returns true if |
| // successful and false otherwise. |
| static bool CreateTrialsFromSharedMemory( |
| std::unique_ptr<base::SharedMemory> shm); |
| |
| // Instantiate the field trial allocator, add all existing field trials to it, |
| // and duplicates its handle to a read-only handle, which gets stored in |
| // |readonly_allocator_handle|. |
| static void InstantiateFieldTrialAllocatorIfNeeded(); |
| |
| // Adds the field trial to the allocator. Caller must hold a lock before |
| // calling this. |
| static void AddToAllocatorWhileLocked(PersistentMemoryAllocator* allocator, |
| FieldTrial* field_trial); |
| |
| // Activate the corresponding field trial entry struct in shared memory. |
| static void ActivateFieldTrialEntryWhileLocked(FieldTrial* field_trial); |
| |
| // A map from FieldTrial names to the actual instances. |
| typedef std::map<std::string, FieldTrial*> RegistrationMap; |
| |
| // If one-time randomization is enabled, returns a weak pointer to the |
| // corresponding EntropyProvider. Otherwise, returns NULL. |
| static const FieldTrial::EntropyProvider* |
| GetEntropyProviderForOneTimeRandomization(); |
| |
| // Helper function should be called only while holding lock_. |
| FieldTrial* PreLockedFind(const std::string& name); |
| |
| // Register() stores a pointer to the given trial in a global map. |
| // This method also AddRef's the indicated trial. |
| // This should always be called after creating a new FieldTrial instance. |
| static void Register(FieldTrial* trial); |
| |
| // Returns all the registered trials. |
| static RegistrationMap GetRegisteredTrials(); |
| |
| static FieldTrialList* global_; // The singleton of this class. |
| |
| // This will tell us if there is an attempt to register a field |
| // trial or check if one-time randomization is enabled without |
| // creating the FieldTrialList. This is not an error, unless a |
| // FieldTrialList is created after that. |
| static bool used_without_global_; |
| |
| // Lock for access to registered_ and field_trial_allocator_. |
| Lock lock_; |
| RegistrationMap registered_; |
| |
| std::map<std::string, std::string> seen_states_; |
| |
| // Entropy provider to be used for one-time randomized field trials. If NULL, |
| // one-time randomization is not supported. |
| std::unique_ptr<const FieldTrial::EntropyProvider> entropy_provider_; |
| |
| // List of observers to be notified when a group is selected for a FieldTrial. |
| scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_; |
| |
| // Single synchronous observer to be notified when a trial group is chosen. |
| Observer* synchronous_observer_ = nullptr; |
| |
| // Allocator in shared memory containing field trial data. Used in both |
| // browser and child processes, but readonly in the child. |
| // In the future, we may want to move this to a more generic place if we want |
| // to start passing more data other than field trials. |
| std::unique_ptr<FieldTrialAllocator> field_trial_allocator_ = nullptr; |
| |
| // Readonly copy of the handle to the allocator. Needs to be a member variable |
| // because it's needed from both CopyFieldTrialStateToFlags() and |
| // AppendFieldTrialHandleIfNeeded(). |
| base::SharedMemoryHandle readonly_allocator_handle_; |
| |
| // Tracks whether CreateTrialsFromCommandLine() has been called. |
| bool create_trials_from_command_line_called_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(FieldTrialList); |
| }; |
| |
| } // namespace base |
| |
| #endif // BASE_METRICS_FIELD_TRIAL_H_ |