blob: 5fe26e5e5c6218214f28dd24682f0c3e781340e1 [file] [log] [blame]
// Copyright (c) 2011 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 "chrome/browser/prerender/prerender_util.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/string_util.h"
#include "content/public/browser/resource_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "url/url_canon.h"
#include "url/url_parse.h"
#include "url/url_util.h"
#include "webkit/common/resource_type.h"
namespace prerender {
namespace {
const char kModPagespeedHeader[] = "X-Mod-Pagespeed";
const char kPageSpeedHeader[] = "X-Page-Speed";
const char kPagespeedServerHistogram[] =
"Prerender.PagespeedHeader.ServerCounts";
const char kPagespeedVersionHistogram[] =
"Prerender.PagespeedHeader.VersionCounts";
enum PagespeedHeaderServerType {
PAGESPEED_TOTAL_RESPONSES = 0,
PAGESPEED_MOD_PAGESPEED_SERVER = 1,
PAGESPEED_NGX_PAGESPEED_SERVER = 2,
PAGESPEED_PAGESPEED_SERVICE_SERVER = 3,
PAGESPEED_UNKNOWN_SERVER = 4,
PAGESPEED_SERVER_MAXIMUM = 5
};
// Private function to parse the PageSpeed version number and encode it in
// buckets 2 through 99: if it is in the format a.b.c.d-e the bucket will be
// 2 + 2 * (max(c, 10) - 10) + (d > 1 ? 1 : 0); if it is not in this format
// we return zero.
int GetXModPagespeedBucketFromVersion(const std::string& version) {
int a, b, c, d, e;
int num_parsed = sscanf(version.c_str(), "%d.%d.%d.%d-%d",
&a, &b, &c, &d, &e);
int output = 0;
if (num_parsed == 5) {
output = 2;
if (c > 10)
output += 2 * (c - 10);
if (d > 1)
output++;
if (output < 2 || output > 99)
output = 0;
}
return output;
}
// Private function to parse the X-Page-Speed header value and determine
// whether it is in the PageSpeed Service format, namely m_n_dc were m_n is
// a version number and dc is an encoded 2-character value.
bool IsPageSpeedServiceVersionNumber(const std::string& version) {
int a, b;
char c, d, e; // e is to detect EOL as we check that it /isn't/ converted.
int num_parsed = sscanf(version.c_str(), "%d_%d_%c%c%c", &a, &b, &c, &d, &e);
return (num_parsed == 4);
}
enum PrerenderSchemeCancelReason {
PRERENDER_SCHEME_CANCEL_REASON_EXTERNAL_PROTOCOL,
PRERENDER_SCHEME_CANCEL_REASON_DATA,
PRERENDER_SCHEME_CANCEL_REASON_BLOB,
PRERENDER_SCHEME_CANCEL_REASON_FILE,
PRERENDER_SCHEME_CANCEL_REASON_FILESYSTEM,
PRERENDER_SCHEME_CANCEL_REASON_WEBSOCKET,
PRERENDER_SCHEME_CANCEL_REASON_FTP,
PRERENDER_SCHEME_CANCEL_REASON_CHROME,
PRERENDER_SCHEME_CANCEL_REASON_CHROME_EXTENSION,
PRERENDER_SCHEME_CANCEL_REASON_ABOUT,
PRERENDER_SCHEME_CANCEL_REASON_UNKNOWN,
PRERENDER_SCHEME_CANCEL_REASON_MAX,
};
void ReportPrerenderSchemeCancelReason(PrerenderSchemeCancelReason reason) {
UMA_HISTOGRAM_ENUMERATION(
"Prerender.SchemeCancelReason", reason,
PRERENDER_SCHEME_CANCEL_REASON_MAX);
}
} // namespace
const char kChromeNavigateExtraDataKey[] = "chrome_navigate";
bool MaybeGetQueryStringBasedAliasURL(
const GURL& url, GURL* alias_url) {
DCHECK(alias_url);
url_parse::Parsed parsed;
url_parse::ParseStandardURL(url.spec().c_str(), url.spec().length(),
&parsed);
url_parse::Component query = parsed.query;
url_parse::Component key, value;
while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key,
&value)) {
if (key.len != 3 || strncmp(url.spec().c_str() + key.begin, "url", key.len))
continue;
// We found a url= query string component.
if (value.len < 1)
continue;
url_canon::RawCanonOutputW<1024> decoded_url;
url_util::DecodeURLEscapeSequences(url.spec().c_str() + value.begin,
value.len, &decoded_url);
GURL new_url(base::string16(decoded_url.data(), decoded_url.length()));
if (!new_url.is_empty() && new_url.is_valid()) {
*alias_url = new_url;
return true;
}
return false;
}
return false;
}
uint8 GetQueryStringBasedExperiment(const GURL& url) {
url_parse::Parsed parsed;
url_parse::ParseStandardURL(url.spec().c_str(), url.spec().length(),
&parsed);
url_parse::Component query = parsed.query;
url_parse::Component key, value;
while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key,
&value)) {
if (key.len != 3 || strncmp(url.spec().c_str() + key.begin, "lpe", key.len))
continue;
// We found a lpe= query string component.
if (value.len != 1)
continue;
uint8 exp = *(url.spec().c_str() + value.begin) - '0';
if (exp < 1 || exp > 9)
continue;
return exp;
}
return kNoExperiment;
}
bool IsGoogleDomain(const GURL& url) {
return StartsWithASCII(url.host(), std::string("www.google."), true);
}
bool IsGoogleSearchResultURL(const GURL& url) {
if (!IsGoogleDomain(url))
return false;
return (url.path().empty() ||
StartsWithASCII(url.path(), std::string("/search"), true) ||
(url.path() == "/") ||
StartsWithASCII(url.path(), std::string("/webhp"), true));
}
bool IsNoSwapInExperiment(uint8 experiment_id) {
// Currently, experiments 5 and 6 fall in this category.
return experiment_id == 5 || experiment_id == 6;
}
bool IsControlGroupExperiment(uint8 experiment_id) {
// Currently, experiments 7 and 8 fall in this category.
return experiment_id == 7 || experiment_id == 8;
}
void GatherPagespeedData(const ResourceType::Type resource_type,
const GURL& request_url,
const net::HttpResponseHeaders* response_headers) {
if (resource_type != ResourceType::MAIN_FRAME ||
!request_url.SchemeIsHTTPOrHTTPS())
return;
// bucket 0 counts every response seen.
UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
PAGESPEED_TOTAL_RESPONSES,
PAGESPEED_SERVER_MAXIMUM);
if (!response_headers)
return;
void* iter = NULL;
std::string name;
std::string value;
while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
if (name == kModPagespeedHeader) {
// Bucket 1 counts occurences of the X-Mod-Pagespeed header.
UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
PAGESPEED_MOD_PAGESPEED_SERVER,
PAGESPEED_SERVER_MAXIMUM);
if (!value.empty()) {
// If the header value is in the X-Mod-Pagespeed version number format
// then increment the appropriate bucket, otherwise increment bucket 1,
// which is the catch-all "unknown version number" bucket.
int bucket = GetXModPagespeedBucketFromVersion(value);
if (bucket > 0) {
UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram, bucket);
} else {
UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram, 1);
}
}
break;
} else if (name == kPageSpeedHeader) {
// X-Page-Speed header versions are either in the X-Mod-Pagespeed format,
// indicating an nginx installation, or they're in the PageSpeed Service
// format, indicating a PSS installation, or in some other format,
// indicating an unknown installation [possibly IISpeed].
if (!value.empty()) {
int bucket = GetXModPagespeedBucketFromVersion(value);
if (bucket > 0) {
// Bucket 2 counts occurences of the X-Page-Speed header with a
// value in the X-Mod-Pagespeed version number format. We also
// count these responses in the version histogram.
UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
PAGESPEED_NGX_PAGESPEED_SERVER,
PAGESPEED_SERVER_MAXIMUM);
UMA_HISTOGRAM_SPARSE_SLOWLY(kPagespeedVersionHistogram, bucket);
} else if (IsPageSpeedServiceVersionNumber(value)) {
// Bucket 3 counts occurences of the X-Page-Speed header with a
// value in the PageSpeed Service version number format.
UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
PAGESPEED_PAGESPEED_SERVICE_SERVER,
PAGESPEED_SERVER_MAXIMUM);
} else {
// Bucket 4 counts occurences of all other values.
UMA_HISTOGRAM_ENUMERATION(kPagespeedServerHistogram,
PAGESPEED_UNKNOWN_SERVER,
PAGESPEED_SERVER_MAXIMUM);
}
}
break;
}
}
}
void URLRequestResponseStarted(net::URLRequest* request) {
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request);
GatherPagespeedData(info->GetResourceType(),
request->url(),
request->response_headers());
}
void ReportPrerenderExternalURL() {
ReportPrerenderSchemeCancelReason(
PRERENDER_SCHEME_CANCEL_REASON_EXTERNAL_PROTOCOL);
}
void ReportUnsupportedPrerenderScheme(const GURL& url) {
if (url.SchemeIs("data")) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_DATA);
} else if (url.SchemeIs("blob")) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_BLOB);
} else if (url.SchemeIsFile()) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_FILE);
} else if (url.SchemeIsFileSystem()) {
ReportPrerenderSchemeCancelReason(
PRERENDER_SCHEME_CANCEL_REASON_FILESYSTEM);
} else if (url.SchemeIs("ws") || url.SchemeIs("wss")) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_WEBSOCKET);
} else if (url.SchemeIs("ftp")) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_FTP);
} else if (url.SchemeIs("chrome")) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_CHROME);
} else if (url.SchemeIs("chrome-extension")) {
ReportPrerenderSchemeCancelReason(
PRERENDER_SCHEME_CANCEL_REASON_CHROME_EXTENSION);
} else if (url.SchemeIs("about")) {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_ABOUT);
} else {
ReportPrerenderSchemeCancelReason(PRERENDER_SCHEME_CANCEL_REASON_UNKNOWN);
}
}
} // namespace prerender