author | Nicholas Hurley <hurley@todesschaf.org> |
Fri, 25 Oct 2013 14:56:51 -0700 | |
changeset 166117 | 4bcbb58917c962d1e4c51ce436d4bc6fe7852754 |
parent 166116 | e61f767c0dcc65d2f7480663c55b006fe9a84937 |
child 166118 | 85ebad9a27c9c21c8aa7e9ad78ed1de7276c869f |
push id | 3066 |
push user | akeybl@mozilla.com |
push date | Mon, 09 Dec 2013 19:58:46 +0000 |
treeherder | mozilla-beta@a31a0dce83aa [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mcmanus, biesi |
bugs | 881804 |
milestone | 27.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1369,16 +1369,34 @@ pref("network.ftp.idleConnectionTimeout" // 2: HTML // 3: XUL directory viewer // all other values are treated like 2 pref("network.dir.format", 2); // enables the prefetch service (i.e., prefetching of <link rel="next"> URLs). pref("network.prefetch-next", true); +// enables the predictive service +pref("network.seer.enabled", true); +pref("network.seer.enable-hover-on-ssl", false); +pref("network.seer.page-degradation.day", 0); +pref("network.seer.page-degradation.week", 5); +pref("network.seer.page-degradation.month", 10); +pref("network.seer.page-degradation.year", 25); +pref("network.seer.page-degradation.max", 50); +pref("network.seer.subresource-degradation.day", 1); +pref("network.seer.subresource-degradation.week", 10); +pref("network.seer.subresource-degradation.month", 25); +pref("network.seer.subresource-degradation.year", 50); +pref("network.seer.subresource-degradation.max", 100); +pref("network.seer.preconnect-min-confidence", 90); +pref("network.seer.preresolve-min-confidence", 60); +pref("network.seer.redirect-likely-confidence", 75); +pref("network.seer.max-queue-size", 50); + // The following prefs pertain to the negotiate-auth extension (see bug 17578), // which provides transparent Kerberos or NTLM authentication using the SPNEGO // protocol. Each pref is a comma-separated list of keys, where each key has // the format: // [scheme "://"] [host [":" port]] // For example, "foo.com" would match "http://www.foo.com/bar", etc.
--- a/netwerk/base/public/moz.build +++ b/netwerk/base/public/moz.build @@ -51,16 +51,18 @@ XPIDL_SOURCES += [ 'nsILoadGroupChild.idl', 'nsIMIMEInputStream.idl', 'nsIMultiPartChannel.idl', 'nsINestedURI.idl', 'nsINetAddr.idl', 'nsINetUtil.idl', 'nsINetworkLinkService.idl', 'nsINetworkProperties.idl', + 'nsINetworkSeer.idl', + 'nsINetworkSeerVerifier.idl', 'nsINSSErrorsService.idl', 'nsIParentChannel.idl', 'nsIParentRedirectingChannel.idl', 'nsIPermission.idl', 'nsIPermissionManager.idl', 'nsIPrivateBrowsingChannel.idl', 'nsIProgressEventSink.idl', 'nsIPrompt.idl',
new file mode 100644 --- /dev/null +++ b/netwerk/base/public/nsINetworkSeer.idl @@ -0,0 +1,164 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsILoadContext; +interface nsINetworkSeerVerifier; + +typedef unsigned long SeerPredictReason; +typedef unsigned long SeerLearnReason; + +/** + * nsINetworkSeer - learn about pages users visit, and allow us to take + * predictive actions upon future visits. + * NOTE: nsINetworkSeer should only be used on the main thread + */ +[scriptable, uuid(884a39a0-a3ed-4855-826a-fabb73ae878d)] +interface nsINetworkSeer : nsISupports +{ + /** + * Prediction reasons + * + * PREDICT_LINK - we are being asked to take predictive action because + * the user is hovering over a link. + * + * PREDICT_LOAD - we are being asked to take predictive action because + * the user has initiated a pageload. + * + * PREDICT_STARTUP - we are being asked to take predictive action + * because the browser is starting up. + */ + const SeerPredictReason PREDICT_LINK = 0; + const SeerPredictReason PREDICT_LOAD = 1; + const SeerPredictReason PREDICT_STARTUP = 2; + + /** + * Start taking predictive actions + * + * Calling this will cause the seer to (possibly) start + * taking actions such as DNS prefetch and/or TCP preconnect based on + * (1) the host name that we are given, and (2) the reason we are being + * asked to take actions. + * + * @param targetURI - The URI we are being asked to take actions based on. + * @param sourceURI - The URI that is currently loaded. This is so we can + * avoid doing predictive actions for link hover on an HTTPS page (for + * example). + * @param reason - The reason we are being asked to take actions. Can be + * any of the PREDICT_* values above. + * In the case of PREDICT_LINK, targetURI should be the URI of the link + * that is being hovered over, and sourceURI should be the URI of the page + * on which the link appears. + * In the case of PREDICT_LOAD, targetURI should be the URI of the page that + * is being loaded and sourceURI should be null. + * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be + * null. + * @param loadContext - The nsILoadContext of the page load we are predicting + * about. + * @param verifier - An nsINetworkSeerVerifier used in testing to ensure we're + * predicting the way we expect to. Not necessary (or desired) for normal + * operation. + */ + void predict(in nsIURI targetURI, + in nsIURI sourceURI, + in SeerPredictReason reason, + in nsILoadContext loadContext, + in nsINetworkSeerVerifier verifier); + + + /* + * Reasons we are learning something + * + * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a + * pageload (NOTE: this should ONLY be used by tests) + * + * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload + * + * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI + * + * LEARN_STARTUP - we are learning about a page loaded during startup + */ + const SeerLearnReason LEARN_LOAD_TOPLEVEL = 0; + const SeerLearnReason LEARN_LOAD_SUBRESOURCE = 1; + const SeerLearnReason LEARN_LOAD_REDIRECT = 2; + const SeerLearnReason LEARN_STARTUP = 3; + + /** + * Add to our compendium of knowledge + * + * This adds to our prediction database to make things (hopefully) + * smarter next time we predict something. + * + * @param targetURI - The URI that was loaded that we are keeping track of. + * @param sourceURI - The URI that caused targetURI to be loaded (for page + * loads). This means the DOCUMENT URI. + * @param reason - The reason we are learning this bit of knowledge. + * Reason can be any of the LEARN_* values. + * In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a + * subresource of a page, and sourceURI should be the top-level URI. + * In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a + * top-level resource that was redirected to, and sourceURI is the + * ORIGINAL URI of said top-level resource. + * In the case of LEARN_STARTUP, targetURI should be the URI of a page + * that was loaded immediately after browser startup, and sourceURI should + * be null. + * @param loadContext - The nsILoadContext for the page load that we are + * learning about. + */ + void learn(in nsIURI targetURI, + in nsIURI sourceURI, + in SeerLearnReason reason, + in nsILoadContext loadContext); + + /** + * Clear out all our learned knowledge + * + * This removes everything from our database so that any predictions begun + * after this completes will start from a blank slate. + */ + void reset(); +}; + +%{C++ +// Wrapper functions to make use of the seer easier and less invasive +class nsIChannel; +class nsIDocument; +class nsILoadContext; +class nsILoadGroup; +class nsINetworkSeerVerifier; + +namespace mozilla { +namespace net { + +nsresult SeerPredict(nsIURI *targetURI, + nsIURI *sourceURI, + SeerPredictReason reason, + nsILoadContext *loadContext, + nsINetworkSeerVerifier *verifier); + +nsresult SeerLearn(nsIURI *targetURI, + nsIURI *sourceURI, + SeerLearnReason reason, + nsILoadContext *loadContext); + +nsresult SeerLearn(nsIURI *targetURI, + nsIURI *sourceURI, + SeerLearnReason reason, + nsILoadGroup *loadGroup); + +nsresult SeerLearn(nsIURI *targetURI, + nsIURI *sourceURI, + SeerLearnReason reason, + nsIDocument *document); + +nsresult SeerLearnRedirect(nsIURI *targetURI, + nsIChannel *channel, + nsILoadContext *loadContext); + +} // mozilla::net +} // mozilla +%}
new file mode 100644 --- /dev/null +++ b/netwerk/base/public/nsINetworkSeerVerifier.idl @@ -0,0 +1,31 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * nsINetworkSeerVerifier - used for testing the network seer to ensure it + * does what we expect it to do. + */ + +#include "nsISupports.idl" + +interface nsIURI; + +[scriptable, uuid(ea273653-43a8-4632-8b30-4032e0918e8b)] +interface nsINetworkSeerVerifier : nsISupports +{ + /** + * Callback for when we do a predictive preconnect + * + * @param uri - The URI that was preconnected to + */ + void onPredictPreconnect(in nsIURI uri); + + /** + * Callback for when we do a predictive DNS lookup + * + * @param uri - The URI that was looked up + */ + void onPredictDNS(in nsIURI uri); +};
--- a/netwerk/base/public/nsISpeculativeConnect.idl +++ b/netwerk/base/public/nsISpeculativeConnect.idl @@ -25,8 +25,36 @@ interface nsISpeculativeConnect : nsISup * such as nsIBadCertListener. May be null. * */ void speculativeConnect(in nsIURI aURI, in nsIInterfaceRequestor aCallbacks); }; +/** + * This is used to override the default values for various values (documented + * inline) to determine whether or not to actually make a speculative + * connection. + */ +[builtinclass, uuid(2b6d6fb6-ab28-4f4c-af84-bfdbb7866d72)] +interface nsISpeculativeConnectionOverrider : nsISupports +{ + /** + * Used to determine the maximum number of unused speculative connections + * we will have open for a host at any one time + */ + [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit; + + /** + * Used to loosen the restrictions nsHttpConnectionMgr::RestrictConnections + * to allow more speculative connections when we're unsure if a host will + * connect via SPDY or not. + */ + [infallible] readonly attribute boolean ignorePossibleSpdyConnections; + + /** + * Used to determine if we will ignore the existence of any currently idle + * connections when we decide whether or not to make a speculative + * connection. + */ + [infallible] readonly attribute boolean ignoreIdle; +};
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/Seer.cpp @@ -0,0 +1,2189 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include "Seer.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsICancelable.h" +#include "nsIChannel.h" +#include "nsIDNSListener.h" +#include "nsIDNSService.h" +#include "nsIDocument.h" +#include "nsIFile.h" +#include "nsILoadContext.h" +#include "nsILoadGroup.h" +#include "nsINetworkSeerVerifier.h" +#include "nsIObserverService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsISpeculativeConnect.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "prlog.h" + +#include "mozIStorageConnection.h" +#include "mozIStorageService.h" +#include "mozIStorageStatement.h" +#include "mozStorageHelper.h" + +#include "mozilla/Preferences.h" +#include "mozilla/storage.h" +#include "mozilla/Telemetry.h" + +#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) +#include "nsIPropertyBag2.h" +static const int32_t ANDROID_23_VERSION = 10; +#endif + +using namespace mozilla; + +namespace mozilla { +namespace net { + +#define RETURN_IF_FAILED(_rv) \ + do { \ + if (NS_FAILED(_rv)) { \ + return; \ + } \ + } while (0) + +const char SEER_ENABLED_PREF[] = "network.seer.enabled"; +const char SEER_SSL_HOVER_PREF[] = "network.seer.enable-hover-on-ssl"; + +const char SEER_PAGE_DELTA_DAY_PREF[] = "network.seer.page-degradation.day"; +const int SEER_PAGE_DELTA_DAY_DEFAULT = 0; +const char SEER_PAGE_DELTA_WEEK_PREF[] = "network.seer.page-degradation.week"; +const int SEER_PAGE_DELTA_WEEK_DEFAULT = 5; +const char SEER_PAGE_DELTA_MONTH_PREF[] = "network.seer.page-degradation.month"; +const int SEER_PAGE_DELTA_MONTH_DEFAULT = 10; +const char SEER_PAGE_DELTA_YEAR_PREF[] = "network.seer.page-degradation.year"; +const int SEER_PAGE_DELTA_YEAR_DEFAULT = 25; +const char SEER_PAGE_DELTA_MAX_PREF[] = "network.seer.page-degradation.max"; +const int SEER_PAGE_DELTA_MAX_DEFAULT = 50; +const char SEER_SUB_DELTA_DAY_PREF[] = + "network.seer.subresource-degradation.day"; +const int SEER_SUB_DELTA_DAY_DEFAULT = 1; +const char SEER_SUB_DELTA_WEEK_PREF[] = + "network.seer.subresource-degradation.week"; +const int SEER_SUB_DELTA_WEEK_DEFAULT = 10; +const char SEER_SUB_DELTA_MONTH_PREF[] = + "network.seer.subresource-degradation.month"; +const int SEER_SUB_DELTA_MONTH_DEFAULT = 25; +const char SEER_SUB_DELTA_YEAR_PREF[] = + "network.seer.subresource-degradation.year"; +const int SEER_SUB_DELTA_YEAR_DEFAULT = 50; +const char SEER_SUB_DELTA_MAX_PREF[] = + "network.seer.subresource-degradation.max"; +const int SEER_SUB_DELTA_MAX_DEFAULT = 100; + +const char SEER_PRECONNECT_MIN_PREF[] = + "network.seer.preconnect-min-confidence"; +const int PRECONNECT_MIN_DEFAULT = 90; +const char SEER_PRERESOLVE_MIN_PREF[] = + "network.seer.preresolve-min-confidence"; +const int PRERESOLVE_MIN_DEFAULT = 60; +const char SEER_REDIRECT_LIKELY_PREF[] = + "network.seer.redirect-likely-confidence"; +const int REDIRECT_LIKELY_DEFAULT = 75; + +const char SEER_MAX_QUEUE_SIZE_PREF[] = "network.seer.max-queue-size"; +const uint32_t SEER_MAX_QUEUE_SIZE_DEFAULT = 50; + +// All these time values are in usec +const long long ONE_DAY = 86400LL * 1000000LL; +const long long ONE_WEEK = 7LL * ONE_DAY; +const long long ONE_MONTH = 30LL * ONE_DAY; +const long long ONE_YEAR = 365LL * ONE_DAY; + +const long STARTUP_WINDOW = 5L * 60L * 1000000L; // 5min + +// Version for the database schema +static const int32_t SEER_SCHEMA_VERSION = 1; + +struct SeerTelemetryAccumulators { + Telemetry::AutoCounter<Telemetry::SEER_PREDICT_ATTEMPTS> mPredictAttempts; + Telemetry::AutoCounter<Telemetry::SEER_LEARN_ATTEMPTS> mLearnAttempts; + Telemetry::AutoCounter<Telemetry::SEER_PREDICT_FULL_QUEUE> mPredictFullQueue; + Telemetry::AutoCounter<Telemetry::SEER_LEARN_FULL_QUEUE> mLearnFullQueue; + Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PREDICTIONS> mTotalPredictions; + Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRECONNECTS> mTotalPreconnects; + Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRERESOLVES> mTotalPreresolves; + Telemetry::AutoCounter<Telemetry::SEER_PREDICTIONS_CALCULATED> mPredictionsCalculated; +}; + +// Listener for the speculative DNS requests we'll fire off, which just ignores +// the result (since we're just trying to warm the cache). This also exists to +// reduce round-trips to the main thread, by being something threadsafe the Seer +// can use. + +class SeerDNSListener : public nsIDNSListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDNSLISTENER + + SeerDNSListener() + { } + + virtual ~SeerDNSListener() + { } +}; + +NS_IMPL_ISUPPORTS1(SeerDNSListener, nsIDNSListener); + +NS_IMETHODIMP +SeerDNSListener::OnLookupComplete(nsICancelable *request, + nsIDNSRecord *rec, + nsresult status) +{ + return NS_OK; +} + +// Are you ready for the fun part? Because here comes the fun part. The seer, +// which will do awesome stuff as you browse to make your browsing experience +// faster. + +static Seer *gSeer = nullptr; + +#if defined(PR_LOGGING) +static PRLogModuleInfo *gSeerLog = nullptr; +#define SEER_LOG(args) PR_LOG(gSeerLog, 4, args) +#else +#define SEER_LOG(args) +#endif + +NS_IMPL_ISUPPORTS4(Seer, + nsINetworkSeer, + nsIObserver, + nsISpeculativeConnectionOverrider, + nsIInterfaceRequestor) + +Seer::Seer() + :mInitialized(false) + ,mEnabled(true) + ,mEnableHoverOnSSL(false) + ,mPageDegradationDay(SEER_PAGE_DELTA_DAY_DEFAULT) + ,mPageDegradationWeek(SEER_PAGE_DELTA_WEEK_DEFAULT) + ,mPageDegradationMonth(SEER_PAGE_DELTA_MONTH_DEFAULT) + ,mPageDegradationYear(SEER_PAGE_DELTA_YEAR_DEFAULT) + ,mPageDegradationMax(SEER_PAGE_DELTA_MAX_DEFAULT) + ,mSubresourceDegradationDay(SEER_SUB_DELTA_DAY_DEFAULT) + ,mSubresourceDegradationWeek(SEER_SUB_DELTA_WEEK_DEFAULT) + ,mSubresourceDegradationMonth(SEER_SUB_DELTA_MONTH_DEFAULT) + ,mSubresourceDegradationYear(SEER_SUB_DELTA_YEAR_DEFAULT) + ,mSubresourceDegradationMax(SEER_SUB_DELTA_MAX_DEFAULT) + ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT) + ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT) + ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT) + ,mMaxQueueSize(SEER_MAX_QUEUE_SIZE_DEFAULT) + ,mStatements(mDB) + ,mLastStartupTime(0) + ,mStartupCount(0) + ,mQueueSize(0) + ,mQueueSizeLock("Seer.mQueueSizeLock") +{ +#if defined(PR_LOGGING) + gSeerLog = PR_NewLogModule("NetworkSeer"); +#endif + + MOZ_ASSERT(!gSeer, "multiple Seer instances!"); + gSeer = this; +} + +Seer::~Seer() +{ + if (mInitialized) + Shutdown(); + + RemoveObserver(); + + gSeer = nullptr; +} + +// Seer::nsIObserver + +nsresult +Seer::InstallObserver() +{ + MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread"); + + nsresult rv = NS_OK; + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_NOT_AVAILABLE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefs) { + return NS_ERROR_NOT_AVAILABLE; + } + + Preferences::AddBoolVarCache(&mEnabled, SEER_ENABLED_PREF, true); + Preferences::AddBoolVarCache(&mEnableHoverOnSSL, SEER_SSL_HOVER_PREF, false); + Preferences::AddIntVarCache(&mPageDegradationDay, SEER_PAGE_DELTA_DAY_PREF, + SEER_PAGE_DELTA_DAY_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationWeek, SEER_PAGE_DELTA_WEEK_PREF, + SEER_PAGE_DELTA_WEEK_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationMonth, + SEER_PAGE_DELTA_MONTH_PREF, + SEER_PAGE_DELTA_MONTH_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationYear, SEER_PAGE_DELTA_YEAR_PREF, + SEER_PAGE_DELTA_YEAR_DEFAULT); + Preferences::AddIntVarCache(&mPageDegradationMax, SEER_PAGE_DELTA_MAX_PREF, + SEER_PAGE_DELTA_MAX_DEFAULT); + + Preferences::AddIntVarCache(&mSubresourceDegradationDay, + SEER_SUB_DELTA_DAY_PREF, + SEER_SUB_DELTA_DAY_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationWeek, + SEER_SUB_DELTA_WEEK_PREF, + SEER_SUB_DELTA_WEEK_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationMonth, + SEER_SUB_DELTA_MONTH_PREF, + SEER_SUB_DELTA_MONTH_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationYear, + SEER_SUB_DELTA_YEAR_PREF, + SEER_SUB_DELTA_YEAR_DEFAULT); + Preferences::AddIntVarCache(&mSubresourceDegradationMax, + SEER_SUB_DELTA_MAX_PREF, + SEER_SUB_DELTA_MAX_DEFAULT); + + Preferences::AddIntVarCache(&mPreconnectMinConfidence, + SEER_PRECONNECT_MIN_PREF, + PRECONNECT_MIN_DEFAULT); + Preferences::AddIntVarCache(&mPreresolveMinConfidence, + SEER_PRERESOLVE_MIN_PREF, + PRERESOLVE_MIN_DEFAULT); + Preferences::AddIntVarCache(&mRedirectLikelyConfidence, + SEER_REDIRECT_LIKELY_PREF, + REDIRECT_LIKELY_DEFAULT); + + Preferences::AddIntVarCache(&mMaxQueueSize, SEER_MAX_QUEUE_SIZE_PREF, + SEER_MAX_QUEUE_SIZE_DEFAULT); + + return rv; +} + +void +Seer::RemoveObserver() +{ + MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread"); + + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } +} + +NS_IMETHODIMP +Seer::Observe(nsISupports *subject, const char *topic, + const PRUnichar *data_unicode) +{ + nsresult rv = NS_OK; + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { + gSeer->Shutdown(); + } + + return rv; +} + +// Seer::nsISpeculativeConnectionOverrider + +NS_IMETHODIMP +Seer::GetIgnoreIdle(bool *ignoreIdle) +{ + *ignoreIdle = true; + return NS_OK; +} + +NS_IMETHODIMP +Seer::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections) +{ + *ignorePossibleSpdyConnections = true; + return NS_OK; +} + +NS_IMETHODIMP +Seer::GetParallelSpeculativeConnectLimit( + uint32_t *parallelSpeculativeConnectLimit) +{ + *parallelSpeculativeConnectLimit = 6; + return NS_OK; +} + +// Seer::nsIInterfaceRequestor + +NS_IMETHODIMP +Seer::GetInterface(const nsIID &iid, void **result) +{ + return QueryInterface(iid, result); +} + +// Seer::nsINetworkSeer + +nsresult +Seer::Init() +{ + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Seer::Init called off the main thread!"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + +#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) + // This is an ugly hack to disable the seer on android < 2.3, as it doesn't + // play nicely with those android versions, at least on our infra. Causes + // timeouts in reftests. See bug 881804 comment 86. + nsCOMPtr<nsIPropertyBag2> infoService = + do_GetService("@mozilla.org/system-info;1"); + if (infoService) { + int32_t androidVersion = -1; + rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"), + &androidVersion); + if (NS_SUCCEEDED(rv) && (androidVersion < ANDROID_23_VERSION)) { + return NS_ERROR_NOT_AVAILABLE; + } + } +#endif + + mStartupTime = PR_Now(); + + mAccumulators = new SeerTelemetryAccumulators(); + + rv = InstallObserver(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mDNSListener) { + mDNSListener = new SeerDNSListener(); + } + + rv = NS_NewNamedThread("Network Seer", getter_AddRefs(mIOThread)); + NS_ENSURE_SUCCESS(rv, rv); + + mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(mDBFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = mDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite")); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + return rv; +} + +// Make sure that our sqlite storage is all set up with all the tables we need +// to do the work. It isn't the end of the world if this fails, since this is +// all an optimization, anyway. + +nsresult +Seer::EnsureInitStorage() +{ + MOZ_ASSERT(!NS_IsMainThread(), "Initializing seer storage on main thread"); + + if (mDB) { + return NS_OK; + } + + nsresult rv; + + rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB)); + if (NS_FAILED(rv)) { + // Retry once by trashing the file and trying to open again. If this fails, + // we can just bail, and hope for better luck next time. + rv = mDBFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB)); + NS_ENSURE_SUCCESS(rv, rv); + } + + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;")); + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA foreign_keys = ON;")); + + // A table to make sure we're working with the database layout we expect + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_seer_version (\n" + " version INTEGER NOT NULL\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIStorageStatement> stmt; + rv = mDB->CreateStatement( + NS_LITERAL_CSTRING("SELECT version FROM moz_seer_version;\n"), + getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasRows; + rv = stmt->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, rv); + if (hasRows) { + int32_t currentVersion; + rv = stmt->GetInt32(0, ¤tVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // This is what we do while we only have one schema version. Later, we'll + // have to change this to actually upgrade things as appropriate. + MOZ_ASSERT(currentVersion == SEER_SCHEMA_VERSION, + "Invalid seer schema version!"); + if (currentVersion != SEER_SCHEMA_VERSION) { + return NS_ERROR_UNEXPECTED; + } + } else { + stmt = nullptr; + rv = mDB->CreateStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_seer_version (version) VALUES " + "(:seer_version);"), + getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("seer_version"), + SEER_SCHEMA_VERSION); + NS_ENSURE_SUCCESS(rv, rv); + + stmt->Execute(); + } + + stmt = nullptr; + + // This table keeps track of the hosts we've seen at the top level of a + // pageload so we can map them to hosts used for subresources of a pageload. + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_hosts (\n" + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " origin TEXT NOT NULL,\n" + " loads INTEGER DEFAULT 0,\n" + " last_load INTEGER DEFAULT 0\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + // And this is the table that keeps track of the hosts for subresources of a + // pageload. + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subhosts (\n" + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " hid INTEGER NOT NULL,\n" + " origin TEXT NOT NULL,\n" + " hits INTEGER DEFAULT 0,\n" + " last_hit INTEGER DEFAULT 0,\n" + " FOREIGN KEY(hid) REFERENCES moz_hosts(id) ON DELETE CASCADE\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_hid_origin_index " + "ON moz_subhosts (hid, origin);")); + NS_ENSURE_SUCCESS(rv, rv); + + // Table to keep track of how many times we've started up, and when the last + // time was. + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startups (\n" + " startups INTEGER,\n" + " last_startup INTEGER\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDB->CreateStatement( + NS_LITERAL_CSTRING("SELECT startups, last_startup FROM moz_startups;\n"), + getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + // We'll go ahead and keep track of our startup count here, since we can + // (mostly) equate "the service was created and asked to do stuff" with + // "the browser was started up". + rv = stmt->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, rv); + if (hasRows) { + // We've started up before. Update our startup statistics + stmt->GetInt32(0, &mStartupCount); + stmt->GetInt64(1, &mLastStartupTime); + + // This finalizes the statement + stmt = nullptr; + + rv = mDB->CreateStatement( + NS_LITERAL_CSTRING("UPDATE moz_startups SET startups = :startup_count " + "last_startup = :startup_time;\n"), + getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("startup_count"), + mStartupCount + 1); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), + mStartupTime); + NS_ENSURE_SUCCESS(rv, rv); + + stmt->Execute(); + } else { + // This is our first startup, so let's go ahead and mark it as such + mStartupCount = 1; + + rv = mDB->CreateStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_startups (startups, last_startup) " + "VALUES (1, :startup_time);\n"), + getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), + mStartupTime); + NS_ENSURE_SUCCESS(rv, rv); + + stmt->Execute(); + } + + // This finalizes the statement + stmt = nullptr; + + // This table lists URIs loaded at startup, along with how many startups + // they've been loaded during, and when the last time was. + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startup_pages (\n" + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " uri TEXT NOT NULL,\n" + " hits INTEGER DEFAULT 0,\n" + " last_hit INTEGER DEFAULT 0\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + // This table is similar to moz_hosts above, but uses full URIs instead of + // hosts so that we can get more specific predictions for URIs that people + // visit often (such as their email or social network home pages). + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_pages (\n" + " id integer PRIMARY KEY AUTOINCREMENT,\n" + " uri TEXT NOT NULL,\n" + " loads INTEGER DEFAULT 0,\n" + " last_load INTEGER DEFAULT 0\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + // This table is similar to moz_subhosts above, but is instead related to + // moz_pages for finer granularity. + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subresources (\n" + " id integer PRIMARY KEY AUTOINCREMENT,\n" + " pid INTEGER NOT NULL,\n" + " uri TEXT NOT NULL,\n" + " hits INTEGER DEFAULT 0,\n" + " last_hit INTEGER DEFAULT 0,\n" + " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_pid_uri_index " + "ON moz_subresources (pid, uri);")); + NS_ENSURE_SUCCESS(rv, rv); + + // This table keeps track of URIs and what they end up finally redirecting to + // so we can handle redirects in a sane fashion, as well. + rv = mDB->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_redirects (\n" + " id integer PRIMARY KEY AUTOINCREMENT,\n" + " pid integer NOT NULL,\n" + " uri TEXT NOT NULL,\n" + " origin TEXT NOT NULL,\n" + " hits INTEGER DEFAULT 0,\n" + " last_hit INTEGER DEFAULT 0,\n" + " FOREIGN KEY(pid) REFERENCES moz_pages(id)\n" + ");\n")); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +class SeerThreadShutdownRunner : public nsRunnable +{ +public: + SeerThreadShutdownRunner(nsIThread *ioThread) + :mIOThread(ioThread) + { } + + NS_IMETHODIMP Run() MOZ_OVERRIDE + { + MOZ_ASSERT(NS_IsMainThread(), "Shut down seer io thread off main thread"); + mIOThread->Shutdown(); + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mIOThread; +}; + +class SeerDBShutdownRunner : public nsRunnable +{ +public: + SeerDBShutdownRunner(nsIThread *ioThread, nsINetworkSeer *seer) + :mIOThread(ioThread) + { + mSeer = new nsMainThreadPtrHolder<nsINetworkSeer>(seer); + } + + NS_IMETHODIMP Run() MOZ_OVERRIDE + { + MOZ_ASSERT(!NS_IsMainThread(), "Shutting down DB on main thread"); + + gSeer->mStatements.FinalizeStatements(); + gSeer->mDB->Close(); + gSeer->mDB = nullptr; + + nsRefPtr<SeerThreadShutdownRunner> runner = + new SeerThreadShutdownRunner(mIOThread); + NS_DispatchToMainThread(runner); + + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mIOThread; + + // Death grip to keep seer alive while we cleanly close its DB connection + nsMainThreadPtrHandle<nsINetworkSeer> mSeer; +}; + +void +Seer::Shutdown() +{ + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Seer::Shutdown called off the main thread!"); + return; + } + + mInitialized = false; + + if (mIOThread) { + nsCOMPtr<nsIThread> ioThread; + mIOThread.swap(ioThread); + + if (mDB) { + nsRefPtr<SeerDBShutdownRunner> runner = + new SeerDBShutdownRunner(ioThread, this); + ioThread->Dispatch(runner, NS_DISPATCH_NORMAL); + } else { + nsRefPtr<SeerThreadShutdownRunner> runner = + new SeerThreadShutdownRunner(ioThread); + NS_DispatchToMainThread(runner); + } + } +} + +nsresult +Seer::Create(nsISupports *aOuter, const nsIID& aIID, + void **aResult) +{ + nsresult rv; + + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + nsRefPtr<Seer> svc = new Seer(); + + rv = svc->Init(); + if (NS_FAILED(rv)) { + SEER_LOG(("Failed to initialize seer, seer will be a noop")); + } + + // We treat init failure the same as the service being disabled, since this + // is all an optimization anyway. No need to freak people out. That's why we + // gladly continue on QI'ing here. + rv = svc->QueryInterface(aIID, aResult); + + return rv; +} + +// Get the full origin (scheme, host, port) out of a URI (maybe should be part +// of nsIURI instead?) +static void +ExtractOrigin(nsIURI *uri, nsAutoCString &s) +{ + s.Truncate(); + + nsAutoCString scheme; + nsresult rv = uri->GetScheme(scheme); + RETURN_IF_FAILED(rv); + + nsAutoCString host; + rv = uri->GetAsciiHost(host); + RETURN_IF_FAILED(rv); + + int32_t port; + rv = uri->GetPort(&port); + RETURN_IF_FAILED(rv); + + s.Assign(scheme); + s.AppendLiteral("://"); + s.Append(host); + if (port != -1) { + s.AppendLiteral(":"); + s.AppendInt(port); + } +} + +// An event to do the work for a prediction that needs to hit the sqlite +// database. These events should be created on the main thread, and run on +// the seer thread. +class SeerPredictionEvent : public nsRunnable +{ +public: + SeerPredictionEvent(nsIURI *targetURI, nsIURI *sourceURI, + SeerPredictReason reason, + nsINetworkSeerVerifier *verifier) + :mReason(reason) + { + MOZ_ASSERT(NS_IsMainThread(), "Creating prediction event off main thread"); + + mEnqueueTime = TimeStamp::Now(); + + if (verifier) { + mVerifier = new nsMainThreadPtrHolder<nsINetworkSeerVerifier>(verifier); + } + if (targetURI) { + targetURI->GetAsciiSpec(mTargetURI.spec); + ExtractOrigin(targetURI, mTargetURI.origin); + } + if (sourceURI) { + sourceURI->GetAsciiSpec(mSourceURI.spec); + ExtractOrigin(sourceURI, mSourceURI.origin); + } + } + + NS_IMETHOD Run() MOZ_OVERRIDE + { + MOZ_ASSERT(!NS_IsMainThread(), "Running prediction event on main thread"); + + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WAIT_TIME, + mEnqueueTime); + + TimeStamp startTime = TimeStamp::Now(); + + nsresult rv = NS_OK; + + switch (mReason) { + case nsINetworkSeer::PREDICT_LOAD: + gSeer->PredictForPageload(mTargetURI, mVerifier, 0, mEnqueueTime); + break; + case nsINetworkSeer::PREDICT_STARTUP: + gSeer->PredictForStartup(mVerifier, mEnqueueTime); + break; + default: + MOZ_ASSERT(false, "Got unexpected value for predict reason"); + rv = NS_ERROR_UNEXPECTED; + } + + gSeer->FreeSpaceInQueue(); + + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WORK_TIME, + startTime); + + return rv; + } + +private: + Seer::UriInfo mTargetURI; + Seer::UriInfo mSourceURI; + SeerPredictReason mReason; + SeerVerifierHandle mVerifier; + TimeStamp mEnqueueTime; +}; + +// Predicting for a link is easy, and doesn't require the round-trip to the +// seer thread and back to the main thread, since we don't have to hit the db +// for that. +void +Seer::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI, + nsINetworkSeerVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread(), "Predicting for link off main thread"); + + if (!mSpeculativeService) { + return; + } + + if (!mEnableHoverOnSSL) { + bool isSSL = false; + sourceURI->SchemeIs("https", &isSSL); + if (isSSL) { + // We don't want to predict from an HTTPS page, to avoid info leakage + SEER_LOG(("Not predicting for link hover - on an SSL page")); + return; + } + } + + mSpeculativeService->SpeculativeConnect(targetURI, this); + if (verifier) { + verifier->OnPredictPreconnect(targetURI); + } +} + +// This runnable runs on the main thread, and is responsible for actually +// firing off predictive actions (such as TCP/TLS preconnects and DNS lookups) +class SeerPredictionRunner : public nsRunnable +{ +public: + SeerPredictionRunner(SeerVerifierHandle &verifier, TimeStamp predictStartTime) + :mVerifier(verifier) + ,mPredictStartTime(predictStartTime) + { } + + void AddPreconnect(const nsACString &uri) + { + mPreconnects.AppendElement(uri); + } + + void AddPreresolve(const nsACString &uri) + { + mPreresolves.AppendElement(uri); + } + + bool HasWork() const + { + return !(mPreconnects.IsEmpty() && mPreresolves.IsEmpty()); + } + + NS_IMETHOD Run() MOZ_OVERRIDE + { + MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread"); + + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_ACTION, + mPredictStartTime); + + uint32_t len, i; + + len = mPreconnects.Length(); + for (i = 0; i < len; ++i) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreconnects[i]); + if (NS_FAILED(rv)) { + continue; + } + + ++gSeer->mAccumulators->mTotalPredictions; + ++gSeer->mAccumulators->mTotalPreconnects; + gSeer->mSpeculativeService->SpeculativeConnect(uri, gSeer); + if (mVerifier) { + mVerifier->OnPredictPreconnect(uri); + } + } + + len = mPreresolves.Length(); + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + for (i = 0; i < len; ++i) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreresolves[i]); + if (NS_FAILED(rv)) { + continue; + } + + ++gSeer->mAccumulators->mTotalPredictions; + ++gSeer->mAccumulators->mTotalPreresolves; + nsAutoCString hostname; + uri->GetAsciiHost(hostname); + nsCOMPtr<nsICancelable> tmpCancelable; + gSeer->mDnsService->AsyncResolve(hostname, + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | + nsIDNSService::RESOLVE_SPECULATE), + gSeer->mDNSListener, nullptr, + getter_AddRefs(tmpCancelable)); + if (mVerifier) { + mVerifier->OnPredictDNS(uri); + } + } + + mPreconnects.Clear(); + mPreresolves.Clear(); + + return NS_OK; + } + +private: + nsTArray<nsCString> mPreconnects; + nsTArray<nsCString> mPreresolves; + SeerVerifierHandle mVerifier; + TimeStamp mPredictStartTime; +}; + +// This calculates how much to degrade our confidence in our data based on +// the last time this top-level resource was loaded. This "global degradation" +// applies to *all* subresources we have associated with the top-level +// resource. This will be in addition to any reduction in confidence we have +// associated with a particular subresource. +int +Seer::CalculateGlobalDegradation(PRTime now, PRTime lastLoad) +{ + int globalDegradation; + PRTime delta = now - lastLoad; + if (delta < ONE_DAY) { + globalDegradation = mPageDegradationDay; + } else if (delta < ONE_WEEK) { + globalDegradation = mPageDegradationWeek; + } else if (delta < ONE_MONTH) { + globalDegradation = mPageDegradationMonth; + } else if (delta < ONE_YEAR) { + globalDegradation = mPageDegradationYear; + } else { + globalDegradation = mPageDegradationMax; + } + + Telemetry::Accumulate(Telemetry::SEER_GLOBAL_DEGRADATION, globalDegradation); + return globalDegradation; +} + +// This calculates our overall confidence that a particular subresource will be +// loaded as part of a top-level load. +// @param baseConfidence - the basic confidence we have for this subresource, +// which is the percentage of time this top-level load +// loads the subresource in question +// @param lastHit - the timestamp of the last time we loaded this subresource as +// part of this top-level load +// @param lastPossible - the timestamp of the last time we performed this +// top-level load +// @param globalDegradation - the degradation for this top-level load as +// determined by CalculateGlobalDegradation +int +Seer::CalculateConfidence(int baseConfidence, PRTime lastHit, + PRTime lastPossible, int globalDegradation) +{ + ++mAccumulators->mPredictionsCalculated; + + int maxConfidence = 100; + int confidenceDegradation = 0; + + if (lastHit < lastPossible) { + // We didn't load this subresource the last time this top-level load was + // performed, so let's not bother preconnecting (at the very least). + maxConfidence = mPreconnectMinConfidence - 1; + + // Now calculate how much we want to degrade our confidence based on how + // long it's been between the last time we did this top-level load and the + // last time this top-level load included this subresource. + PRTime delta = lastPossible - lastHit; + if (delta == 0) { + confidenceDegradation = 0; + } else if (delta < ONE_DAY) { + confidenceDegradation = mSubresourceDegradationDay; + } else if (delta < ONE_WEEK) { + confidenceDegradation = mSubresourceDegradationWeek; + } else if (delta < ONE_MONTH) { + confidenceDegradation = mSubresourceDegradationMonth; + } else if (delta < ONE_YEAR) { + confidenceDegradation = mSubresourceDegradationYear; + } else { + confidenceDegradation = mSubresourceDegradationMax; + maxConfidence = 0; + } + } + + // Calculate our confidence and clamp it to between 0 and maxConfidence + // (<= 100) + int confidence = baseConfidence - confidenceDegradation - globalDegradation; + confidence = std::max(confidence, 0); + confidence = std::min(confidence, maxConfidence); + + Telemetry::Accumulate(Telemetry::SEER_BASE_CONFIDENCE, baseConfidence); + Telemetry::Accumulate(Telemetry::SEER_SUBRESOURCE_DEGRADATION, + confidenceDegradation); + Telemetry::Accumulate(Telemetry::SEER_CONFIDENCE, confidence); + return confidence; +} + +// (Maybe) adds a predictive action to the prediction runner, based on our +// calculated confidence for the subresource in question. +void +Seer::SetupPrediction(int confidence, const nsACString &uri, + SeerPredictionRunner *runner) +{ + if (confidence >= mPreconnectMinConfidence) { + runner->AddPreconnect(uri); + } else if (confidence >= mPreresolveMinConfidence) { + runner->AddPreresolve(uri); + } +} + +// This gets the data about the top-level load from our database, either from +// the pages table (which is specific to a particular URI), or from the hosts +// table (which is for a particular origin). +bool +Seer::LookupTopLevel(QueryType queryType, const nsACString &key, + TopLevelInfo &info) +{ + MOZ_ASSERT(!NS_IsMainThread(), "LookupTopLevel called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_pages WHERE " + "uri = :key;")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_hosts WHERE " + "origin = :key;")); + } + NS_ENSURE_TRUE(stmt, false); + mozStorageStatementScoper scope(stmt); + + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); + NS_ENSURE_SUCCESS(rv, false); + + bool hasRows; + rv = stmt->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, false); + + if (!hasRows) { + return false; + } + + rv = stmt->GetInt32(0, &info.id); + NS_ENSURE_SUCCESS(rv, false); + + rv = stmt->GetInt32(1, &info.loadCount); + NS_ENSURE_SUCCESS(rv, false); + + rv = stmt->GetInt64(2, &info.lastLoad); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +// Insert data about either a top-level page or a top-level origin into +// the database. +void +Seer::AddTopLevel(QueryType queryType, const nsACString &key, PRTime now) +{ + MOZ_ASSERT(!NS_IsMainThread(), "AddTopLevel called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_pages (uri, loads, last_load) " + "VALUES (:key, 1, :now);")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_hosts (origin, loads, last_load) " + "VALUES (:key, 1, :now);")); + } + if (!stmt) { + return; + } + mozStorageStatementScoper scope(stmt); + + // Loading a page implicitly makes the seer learn about the page, + // so since we don't have it already, let's add it. + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); + RETURN_IF_FAILED(rv); + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); + RETURN_IF_FAILED(rv); + + rv = stmt->Execute(); +} + +// Update data about either a top-level page or a top-level origin in the +// database. +void +Seer::UpdateTopLevel(QueryType queryType, const TopLevelInfo &info, PRTime now) +{ + MOZ_ASSERT(!NS_IsMainThread(), "UpdateTopLevel called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("UPDATE moz_pages SET loads = :load_count, " + "last_load = :now WHERE id = :id;")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("UPDATE moz_hosts SET loads = :load_count, " + "last_load = :now WHERE id = :id;")); + } + if (!stmt) { + return; + } + mozStorageStatementScoper scope(stmt); + + // First, let's update the page in the database, since loading a page + // implicitly learns about the page. + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("load_count"), + info.loadCount + 1); + RETURN_IF_FAILED(rv); + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); + RETURN_IF_FAILED(rv); + + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); + RETURN_IF_FAILED(rv); + + rv = stmt->Execute(); +} + +// Tries to predict for a top-level load (either page-based or origin-based). +// Returns false if it failed to predict at all, true if it did some sort of +// prediction. +// @param queryType - whether to predict based on page or origin +// @param info - the db info about the top-level resource +bool +Seer::TryPredict(QueryType queryType, const TopLevelInfo &info, PRTime now, + SeerVerifierHandle &verifier, TimeStamp &predictStartTime) +{ + MOZ_ASSERT(!NS_IsMainThread(), "TryPredict called on main thread."); + + int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad); + + // Now let's look up the subresources we know about for this page + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_subresources " + "WHERE pid = :id;")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT origin, hits, last_hit FROM moz_subhosts " + "WHERE hid = :id;")); + } + NS_ENSURE_TRUE(stmt, false); + mozStorageStatementScoper scope(stmt); + + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); + NS_ENSURE_SUCCESS(rv, false); + + bool hasRows; + rv = stmt->ExecuteStep(&hasRows); + if (NS_FAILED(rv) || !hasRows) { + return false; + } + + nsRefPtr<SeerPredictionRunner> runner = + new SeerPredictionRunner(verifier, predictStartTime); + + while (hasRows) { + int32_t hitCount; + PRTime lastHit; + nsAutoCString subresource; + int baseConfidence, confidence; + + // We use goto nextrow here instead of just failling, because we want + // to do some sort of prediction if at all possible. Of course, it's + // probably unlikely that subsequent rows will succeed if one fails, but + // it's worth a shot. + + rv = stmt->GetUTF8String(0, subresource); + if NS_FAILED(rv) { + goto nextrow; + } + + rv = stmt->GetInt32(1, &hitCount); + if (NS_FAILED(rv)) { + goto nextrow; + } + + rv = stmt->GetInt64(2, &lastHit); + if (NS_FAILED(rv)) { + goto nextrow; + } + + baseConfidence = (hitCount * 100) / info.loadCount; + confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad, + globalDegradation); + SetupPrediction(confidence, subresource, runner); + +nextrow: + rv = stmt->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, false); + } + + bool predicted = false; + + if (runner->HasWork()) { + NS_DispatchToMainThread(runner); + predicted = true; + } + + return predicted; +} + +// Find out if a top-level page is likely to redirect. +bool +Seer::WouldRedirect(const TopLevelInfo &info, PRTime now, UriInfo &newUri) +{ + MOZ_ASSERT(!NS_IsMainThread(), "WouldRedirect called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT uri, origin, hits, last_hit " + "FROM moz_redirects WHERE pid = :id;")); + NS_ENSURE_TRUE(stmt, false); + mozStorageStatementScoper scope(stmt); + + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); + NS_ENSURE_SUCCESS(rv, false); + + bool hasRows; + rv = stmt->ExecuteStep(&hasRows); + if (NS_FAILED(rv) || !hasRows) { + return false; + } + + rv = stmt->GetUTF8String(0, newUri.spec); + NS_ENSURE_SUCCESS(rv, false); + + rv = stmt->GetUTF8String(1, newUri.origin); + NS_ENSURE_SUCCESS(rv, false); + + int32_t hitCount; + rv = stmt->GetInt32(2, &hitCount); + NS_ENSURE_SUCCESS(rv, false); + + PRTime lastHit; + rv = stmt->GetInt64(3, &lastHit); + NS_ENSURE_SUCCESS(rv, false); + + int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad); + int baseConfidence = (hitCount * 100) / info.loadCount; + int confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad, + globalDegradation); + + if (confidence > mRedirectLikelyConfidence) { + return true; + } + + return false; +} + +// This will add a page to our list of startup pages if it's being loaded +// before our startup window has expired. +void +Seer::MaybeLearnForStartup(const UriInfo &uri, const PRTime now) +{ + MOZ_ASSERT(!NS_IsMainThread(), "MaybeLearnForStartup called on main thread."); + + if ((now - mStartupTime) < STARTUP_WINDOW) { + LearnForStartup(uri); + } +} + +const int MAX_PAGELOAD_DEPTH = 10; + +// This is the driver for prediction based on a new pageload. +void +Seer::PredictForPageload(const UriInfo &uri, SeerVerifierHandle &verifier, + int stackCount, TimeStamp &predictStartTime) +{ + MOZ_ASSERT(!NS_IsMainThread(), "PredictForPageload called on main thread."); + + if (stackCount > MAX_PAGELOAD_DEPTH) { + SEER_LOG(("Too deep into pageload prediction")); + return; + } + + if (NS_FAILED(EnsureInitStorage())) { + return; + } + + PRTime now = PR_Now(); + + MaybeLearnForStartup(uri, now); + + TopLevelInfo pageInfo; + TopLevelInfo originInfo; + bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo); + bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo); + + if (!havePage) { + AddTopLevel(QUERY_PAGE, uri.spec, now); + } else { + UpdateTopLevel(QUERY_PAGE, pageInfo, now); + } + + if (!haveOrigin) { + AddTopLevel(QUERY_ORIGIN, uri.origin, now); + } else { + UpdateTopLevel(QUERY_ORIGIN, originInfo, now); + } + + UriInfo newUri; + if (havePage && WouldRedirect(pageInfo, now, newUri)) { + nsRefPtr<SeerPredictionRunner> runner = + new SeerPredictionRunner(verifier, predictStartTime); + runner->AddPreconnect(newUri.spec); + NS_DispatchToMainThread(runner); + PredictForPageload(newUri, verifier, stackCount + 1, predictStartTime); + return; + } + + bool predicted = false; + + // We always try to be as specific as possible in our predictions, so try + // to predict based on the full URI before we fall back to the origin. + if (havePage) { + predicted = TryPredict(QUERY_PAGE, pageInfo, now, verifier, + predictStartTime); + } + + if (!predicted && haveOrigin) { + predicted = TryPredict(QUERY_ORIGIN, originInfo, now, verifier, + predictStartTime); + } + + if (!predicted) { + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION, + predictStartTime); + } +} + +// This is the driver for predicting at browser startup time based on pages that +// have previously been loaded close to startup. +void +Seer::PredictForStartup(SeerVerifierHandle &verifier, + TimeStamp &predictStartTime) +{ + MOZ_ASSERT(!NS_IsMainThread(), "PredictForStartup called on main thread"); + + if (NS_FAILED(EnsureInitStorage())) { + return; + } + + nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_startup_pages;")); + if (!stmt) { + return; + } + mozStorageStatementScoper scope(stmt); + nsresult rv; + bool hasRows; + + nsRefPtr<SeerPredictionRunner> runner = + new SeerPredictionRunner(verifier, predictStartTime); + + rv = stmt->ExecuteStep(&hasRows); + RETURN_IF_FAILED(rv); + + while (hasRows) { + nsAutoCString uri; + int32_t hitCount; + PRTime lastHit; + int baseConfidence, confidence; + + // We use goto nextrow here instead of just failling, because we want + // to do some sort of prediction if at all possible. Of course, it's + // probably unlikely that subsequent rows will succeed if one fails, but + // it's worth a shot. + + rv = stmt->GetUTF8String(0, uri); + if (NS_FAILED(rv)) { + goto nextrow; + } + + rv = stmt->GetInt32(1, &hitCount); + if (NS_FAILED(rv)) { + goto nextrow; + } + + rv = stmt->GetInt64(2, &lastHit); + if (NS_FAILED(rv)) { + goto nextrow; + } + + baseConfidence = (hitCount * 100) / mStartupCount; + confidence = CalculateConfidence(baseConfidence, lastHit, + mLastStartupTime, 0); + SetupPrediction(confidence, uri, runner); + +nextrow: + rv = stmt->ExecuteStep(&hasRows); + RETURN_IF_FAILED(rv); + } + + if (runner->HasWork()) { + NS_DispatchToMainThread(runner); + } else { + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION, + predictStartTime); + } +} + +// All URIs we get passed *must* be http or https if they're not null. This +// helps ensure that. +static bool +IsNullOrHttp(nsIURI *uri) +{ + if (!uri) { + return true; + } + + bool isHTTP = false; + uri->SchemeIs("http", &isHTTP); + if (!isHTTP) { + uri->SchemeIs("https", &isHTTP); + } + + return isHTTP; +} + +nsresult +Seer::ReserveSpaceInQueue() +{ + MutexAutoLock lock(mQueueSizeLock); + + if (mQueueSize >= mMaxQueueSize) { + SEER_LOG(("Not enqueuing event - queue too large")); + return NS_ERROR_NOT_AVAILABLE; + } + + mQueueSize++; + return NS_OK; +} + +void +Seer::FreeSpaceInQueue() +{ + MutexAutoLock lock(mQueueSizeLock); + MOZ_ASSERT(mQueueSize > 0, "unexpected mQueueSize"); + mQueueSize--; +} + +// Called from the main thread to initiate predictive actions +NS_IMETHODIMP +Seer::Predict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason, + nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier) +{ + MOZ_ASSERT(NS_IsMainThread(), + "Seer interface methods must be called on the main thread"); + + if (!mInitialized) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!mEnabled) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (loadContext && loadContext->UsePrivateBrowsing()) { + // Don't want to do anything in PB mode + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + // Nothing we can do for non-HTTP[S] schemes + return NS_OK; + } + + // Ensure we've been given the appropriate arguments for the kind of + // prediction we're being asked to do + switch (reason) { + case nsINetworkSeer::PREDICT_LINK: + if (!targetURI || !sourceURI) { + return NS_ERROR_INVALID_ARG; + } + // Link hover is a special case where we can predict without hitting the + // db, so let's go ahead and fire off that prediction here. + PredictForLink(targetURI, sourceURI, verifier); + return NS_OK; + case nsINetworkSeer::PREDICT_LOAD: + if (!targetURI || sourceURI) { + return NS_ERROR_INVALID_ARG; + } + break; + case nsINetworkSeer::PREDICT_STARTUP: + if (targetURI || sourceURI) { + return NS_ERROR_INVALID_ARG; + } + break; + default: + return NS_ERROR_INVALID_ARG; + } + + ++mAccumulators->mPredictAttempts; + nsresult rv = ReserveSpaceInQueue(); + if (NS_FAILED(rv)) { + ++mAccumulators->mPredictFullQueue; + return NS_ERROR_NOT_AVAILABLE; + } + + nsRefPtr<SeerPredictionEvent> event = new SeerPredictionEvent(targetURI, + sourceURI, + reason, + verifier); + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); +} + +// A runnable for updating our information in the database. This must always +// be dispatched to the seer thread. +class SeerLearnEvent : public nsRunnable +{ +public: + SeerLearnEvent(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason) + :mReason(reason) + { + MOZ_ASSERT(NS_IsMainThread(), "Creating learn event off main thread"); + + mEnqueueTime = TimeStamp::Now(); + + targetURI->GetAsciiSpec(mTargetURI.spec); + ExtractOrigin(targetURI, mTargetURI.origin); + if (sourceURI) { + sourceURI->GetAsciiSpec(mSourceURI.spec); + ExtractOrigin(sourceURI, mSourceURI.origin); + } + } + + NS_IMETHOD Run() MOZ_OVERRIDE + { + MOZ_ASSERT(!NS_IsMainThread(), "Running learn off main thread"); + + nsresult rv = NS_OK; + + Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WAIT_TIME, + mEnqueueTime); + + TimeStamp startTime = TimeStamp::Now(); + + switch (mReason) { + case nsINetworkSeer::LEARN_LOAD_TOPLEVEL: + gSeer->LearnForToplevel(mTargetURI); + break; + case nsINetworkSeer::LEARN_LOAD_REDIRECT: + gSeer->LearnForRedirect(mTargetURI, mSourceURI); + break; + case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE: + gSeer->LearnForSubresource(mTargetURI, mSourceURI); + break; + case nsINetworkSeer::LEARN_STARTUP: + gSeer->LearnForStartup(mTargetURI); + break; + default: + MOZ_ASSERT(false, "Got unexpected value for learn reason"); + rv = NS_ERROR_UNEXPECTED; + } + + gSeer->FreeSpaceInQueue(); + + Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WORK_TIME, startTime); + + return rv; + } +private: + Seer::UriInfo mTargetURI; + Seer::UriInfo mSourceURI; + SeerLearnReason mReason; + TimeStamp mEnqueueTime; +}; + +void +Seer::LearnForToplevel(const UriInfo &uri) +{ + MOZ_ASSERT(!NS_IsMainThread(), "LearnForToplevel called on main thread."); + + if (NS_FAILED(EnsureInitStorage())) { + return; + } + + PRTime now = PR_Now(); + + MaybeLearnForStartup(uri, now); + + TopLevelInfo pageInfo; + TopLevelInfo originInfo; + bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo); + bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo); + + if (!havePage) { + AddTopLevel(QUERY_PAGE, uri.spec, now); + } else { + UpdateTopLevel(QUERY_PAGE, pageInfo, now); + } + + if (!haveOrigin) { + AddTopLevel(QUERY_ORIGIN, uri.origin, now); + } else { + UpdateTopLevel(QUERY_ORIGIN, originInfo, now); + } +} + +// Queries to look up information about a *specific* subresource associated +// with a *specific* top-level load. +bool +Seer::LookupSubresource(QueryType queryType, const int32_t parentId, + const nsACString &key, SubresourceInfo &info) +{ + MOZ_ASSERT(!NS_IsMainThread(), "LookupSubresource called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subresources " + "WHERE pid = :parent_id AND uri = :key;")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subhosts WHERE " + "hid = :parent_id AND origin = :key;")); + } + NS_ENSURE_TRUE(stmt, false); + mozStorageStatementScoper scope(stmt); + + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"), + parentId); + NS_ENSURE_SUCCESS(rv, false); + + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); + NS_ENSURE_SUCCESS(rv, false); + + bool hasRows; + rv = stmt->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, false); + if (!hasRows) { + return false; + } + + rv = stmt->GetInt32(0, &info.id); + NS_ENSURE_SUCCESS(rv, false); + + rv = stmt->GetInt32(1, &info.hitCount); + NS_ENSURE_SUCCESS(rv, false); + + rv = stmt->GetInt64(2, &info.lastHit); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +// Add information about a new subresource associated with a top-level load. +void +Seer::AddSubresource(QueryType queryType, const int32_t parentId, + const nsACString &key, const PRTime now) +{ + MOZ_ASSERT(!NS_IsMainThread(), "AddSubresource called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_subresources " + "(pid, uri, hits, last_hit) VALUES " + "(:parent_id, :key, 1, :now);")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_subhosts " + "(hid, origin, hits, last_hit) VALUES " + "(:parent_id, :key, 1, :now);")); + } + if (!stmt) { + return; + } + mozStorageStatementScoper scope(stmt); + + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"), + parentId); + RETURN_IF_FAILED(rv); + + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); + RETURN_IF_FAILED(rv); + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); + RETURN_IF_FAILED(rv); + + rv = stmt->Execute(); +} + +// Update the information about a particular subresource associated with a +// top-level load +void +Seer::UpdateSubresource(QueryType queryType, const SubresourceInfo &info, + const PRTime now) +{ + MOZ_ASSERT(!NS_IsMainThread(), "UpdateSubresource called on main thread."); + + nsCOMPtr<mozIStorageStatement> stmt; + if (queryType == QUERY_PAGE) { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("UPDATE moz_subresources SET hits = :hit_count, " + "last_hit = :now WHERE id = :id;")); + } else { + stmt = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("UPDATE moz_subhosts SET hits = :hit_count, " + "last_hit = :now WHERE id = :id;")); + } + if (!stmt) { + return; + } + mozStorageStatementScoper scope(stmt); + + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"), + info.hitCount + 1); + RETURN_IF_FAILED(rv); + + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); + RETURN_IF_FAILED(rv); + + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); + RETURN_IF_FAILED(rv); + + rv = stmt->Execute(); +} + +// Called when a subresource has been hit from a top-level load. Uses the two +// helper functions above to update the database appropriately. +void +Seer::LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI) +{ + MOZ_ASSERT(!NS_IsMainThread(), "LearnForSubresource called on main thread."); + + if (NS_FAILED(EnsureInitStorage())) { + return; + } + + TopLevelInfo pageInfo, originInfo; + bool havePage = LookupTopLevel(QUERY_PAGE, sourceURI.spec, pageInfo); + bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, sourceURI.origin, + originInfo); + + if (!havePage && !haveOrigin) { + // Nothing to do, since we know nothing about the top level resource + return; + } + + SubresourceInfo resourceInfo; + bool haveResource = false; + if (havePage) { + haveResource = LookupSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, + resourceInfo); + } + + SubresourceInfo hostInfo; + bool haveHost = false; + if (haveOrigin) { + haveHost = LookupSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, + hostInfo); + } + + PRTime now = PR_Now(); + + if (haveResource) { + UpdateSubresource(QUERY_PAGE, resourceInfo, now); + } else if (havePage) { + AddSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, now); + } + // Can't add a subresource to a page we don't have in our db. + + if (haveHost) { + UpdateSubresource(QUERY_ORIGIN, hostInfo, now); + } else if (haveOrigin) { + AddSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, now); + } + // Can't add a subhost to a host we don't have in our db +} + +// This is called when a top-level loaded ended up redirecting to a different +// URI so we can keep track of that fact. +void +Seer::LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI) +{ + MOZ_ASSERT(!NS_IsMainThread(), "LearnForRedirect called on main thread."); + + if (NS_FAILED(EnsureInitStorage())) { + return; + } + + PRTime now = PR_Now(); + nsresult rv; + + nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id FROM moz_pages WHERE uri = :spec;")); + if (!getPage) { + return; + } + mozStorageStatementScoper scopedPage(getPage); + + // look up source in moz_pages + rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), + sourceURI.spec); + RETURN_IF_FAILED(rv); + + bool hasRows; + rv = getPage->ExecuteStep(&hasRows); + if (NS_FAILED(rv) || !hasRows) { + return; + } + + int32_t pageId; + rv = getPage->GetInt32(0, &pageId); + RETURN_IF_FAILED(rv); + + nsCOMPtr<mozIStorageStatement> getRedirect = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id, hits FROM moz_redirects WHERE " + "pid = :page_id AND uri = :spec;")); + if (!getRedirect) { + return; + } + mozStorageStatementScoper scopedRedirect(getRedirect); + + rv = getRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); + RETURN_IF_FAILED(rv); + + rv = getRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), + targetURI.spec); + RETURN_IF_FAILED(rv); + + rv = getRedirect->ExecuteStep(&hasRows); + RETURN_IF_FAILED(rv); + + if (!hasRows) { + // This is the first time we've seen this top-level redirect to this URI + nsCOMPtr<mozIStorageStatement> addRedirect = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_redirects " + "(pid, uri, origin, hits, last_hit) VALUES " + "(:page_id, :spec, :origin, 1, :now);")); + if (!addRedirect) { + return; + } + mozStorageStatementScoper scopedAdd(addRedirect); + + rv = addRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); + RETURN_IF_FAILED(rv); + + rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), + targetURI.spec); + RETURN_IF_FAILED(rv); + + rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), + targetURI.origin); + RETURN_IF_FAILED(rv); + + rv = addRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); + RETURN_IF_FAILED(rv); + + rv = addRedirect->Execute(); + } else { + // We've seen this redirect before + int32_t redirId, hits; + rv = getRedirect->GetInt32(0, &redirId); + RETURN_IF_FAILED(rv); + + rv = getRedirect->GetInt32(1, &hits); + RETURN_IF_FAILED(rv); + + nsCOMPtr<mozIStorageStatement> updateRedirect = + mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("UPDATE moz_redirects SET hits = :hits, " + "last_hit = :now WHERE id = :redir;")); + if (!updateRedirect) { + return; + } + mozStorageStatementScoper scopedUpdate(updateRedirect); + + rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("hits"), hits + 1); + RETURN_IF_FAILED(rv); + + rv = updateRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); + RETURN_IF_FAILED(rv); + + rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("redir"), redirId); + RETURN_IF_FAILED(rv); + + updateRedirect->Execute(); + } +} + +// Add information about a top-level load to our list of startup pages +void +Seer::LearnForStartup(const UriInfo &uri) +{ + MOZ_ASSERT(!NS_IsMainThread(), "LearnForStartup called on main thread."); + + if (NS_FAILED(EnsureInitStorage())) { + return; + } + + nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("SELECT id, hits FROM moz_startup_pages WHERE " + "uri = :origin;")); + if (!getPage) { + return; + } + mozStorageStatementScoper scopedPage(getPage); + nsresult rv; + + rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), uri.origin); + RETURN_IF_FAILED(rv); + + bool hasRows; + rv = getPage->ExecuteStep(&hasRows); + RETURN_IF_FAILED(rv); + + if (hasRows) { + // We've loaded this page on startup before + int32_t pageId, hitCount; + + rv = getPage->GetInt32(0, &pageId); + RETURN_IF_FAILED(rv); + + rv = getPage->GetInt32(1, &hitCount); + RETURN_IF_FAILED(rv); + + nsCOMPtr<mozIStorageStatement> updatePage = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("UPDATE moz_startup_pages SET hits = :hit_count, " + "last_hit = :startup_time WHERE id = :page_id;")); + if (!updatePage) { + return; + } + mozStorageStatementScoper scopedUpdate(updatePage); + + rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"), + hitCount + 1); + RETURN_IF_FAILED(rv); + + rv = updatePage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), + mStartupTime); + RETURN_IF_FAILED(rv); + + rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); + RETURN_IF_FAILED(rv); + + updatePage->Execute(); + } else { + // New startup page + nsCOMPtr<mozIStorageStatement> addPage = mStatements.GetCachedStatement( + NS_LITERAL_CSTRING("INSERT INTO moz_startup_pages (uri, hits, " + "last_hit) VALUES (:origin, 1, :startup_time);")); + if (!addPage) { + return; + } + mozStorageStatementScoper scopedAdd(addPage); + rv = addPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), + uri.origin); + RETURN_IF_FAILED(rv); + + rv = addPage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), + mStartupTime); + RETURN_IF_FAILED(rv); + + addPage->Execute(); + } +} + +// Called from the main thread to update the database +NS_IMETHODIMP +Seer::Learn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, + nsILoadContext *loadContext) +{ + MOZ_ASSERT(NS_IsMainThread(), + "Seer interface methods must be called on the main thread"); + + if (!mInitialized) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!mEnabled) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (loadContext && loadContext->UsePrivateBrowsing()) { + // Don't want to do anything in PB mode + return NS_OK; + } + + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { + return NS_ERROR_INVALID_ARG; + } + + switch (reason) { + case nsINetworkSeer::LEARN_LOAD_TOPLEVEL: + case nsINetworkSeer::LEARN_STARTUP: + if (!targetURI || sourceURI) { + return NS_ERROR_INVALID_ARG; + } + break; + case nsINetworkSeer::LEARN_LOAD_REDIRECT: + case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE: + if (!targetURI || !sourceURI) { + return NS_ERROR_INVALID_ARG; + } + break; + default: + return NS_ERROR_INVALID_ARG; + } + + ++mAccumulators->mLearnAttempts; + nsresult rv = ReserveSpaceInQueue(); + if (NS_FAILED(rv)) { + ++mAccumulators->mLearnFullQueue; + return NS_ERROR_NOT_AVAILABLE; + } + + nsRefPtr<SeerLearnEvent> event = new SeerLearnEvent(targetURI, sourceURI, + reason); + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); +} + +// Runnable to clear out the database. Dispatched from the main thread to the +// seer thread +class SeerResetEvent : public nsRunnable +{ +public: + SeerResetEvent() + { } + + NS_IMETHOD Run() MOZ_OVERRIDE + { + MOZ_ASSERT(!NS_IsMainThread(), "Running reset on main thread"); + + gSeer->ResetInternal(); + + return NS_OK; + } +}; + +// Helper that actually does the database wipe. +void +Seer::ResetInternal() +{ + MOZ_ASSERT(!NS_IsMainThread(), "Resetting db on main thread"); + + nsresult rv = EnsureInitStorage(); + RETURN_IF_FAILED(rv); + + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_redirects")); + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages")); + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startups")); + + // These cascade to moz_subresources and moz_subhosts, respectively. + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages")); + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts")); +} + +// Called on the main thread to clear out all our knowledge. Tabula Rasa FTW! +NS_IMETHODIMP +Seer::Reset() +{ + MOZ_ASSERT(NS_IsMainThread(), + "Seer interface methods must be called on the main thread"); + + if (!mInitialized) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsRefPtr<SeerResetEvent> event = new SeerResetEvent(); + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); +} + +// Helper functions to make using the seer easier from native code + +static nsresult +EnsureGlobalSeer(nsINetworkSeer **aSeer) +{ + nsresult rv; + nsCOMPtr<nsINetworkSeer> seer = do_GetService("@mozilla.org/network/seer;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aSeer = seer); + return NS_OK; +} + +nsresult +SeerPredict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason, + nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier) +{ + nsCOMPtr<nsINetworkSeer> seer; + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); + NS_ENSURE_SUCCESS(rv, rv); + + return seer->Predict(targetURI, sourceURI, reason, loadContext, verifier); +} + +nsresult +SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, + nsILoadContext *loadContext) +{ + nsCOMPtr<nsINetworkSeer> seer; + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); + NS_ENSURE_SUCCESS(rv, rv); + + return seer->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, + nsILoadGroup *loadGroup) +{ + nsCOMPtr<nsINetworkSeer> seer; + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadContext> loadContext; + + if (loadGroup) { + nsCOMPtr<nsIInterfaceRequestor> callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) { + loadContext = do_GetInterface(callbacks); + } + } + + return seer->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, + nsIDocument *document) +{ + nsCOMPtr<nsINetworkSeer> seer; + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadContext> loadContext; + + if (document) { + loadContext = document->GetLoadContext(); + } + + return seer->Learn(targetURI, sourceURI, reason, loadContext); +} + +nsresult +SeerLearnRedirect(nsIURI *targetURI, nsIChannel *channel, + nsILoadContext *loadContext) +{ + nsCOMPtr<nsINetworkSeer> seer; + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> sourceURI; + rv = channel->GetOriginalURI(getter_AddRefs(sourceURI)); + NS_ENSURE_SUCCESS(rv, rv); + + bool sameUri; + rv = targetURI->Equals(sourceURI, &sameUri); + NS_ENSURE_SUCCESS(rv, rv); + + if (sameUri) { + return NS_OK; + } + + return seer->Learn(targetURI, sourceURI, + nsINetworkSeer::LEARN_LOAD_REDIRECT, loadContext); +} + +} // ::mozilla::net +} // ::mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/Seer.h @@ -0,0 +1,207 @@ +/* vim: set ts=2 sts=2 et sw=2: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_Seer_h +#define mozilla_net_Seer_h + +#include "nsINetworkSeer.h" + +#include "nsCOMPtr.h" +#include "nsIDNSListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserver.h" +#include "nsISpeculativeConnect.h" +#include "nsProxyRelease.h" + +#include "mozilla/Mutex.h" +#include "mozilla/storage/StatementCache.h" +#include "mozilla/TimeStamp.h" + +class nsIDNSService; +class nsINetworkSeerVerifier; +class nsIThread; + +class mozIStorageConnection; +class mozIStorageService; +class mozIStorageStatement; + +namespace mozilla { +namespace net { + +typedef nsMainThreadPtrHandle<nsINetworkSeerVerifier> SeerVerifierHandle; + +class SeerPredictionRunner; +struct SeerTelemetryAccumulators; +class SeerDNSListener; + +class Seer : public nsINetworkSeer + , public nsIObserver + , public nsISpeculativeConnectionOverrider + , public nsIInterfaceRequestor +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSINETWORKSEER + NS_DECL_NSIOBSERVER + NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER + NS_DECL_NSIINTERFACEREQUESTOR + + Seer(); + virtual ~Seer(); + + nsresult Init(); + void Shutdown(); + static nsresult Create(nsISupports *outer, const nsIID& iid, void **result); + +private: + friend class SeerPredictionEvent; + friend class SeerLearnEvent; + friend class SeerResetEvent; + friend class SeerPredictionRunner; + friend class SeerDBShutdownRunner; + + nsresult EnsureInitStorage(); + + // This is a proxy for the information we need from an nsIURI + struct UriInfo { + nsAutoCString spec; + nsAutoCString origin; + }; + + void PredictForLink(nsIURI *targetURI, + nsIURI *sourceURI, + nsINetworkSeerVerifier *verifier); + void PredictForPageload(const UriInfo &dest, + SeerVerifierHandle &verifier, + int stackCount, + TimeStamp &predictStartTime); + void PredictForStartup(SeerVerifierHandle &verifier, + TimeStamp &predictStartTime); + + // Whether we're working on a page or an origin + enum QueryType { + QUERY_PAGE = 0, + QUERY_ORIGIN + }; + + // Holds info from the db about a top-level page or origin + struct TopLevelInfo { + int32_t id; + int32_t loadCount; + PRTime lastLoad; + }; + + // Holds info from the db about a subresource + struct SubresourceInfo { + int32_t id; + int32_t hitCount; + PRTime lastHit; + }; + + nsresult ReserveSpaceInQueue(); + void FreeSpaceInQueue(); + + int CalculateGlobalDegradation(PRTime now, + PRTime lastLoad); + int CalculateConfidence(int baseConfidence, + PRTime lastHit, + PRTime lastPossible, + int globalDegradation); + void SetupPrediction(int confidence, + const nsACString &uri, + SeerPredictionRunner *runner); + + bool LookupTopLevel(QueryType queryType, + const nsACString &key, + TopLevelInfo &info); + void AddTopLevel(QueryType queryType, + const nsACString &key, + PRTime now); + void UpdateTopLevel(QueryType queryType, + const TopLevelInfo &info, + PRTime now); + bool TryPredict(QueryType queryType, + const TopLevelInfo &info, + PRTime now, + SeerVerifierHandle &verifier, + TimeStamp &predictStartTime); + bool WouldRedirect(const TopLevelInfo &info, + PRTime now, + UriInfo &newUri); + + bool LookupSubresource(QueryType queryType, + const int32_t parentId, + const nsACString &key, + SubresourceInfo &info); + void AddSubresource(QueryType queryType, + const int32_t parentId, + const nsACString &key, PRTime now); + void UpdateSubresource(QueryType queryType, + const SubresourceInfo &info, + PRTime now); + + void MaybeLearnForStartup(const UriInfo &uri, const PRTime now); + + void LearnForToplevel(const UriInfo &uri); + void LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI); + void LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI); + void LearnForStartup(const UriInfo &uri); + + void ResetInternal(); + + // Observer-related stuff + nsresult InstallObserver(); + void RemoveObserver(); + + bool mInitialized; + + bool mEnabled; + bool mEnableHoverOnSSL; + + int mPageDegradationDay; + int mPageDegradationWeek; + int mPageDegradationMonth; + int mPageDegradationYear; + int mPageDegradationMax; + + int mSubresourceDegradationDay; + int mSubresourceDegradationWeek; + int mSubresourceDegradationMonth; + int mSubresourceDegradationYear; + int mSubresourceDegradationMax; + + int mPreconnectMinConfidence; + int mPreresolveMinConfidence; + int mRedirectLikelyConfidence; + + int32_t mMaxQueueSize; + + nsCOMPtr<nsIThread> mIOThread; + + nsCOMPtr<nsISpeculativeConnect> mSpeculativeService; + + nsCOMPtr<nsIFile> mDBFile; + nsCOMPtr<mozIStorageService> mStorageService; + nsCOMPtr<mozIStorageConnection> mDB; + mozilla::storage::StatementCache<mozIStorageStatement> mStatements; + + PRTime mStartupTime; + PRTime mLastStartupTime; + int32_t mStartupCount; + + nsCOMPtr<nsIDNSService> mDnsService; + + int32_t mQueueSize; + mozilla::Mutex mQueueSizeLock; + + nsAutoPtr<SeerTelemetryAccumulators> mAccumulators; + + nsRefPtr<SeerDNSListener> mDNSListener; +}; + +} // ::mozilla::net +} // ::mozilla + +#endif // mozilla_net_Seer_h
--- a/netwerk/base/src/moz.build +++ b/netwerk/base/src/moz.build @@ -68,16 +68,17 @@ SOURCES += [ 'nsTransportUtils.cpp', 'nsUDPServerSocket.cpp', 'nsUnicharStreamLoader.cpp', 'nsURIChecker.cpp', 'nsURLHelper.cpp', 'nsURLParsers.cpp', 'ProxyAutoConfig.cpp', 'RedirectChannelRegistrar.cpp', + 'Seer.cpp', 'StreamingProtocolService.cpp', 'Tickler.cpp', ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2': SOURCES += [ 'nsURLHelperOS2.cpp', ]
--- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -431,16 +431,27 @@ #define NS_REDIRECTCHANNELREGISTRAR_CID \ { /* {b69043a6-8929-4d60-8d17-a27e44a8393e} */ \ 0xb69043a6, \ 0x8929, \ 0x4d60, \ { 0x8d, 0x17, 0xa2, 0x7e, 0x44, 0xa8, 0x39, 0x3e } \ } +// service implementing nsINetworkSeer +#define NS_NETWORKSEER_CONTRACTID \ + "@mozilla.org/network/seer;1" +#define NS_NETWORKSEER_CID \ +{ /* {1C218009-A531-46AD-8351-1E7F45D5A3C4} */ \ + 0x1C218009, \ + 0xA531, \ + 0x46AD, \ + { 0x83, 0x51, 0x1E, 0x7F, 0x45, 0xD5, 0xA3, 0xC4 } \ +} + /****************************************************************************** * netwerk/cache/ classes */ // service implementing nsICacheService. #define NS_CACHESERVICE_CONTRACTID \ "@mozilla.org/network/cache-service;1" #define NS_CACHESERVICE_CID \
--- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -31,16 +31,17 @@ #include "nsApplicationCacheService.h" #include "nsMimeTypes.h" #include "nsNetStrings.h" #include "nsDNSPrefetch.h" #include "nsAboutProtocolHandler.h" #include "nsXULAppAPI.h" #include "nsCategoryCache.h" #include "nsIContentSniffer.h" +#include "Seer.h" #include "nsNetUtil.h" #include "nsIThreadPool.h" #include "mozilla/net/NeckoChild.h" #include "nsNetCID.h" #ifndef XP_MACOSX #define BUILD_BINHEX_DECODER 1 @@ -805,16 +806,17 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERV #elif defined(MOZ_ENABLE_QTNETWORK) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_WIDGET_ANDROID) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #endif NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID); NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID); NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID); +NS_DEFINE_NAMED_CID(NS_NETWORKSEER_CID); static const mozilla::Module::CIDEntry kNeckoCIDs[] = { { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor }, { &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor }, { &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor }, { &kNS_SERVERSOCKET_CID, false, nullptr, nsServerSocketConstructor }, { &kNS_UDPSERVERSOCKET_CID, false, nullptr, nsUDPServerSocketConstructor }, { &kNS_SOCKETPROVIDERSERVICE_CID, false, nullptr, nsSocketProviderService::Create }, @@ -945,16 +947,17 @@ static const mozilla::Module::CIDEntry k #elif defined(MOZ_ENABLE_QTNETWORK) { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsQtNetworkLinkServiceConstructor }, #elif defined(MOZ_WIDGET_ANDROID) { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor }, #endif { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor }, { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor }, { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor }, + { &kNS_NETWORKSEER_CID, false, NULL, mozilla::net::Seer::Create }, { nullptr } }; static const mozilla::Module::ContractIDEntry kNeckoContracts[] = { { NS_IOSERVICE_CONTRACTID, &kNS_IOSERVICE_CID }, { NS_NETUTIL_CONTRACTID, &kNS_IOSERVICE_CID }, { NS_STREAMTRANSPORTSERVICE_CONTRACTID, &kNS_STREAMTRANSPORTSERVICE_CID }, { NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID }, @@ -1088,16 +1091,17 @@ static const mozilla::Module::ContractID #elif defined(MOZ_ENABLE_QTNETWORK) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_WIDGET_ANDROID) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #endif { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID }, { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID }, { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID }, + { NS_NETWORKSEER_CONTRACTID, &kNS_NETWORKSEER_CID }, { nullptr } }; static const mozilla::Module kNeckoModule = { mozilla::Module::kVersion, kNeckoCIDs, kNeckoContracts, kNeckoCategories,
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -313,37 +313,80 @@ nsHttpConnectionMgr::DoShiftReloadConnec nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup, 0, connInfo); if (NS_SUCCEEDED(rv)) connInfo.forget(); return rv; } +class SpeculativeConnectArgs +{ +public: + SpeculativeConnectArgs() { mOverridesOK = false; } + virtual ~SpeculativeConnectArgs() {} + + // Added manually so we can use nsRefPtr without inheriting from + // nsISupports + NS_IMETHOD_(nsrefcnt) AddRef(void); + NS_IMETHOD_(nsrefcnt) Release(void); + +public: // intentional! + nsRefPtr<NullHttpTransaction> mTrans; + + bool mOverridesOK; + uint32_t mParallelSpeculativeConnectLimit; + bool mIgnoreIdle; + bool mIgnorePossibleSpdyConnections; + + // As above, added manually so we can use nsRefPtr without inheriting from + // nsISupports +protected: + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD +}; + +NS_IMPL_ADDREF(SpeculativeConnectArgs) +NS_IMPL_RELEASE(SpeculativeConnectArgs) + nsresult nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci, nsIInterfaceRequestor *callbacks, uint32_t caps) { + MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!"); + LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n", ci->HashKey().get())); + nsRefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs(); + // Wrap up the callbacks and the target to ensure they're released on the target // thread properly. nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks; NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks)); caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0; - nsRefPtr<NullHttpTransaction> trans = - new NullHttpTransaction(ci, wrappedCallbacks, caps); + args->mTrans = new NullHttpTransaction(ci, wrappedCallbacks, caps); + + nsCOMPtr<nsISpeculativeConnectionOverrider> overrider = + do_GetInterface(callbacks); + if (overrider) { + args->mOverridesOK = true; + overrider->GetParallelSpeculativeConnectLimit( + &args->mParallelSpeculativeConnectLimit); + overrider->GetIgnoreIdle(&args->mIgnoreIdle); + overrider->GetIgnorePossibleSpdyConnections( + &args->mIgnorePossibleSpdyConnections); + } nsresult rv = - PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, trans); + PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args); if (NS_SUCCEEDED(rv)) - trans.forget(); + args.forget(); return rv; } nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target) { EnsureSocketThreadTarget(); @@ -1222,36 +1265,38 @@ nsHttpConnectionMgr::ClosePersistentConn void *closure) { nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure); self->ClosePersistentConnections(ent); return PL_DHASH_NEXT; } bool -nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent) +nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent, + bool ignorePossibleSpdyConnections) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); // If this host is trying to negotiate a SPDY session right now, // don't create any new ssl connections until the result of the // negotiation is known. bool doRestrict = ent->mConnInfo->UsingSSL() && gHttpHandler->IsSpdyEnabled() && - (!ent->mTestedSpdy || ent->mUsingSpdy) && + ((!ent->mTestedSpdy && !ignorePossibleSpdyConnections) || + ent->mUsingSpdy) && (ent->mHalfOpens.Length() || ent->mActiveConns.Length()); // If there are no restrictions, we are done if (!doRestrict) return false; // If the restriction is based on a tcp handshake in progress // let that connect and then see if it was SPDY or not - if (ent->UnconnectedHalfOpens()) + if (ent->UnconnectedHalfOpens() && !ignorePossibleSpdyConnections) return true; // There is a concern that a host is using a mix of HTTP/1 and SPDY. // In that case we don't want to restrict connections just because // there is a single active HTTP/1 session in use. if (ent->mUsingSpdy && ent->mActiveConns.Length()) { bool confirmedRestrict = false; for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) { @@ -2513,36 +2558,48 @@ nsConnectionHandle::TakeTransport(nsISoc return mConn->TakeTransport(aTransport, aInputStream, aOutputStream); } void nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, void *param) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); - nsRefPtr<NullHttpTransaction> trans = - dont_AddRef(static_cast<NullHttpTransaction *>(param)); + nsRefPtr<SpeculativeConnectArgs> args = + dont_AddRef(static_cast<SpeculativeConnectArgs *>(param)); LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n", - trans->ConnectionInfo()->HashKey().get())); + args->mTrans->ConnectionInfo()->HashKey().get())); nsConnectionEntry *ent = - GetOrCreateConnectionEntry(trans->ConnectionInfo()); + GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo()); // If spdy has previously made a preferred entry for this host via // the ip pooling rules. If so, connect to the preferred host instead of // the one directly passed in here. nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); if (preferredEntry) ent = preferredEntry; - if (mNumHalfOpenConns < gHttpHandler->ParallelSpeculativeConnectLimit() && - !ent->mIdleConns.Length() && !RestrictConnections(ent) && - !AtActiveConnectionLimit(ent, trans->Caps())) { - CreateTransport(ent, trans, trans->Caps(), true); + uint32_t parallelSpeculativeConnectLimit = + gHttpHandler->ParallelSpeculativeConnectLimit(); + bool ignorePossibleSpdyConnections = false; + bool ignoreIdle = false; + + if (args->mOverridesOK) { + parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit; + ignorePossibleSpdyConnections = args->mIgnorePossibleSpdyConnections; + ignoreIdle = args->mIgnoreIdle; + } + + if (mNumHalfOpenConns < parallelSpeculativeConnectLimit && + (ignoreIdle || !ent->mIdleConns.Length()) && + !RestrictConnections(ent, ignorePossibleSpdyConnections) && + !AtActiveConnectionLimit(ent, args->mTrans->Caps())) { + CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true); } else { LOG((" Transport not created due to existing connection count\n")); } } bool nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -507,17 +507,17 @@ private: nsresult DispatchAbstractTransaction(nsConnectionEntry *, nsAHttpTransaction *, uint32_t, nsHttpConnection *, int32_t); nsresult BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); - bool RestrictConnections(nsConnectionEntry *); + bool RestrictConnections(nsConnectionEntry *, bool = false); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTarget(); void ClosePersistentConnections(nsConnectionEntry *ent); void ReportProxyTelemetry(nsConnectionEntry *ent); nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *, uint32_t, bool); void AddActiveConn(nsHttpConnection *, nsConnectionEntry *); void DecrementActiveConnCount(nsHttpConnection *);
new file mode 100644 --- /dev/null +++ b/netwerk/test/unit/test_seer.js @@ -0,0 +1,295 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); + +var seer = null; +var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); +var profile = null; + +function extract_origin(uri) { + var o = uri.scheme + "://" + uri.asciiHost; + if (uri.port !== -1) { + o = o + ":" + uri.port; + } + return o; +} + +var LoadContext = function _loadContext() { +}; + +LoadContext.prototype = { + usePrivateBrowsing: false, + + getInterface: function loadContext_getInterface(iid) { + return this.QueryInterface(iid); + }, + + QueryInterface: function loadContext_QueryInterface(iid) { + if (iid.equals(Ci.nsINetworkSeerVerifier) || + iid.equals(Ci.nsILoadContext)) { + return this; + } + + throw Cr.NS_ERROR_NO_INTERFACE; + } +}; + +var load_context = new LoadContext(); + +var Verifier = function _verifier(testing, expected_preconnects, expected_preresolves) { + this.verifying = testing; + this.expected_preconnects = expected_preconnects; + this.expected_preresolves = expected_preresolves; +}; + +Verifier.prototype = { + verifying: null, + expected_preconnects: null, + expected_preresolves: null, + + getInterface: function verifier_getInterface(iid) { + return this.QueryInterface(iid); + }, + + QueryInterface: function verifier_QueryInterface(iid) { + if (iid.equals(Ci.nsINetworkSeerVerifier) || + iid.equals(Ci.nsISupports)) { + return this; + } + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + maybe_run_next_test: function verifier_maybe_run_next_test() { + if (this.expected_preconnects.length === 0 && + this.expected_preresolves.length === 0) { + do_check_true(true, "Well this is unexpected..."); + run_next_test(); + } + }, + + onPredictPreconnect: function verifier_onPredictPreconnect(uri) { + var origin = extract_origin(uri); + var index = this.expected_preconnects.indexOf(origin); + if (index == -1) { + do_check_true(false, "Got preconnect for unexpected uri " + origin); + } else { + this.expected_preconnects.splice(index, 1); + } + this.maybe_run_next_test(); + }, + + onPredictDNS: function verifier_onPredictDNS(uri) { + var origin = extract_origin(uri); + var index = this.expected_preresolves.indexOf(origin); + if (index == -1) { + do_check_true(false, "Got preresolve for unexpected uri " + origin); + } else { + this.expected_preresolves.splice(index, 1); + } + this.maybe_run_next_test(); + } +}; + +function reset_seer() { + seer.reset(); +} + +function newURI(s) { + return ios.newURI(s, null, null); +} + +function test_link_hover() { + reset_seer(); + var uri = newURI("http://localhost:4444/foo/bar"); + var referrer = newURI("http://localhost:4444/foo"); + var preconns = ["http://localhost:4444"]; + + var verifier = new Verifier("hover", preconns, []); + seer.predict(uri, referrer, seer.PREDICT_LINK, load_context, verifier); +} + +function test_pageload() { + reset_seer(); + var toplevel = "http://localhost:4444/index.html"; + var subresources = [ + "http://localhost:4444/style.css", + "http://localhost:4443/jquery.js", + "http://localhost:4444/image.png" + ]; + + var tluri = newURI(toplevel); + seer.learn(tluri, null, seer.LEARN_LOAD_TOPLEVEL, load_context); + var preconns = []; + for (var i = 0; i < subresources.length; i++) { + var sruri = newURI(subresources[i]); + seer.learn(sruri, tluri, seer.LEARN_LOAD_SUBRESOURCE, load_context); + preconns.push(extract_origin(sruri)); + } + + var verifier = new Verifier("pageload", preconns, []); + seer.predict(tluri, null, seer.PREDICT_LOAD, load_context, verifier); +} + +function test_redirect() { + reset_seer(); + var initial = "http://localhost:4443/redirect"; + var target = "http://localhost:4444/index.html"; + var subresources = [ + "http://localhost:4444/style.css", + "http://localhost:4443/jquery.js", + "http://localhost:4444/image.png" + ]; + + var inituri = newURI(initial); + var targeturi = newURI(target); + seer.learn(inituri, null, seer.LEARN_LOAD_TOPLEVEL, load_context); + seer.learn(targeturi, inituri, seer.LEARN_LOAD_REDIRECT, load_context); + seer.learn(targeturi, null, seer.LEARN_LOAD_TOPLEVEL, load_context); + + var preconns = []; + preconns.push(extract_origin(targeturi)); + for (var i = 0; i < subresources.length; i++) { + var sruri = newURI(subresources[i]); + seer.learn(sruri, targeturi, seer.LEARN_LOAD_SUBRESOURCE, load_context); + preconns.push(extract_origin(sruri)); + } + + var verifier = new Verifier("redirect", preconns, []); + seer.predict(inituri, null, seer.PREDICT_LOAD, load_context, verifier); +} + +function test_startup() { + reset_seer(); + var uris = [ + "http://localhost:4444/startup", + "http://localhost:4443/startup" + ]; + var preconns = []; + for (var i = 0; i < uris.length; i++) { + var uri = newURI(uris[i]); + seer.learn(uri, null, seer.LEARN_STARTUP, load_context); + preconns.push(extract_origin(uri)); + } + + var verifier = new Verifier("startup", preconns, []); + seer.predict(null, null, seer.PREDICT_STARTUP, load_context, verifier); +} + +// A class used to guarantee serialization of SQL queries so we can properly +// update last hit times on subresources to ensure the seer tries to do DNS +// preresolve on them instead of preconnecting +var DnsContinueVerifier = function _dnsContinueVerifier(subresource, tluri, preresolves) { + this.subresource = subresource; + this.tluri = tluri; + this.preresolves = preresolves; +}; + +DnsContinueVerifier.prototype = { + subresource: null, + tluri: null, + preresolves: null, + + getInterface: function _dnsContinueVerifier_getInterface(iid) { + return this.QueryInterface(iid); + }, + + QueryInterface: function _dnsContinueVerifier_QueryInterface(iid) { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsINetworkSeerVerifier)) { + return this; + } + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + onPredictPreconnect: function _dnsContinueVerifier_onPredictPreconnect() { + // This means that the seer has learned and done our "checkpoint" prediction + // Now we can get on with the prediction we actually want to test + + // tstamp is 10 days older than now - just over 1 week, which will ensure we + // hit our cutoff for dns vs. preconnect. This is all in usec, hence the + // x1000 on the Date object value. + var tstamp = (new Date().valueOf() * 1000) - (10 * 86400 * 1000000); + + var dbfile = FileUtils.getFile("ProfD", ["seer.sqlite"]); + var dbconn = Services.storage.openDatabase(dbfile); + // We also need to update hits, since the toplevel has been "loaded" a + // second time (from the prediction that kicked off this callback) to ensure + // that the seer will try to do anything for this subresource. + var stmt = "UPDATE moz_subresources SET last_hit = " + tstamp + ", hits = 2 WHERE uri = '" + this.subresource + "';"; + dbconn.executeSimpleSQL(stmt); + dbconn.close(); + + var verifier = new Verifier("dns", [], this.preresolves); + seer.predict(this.tluri, null, seer.PREDICT_LOAD, load_context, verifier); + }, + + onPredictDNS: function _dnsContinueVerifier_onPredictDNS() { + do_check_true(false, "Shouldn't have gotten a preresolve prediction here!"); + } +}; + +function test_dns() { + reset_seer(); + var toplevel = "http://localhost:4444/index.html"; + var subresource = "http://localhost:4443/jquery.js"; + + var tluri = newURI(toplevel); + seer.learn(tluri, null, seer.LEARN_LOAD_TOPLEVEL, load_context); + var sruri = newURI(subresource); + seer.learn(sruri, tluri, seer.LEARN_LOAD_SUBRESOURCE, load_context); + + var preresolves = [extract_origin(sruri)]; + var continue_verifier = new DnsContinueVerifier(subresource, tluri, preresolves); + // Fire off a prediction that will do preconnects so we know when the seer + // thread has gotten to the point where we can update the database manually + seer.predict(tluri, null, seer.PREDICT_LOAD, load_context, continue_verifier); +} + +function test_origin() { + reset_seer(); + var toplevel = "http://localhost:4444/index.html"; + var subresources = [ + "http://localhost:4444/style.css", + "http://localhost:4443/jquery.js", + "http://localhost:4444/image.png" + ]; + + var tluri = newURI(toplevel); + seer.learn(tluri, null, seer.LEARN_LOAD_TOPLEVEL, load_context); + var preconns = []; + for (var i = 0; i < subresources.length; i++) { + var sruri = newURI(subresources[i]); + seer.learn(sruri, tluri, seer.LEARN_LOAD_SUBRESOURCE, load_context); + var origin = extract_origin(sruri); + if (preconns.indexOf(origin) === -1) { + preconns.push(origin); + } + } + + var loaduri = newURI("http://localhost:4444/anotherpage.html"); + var verifier = new Verifier("origin", preconns, []); + seer.predict(loaduri, null, seer.PREDICT_LOAD, load_context, verifier); +} + +var tests = [ + test_link_hover, + test_pageload, + test_redirect, + test_startup, + test_dns, + test_origin +]; + +function run_test() { + tests.forEach(add_test); + profile = do_get_profile(); + seer = Cc["@mozilla.org/network/seer;1"].getService(Ci.nsINetworkSeer); + do_register_cleanup(reset_seer); + run_next_test(); +}
--- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -290,8 +290,13 @@ skip-if = os == "android" [test_bug856978.js] [test_unix_domain.js] # The xpcshell temp directory on Android doesn't seem to let us create # Unix domain sockets. (Perhaps it's a FAT filesystem?) skip-if = os == "android" [test_addr_in_use_error.js] [test_about_networking.js] [test_ping_aboutnetworking.js] +[test_seer.js] +# Android version detection w/in gecko does not work right on infra, so we just +# disable this test on all android versions, even though it's enabled on 2.3+ in +# the wild. +skip-if = os == "android"
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -1754,16 +1754,136 @@ }, "DNS_FAILED_LOOKUP_TIME": { "kind": "exponential", "high": "60000", "n_buckets": 50, "extended_statistics_ok": true, "description": "Time for an unsuccessful DNS OS resolution (msec)" }, + "SEER_PREDICT_ATTEMPTS": { + "kind": "exponential", + "high": "1000 * 1000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "Number of times nsINetworkSeer::Predict is called and attempts to predict" + }, + "SEER_LEARN_ATTEMPTS": { + "kind": "exponential", + "high": "1000 * 1000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "Number of times nsINetworkSeer::Learn is called and attempts to learn" + }, + "SEER_PREDICT_FULL_QUEUE": { + "kind": "exponential", + "high": "60000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "Number of times nsINetworkSeer::Predict doesn't continue because the queue is full" + }, + "SEER_LEARN_FULL_QUEUE": { + "kind": "exponential", + "high": "60000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "Number of times nsINetworkSeer::Learn doesn't continue because the queue is full" + }, + "SEER_PREDICT_WAIT_TIME": { + "kind": "exponential", + "high": "3000", + "n_buckets": 10, + "extended_statistics_ok": true, + "description": "Amount of time a predict event waits in the queue (ms)" + }, + "SEER_PREDICT_WORK_TIME": { + "kind": "exponential", + "high": "3000", + "n_buckets": 10, + "extended_statistics_ok": true, + "description": "Amount of time spent doing the work for predict (ms)" + }, + "SEER_LEARN_WAIT_TIME": { + "kind": "exponential", + "high": "3000", + "n_buckets": 10, + "extended_statistics_ok": true, + "description": "Amount of time a learn event waits in the queue (ms)" + }, + "SEER_LEARN_WORK_TIME": { + "kind": "exponential", + "high": "3000", + "n_buckets": 10, + "extended_statistics_ok": true, + "description": "Amount of time spent doing the work for learn (ms)" + }, + "SEER_TOTAL_PREDICTIONS": { + "kind": "exponential", + "high": "1000 * 1000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "How many actual predictions (preresolves, preconnects, ...) happen" + }, + "SEER_TOTAL_PRECONNECTS": { + "kind": "exponential", + "high": "1000 * 1000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "How many actual preconnects happen" + }, + "SEER_TOTAL_PRERESOLVES": { + "kind": "exponential", + "high": "1000 * 1000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "How many actual preresolves happen" + }, + "SEER_PREDICTIONS_CALCULATED": { + "kind": "exponential", + "high": "1000 * 1000", + "n_buckets": 50, + "extended_statistics_ok": true, + "description": "How many prediction calculations are performed" + }, + "SEER_GLOBAL_DEGRADATION": { + "kind": "linear", + "high": "100", + "n_buckets": 50, + "description": "The global degradation calculated" + }, + "SEER_SUBRESOURCE_DEGRADATION": { + "kind": "linear", + "high": "100", + "n_buckets": 50, + "description": "The degradation calculated for a subresource" + }, + "SEER_BASE_CONFIDENCE": { + "kind": "linear", + "high": "100", + "n_buckets": 50, + "description": "The base confidence calculated for a subresource" + }, + "SEER_CONFIDENCE": { + "kind": "linear", + "high": "100", + "n_buckets": 50, + "description": "The final confidence calculated for a subresource" + }, + "SEER_PREDICT_TIME_TO_ACTION": { + "kind": "exponential", + "high": "3000", + "n_buckets": 10, + "description": "How long it takes from the time Predict() is called to the time we take action" + }, + "SEER_PREDICT_TIME_TO_INACTION": { + "kind": "exponential", + "high": "3000", + "n_buckets": 10, + "description": "How long it takes from the time Predict() is called to the time we figure out there's nothing to do" + }, "FIND_PLUGINS": { "kind": "exponential", "high": "3000", "n_buckets": 10, "extended_statistics_ok": true, "description": "Time spent scanning filesystem for plugins (ms)" }, "CHECK_JAVA_ENABLED": {