1 // Copyright (c) 2011 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/prerender/prerender_util.h"
7 #include "base/logging.h"
8 #include "base/metrics/histogram.h"
9 #include "base/metrics/sparse_histogram.h"
10 #include "base/strings/string_util.h"
11 #include "content/public/browser/resource_request_info.h"
12 #include "net/http/http_response_headers.h"
13 #include "net/url_request/url_request.h"
14 #include "url/url_canon.h"
15 #include "url/url_parse.h"
16 #include "url/url_util.h"
17 #include "webkit/common/resource_type.h"
23 const char kModPagespeedHeader
[] = "X-Mod-Pagespeed";
24 const char kPageSpeedHeader
[] = "X-Page-Speed";
25 const char kPagespeedServerHistogram
[] =
26 "Prerender.PagespeedHeader.ServerCounts";
27 const char kPagespeedVersionHistogram
[] =
28 "Prerender.PagespeedHeader.VersionCounts";
30 enum PagespeedHeaderServerType
{
31 PAGESPEED_TOTAL_RESPONSES
= 0,
32 PAGESPEED_MOD_PAGESPEED_SERVER
= 1,
33 PAGESPEED_NGX_PAGESPEED_SERVER
= 2,
34 PAGESPEED_PAGESPEED_SERVICE_SERVER
= 3,
35 PAGESPEED_UNKNOWN_SERVER
= 4,
36 PAGESPEED_SERVER_MAXIMUM
= 5
39 // Private function to parse the PageSpeed version number and encode it in
40 // buckets 2 through 99: if it is in the format a.b.c.d-e the bucket will be
41 // 2 + 2 * (max(c, 10) - 10) + (d > 1 ? 1 : 0); if it is not in this format
43 int GetXModPagespeedBucketFromVersion(const std::string
& version
) {
45 int num_parsed
= sscanf(version
.c_str(), "%d.%d.%d.%d-%d",
48 if (num_parsed
== 5) {
51 output
+= 2 * (c
- 10);
54 if (output
< 2 || output
> 99)
60 // Private function to parse the X-Page-Speed header value and determine
61 // whether it is in the PageSpeed Service format, namely m_n_dc were m_n is
62 // a version number and dc is an encoded 2-character value.
63 bool IsPageSpeedServiceVersionNumber(const std::string
& version
) {
65 char c
, d
, e
; // e is to detect EOL as we check that it /isn't/ converted.
66 int num_parsed
= sscanf(version
.c_str(), "%d_%d_%c%c%c", &a
, &b
, &c
, &d
, &e
);
67 return (num_parsed
== 4);
70 enum PrerenderSchemeCancelReason
{
71 PRERENDER_SCHEME_CANCEL_REASON_EXTERNAL_PROTOCOL
,
72 PRERENDER_SCHEME_CANCEL_REASON_DATA
,
73 PRERENDER_SCHEME_CANCEL_REASON_BLOB
,
74 PRERENDER_SCHEME_CANCEL_REASON_FILE
,
75 PRERENDER_SCHEME_CANCEL_REASON_FILESYSTEM
,
76 PRERENDER_SCHEME_CANCEL_REASON_WEBSOCKET
,
77 PRERENDER_SCHEME_CANCEL_REASON_FTP
,
78 PRERENDER_SCHEME_CANCEL_REASON_CHROME
,
79 PRERENDER_SCHEME_CANCEL_REASON_CHROME_EXTENSION
,
80 PRERENDER_SCHEME_CANCEL_REASON_ABOUT
,
81 PRERENDER_SCHEME_CANCEL_REASON_UNKNOWN
,
82 PRERENDER_SCHEME_CANCEL_REASON_MAX
,
85 void ReportPrerenderSchemeCancelReason(PrerenderSchemeCancelReason reason
) {
86 UMA_HISTOGRAM_ENUMERATION(
87 "Prerender.SchemeCancelReason", reason
,
88 PRERENDER_SCHEME_CANCEL_REASON_MAX
);
93 const char kChromeNavigateExtraDataKey
[] = "chrome_navigate";
95 bool MaybeGetQueryStringBasedAliasURL(
96 const GURL
& url
, GURL
* alias_url
) {
98 url_parse::Parsed parsed
;
99 url_parse::ParseStandardURL(url
.spec().c_str(), url
.spec().length(),
101 url_parse::Component query
= parsed
.query
;
102 url_parse::Component key
, value
;
103 while (url_parse::ExtractQueryKeyValue(url
.spec().c_str(), &query
, &key
,
105 if (key
.len
!= 3 || strncmp(url
.spec().c_str() + key
.begin
, "url", key
.len
))
107 // We found a url= query string component.
110 url_canon::RawCanonOutputW
<1024> decoded_url
;
111 url_util::DecodeURLEscapeSequences(url
.spec().c_str() + value
.begin
,
112 value
.len
, &decoded_url
);
113 GURL
new_url(base::string16(decoded_url
.data(), decoded_url
.length()));
114 if (!new_url
.is_empty() && new_url
.is_valid()) {
115 *alias_url
= new_url
;
123 uint8
GetQueryStringBasedExperiment(const GURL
& url
) {
124 url_parse::Parsed parsed
;
125 url_parse::ParseStandardURL(url
.spec().c_str(), url
.spec().length(),
127 url_parse::Component query
= parsed
.query
;
128 url_parse::Component key
, value
;
129 while (url_parse::ExtractQueryKeyValue(url
.spec().c_str(), &query
, &key
,
131 if (key
.len
!= 3 || strncmp(url
.spec().c_str() + key
.begin
, "lpe", key
.len
))
134 // We found a lpe= query string component.
137 uint8 exp
= *(url
.spec().c_str() + value
.begin
) - '0';
138 if (exp
< 1 || exp
> 9)
142 return kNoExperiment
;
145 bool IsGoogleDomain(const GURL
& url
) {
146 return StartsWithASCII(url
.host(), std::string("www.google."), true);
149 bool IsGoogleSearchResultURL(const GURL
& url
) {
150 if (!IsGoogleDomain(url
))
152 return (url
.path().empty() ||
153 StartsWithASCII(url
.path(), std::string("/search"), true) ||
154 (url
.path() == "/") ||
155 StartsWithASCII(url
.path(), std::string("/webhp"), true));
158 bool IsNoSwapInExperiment(uint8 experiment_id
) {
159 // Currently, experiments 5 and 6 fall in this category.
160 return experiment_id
== 5 || experiment_id
== 6;
163 bool IsControlGroupExperiment(uint8 experiment_id
) {
164 // Currently, experiments 7 and 8 fall in this category.
165 return experiment_id
== 7 || experiment_id
== 8;
168 void GatherPagespeedData(const ResourceType::Type resource_type
,
169 const GURL
& request_url
,
170 const net::HttpResponseHeaders
* response_headers
) {
171 if (resource_type
!= ResourceType::MAIN_FRAME
||
172 !request_url
.SchemeIsHTTPOrHTTPS())
175 // bucket 0 counts every response seen.
176 UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram
,
177 PAGESPEED_TOTAL_RESPONSES
,
178 PAGESPEED_SERVER_MAXIMUM
);
179 if (!response_headers
)
185 while (response_headers
->EnumerateHeaderLines(&iter
, &name
, &value
)) {
186 if (name
== kModPagespeedHeader
) {
187 // Bucket 1 counts occurences of the X-Mod-Pagespeed header.
188 UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram
,
189 PAGESPEED_MOD_PAGESPEED_SERVER
,
190 PAGESPEED_SERVER_MAXIMUM
);
191 if (!value
.empty()) {
192 // If the header value is in the X-Mod-Pagespeed version number format
193 // then increment the appropriate bucket, otherwise increment bucket 1,
194 // which is the catch-all "unknown version number" bucket.
195 int bucket
= GetXModPagespeedBucketFromVersion(value
);
197 UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram
, bucket
);
199 UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram
, 1);
203 } else if (name
== kPageSpeedHeader
) {
204 // X-Page-Speed header versions are either in the X-Mod-Pagespeed format,
205 // indicating an nginx installation, or they're in the PageSpeed Service
206 // format, indicating a PSS installation, or in some other format,
207 // indicating an unknown installation [possibly IISpeed].
208 if (!value
.empty()) {
209 int bucket
= GetXModPagespeedBucketFromVersion(value
);
211 // Bucket 2 counts occurences of the X-Page-Speed header with a
212 // value in the X-Mod-Pagespeed version number format. We also
213 // count these responses in the version histogram.
214 UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram
,
215 PAGESPEED_NGX_PAGESPEED_SERVER
,
216 PAGESPEED_SERVER_MAXIMUM
);
217 UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram
, bucket
);
218 } else if (IsPageSpeedServiceVersionNumber(value
)) {
219 // Bucket 3 counts occurences of the X-Page-Speed header with a
220 // value in the PageSpeed Service version number format.
221 UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram
,
222 PAGESPEED_PAGESPEED_SERVICE_SERVER
,
223 PAGESPEED_SERVER_MAXIMUM
);
225 // Bucket 4 counts occurences of all other values.
226 UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram
,
227 PAGESPEED_UNKNOWN_SERVER
,
228 PAGESPEED_SERVER_MAXIMUM
);
236 void URLRequestResponseStarted(net::URLRequest
* request
) {
237 const content::ResourceRequestInfo
* info
=
238 content::ResourceRequestInfo::ForRequest(request
);
239 GatherPagespeedData(info
->GetResourceType(),
241 request
->response_headers());
244 void ReportPrerenderExternalURL() {
245 ReportPrerenderSchemeCancelReason(
246 PRERENDER_SCHEME_CANCEL_REASON_EXTERNAL_PROTOCOL
);
249 void ReportUnsupportedPrerenderScheme(const GURL
& url
) {
250 if (url
.SchemeIs("data")) {
251 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_DATA
);
252 } else if (url
.SchemeIs("blob")) {
253 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_BLOB
);
254 } else if (url
.SchemeIsFile()) {
255 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_FILE
);
256 } else if (url
.SchemeIsFileSystem()) {
257 ReportPrerenderSchemeCancelReason(
258 PRERENDER_SCHEME_CANCEL_REASON_FILESYSTEM
);
259 } else if (url
.SchemeIs("ws") || url
.SchemeIs("wss")) {
260 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_WEBSOCKET
);
261 } else if (url
.SchemeIs("ftp")) {
262 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_FTP
);
263 } else if (url
.SchemeIs("chrome")) {
264 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_CHROME
);
265 } else if (url
.SchemeIs("chrome-extension")) {
266 ReportPrerenderSchemeCancelReason(
267 PRERENDER_SCHEME_CANCEL_REASON_CHROME_EXTENSION
);
268 } else if (url
.SchemeIs("about")) {
269 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_ABOUT
);
271 ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_UNKNOWN
);
275 } // namespace prerender