OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "components/variations/study_filtering.h" | 5 #include "components/variations/study_filtering.h" |
6 | 6 |
7 #include <stddef.h> | 7 #include <stddef.h> |
8 #include <stdint.h> | 8 #include <stdint.h> |
9 | 9 |
10 #include <set> | 10 #include <set> |
11 | 11 |
12 #include "base/stl_util.h" | 12 #include "base/stl_util.h" |
13 #include "build/build_config.h" | 13 #include "base/strings/string_util.h" |
14 #include "components/variations/client_filterable_state.h" | |
14 | 15 |
15 namespace variations { | 16 namespace variations { |
16 | |
17 namespace { | 17 namespace { |
18 | 18 |
19 Study_Platform GetCurrentPlatform() { | |
20 #if defined(OS_WIN) | |
21 return Study_Platform_PLATFORM_WINDOWS; | |
22 #elif defined(OS_IOS) | |
23 return Study_Platform_PLATFORM_IOS; | |
24 #elif defined(OS_MACOSX) | |
25 return Study_Platform_PLATFORM_MAC; | |
26 #elif defined(OS_CHROMEOS) | |
27 return Study_Platform_PLATFORM_CHROMEOS; | |
28 #elif defined(OS_ANDROID) | |
29 return Study_Platform_PLATFORM_ANDROID; | |
30 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) | |
31 // Default BSD and SOLARIS to Linux to not break those builds, although these | |
32 // platforms are not officially supported by Chrome. | |
33 return Study_Platform_PLATFORM_LINUX; | |
34 #else | |
35 #error Unknown platform | |
36 #endif | |
37 } | |
38 | |
39 // Converts |date_time| in Study date format to base::Time. | 19 // Converts |date_time| in Study date format to base::Time. |
40 base::Time ConvertStudyDateToBaseTime(int64_t date_time) { | 20 base::Time ConvertStudyDateToBaseTime(int64_t date_time) { |
41 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time); | 21 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time); |
42 } | 22 } |
43 | 23 |
44 } // namespace | 24 } // namespace |
45 | 25 |
46 namespace internal { | 26 namespace internal { |
47 | 27 |
48 bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) { | 28 bool CheckStudyChannel(const Study::Filter& filter, Study::Channel channel) { |
49 // An empty channel list matches all channels. | 29 // An empty channel list matches all channels. |
50 if (filter.channel_size() == 0) | 30 if (filter.channel_size() == 0) |
51 return true; | 31 return true; |
52 | 32 |
53 for (int i = 0; i < filter.channel_size(); ++i) { | 33 for (int i = 0; i < filter.channel_size(); ++i) { |
54 if (filter.channel(i) == channel) | 34 if (filter.channel(i) == channel) |
55 return true; | 35 return true; |
56 } | 36 } |
57 return false; | 37 return false; |
58 } | 38 } |
59 | 39 |
60 bool CheckStudyFormFactor(const Study_Filter& filter, | 40 bool CheckStudyFormFactor(const Study::Filter& filter, |
61 Study_FormFactor form_factor) { | 41 Study::FormFactor form_factor) { |
62 // Empty whitelist and blacklist signifies matching any form factor. | 42 // Empty whitelist and blacklist signifies matching any form factor. |
63 if (filter.form_factor_size() == 0 && filter.exclude_form_factor_size() == 0) | 43 if (filter.form_factor_size() == 0 && filter.exclude_form_factor_size() == 0) |
64 return true; | 44 return true; |
65 | 45 |
66 // Allow the form_factor if it matches the whitelist. | 46 // Allow the form_factor if it matches the whitelist. |
67 // Note if both a whitelist and blacklist are specified, the blacklist is | 47 // Note if both a whitelist and blacklist are specified, the blacklist is |
68 // ignored. We do not expect both to be present for Chrome due to server-side | 48 // ignored. We do not expect both to be present for Chrome due to server-side |
69 // checks. | 49 // checks. |
70 if (filter.form_factor_size() > 0) | 50 if (filter.form_factor_size() > 0) |
71 return base::ContainsValue(filter.form_factor(), form_factor); | 51 return base::ContainsValue(filter.form_factor(), form_factor); |
72 | 52 |
73 // Omit if we match the blacklist. | 53 // Omit if we match the blacklist. |
74 return !base::ContainsValue(filter.exclude_form_factor(), form_factor); | 54 return !base::ContainsValue(filter.exclude_form_factor(), form_factor); |
75 } | 55 } |
76 | 56 |
77 bool CheckStudyHardwareClass(const Study_Filter& filter, | 57 bool CheckStudyHardwareClass(const Study::Filter& filter, |
78 const std::string& hardware_class) { | 58 const std::string& hardware_class) { |
79 // Empty hardware_class and exclude_hardware_class matches all. | 59 // Empty hardware_class and exclude_hardware_class matches all. |
80 if (filter.hardware_class_size() == 0 && | 60 if (filter.hardware_class_size() == 0 && |
81 filter.exclude_hardware_class_size() == 0) { | 61 filter.exclude_hardware_class_size() == 0) { |
82 return true; | 62 return true; |
83 } | 63 } |
84 | 64 |
85 // Checks if we are supposed to filter for a specified set of | 65 // Checks if we are supposed to filter for a specified set of |
86 // hardware_classes. Note that this means this overrides the | 66 // hardware_classes. Note that this means this overrides the |
87 // exclude_hardware_class in case that ever occurs (which it shouldn't). | 67 // exclude_hardware_class in case that ever occurs (which it shouldn't). |
(...skipping 14 matching lines...) Expand all Loading... | |
102 size_t position = hardware_class.find( | 82 size_t position = hardware_class.find( |
103 filter.exclude_hardware_class(i)); | 83 filter.exclude_hardware_class(i)); |
104 if (position != std::string::npos) | 84 if (position != std::string::npos) |
105 return false; | 85 return false; |
106 } | 86 } |
107 | 87 |
108 // None of the exclusions match, so this accepts. | 88 // None of the exclusions match, so this accepts. |
109 return true; | 89 return true; |
110 } | 90 } |
111 | 91 |
112 bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) { | 92 bool CheckStudyLocale(const Study::Filter& filter, const std::string& locale) { |
113 // Empty locale and exclude_locale lists matches all locales. | 93 // Empty locale and exclude_locale lists matches all locales. |
114 if (filter.locale_size() == 0 && filter.exclude_locale_size() == 0) | 94 if (filter.locale_size() == 0 && filter.exclude_locale_size() == 0) |
115 return true; | 95 return true; |
116 | 96 |
117 // Check if we are supposed to filter for a specified set of countries. Note | 97 // Check if we are supposed to filter for a specified set of countries. Note |
118 // that this means this overrides the exclude_locale in case that ever occurs | 98 // that this means this overrides the exclude_locale in case that ever occurs |
119 // (which it shouldn't). | 99 // (which it shouldn't). |
120 if (filter.locale_size() > 0) | 100 if (filter.locale_size() > 0) |
121 return base::ContainsValue(filter.locale(), locale); | 101 return base::ContainsValue(filter.locale(), locale); |
122 | 102 |
123 // Omit if matches any of the exclude entries. | 103 // Omit if matches any of the exclude entries. |
124 return !base::ContainsValue(filter.exclude_locale(), locale); | 104 return !base::ContainsValue(filter.exclude_locale(), locale); |
125 } | 105 } |
126 | 106 |
127 bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) { | 107 bool CheckStudyPlatform(const Study::Filter& filter, Study::Platform platform) { |
128 // An empty platform list matches all platforms. | 108 // An empty platform list matches all platforms. |
129 if (filter.platform_size() == 0) | 109 if (filter.platform_size() == 0) |
130 return true; | 110 return true; |
131 | 111 |
132 for (int i = 0; i < filter.platform_size(); ++i) { | 112 for (int i = 0; i < filter.platform_size(); ++i) { |
133 if (filter.platform(i) == platform) | 113 if (filter.platform(i) == platform) |
134 return true; | 114 return true; |
135 } | 115 } |
136 return false; | 116 return false; |
137 } | 117 } |
138 | 118 |
139 bool CheckStudyStartDate(const Study_Filter& filter, | 119 bool CheckStudyStartDate(const Study::Filter& filter, |
140 const base::Time& date_time) { | 120 const base::Time& date_time) { |
141 if (filter.has_start_date()) { | 121 if (filter.has_start_date()) { |
142 const base::Time start_date = | 122 const base::Time start_date = |
143 ConvertStudyDateToBaseTime(filter.start_date()); | 123 ConvertStudyDateToBaseTime(filter.start_date()); |
144 return date_time >= start_date; | 124 return date_time >= start_date; |
145 } | 125 } |
146 | 126 |
147 return true; | 127 return true; |
148 } | 128 } |
149 | 129 |
150 bool CheckStudyEndDate(const Study_Filter& filter, | 130 bool CheckStudyEndDate(const Study::Filter& filter, |
151 const base::Time& date_time) { | 131 const base::Time& date_time) { |
152 if (filter.has_end_date()) { | 132 if (filter.has_end_date()) { |
153 const base::Time end_date = ConvertStudyDateToBaseTime(filter.end_date()); | 133 const base::Time end_date = ConvertStudyDateToBaseTime(filter.end_date()); |
154 return end_date >= date_time; | 134 return end_date >= date_time; |
155 } | 135 } |
156 | 136 |
157 return true; | 137 return true; |
158 } | 138 } |
159 | 139 |
160 bool CheckStudyVersion(const Study_Filter& filter, | 140 bool CheckStudyVersion(const Study::Filter& filter, |
161 const base::Version& version) { | 141 const base::Version& version) { |
162 if (filter.has_min_version()) { | 142 if (filter.has_min_version()) { |
163 if (version.CompareToWildcardString(filter.min_version()) < 0) | 143 if (version.CompareToWildcardString(filter.min_version()) < 0) |
164 return false; | 144 return false; |
165 } | 145 } |
166 | 146 |
167 if (filter.has_max_version()) { | 147 if (filter.has_max_version()) { |
168 if (version.CompareToWildcardString(filter.max_version()) > 0) | 148 if (version.CompareToWildcardString(filter.max_version()) > 0) |
169 return false; | 149 return false; |
170 } | 150 } |
171 | 151 |
172 return true; | 152 return true; |
173 } | 153 } |
174 | 154 |
175 bool CheckStudyCountry(const Study_Filter& filter, const std::string& country) { | 155 bool CheckStudyCountry(const Study::Filter& filter, |
156 const std::string& country) { | |
176 // Empty country and exclude_country matches all. | 157 // Empty country and exclude_country matches all. |
177 if (filter.country_size() == 0 && filter.exclude_country_size() == 0) | 158 if (filter.country_size() == 0 && filter.exclude_country_size() == 0) |
178 return true; | 159 return true; |
179 | 160 |
180 // Checks if we are supposed to filter for a specified set of countries. Note | 161 // Checks if we are supposed to filter for a specified set of countries. Note |
181 // that this means this overrides the exclude_country in case that ever occurs | 162 // that this means this overrides the exclude_country in case that ever occurs |
182 // (which it shouldn't). | 163 // (which it shouldn't). |
183 if (filter.country_size() > 0) | 164 if (filter.country_size() > 0) |
184 return base::ContainsValue(filter.country(), country); | 165 return base::ContainsValue(filter.country(), country); |
185 | 166 |
186 // Omit if matches any of the exclude entries. | 167 // Omit if matches any of the exclude entries. |
187 return !base::ContainsValue(filter.exclude_country(), country); | 168 return !base::ContainsValue(filter.exclude_country(), country); |
188 } | 169 } |
189 | 170 |
171 const std::string& GetStudyCountry(const Study& study, | |
Alexei Svitkine (slow)
2017/06/07 15:18:08
Nit: I think name is misleading - since this is ge
Ilya Sherman
2017/06/07 21:50:08
Done. (I dropped "State" though, i.e. GetClientCo
| |
172 const ClientFilterableState& client_state) { | |
173 switch (study.consistency()) { | |
174 case Study::SESSION: | |
175 return client_state.session_consistency_country; | |
176 case Study::PERMANENT: | |
177 // Use the saved country for permanent consistency studies. This allows | |
178 // Chrome to use the same country for filtering permanent consistency | |
179 // studies between Chrome upgrades. Since some studies have user-visible | |
180 // effects, this helps to avoid annoying users with experimental group | |
181 // churn while traveling. | |
182 return client_state.permanent_consistency_country; | |
183 } | |
184 | |
185 // Unless otherwise specified, use an empty country that won't pass any | |
186 // filters that specifically include countries, but will pass any filters | |
187 // that specifically exclude countries. | |
188 return base::EmptyString(); | |
189 } | |
190 | |
190 bool IsStudyExpired(const Study& study, const base::Time& date_time) { | 191 bool IsStudyExpired(const Study& study, const base::Time& date_time) { |
191 if (study.has_expiry_date()) { | 192 if (study.has_expiry_date()) { |
192 const base::Time expiry_date = | 193 const base::Time expiry_date = |
193 ConvertStudyDateToBaseTime(study.expiry_date()); | 194 ConvertStudyDateToBaseTime(study.expiry_date()); |
194 return date_time >= expiry_date; | 195 return date_time >= expiry_date; |
195 } | 196 } |
196 | 197 |
197 return false; | 198 return false; |
198 } | 199 } |
199 | 200 |
200 bool ShouldAddStudy( | 201 bool ShouldAddStudy(const Study& study, |
201 const Study& study, | 202 const ClientFilterableState& client_state) { |
202 const std::string& locale, | |
203 const base::Time& reference_date, | |
204 const base::Version& version, | |
205 Study_Channel channel, | |
206 Study_FormFactor form_factor, | |
207 const std::string& hardware_class, | |
208 const std::string& country) { | |
209 if (study.has_filter()) { | 203 if (study.has_filter()) { |
210 if (!CheckStudyChannel(study.filter(), channel)) { | 204 if (!CheckStudyChannel(study.filter(), client_state.channel)) { |
211 DVLOG(1) << "Filtered out study " << study.name() << " due to channel."; | 205 DVLOG(1) << "Filtered out study " << study.name() << " due to channel."; |
212 return false; | 206 return false; |
213 } | 207 } |
214 | 208 |
215 if (!CheckStudyFormFactor(study.filter(), form_factor)) { | 209 if (!CheckStudyFormFactor(study.filter(), client_state.form_factor)) { |
216 DVLOG(1) << "Filtered out study " << study.name() << | 210 DVLOG(1) << "Filtered out study " << study.name() << |
217 " due to form factor."; | 211 " due to form factor."; |
218 return false; | 212 return false; |
219 } | 213 } |
220 | 214 |
221 if (!CheckStudyLocale(study.filter(), locale)) { | 215 if (!CheckStudyLocale(study.filter(), client_state.locale)) { |
222 DVLOG(1) << "Filtered out study " << study.name() << " due to locale."; | 216 DVLOG(1) << "Filtered out study " << study.name() << " due to locale."; |
223 return false; | 217 return false; |
224 } | 218 } |
225 | 219 |
226 if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) { | 220 if (!CheckStudyPlatform(study.filter(), client_state.platform)) { |
227 DVLOG(1) << "Filtered out study " << study.name() << " due to platform."; | 221 DVLOG(1) << "Filtered out study " << study.name() << " due to platform."; |
228 return false; | 222 return false; |
229 } | 223 } |
230 | 224 |
231 if (!CheckStudyVersion(study.filter(), version)) { | 225 if (!CheckStudyVersion(study.filter(), client_state.version)) { |
232 DVLOG(1) << "Filtered out study " << study.name() << " due to version."; | 226 DVLOG(1) << "Filtered out study " << study.name() << " due to version."; |
233 return false; | 227 return false; |
234 } | 228 } |
235 | 229 |
236 if (!CheckStudyStartDate(study.filter(), reference_date)) { | 230 if (!CheckStudyStartDate(study.filter(), client_state.reference_date)) { |
237 DVLOG(1) << "Filtered out study " << study.name() << | 231 DVLOG(1) << "Filtered out study " << study.name() << |
238 " due to start date."; | 232 " due to start date."; |
239 return false; | 233 return false; |
240 } | 234 } |
241 | 235 |
242 if (!CheckStudyEndDate(study.filter(), reference_date)) { | 236 if (!CheckStudyEndDate(study.filter(), client_state.reference_date)) { |
243 DVLOG(1) << "Filtered out study " << study.name() << " due to end date."; | 237 DVLOG(1) << "Filtered out study " << study.name() << " due to end date."; |
244 return false; | 238 return false; |
245 } | 239 } |
246 | 240 |
247 if (!CheckStudyHardwareClass(study.filter(), hardware_class)) { | 241 if (!CheckStudyHardwareClass(study.filter(), client_state.hardware_class)) { |
248 DVLOG(1) << "Filtered out study " << study.name() << | 242 DVLOG(1) << "Filtered out study " << study.name() << |
249 " due to hardware_class."; | 243 " due to hardware_class."; |
250 return false; | 244 return false; |
251 } | 245 } |
252 | 246 |
247 const std::string& country = GetStudyCountry(study, client_state); | |
253 if (!CheckStudyCountry(study.filter(), country)) { | 248 if (!CheckStudyCountry(study.filter(), country)) { |
254 DVLOG(1) << "Filtered out study " << study.name() << " due to country."; | 249 DVLOG(1) << "Filtered out study " << study.name() << " due to country."; |
255 return false; | 250 return false; |
256 } | 251 } |
257 } | 252 } |
258 | 253 |
259 DVLOG(1) << "Kept study " << study.name() << "."; | 254 DVLOG(1) << "Kept study " << study.name() << "."; |
260 return true; | 255 return true; |
261 } | 256 } |
262 | 257 |
263 } // namespace internal | 258 } // namespace internal |
264 | 259 |
265 void FilterAndValidateStudies(const VariationsSeed& seed, | 260 void FilterAndValidateStudies(const VariationsSeed& seed, |
266 const std::string& locale, | 261 const ClientFilterableState& client_state, |
267 const base::Time& reference_date, | |
268 const base::Version& version, | |
269 Study_Channel channel, | |
270 Study_FormFactor form_factor, | |
271 const std::string& hardware_class, | |
272 const std::string& session_consistency_country, | |
273 const std::string& permanent_consistency_country, | |
274 std::vector<ProcessedStudy>* filtered_studies) { | 262 std::vector<ProcessedStudy>* filtered_studies) { |
275 DCHECK(version.IsValid()); | 263 DCHECK(client_state.version.IsValid()); |
276 | 264 |
277 // Add expired studies (in a disabled state) only after all the non-expired | 265 // Add expired studies (in a disabled state) only after all the non-expired |
278 // studies have been added (and do not add an expired study if a corresponding | 266 // studies have been added (and do not add an expired study if a corresponding |
279 // non-expired study got added). This way, if there's both an expired and a | 267 // non-expired study got added). This way, if there's both an expired and a |
280 // non-expired study that applies, the non-expired study takes priority. | 268 // non-expired study that applies, the non-expired study takes priority. |
281 std::set<std::string> created_studies; | 269 std::set<std::string> created_studies; |
282 std::vector<const Study*> expired_studies; | 270 std::vector<const Study*> expired_studies; |
283 | 271 |
284 for (int i = 0; i < seed.study_size(); ++i) { | 272 for (int i = 0; i < seed.study_size(); ++i) { |
285 const Study& study = seed.study(i); | 273 const Study& study = seed.study(i); |
274 if (!internal::ShouldAddStudy(study, client_state)) | |
275 continue; | |
286 | 276 |
287 // Unless otherwise specified, use an empty country that won't pass any | 277 if (internal::IsStudyExpired(study, client_state.reference_date)) { |
288 // filters that specifically include countries, but will pass any filters | |
289 // that specifically exclude countries. | |
290 std::string country; | |
291 switch (study.consistency()) { | |
292 case Study_Consistency_SESSION: | |
293 country = session_consistency_country; | |
294 break; | |
295 case Study_Consistency_PERMANENT: | |
296 // Use the saved |permanent_consistency_country| for permanent | |
297 // consistency studies. This allows Chrome to use the same country for | |
298 // filtering permanent consistency studies between Chrome upgrades. | |
299 // Since some studies have user-visible effects, this helps to avoid | |
300 // annoying users with experimental group churn while traveling. | |
301 country = permanent_consistency_country; | |
302 break; | |
303 } | |
304 | |
305 if (!internal::ShouldAddStudy(study, locale, reference_date, version, | |
306 channel, form_factor, hardware_class, | |
307 country)) { | |
308 continue; | |
309 } | |
310 | |
311 if (internal::IsStudyExpired(study, reference_date)) { | |
312 expired_studies.push_back(&study); | 278 expired_studies.push_back(&study); |
313 } else if (!base::ContainsKey(created_studies, study.name())) { | 279 } else if (!base::ContainsKey(created_studies, study.name())) { |
314 ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies); | 280 ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies); |
315 created_studies.insert(study.name()); | 281 created_studies.insert(study.name()); |
316 } | 282 } |
317 } | 283 } |
318 | 284 |
319 for (size_t i = 0; i < expired_studies.size(); ++i) { | 285 for (size_t i = 0; i < expired_studies.size(); ++i) { |
320 if (!base::ContainsKey(created_studies, expired_studies[i]->name())) { | 286 if (!base::ContainsKey(created_studies, expired_studies[i]->name())) { |
321 ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true, | 287 ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true, |
322 filtered_studies); | 288 filtered_studies); |
323 } | 289 } |
324 } | 290 } |
325 } | 291 } |
326 | 292 |
327 } // namespace variations | 293 } // namespace variations |
OLD | NEW |