| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/ntp_snippets/remote/request_throttler.h" |
| |
| #include <climits> |
| #include <set> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "components/ntp_snippets/features.h" |
| #include "components/ntp_snippets/ntp_snippets_constants.h" |
| #include "components/ntp_snippets/pref_names.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/variations/variations_associated_data.h" |
| |
| namespace ntp_snippets { |
| |
| namespace { |
| |
| // Enumeration listing all possible outcomes for fetch attempts. Used for UMA |
| // histogram, so do not change existing values. Insert new values at the end, |
| // and update the histogram definition. |
| enum class RequestStatus { |
| INTERACTIVE_QUOTA_GRANTED, |
| BACKGROUND_QUOTA_GRANTED, |
| BACKGROUND_QUOTA_EXCEEDED, |
| INTERACTIVE_QUOTA_EXCEEDED, |
| REQUEST_STATUS_COUNT |
| }; |
| |
| // Quota value to use if no quota should be applied (by default). |
| const int kUnlimitedQuota = INT_MAX; |
| |
| } // namespace |
| |
| struct RequestThrottler::RequestTypeInfo { |
| const char* name; |
| const char* count_pref; |
| const char* interactive_count_pref; |
| const char* day_pref; |
| const int default_quota; |
| const int default_interactive_quota; |
| }; |
| |
| // When adding a new type here, extend also the "RequestThrottlerTypes" |
| // <histogram_suffixes> in histograms.xml with the |name| string. |
| const RequestThrottler::RequestTypeInfo RequestThrottler::kRequestTypeInfo[] = { |
| // The following three types share the same prefs. They differ in quota |
| // values (and UMA histograms). |
| // RequestCounter::RequestType::CONTENT_SUGGESTION_FETCHER_RARE_NTP_USER, |
| {"SuggestionFetcherRareNTPUser", prefs::kSnippetFetcherRequestCount, |
| prefs::kSnippetFetcherInteractiveRequestCount, |
| prefs::kSnippetFetcherRequestsDay, 5, kUnlimitedQuota}, |
| // RequestCounter::RequestType::CONTENT_SUGGESTION_FETCHER_ACTIVE_NTP_USER, |
| {"SuggestionFetcherActiveNTPUser", prefs::kSnippetFetcherRequestCount, |
| prefs::kSnippetFetcherInteractiveRequestCount, |
| prefs::kSnippetFetcherRequestsDay, 20, kUnlimitedQuota}, |
| // RequestCounter::RequestType::CONTENT_SUGGESTION_FETCHER_ACTIVE_SUGGESTIONS_CONSUMER, |
| {"SuggestionFetcherActiveSuggestionsConsumer", |
| prefs::kSnippetFetcherRequestCount, |
| prefs::kSnippetFetcherInteractiveRequestCount, |
| prefs::kSnippetFetcherRequestsDay, 20, kUnlimitedQuota}, |
| // RequestCounter::RequestType::CONTENT_SUGGESTION_THUMBNAIL, |
| {"SuggestionThumbnailFetcher", prefs::kSnippetThumbnailsRequestCount, |
| prefs::kSnippetThumbnailsInteractiveRequestCount, |
| prefs::kSnippetThumbnailsRequestsDay, kUnlimitedQuota, kUnlimitedQuota}}; |
| |
| RequestThrottler::RequestThrottler(PrefService* pref_service, RequestType type) |
| : pref_service_(pref_service), |
| type_info_(kRequestTypeInfo[static_cast<int>(type)]) { |
| DCHECK(pref_service); |
| |
| std::string quota = variations::GetVariationParamValueByFeature( |
| ntp_snippets::kArticleSuggestionsFeature, |
| base::StringPrintf("quota_%s", GetRequestTypeName())); |
| if (!base::StringToInt(quota, "a_)) { |
| LOG_IF(WARNING, !quota.empty()) |
| << "Invalid variation parameter for quota for " << GetRequestTypeName(); |
| quota_ = type_info_.default_quota; |
| } |
| |
| std::string interactive_quota = variations::GetVariationParamValueByFeature( |
| ntp_snippets::kArticleSuggestionsFeature, |
| base::StringPrintf("interactive_quota_%s", GetRequestTypeName())); |
| if (!base::StringToInt(interactive_quota, &interactive_quota_)) { |
| LOG_IF(WARNING, !interactive_quota.empty()) |
| << "Invalid variation parameter for interactive quota for " |
| << GetRequestTypeName(); |
| interactive_quota_ = type_info_.default_interactive_quota; |
| } |
| |
| // Since the histogram names are dynamic, we cannot use the standard macros |
| // and we need to lookup the histograms, instead. |
| int status_count = static_cast<int>(RequestStatus::REQUEST_STATUS_COUNT); |
| // Corresponds to UMA_HISTOGRAM_ENUMERATION(name, sample, |status_count|). |
| histogram_request_status_ = base::LinearHistogram::FactoryGet( |
| base::StringPrintf("NewTabPage.RequestThrottler.RequestStatus_%s", |
| GetRequestTypeName()), |
| 1, status_count, status_count + 1, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| // Corresponds to UMA_HISTOGRAM_COUNTS_100(name, sample). |
| histogram_per_day_background_ = base::Histogram::FactoryGet( |
| base::StringPrintf("NewTabPage.RequestThrottler.PerDay_%s", |
| GetRequestTypeName()), |
| 1, 100, 50, base::HistogramBase::kUmaTargetedHistogramFlag); |
| // Corresponds to UMA_HISTOGRAM_COUNTS_100(name, sample). |
| histogram_per_day_interactive_ = base::Histogram::FactoryGet( |
| base::StringPrintf("NewTabPage.RequestThrottler.PerDayInteractive_%s", |
| GetRequestTypeName()), |
| 1, 100, 50, base::HistogramBase::kUmaTargetedHistogramFlag); |
| } |
| |
| // static |
| void RequestThrottler::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| // Collect all pref keys in a set to make sure we register each key exactly |
| // once, even if they repeat. |
| std::set<std::string> keys_to_register; |
| for (const RequestTypeInfo& info : kRequestTypeInfo) { |
| keys_to_register.insert(info.day_pref); |
| keys_to_register.insert(info.count_pref); |
| keys_to_register.insert(info.interactive_count_pref); |
| } |
| |
| for (const std::string& key : keys_to_register) { |
| registry->RegisterIntegerPref(key, 0); |
| } |
| } |
| |
| bool RequestThrottler::DemandQuotaForRequest(bool interactive_request) { |
| ResetCounterIfDayChanged(); |
| |
| int new_count = GetCount(interactive_request) + 1; |
| SetCount(interactive_request, new_count); |
| bool available = (new_count <= GetQuota(interactive_request)); |
| |
| if (interactive_request) { |
| histogram_request_status_->Add(static_cast<int>( |
| available ? RequestStatus::INTERACTIVE_QUOTA_GRANTED |
| : RequestStatus::INTERACTIVE_QUOTA_EXCEEDED)); |
| } else { |
| histogram_request_status_->Add( |
| static_cast<int>(available ? RequestStatus::BACKGROUND_QUOTA_GRANTED |
| : RequestStatus::BACKGROUND_QUOTA_EXCEEDED)); |
| } |
| return available; |
| } |
| |
| void RequestThrottler::ResetCounterIfDayChanged() { |
| // Get the date, "concatenated" into an int in "YYYYMMDD" format. |
| base::Time::Exploded now_exploded{}; |
| base::Time::Now().LocalExplode(&now_exploded); |
| int now_day = 10000 * now_exploded.year + 100 * now_exploded.month + |
| now_exploded.day_of_month; |
| |
| if (!HasDay()) { |
| // The counter is used for the first time in this profile. |
| SetDay(now_day); |
| } else if (now_day != GetDay()) { |
| // Day has changed - report the number of requests from the previous day. |
| histogram_per_day_background_->Add(GetCount(/*interactive_request=*/false)); |
| histogram_per_day_interactive_->Add(GetCount(/*interactive_request=*/true)); |
| // Reset the counters. |
| SetCount(/*interactive_request=*/false, 0); |
| SetCount(/*interactive_request=*/true, 0); |
| SetDay(now_day); |
| } |
| } |
| |
| const char* RequestThrottler::GetRequestTypeName() const { |
| return type_info_.name; |
| } |
| |
| // TODO(jkrcal): turn RequestTypeInfo into a proper class, move those methods |
| // onto the class and hide the members. |
| int RequestThrottler::GetQuota(bool interactive_request) const { |
| return interactive_request ? interactive_quota_ : quota_; |
| } |
| |
| int RequestThrottler::GetCount(bool interactive_request) const { |
| return pref_service_->GetInteger(interactive_request |
| ? type_info_.interactive_count_pref |
| : type_info_.count_pref); |
| } |
| |
| void RequestThrottler::SetCount(bool interactive_request, int count) { |
| pref_service_->SetInteger(interactive_request |
| ? type_info_.interactive_count_pref |
| : type_info_.count_pref, |
| count); |
| } |
| |
| int RequestThrottler::GetDay() const { |
| return pref_service_->GetInteger(type_info_.day_pref); |
| } |
| |
| void RequestThrottler::SetDay(int day) { |
| pref_service_->SetInteger(type_info_.day_pref, day); |
| } |
| |
| bool RequestThrottler::HasDay() const { |
| return pref_service_->HasPrefPath(type_info_.day_pref); |
| } |
| |
| } // namespace ntp_snippets |