| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/common/metrics/experiments_helper.h" | 5 #include "chrome/common/metrics/experiments_helper.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/memory/singleton.h" | 9 #include "base/memory/singleton.h" |
| 10 #include "base/sha1.h" |
| 10 #include "base/string16.h" | 11 #include "base/string16.h" |
| 11 #include "base/stringprintf.h" | 12 #include "base/stringprintf.h" |
| 13 #include "base/sys_byteorder.h" |
| 12 #include "base/utf_string_conversions.h" | 14 #include "base/utf_string_conversions.h" |
| 13 #include "chrome/common/child_process_logging.h" | 15 #include "chrome/common/child_process_logging.h" |
| 14 | 16 |
| 15 namespace { | 17 namespace { |
| 16 | 18 |
| 17 // We need to pass a Compare class to the std::map template since NameGroupId | |
| 18 // is a user-defined type. | |
| 19 struct NameGroupIdCompare { | |
| 20 bool operator() (const base::FieldTrial::NameGroupId& lhs, | |
| 21 const base::FieldTrial::NameGroupId& rhs) const { | |
| 22 // The group and name fields are just SHA-1 Hashes, so we just need to treat | |
| 23 // them as IDs and do a less-than comparison. We test group first, since | |
| 24 // name is more likely to collide. | |
| 25 return lhs.group == rhs.group ? lhs.name < rhs.name : | |
| 26 lhs.group < rhs.group; | |
| 27 } | |
| 28 }; | |
| 29 | |
| 30 // The internal singleton accessor for the map, used to keep it thread-safe. | 19 // The internal singleton accessor for the map, used to keep it thread-safe. |
| 31 class GroupMapAccessor { | 20 class GroupMapAccessor { |
| 32 public: | 21 public: |
| 33 // Retrieve the singleton. | 22 // Retrieve the singleton. |
| 34 static GroupMapAccessor* GetInstance() { | 23 static GroupMapAccessor* GetInstance() { |
| 35 return Singleton<GroupMapAccessor>::get(); | 24 return Singleton<GroupMapAccessor>::get(); |
| 36 } | 25 } |
| 37 | 26 |
| 38 GroupMapAccessor() {} | 27 GroupMapAccessor() {} |
| 39 ~GroupMapAccessor() {} | 28 ~GroupMapAccessor() {} |
| 40 | 29 |
| 41 void AssociateID(const base::FieldTrial::NameGroupId& group_identifier, | 30 void AssociateID(const experiments_helper::SelectedGroupId& group_identifier, |
| 42 experiments_helper::GoogleExperimentID id) { | 31 experiments_helper::GoogleExperimentID id) { |
| 43 base::AutoLock scoped_lock(lock_); | 32 base::AutoLock scoped_lock(lock_); |
| 44 DCHECK(group_to_id_map_.find(group_identifier) == group_to_id_map_.end()) << | 33 DCHECK(group_to_id_map_.find(group_identifier) == group_to_id_map_.end()) << |
| 45 "You can associate a group with a GoogleExperimentID only once."; | 34 "You can associate a group with a GoogleExperimentID only once."; |
| 46 group_to_id_map_[group_identifier] = id; | 35 group_to_id_map_[group_identifier] = id; |
| 47 } | 36 } |
| 48 | 37 |
| 49 experiments_helper::GoogleExperimentID GetID( | 38 experiments_helper::GoogleExperimentID GetID( |
| 50 const base::FieldTrial::NameGroupId& group_identifier) { | 39 const experiments_helper::SelectedGroupId& group_identifier) { |
| 51 base::AutoLock scoped_lock(lock_); | 40 base::AutoLock scoped_lock(lock_); |
| 52 GroupToIDMap::const_iterator it = group_to_id_map_.find(group_identifier); | 41 GroupToIDMap::const_iterator it = group_to_id_map_.find(group_identifier); |
| 53 if (it == group_to_id_map_.end()) | 42 if (it == group_to_id_map_.end()) |
| 54 return experiments_helper::kEmptyGoogleExperimentID; | 43 return experiments_helper::kEmptyGoogleExperimentID; |
| 55 return it->second; | 44 return it->second; |
| 56 } | 45 } |
| 57 | 46 |
| 58 private: | 47 private: |
| 59 typedef std::map<base::FieldTrial::NameGroupId, | 48 typedef std::map<experiments_helper::SelectedGroupId, |
| 60 experiments_helper::GoogleExperimentID, NameGroupIdCompare> GroupToIDMap; | 49 experiments_helper::GoogleExperimentID, |
| 50 experiments_helper::SelectedGroupIdCompare> GroupToIDMap; |
| 61 | 51 |
| 62 base::Lock lock_; | 52 base::Lock lock_; |
| 63 GroupToIDMap group_to_id_map_; | 53 GroupToIDMap group_to_id_map_; |
| 64 }; | 54 }; |
| 65 | 55 |
| 56 // Creates unique identifier for the trial by hashing a name string, whether |
| 57 // it's for the field trial or the group name. |
| 58 uint32 HashName(const std::string& name) { |
| 59 // SHA-1 is designed to produce a uniformly random spread in its output space, |
| 60 // even for nearly-identical inputs. |
| 61 unsigned char sha1_hash[base::kSHA1Length]; |
| 62 base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(name.c_str()), |
| 63 name.size(), |
| 64 sha1_hash); |
| 65 |
| 66 COMPILE_ASSERT(sizeof(uint32) < sizeof(sha1_hash), need_more_data); |
| 67 uint32 bits; |
| 68 memcpy(&bits, sha1_hash, sizeof(bits)); |
| 69 |
| 70 return base::ByteSwapToLE32(bits); |
| 71 } |
| 72 |
| 73 experiments_helper::SelectedGroupId MakeSelectedGroupId( |
| 74 const std::string& trial_name, |
| 75 const std::string& group_name) { |
| 76 experiments_helper::SelectedGroupId id; |
| 77 id.name = HashName(trial_name); |
| 78 id.group = HashName(group_name); |
| 79 return id; |
| 80 } |
| 81 |
| 82 // Populates |name_group_ids| based on |selected_groups|. |
| 83 void GetFieldTrialSelectedGroupIdsForSelectedGroups( |
| 84 const base::FieldTrial::SelectedGroups& selected_groups, |
| 85 std::vector<experiments_helper::SelectedGroupId>* name_group_ids) { |
| 86 DCHECK(name_group_ids->empty()); |
| 87 for (base::FieldTrial::SelectedGroups::const_iterator it = |
| 88 selected_groups.begin(); it != selected_groups.end(); ++it) { |
| 89 name_group_ids->push_back(MakeSelectedGroupId(it->trial, it->group)); |
| 90 } |
| 91 } |
| 92 |
| 66 } // namespace | 93 } // namespace |
| 67 | 94 |
| 68 namespace experiments_helper { | 95 namespace experiments_helper { |
| 69 | 96 |
| 70 const GoogleExperimentID kEmptyGoogleExperimentID = 0; | 97 const GoogleExperimentID kEmptyGoogleExperimentID = 0; |
| 71 | 98 |
| 72 void AssociateGoogleExperimentID( | 99 void GetFieldTrialSelectedGroupIds( |
| 73 const base::FieldTrial::NameGroupId& group_identifier, | 100 std::vector<SelectedGroupId>* name_group_ids) { |
| 74 GoogleExperimentID id) { | 101 DCHECK(name_group_ids->empty()); |
| 75 GroupMapAccessor::GetInstance()->AssociateID(group_identifier, id); | 102 // A note on thread safety: Since GetFieldTrialSelectedGroups is thread |
| 103 // safe, and we operate on a separate list of that data, this function is |
| 104 // technically thread safe as well, with respect to the FieldTriaList data. |
| 105 base::FieldTrial::SelectedGroups selected_groups; |
| 106 base::FieldTrialList::GetFieldTrialSelectedGroups(&selected_groups); |
| 107 GetFieldTrialSelectedGroupIdsForSelectedGroups(selected_groups, |
| 108 name_group_ids); |
| 76 } | 109 } |
| 77 | 110 |
| 78 GoogleExperimentID GetGoogleExperimentID( | 111 void AssociateGoogleExperimentID(const std::string& trial_name, |
| 79 const base::FieldTrial::NameGroupId& group_identifier) { | 112 const std::string& group_name, |
| 80 return GroupMapAccessor::GetInstance()->GetID(group_identifier); | 113 GoogleExperimentID id) { |
| 114 GroupMapAccessor::GetInstance()->AssociateID( |
| 115 MakeSelectedGroupId(trial_name, group_name), id); |
| 116 } |
| 117 |
| 118 GoogleExperimentID GetGoogleExperimentID(const std::string& trial_name, |
| 119 const std::string& group_name) { |
| 120 return GroupMapAccessor::GetInstance()->GetID( |
| 121 MakeSelectedGroupId(trial_name, group_name)); |
| 81 } | 122 } |
| 82 | 123 |
| 83 void SetChildProcessLoggingExperimentList() { | 124 void SetChildProcessLoggingExperimentList() { |
| 84 std::vector<base::FieldTrial::NameGroupId> name_group_ids; | 125 std::vector<SelectedGroupId> name_group_ids; |
| 85 base::FieldTrialList::GetFieldTrialNameGroupIds(&name_group_ids); | 126 GetFieldTrialSelectedGroupIds(&name_group_ids); |
| 86 std::vector<string16> experiment_strings(name_group_ids.size()); | 127 std::vector<string16> experiment_strings(name_group_ids.size()); |
| 87 for (size_t i = 0; i < name_group_ids.size(); ++i) { | 128 for (size_t i = 0; i < name_group_ids.size(); ++i) { |
| 88 // Wish there was a StringPrintf for string16... :-( | 129 // Wish there was a StringPrintf for string16... :-( |
| 89 experiment_strings[i] = WideToUTF16(base::StringPrintf( | 130 experiment_strings[i] = WideToUTF16(base::StringPrintf( |
| 90 L"%x-%x", name_group_ids[i].name, name_group_ids[i].group)); | 131 L"%x-%x", name_group_ids[i].name, name_group_ids[i].group)); |
| 91 } | 132 } |
| 92 child_process_logging::SetExperimentList(experiment_strings); | 133 child_process_logging::SetExperimentList(experiment_strings); |
| 93 } | 134 } |
| 94 | 135 |
| 95 } // namespace experiments_helper | 136 } // namespace experiments_helper |
| 137 |
| 138 // Functions below are exposed for testing explicitly behind this namespace. |
| 139 // They simply wrap existing functions in this file. |
| 140 namespace testing { |
| 141 |
| 142 void TestGetFieldTrialSelectedGroupIdsForSelectedGroups( |
| 143 const base::FieldTrial::SelectedGroups& selected_groups, |
| 144 std::vector<experiments_helper::SelectedGroupId>* name_group_ids) { |
| 145 ::GetFieldTrialSelectedGroupIdsForSelectedGroups(selected_groups, |
| 146 name_group_ids); |
| 147 } |
| 148 |
| 149 uint32 TestHashName(const std::string& name) { |
| 150 return ::HashName(name); |
| 151 } |
| 152 |
| 153 } // namespace testing |
| OLD | NEW |