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
3 // found in the LICENSE file.
5 #include "chrome/browser/search/suggestions/suggestions_service.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/metrics/sparse_histogram.h"
11 #include "base/strings/string_util.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/history/history_types.h"
14 #include "chrome/browser/metrics/variations/variations_http_header_provider.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "components/variations/variations_associated_data.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "net/base/load_flags.h"
19 #include "net/base/net_errors.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/http/http_status_code.h"
22 #include "net/http/http_util.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "net/url_request/url_request_status.h"
27 namespace suggestions
{
31 // Used to UMA log the state of the last response from the server.
32 enum SuggestionsResponseState
{
39 // Will log the supplied response |state|.
40 void LogResponseState(SuggestionsResponseState state
) {
41 UMA_HISTOGRAM_ENUMERATION("Suggestions.ResponseState", state
,
45 // Obtains the experiment parameter under the supplied |key|.
46 std::string
GetExperimentParam(const std::string
& key
) {
47 return chrome_variations::GetVariationParamValue(kSuggestionsFieldTrialName
,
51 // Runs each callback in |requestors| on |suggestions|, then deallocates
53 void DispatchRequestsAndClear(
54 const SuggestionsProfile
& suggestions
,
55 std::vector
<SuggestionsService::ResponseCallback
>* requestors
) {
56 std::vector
<SuggestionsService::ResponseCallback
>::iterator it
;
57 for (it
= requestors
->begin(); it
!= requestors
->end(); ++it
) {
60 std::vector
<SuggestionsService::ResponseCallback
>().swap(*requestors
);
65 const char kSuggestionsFieldTrialName
[] = "ChromeSuggestions";
66 const char kSuggestionsFieldTrialURLParam
[] = "url";
67 const char kSuggestionsFieldTrialStateParam
[] = "state";
68 const char kSuggestionsFieldTrialStateEnabled
[] = "enabled";
70 SuggestionsService::SuggestionsService(Profile
* profile
)
72 // Obtain the URL to use to fetch suggestions data from the Variations param.
73 suggestions_url_
= GURL(GetExperimentParam(kSuggestionsFieldTrialURLParam
));
76 SuggestionsService::~SuggestionsService() {
80 bool SuggestionsService::IsEnabled() {
81 return GetExperimentParam(kSuggestionsFieldTrialStateParam
) ==
82 kSuggestionsFieldTrialStateEnabled
;
85 void SuggestionsService::FetchSuggestionsData(
86 SuggestionsService::ResponseCallback callback
) {
87 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
89 if (pending_request_
.get()) {
90 // Request already exists, so just add requestor to queue.
91 waiting_requestors_
.push_back(callback
);
96 DCHECK(waiting_requestors_
.empty());
97 waiting_requestors_
.push_back(callback
);
99 pending_request_
.reset(net::URLFetcher::Create(
100 0, suggestions_url_
, net::URLFetcher::GET
, this));
101 pending_request_
->SetLoadFlags(net::LOAD_DISABLE_CACHE
);
102 pending_request_
->SetRequestContext(profile_
->GetRequestContext());
103 // Add Chrome experiment state to the request headers.
104 net::HttpRequestHeaders headers
;
105 chrome_variations::VariationsHttpHeaderProvider::GetInstance()->
106 AppendHeaders(pending_request_
->GetOriginalURL(),
107 profile_
->IsOffTheRecord(), false, &headers
);
108 pending_request_
->SetExtraRequestHeaders(headers
.ToString());
109 pending_request_
->Start();
111 last_request_started_time_
= base::TimeTicks::Now();
114 void SuggestionsService::OnURLFetchComplete(const net::URLFetcher
* source
) {
115 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
116 DCHECK_EQ(pending_request_
.get(), source
);
118 // The fetcher will be deleted when the request is handled.
119 scoped_ptr
<const net::URLFetcher
> request(pending_request_
.release());
120 const net::URLRequestStatus
& request_status
= request
->GetStatus();
121 if (request_status
.status() != net::URLRequestStatus::SUCCESS
) {
122 UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FailedRequestErrorCode",
123 -request_status
.error());
124 DVLOG(1) << "Suggestions server request failed with error: "
125 << request_status
.error() << ": "
126 << net::ErrorToString(request_status
.error());
127 // Dispatch an empty profile on error.
128 DispatchRequestsAndClear(SuggestionsProfile(), &waiting_requestors_
);
132 // Log the response code.
133 const int response_code
= request
->GetResponseCode();
134 UMA_HISTOGRAM_SPARSE_SLOWLY("Suggestions.FetchResponseCode",
136 if (response_code
!= net::HTTP_OK
) {
137 // Dispatch an empty profile on error.
138 DispatchRequestsAndClear(SuggestionsProfile(), &waiting_requestors_
);
142 const base::TimeDelta latency
=
143 base::TimeTicks::Now() - last_request_started_time_
;
144 UMA_HISTOGRAM_MEDIUM_TIMES("Suggestions.FetchSuccessLatency", latency
);
146 std::string suggestions_data
;
147 bool success
= request
->GetResponseAsString(&suggestions_data
);
150 // Compute suggestions, and dispatch then to requestors. On error still
151 // dispatch empty suggestions.
152 SuggestionsProfile suggestions
;
153 if (suggestions_data
.empty()) {
154 LogResponseState(RESPONSE_EMPTY
);
155 } else if (suggestions
.ParseFromString(suggestions_data
)) {
156 LogResponseState(RESPONSE_VALID
);
158 LogResponseState(RESPONSE_INVALID
);
161 DispatchRequestsAndClear(suggestions
, &waiting_requestors_
);
164 void SuggestionsService::Shutdown() {
165 // Cancel pending request.
166 pending_request_
.reset(NULL
);
168 // Dispatch empty suggestions to requestors.
169 DispatchRequestsAndClear(SuggestionsProfile(), &waiting_requestors_
);
172 } // namespace suggestions