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 |