Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / components / variations / variations_seed_processor.cc
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/variations/variations_seed_processor.h"
6
7 #include <map>
8 #include <set>
9 #include <vector>
10
11 #include "base/command_line.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/stl_util.h"
14 #include "base/version.h"
15 #include "components/variations/processed_study.h"
16 #include "components/variations/variations_associated_data.h"
17
18 namespace chrome_variations {
19
20 namespace {
21
22 Study_Platform GetCurrentPlatform() {
23 #if defined(OS_WIN)
24   return Study_Platform_PLATFORM_WINDOWS;
25 #elif defined(OS_IOS)
26   return Study_Platform_PLATFORM_IOS;
27 #elif defined(OS_MACOSX)
28   return Study_Platform_PLATFORM_MAC;
29 #elif defined(OS_CHROMEOS)
30   return Study_Platform_PLATFORM_CHROMEOS;
31 #elif defined(OS_ANDROID)
32   return Study_Platform_PLATFORM_ANDROID;
33 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
34   // Default BSD and SOLARIS to Linux to not break those builds, although these
35   // platforms are not officially supported by Chrome.
36   return Study_Platform_PLATFORM_LINUX;
37 #else
38 #error Unknown platform
39 #endif
40 }
41
42 // Converts |date_time| in Study date format to base::Time.
43 base::Time ConvertStudyDateToBaseTime(int64 date_time) {
44   return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
45 }
46
47 // Associates the variations params of |experiment|, if present.
48 void RegisterExperimentParams(const Study& study,
49                               const Study_Experiment& experiment) {
50   std::map<std::string, std::string> params;
51   for (int i = 0; i < experiment.param_size(); ++i) {
52     if (experiment.param(i).has_name() && experiment.param(i).has_value())
53       params[experiment.param(i).name()] = experiment.param(i).value();
54   }
55   if (!params.empty())
56     AssociateVariationParams(study.name(), experiment.name(), params);
57 }
58
59 // If there are variation ids associated with |experiment|, register the
60 // variation ids.
61 void RegisterVariationIds(const Study_Experiment& experiment,
62                           const std::string& trial_name) {
63   if (experiment.has_google_web_experiment_id()) {
64     const VariationID variation_id =
65         static_cast<VariationID>(experiment.google_web_experiment_id());
66     AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES,
67                                     trial_name,
68                                     experiment.name(),
69                                     variation_id);
70   }
71   if (experiment.has_google_update_experiment_id()) {
72     const VariationID variation_id =
73         static_cast<VariationID>(experiment.google_update_experiment_id());
74     AssociateGoogleVariationIDForce(GOOGLE_UPDATE_SERVICE,
75                                     trial_name,
76                                     experiment.name(),
77                                     variation_id);
78   }
79 }
80
81 }  // namespace
82
83 VariationsSeedProcessor::VariationsSeedProcessor() {
84 }
85
86 VariationsSeedProcessor::~VariationsSeedProcessor() {
87 }
88
89 void VariationsSeedProcessor::CreateTrialsFromSeed(
90     const VariationsSeed& seed,
91     const std::string& locale,
92     const base::Time& reference_date,
93     const base::Version& version,
94     Study_Channel channel,
95     Study_FormFactor form_factor) {
96   std::vector<ProcessedStudy> filtered_studies;
97   FilterAndValidateStudies(seed, locale, reference_date, version, channel,
98                            form_factor, &filtered_studies);
99
100   for (size_t i = 0; i < filtered_studies.size(); ++i)
101     CreateTrialFromStudy(filtered_studies[i]);
102 }
103
104 void VariationsSeedProcessor::FilterAndValidateStudies(
105     const VariationsSeed& seed,
106     const std::string& locale,
107     const base::Time& reference_date,
108     const base::Version& version,
109     Study_Channel channel,
110     Study_FormFactor form_factor,
111     std::vector<ProcessedStudy>* filtered_studies) {
112   DCHECK(version.IsValid());
113
114   // Add expired studies (in a disabled state) only after all the non-expired
115   // studies have been added (and do not add an expired study if a corresponding
116   // non-expired study got added). This way, if there's both an expired and a
117   // non-expired study that applies, the non-expired study takes priority.
118   std::set<std::string> created_studies;
119   std::vector<const Study*> expired_studies;
120
121   for (int i = 0; i < seed.study_size(); ++i) {
122     const Study& study = seed.study(i);
123     if (!ShouldAddStudy(study, locale, reference_date,
124                         version, channel, form_factor))
125       continue;
126
127     if (IsStudyExpired(study, reference_date)) {
128       expired_studies.push_back(&study);
129     } else if (!ContainsKey(created_studies, study.name())) {
130       ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
131       created_studies.insert(study.name());
132     }
133   }
134
135   for (size_t i = 0; i < expired_studies.size(); ++i) {
136     if (!ContainsKey(created_studies, expired_studies[i]->name())) {
137       ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
138                                              filtered_studies);
139     }
140   }
141 }
142
143 bool VariationsSeedProcessor::CheckStudyChannel(const Study_Filter& filter,
144                                                 Study_Channel channel) {
145   // An empty channel list matches all channels.
146   if (filter.channel_size() == 0)
147     return true;
148
149   for (int i = 0; i < filter.channel_size(); ++i) {
150     if (filter.channel(i) == channel)
151       return true;
152   }
153   return false;
154 }
155
156 bool VariationsSeedProcessor::CheckStudyFormFactor(
157     const Study_Filter& filter,
158     Study_FormFactor form_factor) {
159   // An empty form factor list matches all form factors.
160   if (filter.form_factor_size() == 0)
161     return true;
162
163   for (int i = 0; i < filter.form_factor_size(); ++i) {
164     if (filter.form_factor(i) == form_factor)
165       return true;
166   }
167   return false;
168 }
169
170 bool VariationsSeedProcessor::CheckStudyLocale(
171     const Study_Filter& filter,
172     const std::string& locale) {
173   // An empty locale list matches all locales.
174   if (filter.locale_size() == 0)
175     return true;
176
177   for (int i = 0; i < filter.locale_size(); ++i) {
178     if (filter.locale(i) == locale)
179       return true;
180   }
181   return false;
182 }
183
184 bool VariationsSeedProcessor::CheckStudyPlatform(
185     const Study_Filter& filter,
186     Study_Platform platform) {
187   // An empty platform list matches all platforms.
188   if (filter.platform_size() == 0)
189     return true;
190
191   for (int i = 0; i < filter.platform_size(); ++i) {
192     if (filter.platform(i) == platform)
193       return true;
194   }
195   return false;
196 }
197
198 bool VariationsSeedProcessor::CheckStudyStartDate(
199     const Study_Filter& filter,
200     const base::Time& date_time) {
201   if (filter.has_start_date()) {
202     const base::Time start_date =
203         ConvertStudyDateToBaseTime(filter.start_date());
204     return date_time >= start_date;
205   }
206
207   return true;
208 }
209
210 bool VariationsSeedProcessor::CheckStudyVersion(
211     const Study_Filter& filter,
212     const base::Version& version) {
213   if (filter.has_min_version()) {
214     if (version.CompareToWildcardString(filter.min_version()) < 0)
215       return false;
216   }
217
218   if (filter.has_max_version()) {
219     if (version.CompareToWildcardString(filter.max_version()) > 0)
220       return false;
221   }
222
223   return true;
224 }
225
226 void VariationsSeedProcessor::CreateTrialFromStudy(
227     const ProcessedStudy& processed_study) {
228   const Study& study = *processed_study.study();
229
230   // Check if any experiments need to be forced due to a command line
231   // flag. Force the first experiment with an existing flag.
232   CommandLine* command_line = CommandLine::ForCurrentProcess();
233   for (int i = 0; i < study.experiment_size(); ++i) {
234     const Study_Experiment& experiment = study.experiment(i);
235     if (experiment.has_forcing_flag() &&
236         command_line->HasSwitch(experiment.forcing_flag())) {
237       scoped_refptr<base::FieldTrial> trial(
238           base::FieldTrialList::CreateFieldTrial(study.name(),
239                                                  experiment.name()));
240       RegisterExperimentParams(study, experiment);
241       RegisterVariationIds(experiment, study.name());
242       if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO)
243         trial->group();
244
245       DVLOG(1) << "Trial " << study.name() << " forced by flag: "
246                << experiment.forcing_flag();
247       return;
248     }
249   }
250
251   uint32 randomization_seed = 0;
252   base::FieldTrial::RandomizationType randomization_type =
253       base::FieldTrial::SESSION_RANDOMIZED;
254   if (study.has_consistency() &&
255       study.consistency() == Study_Consistency_PERMANENT) {
256     randomization_type = base::FieldTrial::ONE_TIME_RANDOMIZED;
257     if (study.has_randomization_seed())
258       randomization_seed = study.randomization_seed();
259   }
260
261   // The trial is created without specifying an expiration date because the
262   // expiration check in field_trial.cc is based on the build date. Instead,
263   // the expiration check using |reference_date| is done explicitly below.
264   scoped_refptr<base::FieldTrial> trial(
265       base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
266           study.name(), processed_study.total_probability(),
267           study.default_experiment_name(),
268           base::FieldTrialList::kNoExpirationYear, 1, 1, randomization_type,
269           randomization_seed, NULL));
270
271   for (int i = 0; i < study.experiment_size(); ++i) {
272     const Study_Experiment& experiment = study.experiment(i);
273     RegisterExperimentParams(study, experiment);
274
275     // Groups with forcing flags have probability 0 and will never be selected.
276     // Therefore, there's no need to add them to the field trial.
277     if (experiment.has_forcing_flag())
278       continue;
279
280     if (experiment.name() != study.default_experiment_name())
281       trial->AppendGroup(experiment.name(), experiment.probability_weight());
282
283     RegisterVariationIds(experiment, study.name());
284   }
285
286   trial->SetForced();
287   if (processed_study.is_expired())
288     trial->Disable();
289   else if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO)
290     trial->group();
291 }
292
293 bool VariationsSeedProcessor::IsStudyExpired(const Study& study,
294                                        const base::Time& date_time) {
295   if (study.has_expiry_date()) {
296     const base::Time expiry_date =
297         ConvertStudyDateToBaseTime(study.expiry_date());
298     return date_time >= expiry_date;
299   }
300
301   return false;
302 }
303
304 bool VariationsSeedProcessor::ShouldAddStudy(
305     const Study& study,
306     const std::string& locale,
307     const base::Time& reference_date,
308     const base::Version& version,
309     Study_Channel channel,
310     Study_FormFactor form_factor) {
311   if (study.has_filter()) {
312     if (!CheckStudyChannel(study.filter(), channel)) {
313       DVLOG(1) << "Filtered out study " << study.name() << " due to channel.";
314       return false;
315     }
316
317     if (!CheckStudyFormFactor(study.filter(), form_factor)) {
318       DVLOG(1) << "Filtered out study " << study.name() <<
319                   " due to form factor.";
320       return false;
321     }
322
323     if (!CheckStudyLocale(study.filter(), locale)) {
324       DVLOG(1) << "Filtered out study " << study.name() << " due to locale.";
325       return false;
326     }
327
328     if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) {
329       DVLOG(1) << "Filtered out study " << study.name() << " due to platform.";
330       return false;
331     }
332
333     if (!CheckStudyVersion(study.filter(), version)) {
334       DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
335       return false;
336     }
337
338     if (!CheckStudyStartDate(study.filter(), reference_date)) {
339       DVLOG(1) << "Filtered out study " << study.name() <<
340                   " due to start date.";
341       return false;
342     }
343   }
344
345   DVLOG(1) << "Kept study " << study.name() << ".";
346   return true;
347 }
348
349 }  // namespace chrome_variations