OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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/ntp_snippets/user_classifier.h" | 5 #include "components/ntp_snippets/user_classifier.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cfloat> | 8 #include <cfloat> |
9 #include <string> | 9 #include <string> |
10 | 10 |
11 #include "base/metrics/histogram_macros.h" | 11 #include "base/metrics/histogram_macros.h" |
12 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" |
13 #include "base/time/clock.h" | 13 #include "base/time/clock.h" |
14 #include "components/ntp_snippets/features.h" | 14 #include "components/ntp_snippets/features.h" |
15 #include "components/ntp_snippets/pref_names.h" | 15 #include "components/ntp_snippets/pref_names.h" |
16 #include "components/prefs/pref_registry_simple.h" | 16 #include "components/prefs/pref_registry_simple.h" |
17 #include "components/prefs/pref_service.h" | 17 #include "components/prefs/pref_service.h" |
18 #include "components/variations/variations_associated_data.h" | 18 #include "components/variations/variations_associated_data.h" |
19 | 19 |
20 namespace ntp_snippets { | 20 namespace ntp_snippets { |
21 | 21 |
22 namespace { | 22 namespace { |
23 | 23 |
24 // The discount rate for computing the discounted-average metrics. Must be | 24 // The discount rate for computing the discounted-average metrics. Must be |
25 // strictly larger than 0 and strictly smaller than 1! | 25 // strictly larger than 0 and strictly smaller than 1! |
26 const double kDiscountRatePerDay = 0.25; | 26 const double kDiscountRatePerDay = 0.25; |
27 const char kDiscountRatePerDayParam[] = "user_classifier_discount_rate_per_day"; | 27 constexpr base::FeatureParam<double> kDiscountRatePerDayParam{ |
| 28 &kArticleSuggestionsFeature, "user_classifier_discount_rate_per_day", 0.25}; |
28 | 29 |
29 // Never consider any larger interval than this (so that extreme situations such | 30 // Never consider any larger interval than this (so that extreme situations such |
30 // as losing your phone or going for a long offline vacation do not skew the | 31 // as losing your phone or going for a long offline vacation do not skew the |
31 // average too much). | 32 // average too much). |
32 // When everriding via variation parameters, it is better to use smaller values | 33 // When everriding via variation parameters, it is better to use smaller values |
33 // than |kMaxHours| as this it the maximum value reported in the histograms. | 34 // than |kMaxHours| as this it the maximum value reported in the histograms. |
34 const double kMaxHours = 7 * 24; | 35 constexpr base::FeatureParam<double> kMaxHoursParam{ |
35 const char kMaxHoursParam[] = "user_classifier_max_hours"; | 36 &kArticleSuggestionsFeature, "user_classifier_max_hours", 7 * 24}; |
36 | 37 |
37 // Ignore events within |kMinHours| hours since the last event (|kMinHours| is | 38 // Ignore events within |kMinHours| hours since the last event (|kMinHours| is |
38 // the length of the browsing session where subsequent events of the same type | 39 // the length of the browsing session where subsequent events of the same type |
39 // do not count again). | 40 // do not count again). |
40 const double kMinHours = 0.5; | 41 constexpr base::FeatureParam<double> kMinHoursParam{ |
41 const char kMinHoursParam[] = "user_classifier_min_hours"; | 42 &kArticleSuggestionsFeature, "user_classifier_min_hours", 0.5}; |
42 | 43 |
43 // Classification constants. | 44 // Classification constants. |
44 const double kActiveConsumerClicksAtLeastOncePerHours = 72; | 45 constexpr base::FeatureParam<double> |
45 const char kActiveConsumerClicksAtLeastOncePerHoursParam[] = | 46 kActiveConsumerClicksAtLeastOncePerHoursParam{ |
46 "user_classifier_active_consumer_clicks_at_least_once_per_hours"; | 47 &kArticleSuggestionsFeature, |
| 48 "user_classifier_active_consumer_clicks_at_least_once_per_hours", 72}; |
47 | 49 |
48 const double kRareUserOpensNTPAtMostOncePerHours = 96; | 50 constexpr base::FeatureParam<double> kRareUserOpensNTPAtMostOncePerHoursParam{ |
49 const char kRareUserOpensNTPAtMostOncePerHoursParam[] = | 51 &kArticleSuggestionsFeature, |
50 "user_classifier_rare_user_opens_ntp_at_most_once_per_hours"; | 52 "user_classifier_rare_user_opens_ntp_at_most_once_per_hours", 96}; |
51 | 53 |
52 // Histograms for logging the estimated average hours to next event. | 54 // Histograms for logging the estimated average hours to next event. |
53 const char kHistogramAverageHoursToOpenNTP[] = | 55 const char kHistogramAverageHoursToOpenNTP[] = |
54 "NewTabPage.UserClassifier.AverageHoursToOpenNTP"; | 56 "NewTabPage.UserClassifier.AverageHoursToOpenNTP"; |
55 const char kHistogramAverageHoursToShowSuggestions[] = | 57 const char kHistogramAverageHoursToShowSuggestions[] = |
56 "NewTabPage.UserClassifier.AverageHoursToShowSuggestions"; | 58 "NewTabPage.UserClassifier.AverageHoursToShowSuggestions"; |
57 const char kHistogramAverageHoursToUseSuggestions[] = | 59 const char kHistogramAverageHoursToUseSuggestions[] = |
58 "NewTabPage.UserClassifier.AverageHoursToUseSuggestions"; | 60 "NewTabPage.UserClassifier.AverageHoursToUseSuggestions"; |
59 | 61 |
60 // The enum used for iteration. | 62 // The enum used for iteration. |
61 const UserClassifier::Metric kMetrics[] = { | 63 const UserClassifier::Metric kMetrics[] = { |
62 UserClassifier::Metric::NTP_OPENED, | 64 UserClassifier::Metric::NTP_OPENED, |
63 UserClassifier::Metric::SUGGESTIONS_SHOWN, | 65 UserClassifier::Metric::SUGGESTIONS_SHOWN, |
64 UserClassifier::Metric::SUGGESTIONS_USED}; | 66 UserClassifier::Metric::SUGGESTIONS_USED}; |
65 | 67 |
66 // The summary of the prefs. | 68 // The summary of the prefs. |
67 const char* kMetricKeys[] = { | 69 const char* kMetricKeys[] = { |
68 prefs::kUserClassifierAverageNTPOpenedPerHour, | 70 prefs::kUserClassifierAverageNTPOpenedPerHour, |
69 prefs::kUserClassifierAverageSuggestionsShownPerHour, | 71 prefs::kUserClassifierAverageSuggestionsShownPerHour, |
70 prefs::kUserClassifierAverageSuggestionsUsedPerHour}; | 72 prefs::kUserClassifierAverageSuggestionsUsedPerHour}; |
71 const char* kLastTimeKeys[] = {prefs::kUserClassifierLastTimeToOpenNTP, | 73 const char* kLastTimeKeys[] = {prefs::kUserClassifierLastTimeToOpenNTP, |
72 prefs::kUserClassifierLastTimeToShowSuggestions, | 74 prefs::kUserClassifierLastTimeToShowSuggestions, |
73 prefs::kUserClassifierLastTimeToUseSuggestions}; | 75 prefs::kUserClassifierLastTimeToUseSuggestions}; |
74 | 76 |
75 // Default lengths of the intervals for new users for the metrics. | 77 // Default lengths of the intervals for new users for the metrics. |
76 const double kInitialHoursBetweenEvents[] = {24, 48, 96}; | 78 constexpr base::FeatureParam<double> kInitialHoursBetweenEventsParams[] = { |
77 const char* kInitialHoursBetweenEventsParams[] = { | 79 {&kArticleSuggestionsFeature, "user_classifier_default_interval_ntp_opened", |
78 "user_classifier_default_interval_ntp_opened", | 80 24}, |
79 "user_classifier_default_interval_suggestions_shown", | 81 {&kArticleSuggestionsFeature, |
80 "user_classifier_default_interval_suggestions_used"}; | 82 "user_classifier_default_interval_suggestions_shown", 48}, |
| 83 {&kArticleSuggestionsFeature, |
| 84 "user_classifier_default_interval_suggestions_used", 96}}; |
81 | 85 |
82 static_assert(arraysize(kMetrics) == | 86 static_assert(arraysize(kMetrics) == |
83 static_cast<int>(UserClassifier::Metric::COUNT) && | 87 static_cast<int>(UserClassifier::Metric::COUNT) && |
84 arraysize(kMetricKeys) == | 88 arraysize(kMetricKeys) == |
85 static_cast<int>(UserClassifier::Metric::COUNT) && | 89 static_cast<int>(UserClassifier::Metric::COUNT) && |
86 arraysize(kLastTimeKeys) == | 90 arraysize(kLastTimeKeys) == |
87 static_cast<int>(UserClassifier::Metric::COUNT) && | 91 static_cast<int>(UserClassifier::Metric::COUNT) && |
88 arraysize(kInitialHoursBetweenEvents) == | |
89 static_cast<int>(UserClassifier::Metric::COUNT) && | |
90 arraysize(kInitialHoursBetweenEventsParams) == | 92 arraysize(kInitialHoursBetweenEventsParams) == |
91 static_cast<int>(UserClassifier::Metric::COUNT), | 93 static_cast<int>(UserClassifier::Metric::COUNT), |
92 "Fill in info for all metrics."); | 94 "Fill in info for all metrics."); |
93 | 95 |
94 // Computes the discount rate. | 96 // Computes the discount rate. |
95 double GetDiscountRatePerHour() { | 97 double GetDiscountRatePerHour() { |
96 double discount_rate_per_day = variations::GetVariationParamByFeatureAsDouble( | 98 double discount_rate_per_day = kDiscountRatePerDayParam.Get(); |
97 kArticleSuggestionsFeature, kDiscountRatePerDayParam, | |
98 kDiscountRatePerDay); | |
99 // Check for illegal values. | 99 // Check for illegal values. |
100 if (discount_rate_per_day <= 0 || discount_rate_per_day >= 1) { | 100 if (discount_rate_per_day <= 0 || discount_rate_per_day >= 1) { |
101 DLOG(WARNING) << "Illegal value " << discount_rate_per_day | 101 DLOG(WARNING) << "Illegal value " << discount_rate_per_day |
102 << " for the parameter " << kDiscountRatePerDayParam | 102 << " for the parameter " << kDiscountRatePerDayParam.name |
103 << " (must be strictly between 0 and 1; the default " | 103 << " (must be strictly between 0 and 1; the default " |
104 << kDiscountRatePerDay << " is used, instead)."; | 104 << kDiscountRatePerDay << " is used, instead)."; |
105 discount_rate_per_day = kDiscountRatePerDay; | 105 discount_rate_per_day = kDiscountRatePerDay; |
106 } | 106 } |
107 // Compute discount_rate_per_hour such that | 107 // Compute discount_rate_per_hour such that |
108 // discount_rate_per_day = 1 - e^{-discount_rate_per_hour * 24}. | 108 // discount_rate_per_day = 1 - e^{-discount_rate_per_hour * 24}. |
109 return std::log(1.0 / (1.0 - discount_rate_per_day)) / 24.0; | 109 return std::log(1.0 / (1.0 - discount_rate_per_day)) / 24.0; |
110 } | 110 } |
111 | 111 |
112 double GetInitialHoursBetweenEvents(UserClassifier::Metric metric) { | 112 double GetInitialHoursBetweenEvents(UserClassifier::Metric metric) { |
113 return variations::GetVariationParamByFeatureAsDouble( | 113 return kInitialHoursBetweenEventsParams[static_cast<int>(metric)].Get(); |
114 kArticleSuggestionsFeature, | |
115 kInitialHoursBetweenEventsParams[static_cast<int>(metric)], | |
116 kInitialHoursBetweenEvents[static_cast<int>(metric)]); | |
117 } | |
118 | |
119 double GetMinHours() { | |
120 return variations::GetVariationParamByFeatureAsDouble( | |
121 kArticleSuggestionsFeature, kMinHoursParam, kMinHours); | |
122 } | |
123 | |
124 double GetMaxHours() { | |
125 return variations::GetVariationParamByFeatureAsDouble( | |
126 kArticleSuggestionsFeature, kMaxHoursParam, kMaxHours); | |
127 } | 114 } |
128 | 115 |
129 // Returns the new value of the metric using its |old_value|, assuming | 116 // Returns the new value of the metric using its |old_value|, assuming |
130 // |hours_since_last_time| hours have passed since it was last discounted. | 117 // |hours_since_last_time| hours have passed since it was last discounted. |
131 double DiscountMetric(double old_value, | 118 double DiscountMetric(double old_value, |
132 double hours_since_last_time, | 119 double hours_since_last_time, |
133 double discount_rate_per_hour) { | 120 double discount_rate_per_hour) { |
134 // Compute the new discounted average according to the formula | 121 // Compute the new discounted average according to the formula |
135 // avg_events := e^{-discount_rate_per_hour * hours_since} * avg_events | 122 // avg_events := e^{-discount_rate_per_hour * hours_since} * avg_events |
136 return std::exp(-discount_rate_per_hour * hours_since_last_time) * old_value; | 123 return std::exp(-discount_rate_per_hour * hours_since_last_time) * old_value; |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
179 return 1.0 / (1.0 - std::exp(-discount_rate_per_hour * estimate_hours)); | 166 return 1.0 / (1.0 - std::exp(-discount_rate_per_hour * estimate_hours)); |
180 } | 167 } |
181 | 168 |
182 } // namespace | 169 } // namespace |
183 | 170 |
184 UserClassifier::UserClassifier(PrefService* pref_service, | 171 UserClassifier::UserClassifier(PrefService* pref_service, |
185 std::unique_ptr<base::Clock> clock) | 172 std::unique_ptr<base::Clock> clock) |
186 : pref_service_(pref_service), | 173 : pref_service_(pref_service), |
187 clock_(std::move(clock)), | 174 clock_(std::move(clock)), |
188 discount_rate_per_hour_(GetDiscountRatePerHour()), | 175 discount_rate_per_hour_(GetDiscountRatePerHour()), |
189 min_hours_(GetMinHours()), | 176 min_hours_(kMinHoursParam.Get()), |
190 max_hours_(GetMaxHours()), | 177 max_hours_(kMaxHoursParam.Get()), |
191 active_consumer_clicks_at_least_once_per_hours_( | 178 active_consumer_clicks_at_least_once_per_hours_( |
192 variations::GetVariationParamByFeatureAsDouble( | 179 kActiveConsumerClicksAtLeastOncePerHoursParam.Get()), |
193 kArticleSuggestionsFeature, | |
194 kActiveConsumerClicksAtLeastOncePerHoursParam, | |
195 kActiveConsumerClicksAtLeastOncePerHours)), | |
196 rare_user_opens_ntp_at_most_once_per_hours_( | 180 rare_user_opens_ntp_at_most_once_per_hours_( |
197 variations::GetVariationParamByFeatureAsDouble( | 181 kRareUserOpensNTPAtMostOncePerHoursParam.Get()) { |
198 kArticleSuggestionsFeature, | |
199 kRareUserOpensNTPAtMostOncePerHoursParam, | |
200 kRareUserOpensNTPAtMostOncePerHours)) { | |
201 // The pref_service_ can be null in tests. | 182 // The pref_service_ can be null in tests. |
202 if (!pref_service_) { | 183 if (!pref_service_) { |
203 return; | 184 return; |
204 } | 185 } |
205 | 186 |
206 // TODO(jkrcal): Store the current discount rate per hour into prefs. If it | 187 // TODO(jkrcal): Store the current discount rate per hour into prefs. If it |
207 // differs from the previous value, rescale the metric values so that the | 188 // differs from the previous value, rescale the metric values so that the |
208 // expectation does not change abruptly! | 189 // expectation does not change abruptly! |
209 | 190 |
210 // Initialize the prefs storing the last time: the counter has just started! | 191 // Initialize the prefs storing the last time: the counter has just started! |
211 for (const Metric metric : kMetrics) { | 192 for (const Metric metric : kMetrics) { |
212 if (!HasLastTime(metric)) { | 193 if (!HasLastTime(metric)) { |
213 SetLastTimeToNow(metric); | 194 SetLastTimeToNow(metric); |
214 } | 195 } |
215 } | 196 } |
216 } | 197 } |
217 | 198 |
218 UserClassifier::~UserClassifier() = default; | 199 UserClassifier::~UserClassifier() = default; |
219 | 200 |
220 // static | 201 // static |
221 void UserClassifier::RegisterProfilePrefs(PrefRegistrySimple* registry) { | 202 void UserClassifier::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
222 double discount_rate = GetDiscountRatePerHour(); | 203 double discount_rate = GetDiscountRatePerHour(); |
223 double min_hours = GetMinHours(); | 204 double min_hours = kMinHoursParam.Get(); |
224 double max_hours = GetMaxHours(); | 205 double max_hours = kMaxHoursParam.Get(); |
225 | 206 |
226 for (Metric metric : kMetrics) { | 207 for (Metric metric : kMetrics) { |
227 double default_metric_value = GetMetricValueForEstimateHoursBetweenEvents( | 208 double default_metric_value = GetMetricValueForEstimateHoursBetweenEvents( |
228 GetInitialHoursBetweenEvents(metric), discount_rate, min_hours, | 209 GetInitialHoursBetweenEvents(metric), discount_rate, min_hours, |
229 max_hours); | 210 max_hours); |
230 registry->RegisterDoublePref(kMetricKeys[static_cast<int>(metric)], | 211 registry->RegisterDoublePref(kMetricKeys[static_cast<int>(metric)], |
231 default_metric_value); | 212 default_metric_value); |
232 registry->RegisterInt64Pref(kLastTimeKeys[static_cast<int>(metric)], 0); | 213 registry->RegisterInt64Pref(kLastTimeKeys[static_cast<int>(metric)], 0); |
233 } | 214 } |
234 } | 215 } |
235 | 216 |
236 void UserClassifier::OnEvent(Metric metric) { | 217 void UserClassifier::OnEvent(Metric metric) { |
237 DCHECK_NE(metric, Metric::COUNT); | 218 DCHECK_NE(metric, Metric::COUNT); |
238 double metric_value = UpdateMetricOnEvent(metric); | 219 double metric_value = UpdateMetricOnEvent(metric); |
239 | 220 |
240 double avg = GetEstimateHoursBetweenEvents( | 221 double avg = GetEstimateHoursBetweenEvents( |
241 metric_value, discount_rate_per_hour_, min_hours_, max_hours_); | 222 metric_value, discount_rate_per_hour_, min_hours_, max_hours_); |
242 // We use kMaxHours as the max value below as the maximum value for the | 223 // We use kMaxHours as the max value below as the maximum value for the |
243 // histograms must be constant. | 224 // histograms must be constant. |
244 switch (metric) { | 225 switch (metric) { |
245 case Metric::NTP_OPENED: | 226 case Metric::NTP_OPENED: |
246 UMA_HISTOGRAM_CUSTOM_COUNTS(kHistogramAverageHoursToOpenNTP, avg, 1, | 227 UMA_HISTOGRAM_CUSTOM_COUNTS(kHistogramAverageHoursToOpenNTP, avg, 1, |
247 kMaxHours, 50); | 228 kMaxHoursParam.default_value, 50); |
248 break; | 229 break; |
249 case Metric::SUGGESTIONS_SHOWN: | 230 case Metric::SUGGESTIONS_SHOWN: |
250 UMA_HISTOGRAM_CUSTOM_COUNTS(kHistogramAverageHoursToShowSuggestions, avg, | 231 UMA_HISTOGRAM_CUSTOM_COUNTS(kHistogramAverageHoursToShowSuggestions, avg, |
251 1, kMaxHours, 50); | 232 1, kMaxHoursParam.default_value, 50); |
252 break; | 233 break; |
253 case Metric::SUGGESTIONS_USED: | 234 case Metric::SUGGESTIONS_USED: |
254 UMA_HISTOGRAM_CUSTOM_COUNTS(kHistogramAverageHoursToUseSuggestions, avg, | 235 UMA_HISTOGRAM_CUSTOM_COUNTS(kHistogramAverageHoursToUseSuggestions, avg, |
255 1, kMaxHours, 50); | 236 1, kMaxHoursParam.default_value, 50); |
256 break; | 237 break; |
257 case Metric::COUNT: | 238 case Metric::COUNT: |
258 NOTREACHED(); | 239 NOTREACHED(); |
259 break; | 240 break; |
260 } | 241 } |
261 } | 242 } |
262 | 243 |
263 double UserClassifier::GetEstimatedAvgTime(Metric metric) const { | 244 double UserClassifier::GetEstimatedAvgTime(Metric metric) const { |
264 DCHECK_NE(metric, Metric::COUNT); | 245 DCHECK_NE(metric, Metric::COUNT); |
265 double metric_value = GetUpToDateMetricValue(metric); | 246 double metric_value = GetUpToDateMetricValue(metric); |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
375 | 356 |
376 void UserClassifier::SetMetricValue(Metric metric, double metric_value) { | 357 void UserClassifier::SetMetricValue(Metric metric, double metric_value) { |
377 pref_service_->SetDouble(kMetricKeys[static_cast<int>(metric)], metric_value); | 358 pref_service_->SetDouble(kMetricKeys[static_cast<int>(metric)], metric_value); |
378 } | 359 } |
379 | 360 |
380 void UserClassifier::ClearMetricValue(Metric metric) { | 361 void UserClassifier::ClearMetricValue(Metric metric) { |
381 pref_service_->ClearPref(kMetricKeys[static_cast<int>(metric)]); | 362 pref_service_->ClearPref(kMetricKeys[static_cast<int>(metric)]); |
382 } | 363 } |
383 | 364 |
384 } // namespace ntp_snippets | 365 } // namespace ntp_snippets |
OLD | NEW |