| 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 |