1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/base/src/Seer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2844 @@ 1.4 +/* vim: set ts=2 sts=2 et sw=2: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include <algorithm> 1.10 + 1.11 +#include "Seer.h" 1.12 + 1.13 +#include "nsAppDirectoryServiceDefs.h" 1.14 +#include "nsICancelable.h" 1.15 +#include "nsIChannel.h" 1.16 +#include "nsIDNSListener.h" 1.17 +#include "nsIDNSService.h" 1.18 +#include "nsIDocument.h" 1.19 +#include "nsIFile.h" 1.20 +#include "nsILoadContext.h" 1.21 +#include "nsILoadGroup.h" 1.22 +#include "nsINetworkSeerVerifier.h" 1.23 +#include "nsIObserverService.h" 1.24 +#include "nsIPrefBranch.h" 1.25 +#include "nsIPrefService.h" 1.26 +#include "nsISpeculativeConnect.h" 1.27 +#include "nsITimer.h" 1.28 +#include "nsIURI.h" 1.29 +#include "nsNetUtil.h" 1.30 +#include "nsServiceManagerUtils.h" 1.31 +#include "nsTArray.h" 1.32 +#include "nsThreadUtils.h" 1.33 +#ifdef MOZ_NUWA_PROCESS 1.34 +#include "ipc/Nuwa.h" 1.35 +#endif 1.36 +#include "prlog.h" 1.37 + 1.38 +#include "mozIStorageConnection.h" 1.39 +#include "mozIStorageService.h" 1.40 +#include "mozIStorageStatement.h" 1.41 +#include "mozStorageHelper.h" 1.42 + 1.43 +#include "mozilla/Preferences.h" 1.44 +#include "mozilla/storage.h" 1.45 +#include "mozilla/Telemetry.h" 1.46 + 1.47 +#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) 1.48 +#include "nsIPropertyBag2.h" 1.49 +static const int32_t ANDROID_23_VERSION = 10; 1.50 +#endif 1.51 + 1.52 +using namespace mozilla; 1.53 + 1.54 +namespace mozilla { 1.55 +namespace net { 1.56 + 1.57 +#define RETURN_IF_FAILED(_rv) \ 1.58 + do { \ 1.59 + if (NS_FAILED(_rv)) { \ 1.60 + return; \ 1.61 + } \ 1.62 + } while (0) 1.63 + 1.64 +const char SEER_ENABLED_PREF[] = "network.seer.enabled"; 1.65 +const char SEER_SSL_HOVER_PREF[] = "network.seer.enable-hover-on-ssl"; 1.66 + 1.67 +const char SEER_PAGE_DELTA_DAY_PREF[] = "network.seer.page-degradation.day"; 1.68 +const int SEER_PAGE_DELTA_DAY_DEFAULT = 0; 1.69 +const char SEER_PAGE_DELTA_WEEK_PREF[] = "network.seer.page-degradation.week"; 1.70 +const int SEER_PAGE_DELTA_WEEK_DEFAULT = 5; 1.71 +const char SEER_PAGE_DELTA_MONTH_PREF[] = "network.seer.page-degradation.month"; 1.72 +const int SEER_PAGE_DELTA_MONTH_DEFAULT = 10; 1.73 +const char SEER_PAGE_DELTA_YEAR_PREF[] = "network.seer.page-degradation.year"; 1.74 +const int SEER_PAGE_DELTA_YEAR_DEFAULT = 25; 1.75 +const char SEER_PAGE_DELTA_MAX_PREF[] = "network.seer.page-degradation.max"; 1.76 +const int SEER_PAGE_DELTA_MAX_DEFAULT = 50; 1.77 +const char SEER_SUB_DELTA_DAY_PREF[] = 1.78 + "network.seer.subresource-degradation.day"; 1.79 +const int SEER_SUB_DELTA_DAY_DEFAULT = 1; 1.80 +const char SEER_SUB_DELTA_WEEK_PREF[] = 1.81 + "network.seer.subresource-degradation.week"; 1.82 +const int SEER_SUB_DELTA_WEEK_DEFAULT = 10; 1.83 +const char SEER_SUB_DELTA_MONTH_PREF[] = 1.84 + "network.seer.subresource-degradation.month"; 1.85 +const int SEER_SUB_DELTA_MONTH_DEFAULT = 25; 1.86 +const char SEER_SUB_DELTA_YEAR_PREF[] = 1.87 + "network.seer.subresource-degradation.year"; 1.88 +const int SEER_SUB_DELTA_YEAR_DEFAULT = 50; 1.89 +const char SEER_SUB_DELTA_MAX_PREF[] = 1.90 + "network.seer.subresource-degradation.max"; 1.91 +const int SEER_SUB_DELTA_MAX_DEFAULT = 100; 1.92 + 1.93 +const char SEER_PRECONNECT_MIN_PREF[] = 1.94 + "network.seer.preconnect-min-confidence"; 1.95 +const int PRECONNECT_MIN_DEFAULT = 90; 1.96 +const char SEER_PRERESOLVE_MIN_PREF[] = 1.97 + "network.seer.preresolve-min-confidence"; 1.98 +const int PRERESOLVE_MIN_DEFAULT = 60; 1.99 +const char SEER_REDIRECT_LIKELY_PREF[] = 1.100 + "network.seer.redirect-likely-confidence"; 1.101 +const int REDIRECT_LIKELY_DEFAULT = 75; 1.102 + 1.103 +const char SEER_MAX_QUEUE_SIZE_PREF[] = "network.seer.max-queue-size"; 1.104 +const uint32_t SEER_MAX_QUEUE_SIZE_DEFAULT = 50; 1.105 + 1.106 +const char SEER_MAX_DB_SIZE_PREF[] = "network.seer.max-db-size"; 1.107 +const int32_t SEER_MAX_DB_SIZE_DEFAULT_BYTES = 150 * 1024 * 1024; 1.108 +const char SEER_PRESERVE_PERCENTAGE_PREF[] = "network.seer.preserve"; 1.109 +const int32_t SEER_PRESERVE_PERCENTAGE_DEFAULT = 80; 1.110 + 1.111 +// All these time values are in usec 1.112 +const long long ONE_DAY = 86400LL * 1000000LL; 1.113 +const long long ONE_WEEK = 7LL * ONE_DAY; 1.114 +const long long ONE_MONTH = 30LL * ONE_DAY; 1.115 +const long long ONE_YEAR = 365LL * ONE_DAY; 1.116 + 1.117 +const long STARTUP_WINDOW = 5L * 60L * 1000000L; // 5min 1.118 + 1.119 +// Version for the database schema 1.120 +static const int32_t SEER_SCHEMA_VERSION = 1; 1.121 + 1.122 +struct SeerTelemetryAccumulators { 1.123 + Telemetry::AutoCounter<Telemetry::SEER_PREDICT_ATTEMPTS> mPredictAttempts; 1.124 + Telemetry::AutoCounter<Telemetry::SEER_LEARN_ATTEMPTS> mLearnAttempts; 1.125 + Telemetry::AutoCounter<Telemetry::SEER_PREDICT_FULL_QUEUE> mPredictFullQueue; 1.126 + Telemetry::AutoCounter<Telemetry::SEER_LEARN_FULL_QUEUE> mLearnFullQueue; 1.127 + Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PREDICTIONS> mTotalPredictions; 1.128 + Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRECONNECTS> mTotalPreconnects; 1.129 + Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRERESOLVES> mTotalPreresolves; 1.130 + Telemetry::AutoCounter<Telemetry::SEER_PREDICTIONS_CALCULATED> mPredictionsCalculated; 1.131 + Telemetry::AutoCounter<Telemetry::SEER_LOAD_COUNT_IS_ZERO> mLoadCountZeroes; 1.132 + Telemetry::AutoCounter<Telemetry::SEER_LOAD_COUNT_OVERFLOWS> mLoadCountOverflows; 1.133 + Telemetry::AutoCounter<Telemetry::SEER_STARTUP_COUNT_IS_ZERO> mStartupCountZeroes; 1.134 + Telemetry::AutoCounter<Telemetry::SEER_STARTUP_COUNT_OVERFLOWS> mStartupCountOverflows; 1.135 +}; 1.136 + 1.137 +// Listener for the speculative DNS requests we'll fire off, which just ignores 1.138 +// the result (since we're just trying to warm the cache). This also exists to 1.139 +// reduce round-trips to the main thread, by being something threadsafe the Seer 1.140 +// can use. 1.141 + 1.142 +class SeerDNSListener : public nsIDNSListener 1.143 +{ 1.144 +public: 1.145 + NS_DECL_THREADSAFE_ISUPPORTS 1.146 + NS_DECL_NSIDNSLISTENER 1.147 + 1.148 + SeerDNSListener() 1.149 + { } 1.150 + 1.151 + virtual ~SeerDNSListener() 1.152 + { } 1.153 +}; 1.154 + 1.155 +NS_IMPL_ISUPPORTS(SeerDNSListener, nsIDNSListener); 1.156 + 1.157 +NS_IMETHODIMP 1.158 +SeerDNSListener::OnLookupComplete(nsICancelable *request, 1.159 + nsIDNSRecord *rec, 1.160 + nsresult status) 1.161 +{ 1.162 + return NS_OK; 1.163 +} 1.164 + 1.165 +// Are you ready for the fun part? Because here comes the fun part. The seer, 1.166 +// which will do awesome stuff as you browse to make your browsing experience 1.167 +// faster. 1.168 + 1.169 +static Seer *gSeer = nullptr; 1.170 + 1.171 +#if defined(PR_LOGGING) 1.172 +static PRLogModuleInfo *gSeerLog = nullptr; 1.173 +#define SEER_LOG(args) PR_LOG(gSeerLog, 4, args) 1.174 +#else 1.175 +#define SEER_LOG(args) 1.176 +#endif 1.177 + 1.178 +NS_IMPL_ISUPPORTS(Seer, 1.179 + nsINetworkSeer, 1.180 + nsIObserver, 1.181 + nsISpeculativeConnectionOverrider, 1.182 + nsIInterfaceRequestor) 1.183 + 1.184 +Seer::Seer() 1.185 + :mInitialized(false) 1.186 + ,mEnabled(true) 1.187 + ,mEnableHoverOnSSL(false) 1.188 + ,mPageDegradationDay(SEER_PAGE_DELTA_DAY_DEFAULT) 1.189 + ,mPageDegradationWeek(SEER_PAGE_DELTA_WEEK_DEFAULT) 1.190 + ,mPageDegradationMonth(SEER_PAGE_DELTA_MONTH_DEFAULT) 1.191 + ,mPageDegradationYear(SEER_PAGE_DELTA_YEAR_DEFAULT) 1.192 + ,mPageDegradationMax(SEER_PAGE_DELTA_MAX_DEFAULT) 1.193 + ,mSubresourceDegradationDay(SEER_SUB_DELTA_DAY_DEFAULT) 1.194 + ,mSubresourceDegradationWeek(SEER_SUB_DELTA_WEEK_DEFAULT) 1.195 + ,mSubresourceDegradationMonth(SEER_SUB_DELTA_MONTH_DEFAULT) 1.196 + ,mSubresourceDegradationYear(SEER_SUB_DELTA_YEAR_DEFAULT) 1.197 + ,mSubresourceDegradationMax(SEER_SUB_DELTA_MAX_DEFAULT) 1.198 + ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT) 1.199 + ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT) 1.200 + ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT) 1.201 + ,mMaxQueueSize(SEER_MAX_QUEUE_SIZE_DEFAULT) 1.202 + ,mStatements(mDB) 1.203 + ,mLastStartupTime(0) 1.204 + ,mStartupCount(0) 1.205 + ,mQueueSize(0) 1.206 + ,mQueueSizeLock("Seer.mQueueSizeLock") 1.207 + ,mCleanupScheduled(false) 1.208 + ,mMaxDBSize(SEER_MAX_DB_SIZE_DEFAULT_BYTES) 1.209 + ,mPreservePercentage(SEER_PRESERVE_PERCENTAGE_DEFAULT) 1.210 + ,mLastCleanupTime(0) 1.211 +{ 1.212 +#if defined(PR_LOGGING) 1.213 + gSeerLog = PR_NewLogModule("NetworkSeer"); 1.214 +#endif 1.215 + 1.216 + MOZ_ASSERT(!gSeer, "multiple Seer instances!"); 1.217 + gSeer = this; 1.218 +} 1.219 + 1.220 +Seer::~Seer() 1.221 +{ 1.222 + if (mInitialized) 1.223 + Shutdown(); 1.224 + 1.225 + RemoveObserver(); 1.226 + 1.227 + gSeer = nullptr; 1.228 +} 1.229 + 1.230 +// Seer::nsIObserver 1.231 + 1.232 +nsresult 1.233 +Seer::InstallObserver() 1.234 +{ 1.235 + MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread"); 1.236 + 1.237 + nsresult rv = NS_OK; 1.238 + nsCOMPtr<nsIObserverService> obs = 1.239 + mozilla::services::GetObserverService(); 1.240 + if (!obs) { 1.241 + return NS_ERROR_NOT_AVAILABLE; 1.242 + } 1.243 + 1.244 + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 1.245 + NS_ENSURE_SUCCESS(rv, rv); 1.246 + 1.247 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.248 + if (!prefs) { 1.249 + return NS_ERROR_NOT_AVAILABLE; 1.250 + } 1.251 + 1.252 + Preferences::AddBoolVarCache(&mEnabled, SEER_ENABLED_PREF, true); 1.253 + Preferences::AddBoolVarCache(&mEnableHoverOnSSL, SEER_SSL_HOVER_PREF, false); 1.254 + Preferences::AddIntVarCache(&mPageDegradationDay, SEER_PAGE_DELTA_DAY_PREF, 1.255 + SEER_PAGE_DELTA_DAY_DEFAULT); 1.256 + Preferences::AddIntVarCache(&mPageDegradationWeek, SEER_PAGE_DELTA_WEEK_PREF, 1.257 + SEER_PAGE_DELTA_WEEK_DEFAULT); 1.258 + Preferences::AddIntVarCache(&mPageDegradationMonth, 1.259 + SEER_PAGE_DELTA_MONTH_PREF, 1.260 + SEER_PAGE_DELTA_MONTH_DEFAULT); 1.261 + Preferences::AddIntVarCache(&mPageDegradationYear, SEER_PAGE_DELTA_YEAR_PREF, 1.262 + SEER_PAGE_DELTA_YEAR_DEFAULT); 1.263 + Preferences::AddIntVarCache(&mPageDegradationMax, SEER_PAGE_DELTA_MAX_PREF, 1.264 + SEER_PAGE_DELTA_MAX_DEFAULT); 1.265 + 1.266 + Preferences::AddIntVarCache(&mSubresourceDegradationDay, 1.267 + SEER_SUB_DELTA_DAY_PREF, 1.268 + SEER_SUB_DELTA_DAY_DEFAULT); 1.269 + Preferences::AddIntVarCache(&mSubresourceDegradationWeek, 1.270 + SEER_SUB_DELTA_WEEK_PREF, 1.271 + SEER_SUB_DELTA_WEEK_DEFAULT); 1.272 + Preferences::AddIntVarCache(&mSubresourceDegradationMonth, 1.273 + SEER_SUB_DELTA_MONTH_PREF, 1.274 + SEER_SUB_DELTA_MONTH_DEFAULT); 1.275 + Preferences::AddIntVarCache(&mSubresourceDegradationYear, 1.276 + SEER_SUB_DELTA_YEAR_PREF, 1.277 + SEER_SUB_DELTA_YEAR_DEFAULT); 1.278 + Preferences::AddIntVarCache(&mSubresourceDegradationMax, 1.279 + SEER_SUB_DELTA_MAX_PREF, 1.280 + SEER_SUB_DELTA_MAX_DEFAULT); 1.281 + 1.282 + Preferences::AddIntVarCache(&mPreconnectMinConfidence, 1.283 + SEER_PRECONNECT_MIN_PREF, 1.284 + PRECONNECT_MIN_DEFAULT); 1.285 + Preferences::AddIntVarCache(&mPreresolveMinConfidence, 1.286 + SEER_PRERESOLVE_MIN_PREF, 1.287 + PRERESOLVE_MIN_DEFAULT); 1.288 + Preferences::AddIntVarCache(&mRedirectLikelyConfidence, 1.289 + SEER_REDIRECT_LIKELY_PREF, 1.290 + REDIRECT_LIKELY_DEFAULT); 1.291 + 1.292 + Preferences::AddIntVarCache(&mMaxQueueSize, SEER_MAX_QUEUE_SIZE_PREF, 1.293 + SEER_MAX_QUEUE_SIZE_DEFAULT); 1.294 + 1.295 + Preferences::AddIntVarCache(&mMaxDBSize, SEER_MAX_DB_SIZE_PREF, 1.296 + SEER_MAX_DB_SIZE_DEFAULT_BYTES); 1.297 + Preferences::AddIntVarCache(&mPreservePercentage, 1.298 + SEER_PRESERVE_PERCENTAGE_PREF, 1.299 + SEER_PRESERVE_PERCENTAGE_DEFAULT); 1.300 + 1.301 + return rv; 1.302 +} 1.303 + 1.304 +void 1.305 +Seer::RemoveObserver() 1.306 +{ 1.307 + MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread"); 1.308 + 1.309 + nsCOMPtr<nsIObserverService> obs = 1.310 + mozilla::services::GetObserverService(); 1.311 + if (obs) { 1.312 + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 1.313 + } 1.314 +} 1.315 + 1.316 +static const uint32_t COMMIT_TIMER_DELTA_MS = 5 * 1000; 1.317 + 1.318 +class SeerCommitTimerInitEvent : public nsRunnable 1.319 +{ 1.320 +public: 1.321 + NS_IMETHOD Run() MOZ_OVERRIDE 1.322 + { 1.323 + nsresult rv = NS_OK; 1.324 + 1.325 + if (!gSeer->mCommitTimer) { 1.326 + gSeer->mCommitTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); 1.327 + } else { 1.328 + gSeer->mCommitTimer->Cancel(); 1.329 + } 1.330 + if (NS_SUCCEEDED(rv)) { 1.331 + gSeer->mCommitTimer->Init(gSeer, COMMIT_TIMER_DELTA_MS, 1.332 + nsITimer::TYPE_ONE_SHOT); 1.333 + } 1.334 + 1.335 + return NS_OK; 1.336 + } 1.337 +}; 1.338 + 1.339 +class SeerNewTransactionEvent : public nsRunnable 1.340 +{ 1.341 + NS_IMETHODIMP Run() MOZ_OVERRIDE 1.342 + { 1.343 + gSeer->CommitTransaction(); 1.344 + gSeer->BeginTransaction(); 1.345 + gSeer->MaybeScheduleCleanup(); 1.346 + nsRefPtr<SeerCommitTimerInitEvent> event = new SeerCommitTimerInitEvent(); 1.347 + NS_DispatchToMainThread(event); 1.348 + return NS_OK; 1.349 + } 1.350 +}; 1.351 + 1.352 +NS_IMETHODIMP 1.353 +Seer::Observe(nsISupports *subject, const char *topic, 1.354 + const char16_t *data_unicode) 1.355 +{ 1.356 + nsresult rv = NS_OK; 1.357 + MOZ_ASSERT(NS_IsMainThread(), "Seer observing something off main thread!"); 1.358 + 1.359 + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { 1.360 + Shutdown(); 1.361 + } else if (!strcmp(NS_TIMER_CALLBACK_TOPIC, topic)) { 1.362 + if (mInitialized) { // Can't access io thread if we're not initialized! 1.363 + nsRefPtr<SeerNewTransactionEvent> event = new SeerNewTransactionEvent(); 1.364 + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.365 + } 1.366 + } 1.367 + 1.368 + return rv; 1.369 +} 1.370 + 1.371 +// Seer::nsISpeculativeConnectionOverrider 1.372 + 1.373 +NS_IMETHODIMP 1.374 +Seer::GetIgnoreIdle(bool *ignoreIdle) 1.375 +{ 1.376 + *ignoreIdle = true; 1.377 + return NS_OK; 1.378 +} 1.379 + 1.380 +NS_IMETHODIMP 1.381 +Seer::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections) 1.382 +{ 1.383 + *ignorePossibleSpdyConnections = true; 1.384 + return NS_OK; 1.385 +} 1.386 + 1.387 +NS_IMETHODIMP 1.388 +Seer::GetParallelSpeculativeConnectLimit( 1.389 + uint32_t *parallelSpeculativeConnectLimit) 1.390 +{ 1.391 + *parallelSpeculativeConnectLimit = 6; 1.392 + return NS_OK; 1.393 +} 1.394 + 1.395 +// Seer::nsIInterfaceRequestor 1.396 + 1.397 +NS_IMETHODIMP 1.398 +Seer::GetInterface(const nsIID &iid, void **result) 1.399 +{ 1.400 + return QueryInterface(iid, result); 1.401 +} 1.402 + 1.403 +#ifdef MOZ_NUWA_PROCESS 1.404 +class NuwaMarkSeerThreadRunner : public nsRunnable 1.405 +{ 1.406 + NS_IMETHODIMP Run() MOZ_OVERRIDE 1.407 + { 1.408 + if (IsNuwaProcess()) { 1.409 + NS_ASSERTION(NuwaMarkCurrentThread != nullptr, 1.410 + "NuwaMarkCurrentThread is undefined!"); 1.411 + NuwaMarkCurrentThread(nullptr, nullptr); 1.412 + } 1.413 + return NS_OK; 1.414 + } 1.415 +}; 1.416 +#endif 1.417 + 1.418 +// Seer::nsINetworkSeer 1.419 + 1.420 +nsresult 1.421 +Seer::Init() 1.422 +{ 1.423 + if (!NS_IsMainThread()) { 1.424 + MOZ_ASSERT(false, "Seer::Init called off the main thread!"); 1.425 + return NS_ERROR_UNEXPECTED; 1.426 + } 1.427 + 1.428 + nsresult rv = NS_OK; 1.429 + 1.430 +#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) 1.431 + // This is an ugly hack to disable the seer on android < 2.3, as it doesn't 1.432 + // play nicely with those android versions, at least on our infra. Causes 1.433 + // timeouts in reftests. See bug 881804 comment 86. 1.434 + nsCOMPtr<nsIPropertyBag2> infoService = 1.435 + do_GetService("@mozilla.org/system-info;1"); 1.436 + if (infoService) { 1.437 + int32_t androidVersion = -1; 1.438 + rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"), 1.439 + &androidVersion); 1.440 + if (NS_SUCCEEDED(rv) && (androidVersion < ANDROID_23_VERSION)) { 1.441 + return NS_ERROR_NOT_AVAILABLE; 1.442 + } 1.443 + } 1.444 +#endif 1.445 + 1.446 + mStartupTime = PR_Now(); 1.447 + 1.448 + mAccumulators = new SeerTelemetryAccumulators(); 1.449 + 1.450 + rv = InstallObserver(); 1.451 + NS_ENSURE_SUCCESS(rv, rv); 1.452 + 1.453 + if (!mDNSListener) { 1.454 + mDNSListener = new SeerDNSListener(); 1.455 + } 1.456 + 1.457 + rv = NS_NewNamedThread("Network Seer", getter_AddRefs(mIOThread)); 1.458 + NS_ENSURE_SUCCESS(rv, rv); 1.459 + 1.460 +#ifdef MOZ_NUWA_PROCESS 1.461 + nsCOMPtr<nsIRunnable> runner = new NuwaMarkSeerThreadRunner(); 1.462 + mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL); 1.463 +#endif 1.464 + 1.465 + mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv); 1.466 + NS_ENSURE_SUCCESS(rv, rv); 1.467 + 1.468 + mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); 1.469 + NS_ENSURE_SUCCESS(rv, rv); 1.470 + 1.471 + mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv); 1.472 + NS_ENSURE_SUCCESS(rv, rv); 1.473 + 1.474 + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1.475 + getter_AddRefs(mDBFile)); 1.476 + NS_ENSURE_SUCCESS(rv, rv); 1.477 + rv = mDBFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite")); 1.478 + NS_ENSURE_SUCCESS(rv, rv); 1.479 + 1.480 + mInitialized = true; 1.481 + 1.482 + return rv; 1.483 +} 1.484 + 1.485 +void 1.486 +Seer::CheckForAndDeleteOldDBFile() 1.487 +{ 1.488 + nsCOMPtr<nsIFile> oldDBFile; 1.489 + nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile)); 1.490 + RETURN_IF_FAILED(rv); 1.491 + 1.492 + rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite")); 1.493 + RETURN_IF_FAILED(rv); 1.494 + 1.495 + bool oldFileExists = false; 1.496 + rv = oldDBFile->Exists(&oldFileExists); 1.497 + if (NS_FAILED(rv) || !oldFileExists) { 1.498 + return; 1.499 + } 1.500 + 1.501 + oldDBFile->Remove(false); 1.502 +} 1.503 + 1.504 +// Make sure that our sqlite storage is all set up with all the tables we need 1.505 +// to do the work. It isn't the end of the world if this fails, since this is 1.506 +// all an optimization, anyway. 1.507 + 1.508 +nsresult 1.509 +Seer::EnsureInitStorage() 1.510 +{ 1.511 + MOZ_ASSERT(!NS_IsMainThread(), "Initializing seer storage on main thread"); 1.512 + 1.513 + if (mDB) { 1.514 + return NS_OK; 1.515 + } 1.516 + 1.517 + nsresult rv; 1.518 + 1.519 + CheckForAndDeleteOldDBFile(); 1.520 + 1.521 + rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB)); 1.522 + if (NS_FAILED(rv)) { 1.523 + // Retry once by trashing the file and trying to open again. If this fails, 1.524 + // we can just bail, and hope for better luck next time. 1.525 + rv = mDBFile->Remove(false); 1.526 + NS_ENSURE_SUCCESS(rv, rv); 1.527 + 1.528 + rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB)); 1.529 + NS_ENSURE_SUCCESS(rv, rv); 1.530 + } 1.531 + 1.532 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;")); 1.533 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA foreign_keys = ON;")); 1.534 + 1.535 + BeginTransaction(); 1.536 + 1.537 + // A table to make sure we're working with the database layout we expect 1.538 + rv = mDB->ExecuteSimpleSQL( 1.539 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_seer_version (\n" 1.540 + " version INTEGER NOT NULL\n" 1.541 + ");\n")); 1.542 + NS_ENSURE_SUCCESS(rv, rv); 1.543 + 1.544 + nsCOMPtr<mozIStorageStatement> stmt; 1.545 + rv = mDB->CreateStatement( 1.546 + NS_LITERAL_CSTRING("SELECT version FROM moz_seer_version;\n"), 1.547 + getter_AddRefs(stmt)); 1.548 + NS_ENSURE_SUCCESS(rv, rv); 1.549 + 1.550 + bool hasRows; 1.551 + rv = stmt->ExecuteStep(&hasRows); 1.552 + NS_ENSURE_SUCCESS(rv, rv); 1.553 + if (hasRows) { 1.554 + int32_t currentVersion; 1.555 + rv = stmt->GetInt32(0, ¤tVersion); 1.556 + NS_ENSURE_SUCCESS(rv, rv); 1.557 + 1.558 + // This is what we do while we only have one schema version. Later, we'll 1.559 + // have to change this to actually upgrade things as appropriate. 1.560 + MOZ_ASSERT(currentVersion == SEER_SCHEMA_VERSION, 1.561 + "Invalid seer schema version!"); 1.562 + if (currentVersion != SEER_SCHEMA_VERSION) { 1.563 + return NS_ERROR_UNEXPECTED; 1.564 + } 1.565 + } else { 1.566 + stmt = nullptr; 1.567 + rv = mDB->CreateStatement( 1.568 + NS_LITERAL_CSTRING("INSERT INTO moz_seer_version (version) VALUES " 1.569 + "(:seer_version);"), 1.570 + getter_AddRefs(stmt)); 1.571 + NS_ENSURE_SUCCESS(rv, rv); 1.572 + 1.573 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("seer_version"), 1.574 + SEER_SCHEMA_VERSION); 1.575 + NS_ENSURE_SUCCESS(rv, rv); 1.576 + 1.577 + stmt->Execute(); 1.578 + } 1.579 + 1.580 + stmt = nullptr; 1.581 + 1.582 + // This table keeps track of the hosts we've seen at the top level of a 1.583 + // pageload so we can map them to hosts used for subresources of a pageload. 1.584 + rv = mDB->ExecuteSimpleSQL( 1.585 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_hosts (\n" 1.586 + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" 1.587 + " origin TEXT NOT NULL,\n" 1.588 + " loads INTEGER DEFAULT 0,\n" 1.589 + " last_load INTEGER DEFAULT 0\n" 1.590 + ");\n")); 1.591 + NS_ENSURE_SUCCESS(rv, rv); 1.592 + 1.593 + rv = mDB->ExecuteSimpleSQL( 1.594 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_id_origin_index " 1.595 + "ON moz_hosts (id, origin);")); 1.596 + NS_ENSURE_SUCCESS(rv, rv); 1.597 + 1.598 + rv = mDB->ExecuteSimpleSQL( 1.599 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_origin_index " 1.600 + "ON moz_hosts (origin);")); 1.601 + NS_ENSURE_SUCCESS(rv, rv); 1.602 + 1.603 + rv = mDB->ExecuteSimpleSQL( 1.604 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_load_index " 1.605 + "ON moz_hosts (last_load);")); 1.606 + NS_ENSURE_SUCCESS(rv, rv); 1.607 + 1.608 + // And this is the table that keeps track of the hosts for subresources of a 1.609 + // pageload. 1.610 + rv = mDB->ExecuteSimpleSQL( 1.611 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subhosts (\n" 1.612 + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" 1.613 + " hid INTEGER NOT NULL,\n" 1.614 + " origin TEXT NOT NULL,\n" 1.615 + " hits INTEGER DEFAULT 0,\n" 1.616 + " last_hit INTEGER DEFAULT 0,\n" 1.617 + " FOREIGN KEY(hid) REFERENCES moz_hosts(id) ON DELETE CASCADE\n" 1.618 + ");\n")); 1.619 + NS_ENSURE_SUCCESS(rv, rv); 1.620 + 1.621 + rv = mDB->ExecuteSimpleSQL( 1.622 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_hid_origin_index " 1.623 + "ON moz_subhosts (hid, origin);")); 1.624 + NS_ENSURE_SUCCESS(rv, rv); 1.625 + 1.626 + rv = mDB->ExecuteSimpleSQL( 1.627 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_id_index " 1.628 + "ON moz_subhosts (id);")); 1.629 + NS_ENSURE_SUCCESS(rv, rv); 1.630 + 1.631 + // Table to keep track of how many times we've started up, and when the last 1.632 + // time was. 1.633 + rv = mDB->ExecuteSimpleSQL( 1.634 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startups (\n" 1.635 + " startups INTEGER,\n" 1.636 + " last_startup INTEGER\n" 1.637 + ");\n")); 1.638 + NS_ENSURE_SUCCESS(rv, rv); 1.639 + 1.640 + rv = mDB->CreateStatement( 1.641 + NS_LITERAL_CSTRING("SELECT startups, last_startup FROM moz_startups;\n"), 1.642 + getter_AddRefs(stmt)); 1.643 + NS_ENSURE_SUCCESS(rv, rv); 1.644 + 1.645 + // We'll go ahead and keep track of our startup count here, since we can 1.646 + // (mostly) equate "the service was created and asked to do stuff" with 1.647 + // "the browser was started up". 1.648 + rv = stmt->ExecuteStep(&hasRows); 1.649 + NS_ENSURE_SUCCESS(rv, rv); 1.650 + if (hasRows) { 1.651 + // We've started up before. Update our startup statistics 1.652 + stmt->GetInt32(0, &mStartupCount); 1.653 + stmt->GetInt64(1, &mLastStartupTime); 1.654 + 1.655 + // This finalizes the statement 1.656 + stmt = nullptr; 1.657 + 1.658 + rv = mDB->CreateStatement( 1.659 + NS_LITERAL_CSTRING("UPDATE moz_startups SET startups = :startup_count, " 1.660 + "last_startup = :startup_time;\n"), 1.661 + getter_AddRefs(stmt)); 1.662 + NS_ENSURE_SUCCESS(rv, rv); 1.663 + 1.664 + int32_t newStartupCount = mStartupCount + 1; 1.665 + if (newStartupCount <= 0) { 1.666 + SEER_LOG(("Seer::EnsureInitStorage startup count overflow\n")); 1.667 + newStartupCount = mStartupCount; 1.668 + ++mAccumulators->mStartupCountOverflows; 1.669 + } 1.670 + 1.671 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("startup_count"), 1.672 + mStartupCount + 1); 1.673 + NS_ENSURE_SUCCESS(rv, rv); 1.674 + 1.675 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), 1.676 + mStartupTime); 1.677 + NS_ENSURE_SUCCESS(rv, rv); 1.678 + 1.679 + stmt->Execute(); 1.680 + } else { 1.681 + // This is our first startup, so let's go ahead and mark it as such 1.682 + mStartupCount = 1; 1.683 + 1.684 + rv = mDB->CreateStatement( 1.685 + NS_LITERAL_CSTRING("INSERT INTO moz_startups (startups, last_startup) " 1.686 + "VALUES (1, :startup_time);\n"), 1.687 + getter_AddRefs(stmt)); 1.688 + NS_ENSURE_SUCCESS(rv, rv); 1.689 + 1.690 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), 1.691 + mStartupTime); 1.692 + NS_ENSURE_SUCCESS(rv, rv); 1.693 + 1.694 + stmt->Execute(); 1.695 + } 1.696 + 1.697 + // This finalizes the statement 1.698 + stmt = nullptr; 1.699 + 1.700 + // This table lists URIs loaded at startup, along with how many startups 1.701 + // they've been loaded during, and when the last time was. 1.702 + rv = mDB->ExecuteSimpleSQL( 1.703 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startup_pages (\n" 1.704 + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" 1.705 + " uri TEXT NOT NULL,\n" 1.706 + " hits INTEGER DEFAULT 0,\n" 1.707 + " last_hit INTEGER DEFAULT 0\n" 1.708 + ");\n")); 1.709 + NS_ENSURE_SUCCESS(rv, rv); 1.710 + 1.711 + rv = mDB->ExecuteSimpleSQL( 1.712 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_uri_index " 1.713 + "ON moz_startup_pages (uri);")); 1.714 + NS_ENSURE_SUCCESS(rv, rv); 1.715 + 1.716 + rv = mDB->ExecuteSimpleSQL( 1.717 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_hit_index " 1.718 + "ON moz_startup_pages (last_hit);")); 1.719 + NS_ENSURE_SUCCESS(rv, rv); 1.720 + 1.721 + // This table is similar to moz_hosts above, but uses full URIs instead of 1.722 + // hosts so that we can get more specific predictions for URIs that people 1.723 + // visit often (such as their email or social network home pages). 1.724 + rv = mDB->ExecuteSimpleSQL( 1.725 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_pages (\n" 1.726 + " id integer PRIMARY KEY AUTOINCREMENT,\n" 1.727 + " uri TEXT NOT NULL,\n" 1.728 + " loads INTEGER DEFAULT 0,\n" 1.729 + " last_load INTEGER DEFAULT 0\n" 1.730 + ");\n")); 1.731 + NS_ENSURE_SUCCESS(rv, rv); 1.732 + 1.733 + rv = mDB->ExecuteSimpleSQL( 1.734 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_id_uri_index " 1.735 + "ON moz_pages (id, uri);")); 1.736 + NS_ENSURE_SUCCESS(rv, rv); 1.737 + 1.738 + rv = mDB->ExecuteSimpleSQL( 1.739 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_uri_index " 1.740 + "ON moz_pages (uri);")); 1.741 + NS_ENSURE_SUCCESS(rv, rv); 1.742 + 1.743 + // This table is similar to moz_subhosts above, but is instead related to 1.744 + // moz_pages for finer granularity. 1.745 + rv = mDB->ExecuteSimpleSQL( 1.746 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subresources (\n" 1.747 + " id integer PRIMARY KEY AUTOINCREMENT,\n" 1.748 + " pid INTEGER NOT NULL,\n" 1.749 + " uri TEXT NOT NULL,\n" 1.750 + " hits INTEGER DEFAULT 0,\n" 1.751 + " last_hit INTEGER DEFAULT 0,\n" 1.752 + " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n" 1.753 + ");\n")); 1.754 + NS_ENSURE_SUCCESS(rv, rv); 1.755 + 1.756 + rv = mDB->ExecuteSimpleSQL( 1.757 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_pid_uri_index " 1.758 + "ON moz_subresources (pid, uri);")); 1.759 + NS_ENSURE_SUCCESS(rv, rv); 1.760 + 1.761 + rv = mDB->ExecuteSimpleSQL( 1.762 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_id_index " 1.763 + "ON moz_subresources (id);")); 1.764 + NS_ENSURE_SUCCESS(rv, rv); 1.765 + 1.766 + // This table keeps track of URIs and what they end up finally redirecting to 1.767 + // so we can handle redirects in a sane fashion, as well. 1.768 + rv = mDB->ExecuteSimpleSQL( 1.769 + NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_redirects (\n" 1.770 + " id integer PRIMARY KEY AUTOINCREMENT,\n" 1.771 + " pid integer NOT NULL,\n" 1.772 + " uri TEXT NOT NULL,\n" 1.773 + " origin TEXT NOT NULL,\n" 1.774 + " hits INTEGER DEFAULT 0,\n" 1.775 + " last_hit INTEGER DEFAULT 0,\n" 1.776 + " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n" 1.777 + ");\n")); 1.778 + NS_ENSURE_SUCCESS(rv, rv); 1.779 + 1.780 + rv = mDB->ExecuteSimpleSQL( 1.781 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_pid_uri_index " 1.782 + "ON moz_redirects (pid, uri);")); 1.783 + NS_ENSURE_SUCCESS(rv, rv); 1.784 + 1.785 + rv = mDB->ExecuteSimpleSQL( 1.786 + NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_id_index " 1.787 + "ON moz_redirects (id);")); 1.788 + NS_ENSURE_SUCCESS(rv, rv); 1.789 + 1.790 + CommitTransaction(); 1.791 + BeginTransaction(); 1.792 + 1.793 + nsRefPtr<SeerCommitTimerInitEvent> event = new SeerCommitTimerInitEvent(); 1.794 + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 1.795 + 1.796 + return NS_OK; 1.797 +} 1.798 + 1.799 +class SeerThreadShutdownRunner : public nsRunnable 1.800 +{ 1.801 +public: 1.802 + SeerThreadShutdownRunner(nsIThread *ioThread) 1.803 + :mIOThread(ioThread) 1.804 + { } 1.805 + 1.806 + NS_IMETHODIMP Run() MOZ_OVERRIDE 1.807 + { 1.808 + MOZ_ASSERT(NS_IsMainThread(), "Shut down seer io thread off main thread"); 1.809 + mIOThread->Shutdown(); 1.810 + return NS_OK; 1.811 + } 1.812 + 1.813 +private: 1.814 + nsCOMPtr<nsIThread> mIOThread; 1.815 +}; 1.816 + 1.817 +class SeerDBShutdownRunner : public nsRunnable 1.818 +{ 1.819 +public: 1.820 + SeerDBShutdownRunner(nsIThread *ioThread, nsINetworkSeer *seer) 1.821 + :mIOThread(ioThread) 1.822 + { 1.823 + mSeer = new nsMainThreadPtrHolder<nsINetworkSeer>(seer); 1.824 + } 1.825 + 1.826 + NS_IMETHODIMP Run() MOZ_OVERRIDE 1.827 + { 1.828 + MOZ_ASSERT(!NS_IsMainThread(), "Shutting down DB on main thread"); 1.829 + 1.830 + // Ensure everything is written to disk before we shut down the db 1.831 + gSeer->CommitTransaction(); 1.832 + 1.833 + gSeer->mStatements.FinalizeStatements(); 1.834 + gSeer->mDB->Close(); 1.835 + gSeer->mDB = nullptr; 1.836 + 1.837 + nsRefPtr<SeerThreadShutdownRunner> runner = 1.838 + new SeerThreadShutdownRunner(mIOThread); 1.839 + NS_DispatchToMainThread(runner); 1.840 + 1.841 + return NS_OK; 1.842 + } 1.843 + 1.844 +private: 1.845 + nsCOMPtr<nsIThread> mIOThread; 1.846 + 1.847 + // Death grip to keep seer alive while we cleanly close its DB connection 1.848 + nsMainThreadPtrHandle<nsINetworkSeer> mSeer; 1.849 +}; 1.850 + 1.851 +void 1.852 +Seer::Shutdown() 1.853 +{ 1.854 + if (!NS_IsMainThread()) { 1.855 + MOZ_ASSERT(false, "Seer::Shutdown called off the main thread!"); 1.856 + return; 1.857 + } 1.858 + 1.859 + mInitialized = false; 1.860 + 1.861 + if (mCommitTimer) { 1.862 + mCommitTimer->Cancel(); 1.863 + } 1.864 + 1.865 + if (mIOThread) { 1.866 + if (mDB) { 1.867 + nsRefPtr<SeerDBShutdownRunner> runner = 1.868 + new SeerDBShutdownRunner(mIOThread, this); 1.869 + mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL); 1.870 + } else { 1.871 + nsRefPtr<SeerThreadShutdownRunner> runner = 1.872 + new SeerThreadShutdownRunner(mIOThread); 1.873 + NS_DispatchToMainThread(runner); 1.874 + } 1.875 + } 1.876 +} 1.877 + 1.878 +nsresult 1.879 +Seer::Create(nsISupports *aOuter, const nsIID& aIID, 1.880 + void **aResult) 1.881 +{ 1.882 + nsresult rv; 1.883 + 1.884 + if (aOuter != nullptr) { 1.885 + return NS_ERROR_NO_AGGREGATION; 1.886 + } 1.887 + 1.888 + nsRefPtr<Seer> svc = new Seer(); 1.889 + 1.890 + rv = svc->Init(); 1.891 + if (NS_FAILED(rv)) { 1.892 + SEER_LOG(("Failed to initialize seer, seer will be a noop")); 1.893 + } 1.894 + 1.895 + // We treat init failure the same as the service being disabled, since this 1.896 + // is all an optimization anyway. No need to freak people out. That's why we 1.897 + // gladly continue on QI'ing here. 1.898 + rv = svc->QueryInterface(aIID, aResult); 1.899 + 1.900 + return rv; 1.901 +} 1.902 + 1.903 +// Get the full origin (scheme, host, port) out of a URI (maybe should be part 1.904 +// of nsIURI instead?) 1.905 +static void 1.906 +ExtractOrigin(nsIURI *uri, nsAutoCString &s) 1.907 +{ 1.908 + s.Truncate(); 1.909 + 1.910 + nsAutoCString scheme; 1.911 + nsresult rv = uri->GetScheme(scheme); 1.912 + RETURN_IF_FAILED(rv); 1.913 + 1.914 + nsAutoCString host; 1.915 + rv = uri->GetAsciiHost(host); 1.916 + RETURN_IF_FAILED(rv); 1.917 + 1.918 + int32_t port; 1.919 + rv = uri->GetPort(&port); 1.920 + RETURN_IF_FAILED(rv); 1.921 + 1.922 + s.Assign(scheme); 1.923 + s.AppendLiteral("://"); 1.924 + s.Append(host); 1.925 + if (port != -1) { 1.926 + s.AppendLiteral(":"); 1.927 + s.AppendInt(port); 1.928 + } 1.929 +} 1.930 + 1.931 +// An event to do the work for a prediction that needs to hit the sqlite 1.932 +// database. These events should be created on the main thread, and run on 1.933 +// the seer thread. 1.934 +class SeerPredictionEvent : public nsRunnable 1.935 +{ 1.936 +public: 1.937 + SeerPredictionEvent(nsIURI *targetURI, nsIURI *sourceURI, 1.938 + SeerPredictReason reason, 1.939 + nsINetworkSeerVerifier *verifier) 1.940 + :mReason(reason) 1.941 + { 1.942 + MOZ_ASSERT(NS_IsMainThread(), "Creating prediction event off main thread"); 1.943 + 1.944 + mEnqueueTime = TimeStamp::Now(); 1.945 + 1.946 + if (verifier) { 1.947 + mVerifier = new nsMainThreadPtrHolder<nsINetworkSeerVerifier>(verifier); 1.948 + } 1.949 + if (targetURI) { 1.950 + targetURI->GetAsciiSpec(mTargetURI.spec); 1.951 + ExtractOrigin(targetURI, mTargetURI.origin); 1.952 + } 1.953 + if (sourceURI) { 1.954 + sourceURI->GetAsciiSpec(mSourceURI.spec); 1.955 + ExtractOrigin(sourceURI, mSourceURI.origin); 1.956 + } 1.957 + } 1.958 + 1.959 + NS_IMETHOD Run() MOZ_OVERRIDE 1.960 + { 1.961 + MOZ_ASSERT(!NS_IsMainThread(), "Running prediction event on main thread"); 1.962 + 1.963 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WAIT_TIME, 1.964 + mEnqueueTime); 1.965 + 1.966 + TimeStamp startTime = TimeStamp::Now(); 1.967 + 1.968 + nsresult rv = NS_OK; 1.969 + 1.970 + switch (mReason) { 1.971 + case nsINetworkSeer::PREDICT_LOAD: 1.972 + gSeer->PredictForPageload(mTargetURI, mVerifier, 0, mEnqueueTime); 1.973 + break; 1.974 + case nsINetworkSeer::PREDICT_STARTUP: 1.975 + gSeer->PredictForStartup(mVerifier, mEnqueueTime); 1.976 + break; 1.977 + default: 1.978 + MOZ_ASSERT(false, "Got unexpected value for predict reason"); 1.979 + rv = NS_ERROR_UNEXPECTED; 1.980 + } 1.981 + 1.982 + gSeer->FreeSpaceInQueue(); 1.983 + 1.984 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WORK_TIME, 1.985 + startTime); 1.986 + 1.987 + gSeer->MaybeScheduleCleanup(); 1.988 + 1.989 + return rv; 1.990 + } 1.991 + 1.992 +private: 1.993 + Seer::UriInfo mTargetURI; 1.994 + Seer::UriInfo mSourceURI; 1.995 + SeerPredictReason mReason; 1.996 + SeerVerifierHandle mVerifier; 1.997 + TimeStamp mEnqueueTime; 1.998 +}; 1.999 + 1.1000 +// Predicting for a link is easy, and doesn't require the round-trip to the 1.1001 +// seer thread and back to the main thread, since we don't have to hit the db 1.1002 +// for that. 1.1003 +void 1.1004 +Seer::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI, 1.1005 + nsINetworkSeerVerifier *verifier) 1.1006 +{ 1.1007 + MOZ_ASSERT(NS_IsMainThread(), "Predicting for link off main thread"); 1.1008 + 1.1009 + if (!mSpeculativeService) { 1.1010 + return; 1.1011 + } 1.1012 + 1.1013 + if (!mEnableHoverOnSSL) { 1.1014 + bool isSSL = false; 1.1015 + sourceURI->SchemeIs("https", &isSSL); 1.1016 + if (isSSL) { 1.1017 + // We don't want to predict from an HTTPS page, to avoid info leakage 1.1018 + SEER_LOG(("Not predicting for link hover - on an SSL page")); 1.1019 + return; 1.1020 + } 1.1021 + } 1.1022 + 1.1023 + mSpeculativeService->SpeculativeConnect(targetURI, nullptr); 1.1024 + if (verifier) { 1.1025 + verifier->OnPredictPreconnect(targetURI); 1.1026 + } 1.1027 +} 1.1028 + 1.1029 +// This runnable runs on the main thread, and is responsible for actually 1.1030 +// firing off predictive actions (such as TCP/TLS preconnects and DNS lookups) 1.1031 +class SeerPredictionRunner : public nsRunnable 1.1032 +{ 1.1033 +public: 1.1034 + SeerPredictionRunner(SeerVerifierHandle &verifier, TimeStamp predictStartTime) 1.1035 + :mVerifier(verifier) 1.1036 + ,mPredictStartTime(predictStartTime) 1.1037 + { } 1.1038 + 1.1039 + void AddPreconnect(const nsACString &uri) 1.1040 + { 1.1041 + mPreconnects.AppendElement(uri); 1.1042 + } 1.1043 + 1.1044 + void AddPreresolve(const nsACString &uri) 1.1045 + { 1.1046 + mPreresolves.AppendElement(uri); 1.1047 + } 1.1048 + 1.1049 + bool HasWork() const 1.1050 + { 1.1051 + return !(mPreconnects.IsEmpty() && mPreresolves.IsEmpty()); 1.1052 + } 1.1053 + 1.1054 + NS_IMETHOD Run() MOZ_OVERRIDE 1.1055 + { 1.1056 + MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread"); 1.1057 + 1.1058 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_ACTION, 1.1059 + mPredictStartTime); 1.1060 + 1.1061 + uint32_t len, i; 1.1062 + 1.1063 + len = mPreconnects.Length(); 1.1064 + for (i = 0; i < len; ++i) { 1.1065 + nsCOMPtr<nsIURI> uri; 1.1066 + nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreconnects[i]); 1.1067 + if (NS_FAILED(rv)) { 1.1068 + continue; 1.1069 + } 1.1070 + 1.1071 + ++gSeer->mAccumulators->mTotalPredictions; 1.1072 + ++gSeer->mAccumulators->mTotalPreconnects; 1.1073 + gSeer->mSpeculativeService->SpeculativeConnect(uri, gSeer); 1.1074 + if (mVerifier) { 1.1075 + mVerifier->OnPredictPreconnect(uri); 1.1076 + } 1.1077 + } 1.1078 + 1.1079 + len = mPreresolves.Length(); 1.1080 + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); 1.1081 + for (i = 0; i < len; ++i) { 1.1082 + nsCOMPtr<nsIURI> uri; 1.1083 + nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreresolves[i]); 1.1084 + if (NS_FAILED(rv)) { 1.1085 + continue; 1.1086 + } 1.1087 + 1.1088 + ++gSeer->mAccumulators->mTotalPredictions; 1.1089 + ++gSeer->mAccumulators->mTotalPreresolves; 1.1090 + nsAutoCString hostname; 1.1091 + uri->GetAsciiHost(hostname); 1.1092 + nsCOMPtr<nsICancelable> tmpCancelable; 1.1093 + gSeer->mDnsService->AsyncResolve(hostname, 1.1094 + (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | 1.1095 + nsIDNSService::RESOLVE_SPECULATE), 1.1096 + gSeer->mDNSListener, nullptr, 1.1097 + getter_AddRefs(tmpCancelable)); 1.1098 + if (mVerifier) { 1.1099 + mVerifier->OnPredictDNS(uri); 1.1100 + } 1.1101 + } 1.1102 + 1.1103 + mPreconnects.Clear(); 1.1104 + mPreresolves.Clear(); 1.1105 + 1.1106 + return NS_OK; 1.1107 + } 1.1108 + 1.1109 +private: 1.1110 + nsTArray<nsCString> mPreconnects; 1.1111 + nsTArray<nsCString> mPreresolves; 1.1112 + SeerVerifierHandle mVerifier; 1.1113 + TimeStamp mPredictStartTime; 1.1114 +}; 1.1115 + 1.1116 +// This calculates how much to degrade our confidence in our data based on 1.1117 +// the last time this top-level resource was loaded. This "global degradation" 1.1118 +// applies to *all* subresources we have associated with the top-level 1.1119 +// resource. This will be in addition to any reduction in confidence we have 1.1120 +// associated with a particular subresource. 1.1121 +int 1.1122 +Seer::CalculateGlobalDegradation(PRTime now, PRTime lastLoad) 1.1123 +{ 1.1124 + int globalDegradation; 1.1125 + PRTime delta = now - lastLoad; 1.1126 + if (delta < ONE_DAY) { 1.1127 + globalDegradation = mPageDegradationDay; 1.1128 + } else if (delta < ONE_WEEK) { 1.1129 + globalDegradation = mPageDegradationWeek; 1.1130 + } else if (delta < ONE_MONTH) { 1.1131 + globalDegradation = mPageDegradationMonth; 1.1132 + } else if (delta < ONE_YEAR) { 1.1133 + globalDegradation = mPageDegradationYear; 1.1134 + } else { 1.1135 + globalDegradation = mPageDegradationMax; 1.1136 + } 1.1137 + 1.1138 + Telemetry::Accumulate(Telemetry::SEER_GLOBAL_DEGRADATION, globalDegradation); 1.1139 + return globalDegradation; 1.1140 +} 1.1141 + 1.1142 +// This calculates our overall confidence that a particular subresource will be 1.1143 +// loaded as part of a top-level load. 1.1144 +// @param baseConfidence - the basic confidence we have for this subresource, 1.1145 +// which is the percentage of time this top-level load 1.1146 +// loads the subresource in question 1.1147 +// @param lastHit - the timestamp of the last time we loaded this subresource as 1.1148 +// part of this top-level load 1.1149 +// @param lastPossible - the timestamp of the last time we performed this 1.1150 +// top-level load 1.1151 +// @param globalDegradation - the degradation for this top-level load as 1.1152 +// determined by CalculateGlobalDegradation 1.1153 +int 1.1154 +Seer::CalculateConfidence(int baseConfidence, PRTime lastHit, 1.1155 + PRTime lastPossible, int globalDegradation) 1.1156 +{ 1.1157 + ++mAccumulators->mPredictionsCalculated; 1.1158 + 1.1159 + int maxConfidence = 100; 1.1160 + int confidenceDegradation = 0; 1.1161 + 1.1162 + if (lastHit < lastPossible) { 1.1163 + // We didn't load this subresource the last time this top-level load was 1.1164 + // performed, so let's not bother preconnecting (at the very least). 1.1165 + maxConfidence = mPreconnectMinConfidence - 1; 1.1166 + 1.1167 + // Now calculate how much we want to degrade our confidence based on how 1.1168 + // long it's been between the last time we did this top-level load and the 1.1169 + // last time this top-level load included this subresource. 1.1170 + PRTime delta = lastPossible - lastHit; 1.1171 + if (delta == 0) { 1.1172 + confidenceDegradation = 0; 1.1173 + } else if (delta < ONE_DAY) { 1.1174 + confidenceDegradation = mSubresourceDegradationDay; 1.1175 + } else if (delta < ONE_WEEK) { 1.1176 + confidenceDegradation = mSubresourceDegradationWeek; 1.1177 + } else if (delta < ONE_MONTH) { 1.1178 + confidenceDegradation = mSubresourceDegradationMonth; 1.1179 + } else if (delta < ONE_YEAR) { 1.1180 + confidenceDegradation = mSubresourceDegradationYear; 1.1181 + } else { 1.1182 + confidenceDegradation = mSubresourceDegradationMax; 1.1183 + maxConfidence = 0; 1.1184 + } 1.1185 + } 1.1186 + 1.1187 + // Calculate our confidence and clamp it to between 0 and maxConfidence 1.1188 + // (<= 100) 1.1189 + int confidence = baseConfidence - confidenceDegradation - globalDegradation; 1.1190 + confidence = std::max(confidence, 0); 1.1191 + confidence = std::min(confidence, maxConfidence); 1.1192 + 1.1193 + Telemetry::Accumulate(Telemetry::SEER_BASE_CONFIDENCE, baseConfidence); 1.1194 + Telemetry::Accumulate(Telemetry::SEER_SUBRESOURCE_DEGRADATION, 1.1195 + confidenceDegradation); 1.1196 + Telemetry::Accumulate(Telemetry::SEER_CONFIDENCE, confidence); 1.1197 + return confidence; 1.1198 +} 1.1199 + 1.1200 +// (Maybe) adds a predictive action to the prediction runner, based on our 1.1201 +// calculated confidence for the subresource in question. 1.1202 +void 1.1203 +Seer::SetupPrediction(int confidence, const nsACString &uri, 1.1204 + SeerPredictionRunner *runner) 1.1205 +{ 1.1206 + if (confidence >= mPreconnectMinConfidence) { 1.1207 + runner->AddPreconnect(uri); 1.1208 + } else if (confidence >= mPreresolveMinConfidence) { 1.1209 + runner->AddPreresolve(uri); 1.1210 + } 1.1211 +} 1.1212 + 1.1213 +// This gets the data about the top-level load from our database, either from 1.1214 +// the pages table (which is specific to a particular URI), or from the hosts 1.1215 +// table (which is for a particular origin). 1.1216 +bool 1.1217 +Seer::LookupTopLevel(QueryType queryType, const nsACString &key, 1.1218 + TopLevelInfo &info) 1.1219 +{ 1.1220 + MOZ_ASSERT(!NS_IsMainThread(), "LookupTopLevel called on main thread."); 1.1221 + 1.1222 + nsCOMPtr<mozIStorageStatement> stmt; 1.1223 + if (queryType == QUERY_PAGE) { 1.1224 + stmt = mStatements.GetCachedStatement( 1.1225 + NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_pages WHERE " 1.1226 + "uri = :key;")); 1.1227 + } else { 1.1228 + stmt = mStatements.GetCachedStatement( 1.1229 + NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_hosts WHERE " 1.1230 + "origin = :key;")); 1.1231 + } 1.1232 + NS_ENSURE_TRUE(stmt, false); 1.1233 + mozStorageStatementScoper scope(stmt); 1.1234 + 1.1235 + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); 1.1236 + NS_ENSURE_SUCCESS(rv, false); 1.1237 + 1.1238 + bool hasRows; 1.1239 + rv = stmt->ExecuteStep(&hasRows); 1.1240 + NS_ENSURE_SUCCESS(rv, false); 1.1241 + 1.1242 + if (!hasRows) { 1.1243 + return false; 1.1244 + } 1.1245 + 1.1246 + rv = stmt->GetInt32(0, &info.id); 1.1247 + NS_ENSURE_SUCCESS(rv, false); 1.1248 + 1.1249 + rv = stmt->GetInt32(1, &info.loadCount); 1.1250 + NS_ENSURE_SUCCESS(rv, false); 1.1251 + 1.1252 + rv = stmt->GetInt64(2, &info.lastLoad); 1.1253 + NS_ENSURE_SUCCESS(rv, false); 1.1254 + 1.1255 + return true; 1.1256 +} 1.1257 + 1.1258 +// Insert data about either a top-level page or a top-level origin into 1.1259 +// the database. 1.1260 +void 1.1261 +Seer::AddTopLevel(QueryType queryType, const nsACString &key, PRTime now) 1.1262 +{ 1.1263 + MOZ_ASSERT(!NS_IsMainThread(), "AddTopLevel called on main thread."); 1.1264 + 1.1265 + nsCOMPtr<mozIStorageStatement> stmt; 1.1266 + if (queryType == QUERY_PAGE) { 1.1267 + stmt = mStatements.GetCachedStatement( 1.1268 + NS_LITERAL_CSTRING("INSERT INTO moz_pages (uri, loads, last_load) " 1.1269 + "VALUES (:key, 1, :now);")); 1.1270 + } else { 1.1271 + stmt = mStatements.GetCachedStatement( 1.1272 + NS_LITERAL_CSTRING("INSERT INTO moz_hosts (origin, loads, last_load) " 1.1273 + "VALUES (:key, 1, :now);")); 1.1274 + } 1.1275 + if (!stmt) { 1.1276 + return; 1.1277 + } 1.1278 + mozStorageStatementScoper scope(stmt); 1.1279 + 1.1280 + // Loading a page implicitly makes the seer learn about the page, 1.1281 + // so since we don't have it already, let's add it. 1.1282 + nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); 1.1283 + RETURN_IF_FAILED(rv); 1.1284 + 1.1285 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); 1.1286 + RETURN_IF_FAILED(rv); 1.1287 + 1.1288 + rv = stmt->Execute(); 1.1289 +} 1.1290 + 1.1291 +// Update data about either a top-level page or a top-level origin in the 1.1292 +// database. 1.1293 +void 1.1294 +Seer::UpdateTopLevel(QueryType queryType, const TopLevelInfo &info, PRTime now) 1.1295 +{ 1.1296 + MOZ_ASSERT(!NS_IsMainThread(), "UpdateTopLevel called on main thread."); 1.1297 + 1.1298 + nsCOMPtr<mozIStorageStatement> stmt; 1.1299 + if (queryType == QUERY_PAGE) { 1.1300 + stmt = mStatements.GetCachedStatement( 1.1301 + NS_LITERAL_CSTRING("UPDATE moz_pages SET loads = :load_count, " 1.1302 + "last_load = :now WHERE id = :id;")); 1.1303 + } else { 1.1304 + stmt = mStatements.GetCachedStatement( 1.1305 + NS_LITERAL_CSTRING("UPDATE moz_hosts SET loads = :load_count, " 1.1306 + "last_load = :now WHERE id = :id;")); 1.1307 + } 1.1308 + if (!stmt) { 1.1309 + return; 1.1310 + } 1.1311 + mozStorageStatementScoper scope(stmt); 1.1312 + 1.1313 + int32_t newLoadCount = info.loadCount + 1; 1.1314 + if (newLoadCount <= 0) { 1.1315 + SEER_LOG(("Seer::UpdateTopLevel type %d id %d load count overflow\n", 1.1316 + queryType, info.id)); 1.1317 + newLoadCount = info.loadCount; 1.1318 + ++mAccumulators->mLoadCountOverflows; 1.1319 + } 1.1320 + 1.1321 + // First, let's update the page in the database, since loading a page 1.1322 + // implicitly learns about the page. 1.1323 + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("load_count"), 1.1324 + newLoadCount); 1.1325 + RETURN_IF_FAILED(rv); 1.1326 + 1.1327 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); 1.1328 + RETURN_IF_FAILED(rv); 1.1329 + 1.1330 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); 1.1331 + RETURN_IF_FAILED(rv); 1.1332 + 1.1333 + rv = stmt->Execute(); 1.1334 +} 1.1335 + 1.1336 +// Tries to predict for a top-level load (either page-based or origin-based). 1.1337 +// Returns false if it failed to predict at all, true if it did some sort of 1.1338 +// prediction. 1.1339 +// @param queryType - whether to predict based on page or origin 1.1340 +// @param info - the db info about the top-level resource 1.1341 +bool 1.1342 +Seer::TryPredict(QueryType queryType, const TopLevelInfo &info, PRTime now, 1.1343 + SeerVerifierHandle &verifier, TimeStamp &predictStartTime) 1.1344 +{ 1.1345 + MOZ_ASSERT(!NS_IsMainThread(), "TryPredict called on main thread."); 1.1346 + 1.1347 + if (!info.loadCount) { 1.1348 + SEER_LOG(("Seer::TryPredict info.loadCount is zero!\n")); 1.1349 + ++mAccumulators->mLoadCountZeroes; 1.1350 + return false; 1.1351 + } 1.1352 + 1.1353 + int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad); 1.1354 + 1.1355 + // Now let's look up the subresources we know about for this page 1.1356 + nsCOMPtr<mozIStorageStatement> stmt; 1.1357 + if (queryType == QUERY_PAGE) { 1.1358 + stmt = mStatements.GetCachedStatement( 1.1359 + NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_subresources " 1.1360 + "WHERE pid = :id;")); 1.1361 + } else { 1.1362 + stmt = mStatements.GetCachedStatement( 1.1363 + NS_LITERAL_CSTRING("SELECT origin, hits, last_hit FROM moz_subhosts " 1.1364 + "WHERE hid = :id;")); 1.1365 + } 1.1366 + NS_ENSURE_TRUE(stmt, false); 1.1367 + mozStorageStatementScoper scope(stmt); 1.1368 + 1.1369 + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); 1.1370 + NS_ENSURE_SUCCESS(rv, false); 1.1371 + 1.1372 + bool hasRows; 1.1373 + rv = stmt->ExecuteStep(&hasRows); 1.1374 + if (NS_FAILED(rv) || !hasRows) { 1.1375 + return false; 1.1376 + } 1.1377 + 1.1378 + nsRefPtr<SeerPredictionRunner> runner = 1.1379 + new SeerPredictionRunner(verifier, predictStartTime); 1.1380 + 1.1381 + while (hasRows) { 1.1382 + int32_t hitCount; 1.1383 + PRTime lastHit; 1.1384 + nsAutoCString subresource; 1.1385 + int baseConfidence, confidence; 1.1386 + 1.1387 + // We use goto nextrow here instead of just failling, because we want 1.1388 + // to do some sort of prediction if at all possible. Of course, it's 1.1389 + // probably unlikely that subsequent rows will succeed if one fails, but 1.1390 + // it's worth a shot. 1.1391 + 1.1392 + rv = stmt->GetUTF8String(0, subresource); 1.1393 + if NS_FAILED(rv) { 1.1394 + goto nextrow; 1.1395 + } 1.1396 + 1.1397 + rv = stmt->GetInt32(1, &hitCount); 1.1398 + if (NS_FAILED(rv)) { 1.1399 + goto nextrow; 1.1400 + } 1.1401 + 1.1402 + rv = stmt->GetInt64(2, &lastHit); 1.1403 + if (NS_FAILED(rv)) { 1.1404 + goto nextrow; 1.1405 + } 1.1406 + 1.1407 + baseConfidence = (hitCount * 100) / info.loadCount; 1.1408 + confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad, 1.1409 + globalDegradation); 1.1410 + SetupPrediction(confidence, subresource, runner); 1.1411 + 1.1412 +nextrow: 1.1413 + rv = stmt->ExecuteStep(&hasRows); 1.1414 + NS_ENSURE_SUCCESS(rv, false); 1.1415 + } 1.1416 + 1.1417 + bool predicted = false; 1.1418 + 1.1419 + if (runner->HasWork()) { 1.1420 + NS_DispatchToMainThread(runner); 1.1421 + predicted = true; 1.1422 + } 1.1423 + 1.1424 + return predicted; 1.1425 +} 1.1426 + 1.1427 +// Find out if a top-level page is likely to redirect. 1.1428 +bool 1.1429 +Seer::WouldRedirect(const TopLevelInfo &info, PRTime now, UriInfo &newUri) 1.1430 +{ 1.1431 + MOZ_ASSERT(!NS_IsMainThread(), "WouldRedirect called on main thread."); 1.1432 + 1.1433 + if (!info.loadCount) { 1.1434 + SEER_LOG(("Seer::WouldRedirect info.loadCount is zero!\n")); 1.1435 + ++mAccumulators->mLoadCountZeroes; 1.1436 + return false; 1.1437 + } 1.1438 + 1.1439 + nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement( 1.1440 + NS_LITERAL_CSTRING("SELECT uri, origin, hits, last_hit " 1.1441 + "FROM moz_redirects WHERE pid = :id;")); 1.1442 + NS_ENSURE_TRUE(stmt, false); 1.1443 + mozStorageStatementScoper scope(stmt); 1.1444 + 1.1445 + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); 1.1446 + NS_ENSURE_SUCCESS(rv, false); 1.1447 + 1.1448 + bool hasRows; 1.1449 + rv = stmt->ExecuteStep(&hasRows); 1.1450 + if (NS_FAILED(rv) || !hasRows) { 1.1451 + return false; 1.1452 + } 1.1453 + 1.1454 + rv = stmt->GetUTF8String(0, newUri.spec); 1.1455 + NS_ENSURE_SUCCESS(rv, false); 1.1456 + 1.1457 + rv = stmt->GetUTF8String(1, newUri.origin); 1.1458 + NS_ENSURE_SUCCESS(rv, false); 1.1459 + 1.1460 + int32_t hitCount; 1.1461 + rv = stmt->GetInt32(2, &hitCount); 1.1462 + NS_ENSURE_SUCCESS(rv, false); 1.1463 + 1.1464 + PRTime lastHit; 1.1465 + rv = stmt->GetInt64(3, &lastHit); 1.1466 + NS_ENSURE_SUCCESS(rv, false); 1.1467 + 1.1468 + int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad); 1.1469 + int baseConfidence = (hitCount * 100) / info.loadCount; 1.1470 + int confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad, 1.1471 + globalDegradation); 1.1472 + 1.1473 + if (confidence > mRedirectLikelyConfidence) { 1.1474 + return true; 1.1475 + } 1.1476 + 1.1477 + return false; 1.1478 +} 1.1479 + 1.1480 +// This will add a page to our list of startup pages if it's being loaded 1.1481 +// before our startup window has expired. 1.1482 +void 1.1483 +Seer::MaybeLearnForStartup(const UriInfo &uri, const PRTime now) 1.1484 +{ 1.1485 + MOZ_ASSERT(!NS_IsMainThread(), "MaybeLearnForStartup called on main thread."); 1.1486 + 1.1487 + if ((now - mStartupTime) < STARTUP_WINDOW) { 1.1488 + LearnForStartup(uri); 1.1489 + } 1.1490 +} 1.1491 + 1.1492 +const int MAX_PAGELOAD_DEPTH = 10; 1.1493 + 1.1494 +// This is the driver for prediction based on a new pageload. 1.1495 +void 1.1496 +Seer::PredictForPageload(const UriInfo &uri, SeerVerifierHandle &verifier, 1.1497 + int stackCount, TimeStamp &predictStartTime) 1.1498 +{ 1.1499 + MOZ_ASSERT(!NS_IsMainThread(), "PredictForPageload called on main thread."); 1.1500 + 1.1501 + if (stackCount > MAX_PAGELOAD_DEPTH) { 1.1502 + SEER_LOG(("Too deep into pageload prediction")); 1.1503 + return; 1.1504 + } 1.1505 + 1.1506 + if (NS_FAILED(EnsureInitStorage())) { 1.1507 + return; 1.1508 + } 1.1509 + 1.1510 + PRTime now = PR_Now(); 1.1511 + 1.1512 + MaybeLearnForStartup(uri, now); 1.1513 + 1.1514 + TopLevelInfo pageInfo; 1.1515 + TopLevelInfo originInfo; 1.1516 + bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo); 1.1517 + bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo); 1.1518 + 1.1519 + if (!havePage) { 1.1520 + AddTopLevel(QUERY_PAGE, uri.spec, now); 1.1521 + } else { 1.1522 + UpdateTopLevel(QUERY_PAGE, pageInfo, now); 1.1523 + } 1.1524 + 1.1525 + if (!haveOrigin) { 1.1526 + AddTopLevel(QUERY_ORIGIN, uri.origin, now); 1.1527 + } else { 1.1528 + UpdateTopLevel(QUERY_ORIGIN, originInfo, now); 1.1529 + } 1.1530 + 1.1531 + UriInfo newUri; 1.1532 + if (havePage && WouldRedirect(pageInfo, now, newUri)) { 1.1533 + nsRefPtr<SeerPredictionRunner> runner = 1.1534 + new SeerPredictionRunner(verifier, predictStartTime); 1.1535 + runner->AddPreconnect(newUri.spec); 1.1536 + NS_DispatchToMainThread(runner); 1.1537 + PredictForPageload(newUri, verifier, stackCount + 1, predictStartTime); 1.1538 + return; 1.1539 + } 1.1540 + 1.1541 + bool predicted = false; 1.1542 + 1.1543 + // We always try to be as specific as possible in our predictions, so try 1.1544 + // to predict based on the full URI before we fall back to the origin. 1.1545 + if (havePage) { 1.1546 + predicted = TryPredict(QUERY_PAGE, pageInfo, now, verifier, 1.1547 + predictStartTime); 1.1548 + } 1.1549 + 1.1550 + if (!predicted && haveOrigin) { 1.1551 + predicted = TryPredict(QUERY_ORIGIN, originInfo, now, verifier, 1.1552 + predictStartTime); 1.1553 + } 1.1554 + 1.1555 + if (!predicted) { 1.1556 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION, 1.1557 + predictStartTime); 1.1558 + } 1.1559 +} 1.1560 + 1.1561 +// This is the driver for predicting at browser startup time based on pages that 1.1562 +// have previously been loaded close to startup. 1.1563 +void 1.1564 +Seer::PredictForStartup(SeerVerifierHandle &verifier, 1.1565 + TimeStamp &predictStartTime) 1.1566 +{ 1.1567 + MOZ_ASSERT(!NS_IsMainThread(), "PredictForStartup called on main thread"); 1.1568 + 1.1569 + if (!mStartupCount) { 1.1570 + SEER_LOG(("Seer::PredictForStartup mStartupCount is zero!\n")); 1.1571 + ++mAccumulators->mStartupCountZeroes; 1.1572 + return; 1.1573 + } 1.1574 + 1.1575 + if (NS_FAILED(EnsureInitStorage())) { 1.1576 + return; 1.1577 + } 1.1578 + 1.1579 + nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement( 1.1580 + NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_startup_pages;")); 1.1581 + if (!stmt) { 1.1582 + return; 1.1583 + } 1.1584 + mozStorageStatementScoper scope(stmt); 1.1585 + nsresult rv; 1.1586 + bool hasRows; 1.1587 + 1.1588 + nsRefPtr<SeerPredictionRunner> runner = 1.1589 + new SeerPredictionRunner(verifier, predictStartTime); 1.1590 + 1.1591 + rv = stmt->ExecuteStep(&hasRows); 1.1592 + RETURN_IF_FAILED(rv); 1.1593 + 1.1594 + while (hasRows) { 1.1595 + nsAutoCString uri; 1.1596 + int32_t hitCount; 1.1597 + PRTime lastHit; 1.1598 + int baseConfidence, confidence; 1.1599 + 1.1600 + // We use goto nextrow here instead of just failling, because we want 1.1601 + // to do some sort of prediction if at all possible. Of course, it's 1.1602 + // probably unlikely that subsequent rows will succeed if one fails, but 1.1603 + // it's worth a shot. 1.1604 + 1.1605 + rv = stmt->GetUTF8String(0, uri); 1.1606 + if (NS_FAILED(rv)) { 1.1607 + goto nextrow; 1.1608 + } 1.1609 + 1.1610 + rv = stmt->GetInt32(1, &hitCount); 1.1611 + if (NS_FAILED(rv)) { 1.1612 + goto nextrow; 1.1613 + } 1.1614 + 1.1615 + rv = stmt->GetInt64(2, &lastHit); 1.1616 + if (NS_FAILED(rv)) { 1.1617 + goto nextrow; 1.1618 + } 1.1619 + 1.1620 + baseConfidence = (hitCount * 100) / mStartupCount; 1.1621 + confidence = CalculateConfidence(baseConfidence, lastHit, 1.1622 + mLastStartupTime, 0); 1.1623 + SetupPrediction(confidence, uri, runner); 1.1624 + 1.1625 +nextrow: 1.1626 + rv = stmt->ExecuteStep(&hasRows); 1.1627 + RETURN_IF_FAILED(rv); 1.1628 + } 1.1629 + 1.1630 + if (runner->HasWork()) { 1.1631 + NS_DispatchToMainThread(runner); 1.1632 + } else { 1.1633 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION, 1.1634 + predictStartTime); 1.1635 + } 1.1636 +} 1.1637 + 1.1638 +// All URIs we get passed *must* be http or https if they're not null. This 1.1639 +// helps ensure that. 1.1640 +static bool 1.1641 +IsNullOrHttp(nsIURI *uri) 1.1642 +{ 1.1643 + if (!uri) { 1.1644 + return true; 1.1645 + } 1.1646 + 1.1647 + bool isHTTP = false; 1.1648 + uri->SchemeIs("http", &isHTTP); 1.1649 + if (!isHTTP) { 1.1650 + uri->SchemeIs("https", &isHTTP); 1.1651 + } 1.1652 + 1.1653 + return isHTTP; 1.1654 +} 1.1655 + 1.1656 +nsresult 1.1657 +Seer::ReserveSpaceInQueue() 1.1658 +{ 1.1659 + MutexAutoLock lock(mQueueSizeLock); 1.1660 + 1.1661 + if (mQueueSize >= mMaxQueueSize) { 1.1662 + SEER_LOG(("Not enqueuing event - queue too large")); 1.1663 + return NS_ERROR_NOT_AVAILABLE; 1.1664 + } 1.1665 + 1.1666 + mQueueSize++; 1.1667 + return NS_OK; 1.1668 +} 1.1669 + 1.1670 +void 1.1671 +Seer::FreeSpaceInQueue() 1.1672 +{ 1.1673 + MutexAutoLock lock(mQueueSizeLock); 1.1674 + MOZ_ASSERT(mQueueSize > 0, "unexpected mQueueSize"); 1.1675 + mQueueSize--; 1.1676 +} 1.1677 + 1.1678 +// Called from the main thread to initiate predictive actions 1.1679 +NS_IMETHODIMP 1.1680 +Seer::Predict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason, 1.1681 + nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier) 1.1682 +{ 1.1683 + MOZ_ASSERT(NS_IsMainThread(), 1.1684 + "Seer interface methods must be called on the main thread"); 1.1685 + 1.1686 + if (!mInitialized) { 1.1687 + return NS_ERROR_NOT_AVAILABLE; 1.1688 + } 1.1689 + 1.1690 + if (!mEnabled) { 1.1691 + return NS_ERROR_NOT_AVAILABLE; 1.1692 + } 1.1693 + 1.1694 + if (loadContext && loadContext->UsePrivateBrowsing()) { 1.1695 + // Don't want to do anything in PB mode 1.1696 + return NS_OK; 1.1697 + } 1.1698 + 1.1699 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.1700 + // Nothing we can do for non-HTTP[S] schemes 1.1701 + return NS_OK; 1.1702 + } 1.1703 + 1.1704 + // Ensure we've been given the appropriate arguments for the kind of 1.1705 + // prediction we're being asked to do 1.1706 + switch (reason) { 1.1707 + case nsINetworkSeer::PREDICT_LINK: 1.1708 + if (!targetURI || !sourceURI) { 1.1709 + return NS_ERROR_INVALID_ARG; 1.1710 + } 1.1711 + // Link hover is a special case where we can predict without hitting the 1.1712 + // db, so let's go ahead and fire off that prediction here. 1.1713 + PredictForLink(targetURI, sourceURI, verifier); 1.1714 + return NS_OK; 1.1715 + case nsINetworkSeer::PREDICT_LOAD: 1.1716 + if (!targetURI || sourceURI) { 1.1717 + return NS_ERROR_INVALID_ARG; 1.1718 + } 1.1719 + break; 1.1720 + case nsINetworkSeer::PREDICT_STARTUP: 1.1721 + if (targetURI || sourceURI) { 1.1722 + return NS_ERROR_INVALID_ARG; 1.1723 + } 1.1724 + break; 1.1725 + default: 1.1726 + return NS_ERROR_INVALID_ARG; 1.1727 + } 1.1728 + 1.1729 + ++mAccumulators->mPredictAttempts; 1.1730 + nsresult rv = ReserveSpaceInQueue(); 1.1731 + if (NS_FAILED(rv)) { 1.1732 + ++mAccumulators->mPredictFullQueue; 1.1733 + return NS_ERROR_NOT_AVAILABLE; 1.1734 + } 1.1735 + 1.1736 + nsRefPtr<SeerPredictionEvent> event = new SeerPredictionEvent(targetURI, 1.1737 + sourceURI, 1.1738 + reason, 1.1739 + verifier); 1.1740 + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.1741 +} 1.1742 + 1.1743 +// A runnable for updating our information in the database. This must always 1.1744 +// be dispatched to the seer thread. 1.1745 +class SeerLearnEvent : public nsRunnable 1.1746 +{ 1.1747 +public: 1.1748 + SeerLearnEvent(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason) 1.1749 + :mReason(reason) 1.1750 + { 1.1751 + MOZ_ASSERT(NS_IsMainThread(), "Creating learn event off main thread"); 1.1752 + 1.1753 + mEnqueueTime = TimeStamp::Now(); 1.1754 + 1.1755 + targetURI->GetAsciiSpec(mTargetURI.spec); 1.1756 + ExtractOrigin(targetURI, mTargetURI.origin); 1.1757 + if (sourceURI) { 1.1758 + sourceURI->GetAsciiSpec(mSourceURI.spec); 1.1759 + ExtractOrigin(sourceURI, mSourceURI.origin); 1.1760 + } 1.1761 + } 1.1762 + 1.1763 + NS_IMETHOD Run() MOZ_OVERRIDE 1.1764 + { 1.1765 + MOZ_ASSERT(!NS_IsMainThread(), "Running learn off main thread"); 1.1766 + 1.1767 + nsresult rv = NS_OK; 1.1768 + 1.1769 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WAIT_TIME, 1.1770 + mEnqueueTime); 1.1771 + 1.1772 + TimeStamp startTime = TimeStamp::Now(); 1.1773 + 1.1774 + switch (mReason) { 1.1775 + case nsINetworkSeer::LEARN_LOAD_TOPLEVEL: 1.1776 + gSeer->LearnForToplevel(mTargetURI); 1.1777 + break; 1.1778 + case nsINetworkSeer::LEARN_LOAD_REDIRECT: 1.1779 + gSeer->LearnForRedirect(mTargetURI, mSourceURI); 1.1780 + break; 1.1781 + case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE: 1.1782 + gSeer->LearnForSubresource(mTargetURI, mSourceURI); 1.1783 + break; 1.1784 + case nsINetworkSeer::LEARN_STARTUP: 1.1785 + gSeer->LearnForStartup(mTargetURI); 1.1786 + break; 1.1787 + default: 1.1788 + MOZ_ASSERT(false, "Got unexpected value for learn reason"); 1.1789 + rv = NS_ERROR_UNEXPECTED; 1.1790 + } 1.1791 + 1.1792 + gSeer->FreeSpaceInQueue(); 1.1793 + 1.1794 + Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WORK_TIME, startTime); 1.1795 + 1.1796 + gSeer->MaybeScheduleCleanup(); 1.1797 + 1.1798 + return rv; 1.1799 + } 1.1800 +private: 1.1801 + Seer::UriInfo mTargetURI; 1.1802 + Seer::UriInfo mSourceURI; 1.1803 + SeerLearnReason mReason; 1.1804 + TimeStamp mEnqueueTime; 1.1805 +}; 1.1806 + 1.1807 +void 1.1808 +Seer::LearnForToplevel(const UriInfo &uri) 1.1809 +{ 1.1810 + MOZ_ASSERT(!NS_IsMainThread(), "LearnForToplevel called on main thread."); 1.1811 + 1.1812 + if (NS_FAILED(EnsureInitStorage())) { 1.1813 + return; 1.1814 + } 1.1815 + 1.1816 + PRTime now = PR_Now(); 1.1817 + 1.1818 + MaybeLearnForStartup(uri, now); 1.1819 + 1.1820 + TopLevelInfo pageInfo; 1.1821 + TopLevelInfo originInfo; 1.1822 + bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo); 1.1823 + bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo); 1.1824 + 1.1825 + if (!havePage) { 1.1826 + AddTopLevel(QUERY_PAGE, uri.spec, now); 1.1827 + } else { 1.1828 + UpdateTopLevel(QUERY_PAGE, pageInfo, now); 1.1829 + } 1.1830 + 1.1831 + if (!haveOrigin) { 1.1832 + AddTopLevel(QUERY_ORIGIN, uri.origin, now); 1.1833 + } else { 1.1834 + UpdateTopLevel(QUERY_ORIGIN, originInfo, now); 1.1835 + } 1.1836 +} 1.1837 + 1.1838 +// Queries to look up information about a *specific* subresource associated 1.1839 +// with a *specific* top-level load. 1.1840 +bool 1.1841 +Seer::LookupSubresource(QueryType queryType, const int32_t parentId, 1.1842 + const nsACString &key, SubresourceInfo &info) 1.1843 +{ 1.1844 + MOZ_ASSERT(!NS_IsMainThread(), "LookupSubresource called on main thread."); 1.1845 + 1.1846 + nsCOMPtr<mozIStorageStatement> stmt; 1.1847 + if (queryType == QUERY_PAGE) { 1.1848 + stmt = mStatements.GetCachedStatement( 1.1849 + NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subresources " 1.1850 + "WHERE pid = :parent_id AND uri = :key;")); 1.1851 + } else { 1.1852 + stmt = mStatements.GetCachedStatement( 1.1853 + NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subhosts WHERE " 1.1854 + "hid = :parent_id AND origin = :key;")); 1.1855 + } 1.1856 + NS_ENSURE_TRUE(stmt, false); 1.1857 + mozStorageStatementScoper scope(stmt); 1.1858 + 1.1859 + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"), 1.1860 + parentId); 1.1861 + NS_ENSURE_SUCCESS(rv, false); 1.1862 + 1.1863 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); 1.1864 + NS_ENSURE_SUCCESS(rv, false); 1.1865 + 1.1866 + bool hasRows; 1.1867 + rv = stmt->ExecuteStep(&hasRows); 1.1868 + NS_ENSURE_SUCCESS(rv, false); 1.1869 + if (!hasRows) { 1.1870 + return false; 1.1871 + } 1.1872 + 1.1873 + rv = stmt->GetInt32(0, &info.id); 1.1874 + NS_ENSURE_SUCCESS(rv, false); 1.1875 + 1.1876 + rv = stmt->GetInt32(1, &info.hitCount); 1.1877 + NS_ENSURE_SUCCESS(rv, false); 1.1878 + 1.1879 + rv = stmt->GetInt64(2, &info.lastHit); 1.1880 + NS_ENSURE_SUCCESS(rv, false); 1.1881 + 1.1882 + return true; 1.1883 +} 1.1884 + 1.1885 +// Add information about a new subresource associated with a top-level load. 1.1886 +void 1.1887 +Seer::AddSubresource(QueryType queryType, const int32_t parentId, 1.1888 + const nsACString &key, const PRTime now) 1.1889 +{ 1.1890 + MOZ_ASSERT(!NS_IsMainThread(), "AddSubresource called on main thread."); 1.1891 + 1.1892 + nsCOMPtr<mozIStorageStatement> stmt; 1.1893 + if (queryType == QUERY_PAGE) { 1.1894 + stmt = mStatements.GetCachedStatement( 1.1895 + NS_LITERAL_CSTRING("INSERT INTO moz_subresources " 1.1896 + "(pid, uri, hits, last_hit) VALUES " 1.1897 + "(:parent_id, :key, 1, :now);")); 1.1898 + } else { 1.1899 + stmt = mStatements.GetCachedStatement( 1.1900 + NS_LITERAL_CSTRING("INSERT INTO moz_subhosts " 1.1901 + "(hid, origin, hits, last_hit) VALUES " 1.1902 + "(:parent_id, :key, 1, :now);")); 1.1903 + } 1.1904 + if (!stmt) { 1.1905 + return; 1.1906 + } 1.1907 + mozStorageStatementScoper scope(stmt); 1.1908 + 1.1909 + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"), 1.1910 + parentId); 1.1911 + RETURN_IF_FAILED(rv); 1.1912 + 1.1913 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); 1.1914 + RETURN_IF_FAILED(rv); 1.1915 + 1.1916 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); 1.1917 + RETURN_IF_FAILED(rv); 1.1918 + 1.1919 + rv = stmt->Execute(); 1.1920 +} 1.1921 + 1.1922 +// Update the information about a particular subresource associated with a 1.1923 +// top-level load 1.1924 +void 1.1925 +Seer::UpdateSubresource(QueryType queryType, const SubresourceInfo &info, 1.1926 + const PRTime now) 1.1927 +{ 1.1928 + MOZ_ASSERT(!NS_IsMainThread(), "UpdateSubresource called on main thread."); 1.1929 + 1.1930 + nsCOMPtr<mozIStorageStatement> stmt; 1.1931 + if (queryType == QUERY_PAGE) { 1.1932 + stmt = mStatements.GetCachedStatement( 1.1933 + NS_LITERAL_CSTRING("UPDATE moz_subresources SET hits = :hit_count, " 1.1934 + "last_hit = :now WHERE id = :id;")); 1.1935 + } else { 1.1936 + stmt = mStatements.GetCachedStatement( 1.1937 + NS_LITERAL_CSTRING("UPDATE moz_subhosts SET hits = :hit_count, " 1.1938 + "last_hit = :now WHERE id = :id;")); 1.1939 + } 1.1940 + if (!stmt) { 1.1941 + return; 1.1942 + } 1.1943 + mozStorageStatementScoper scope(stmt); 1.1944 + 1.1945 + nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"), 1.1946 + info.hitCount + 1); 1.1947 + RETURN_IF_FAILED(rv); 1.1948 + 1.1949 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); 1.1950 + RETURN_IF_FAILED(rv); 1.1951 + 1.1952 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); 1.1953 + RETURN_IF_FAILED(rv); 1.1954 + 1.1955 + rv = stmt->Execute(); 1.1956 +} 1.1957 + 1.1958 +// Called when a subresource has been hit from a top-level load. Uses the two 1.1959 +// helper functions above to update the database appropriately. 1.1960 +void 1.1961 +Seer::LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI) 1.1962 +{ 1.1963 + MOZ_ASSERT(!NS_IsMainThread(), "LearnForSubresource called on main thread."); 1.1964 + 1.1965 + if (NS_FAILED(EnsureInitStorage())) { 1.1966 + return; 1.1967 + } 1.1968 + 1.1969 + TopLevelInfo pageInfo, originInfo; 1.1970 + bool havePage = LookupTopLevel(QUERY_PAGE, sourceURI.spec, pageInfo); 1.1971 + bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, sourceURI.origin, 1.1972 + originInfo); 1.1973 + 1.1974 + if (!havePage && !haveOrigin) { 1.1975 + // Nothing to do, since we know nothing about the top level resource 1.1976 + return; 1.1977 + } 1.1978 + 1.1979 + SubresourceInfo resourceInfo; 1.1980 + bool haveResource = false; 1.1981 + if (havePage) { 1.1982 + haveResource = LookupSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, 1.1983 + resourceInfo); 1.1984 + } 1.1985 + 1.1986 + SubresourceInfo hostInfo; 1.1987 + bool haveHost = false; 1.1988 + if (haveOrigin) { 1.1989 + haveHost = LookupSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, 1.1990 + hostInfo); 1.1991 + } 1.1992 + 1.1993 + PRTime now = PR_Now(); 1.1994 + 1.1995 + if (haveResource) { 1.1996 + UpdateSubresource(QUERY_PAGE, resourceInfo, now); 1.1997 + } else if (havePage) { 1.1998 + AddSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, now); 1.1999 + } 1.2000 + // Can't add a subresource to a page we don't have in our db. 1.2001 + 1.2002 + if (haveHost) { 1.2003 + UpdateSubresource(QUERY_ORIGIN, hostInfo, now); 1.2004 + } else if (haveOrigin) { 1.2005 + AddSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, now); 1.2006 + } 1.2007 + // Can't add a subhost to a host we don't have in our db 1.2008 +} 1.2009 + 1.2010 +// This is called when a top-level loaded ended up redirecting to a different 1.2011 +// URI so we can keep track of that fact. 1.2012 +void 1.2013 +Seer::LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI) 1.2014 +{ 1.2015 + MOZ_ASSERT(!NS_IsMainThread(), "LearnForRedirect called on main thread."); 1.2016 + 1.2017 + if (NS_FAILED(EnsureInitStorage())) { 1.2018 + return; 1.2019 + } 1.2020 + 1.2021 + PRTime now = PR_Now(); 1.2022 + nsresult rv; 1.2023 + 1.2024 + nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement( 1.2025 + NS_LITERAL_CSTRING("SELECT id FROM moz_pages WHERE uri = :spec;")); 1.2026 + if (!getPage) { 1.2027 + return; 1.2028 + } 1.2029 + mozStorageStatementScoper scopedPage(getPage); 1.2030 + 1.2031 + // look up source in moz_pages 1.2032 + rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), 1.2033 + sourceURI.spec); 1.2034 + RETURN_IF_FAILED(rv); 1.2035 + 1.2036 + bool hasRows; 1.2037 + rv = getPage->ExecuteStep(&hasRows); 1.2038 + if (NS_FAILED(rv) || !hasRows) { 1.2039 + return; 1.2040 + } 1.2041 + 1.2042 + int32_t pageId; 1.2043 + rv = getPage->GetInt32(0, &pageId); 1.2044 + RETURN_IF_FAILED(rv); 1.2045 + 1.2046 + nsCOMPtr<mozIStorageStatement> getRedirect = mStatements.GetCachedStatement( 1.2047 + NS_LITERAL_CSTRING("SELECT id, hits FROM moz_redirects WHERE " 1.2048 + "pid = :page_id AND uri = :spec;")); 1.2049 + if (!getRedirect) { 1.2050 + return; 1.2051 + } 1.2052 + mozStorageStatementScoper scopedRedirect(getRedirect); 1.2053 + 1.2054 + rv = getRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); 1.2055 + RETURN_IF_FAILED(rv); 1.2056 + 1.2057 + rv = getRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), 1.2058 + targetURI.spec); 1.2059 + RETURN_IF_FAILED(rv); 1.2060 + 1.2061 + rv = getRedirect->ExecuteStep(&hasRows); 1.2062 + RETURN_IF_FAILED(rv); 1.2063 + 1.2064 + if (!hasRows) { 1.2065 + // This is the first time we've seen this top-level redirect to this URI 1.2066 + nsCOMPtr<mozIStorageStatement> addRedirect = mStatements.GetCachedStatement( 1.2067 + NS_LITERAL_CSTRING("INSERT INTO moz_redirects " 1.2068 + "(pid, uri, origin, hits, last_hit) VALUES " 1.2069 + "(:page_id, :spec, :origin, 1, :now);")); 1.2070 + if (!addRedirect) { 1.2071 + return; 1.2072 + } 1.2073 + mozStorageStatementScoper scopedAdd(addRedirect); 1.2074 + 1.2075 + rv = addRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); 1.2076 + RETURN_IF_FAILED(rv); 1.2077 + 1.2078 + rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), 1.2079 + targetURI.spec); 1.2080 + RETURN_IF_FAILED(rv); 1.2081 + 1.2082 + rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), 1.2083 + targetURI.origin); 1.2084 + RETURN_IF_FAILED(rv); 1.2085 + 1.2086 + rv = addRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); 1.2087 + RETURN_IF_FAILED(rv); 1.2088 + 1.2089 + rv = addRedirect->Execute(); 1.2090 + } else { 1.2091 + // We've seen this redirect before 1.2092 + int32_t redirId, hits; 1.2093 + rv = getRedirect->GetInt32(0, &redirId); 1.2094 + RETURN_IF_FAILED(rv); 1.2095 + 1.2096 + rv = getRedirect->GetInt32(1, &hits); 1.2097 + RETURN_IF_FAILED(rv); 1.2098 + 1.2099 + nsCOMPtr<mozIStorageStatement> updateRedirect = 1.2100 + mStatements.GetCachedStatement( 1.2101 + NS_LITERAL_CSTRING("UPDATE moz_redirects SET hits = :hits, " 1.2102 + "last_hit = :now WHERE id = :redir;")); 1.2103 + if (!updateRedirect) { 1.2104 + return; 1.2105 + } 1.2106 + mozStorageStatementScoper scopedUpdate(updateRedirect); 1.2107 + 1.2108 + rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("hits"), hits + 1); 1.2109 + RETURN_IF_FAILED(rv); 1.2110 + 1.2111 + rv = updateRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); 1.2112 + RETURN_IF_FAILED(rv); 1.2113 + 1.2114 + rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("redir"), redirId); 1.2115 + RETURN_IF_FAILED(rv); 1.2116 + 1.2117 + updateRedirect->Execute(); 1.2118 + } 1.2119 +} 1.2120 + 1.2121 +// Add information about a top-level load to our list of startup pages 1.2122 +void 1.2123 +Seer::LearnForStartup(const UriInfo &uri) 1.2124 +{ 1.2125 + MOZ_ASSERT(!NS_IsMainThread(), "LearnForStartup called on main thread."); 1.2126 + 1.2127 + if (NS_FAILED(EnsureInitStorage())) { 1.2128 + return; 1.2129 + } 1.2130 + 1.2131 + nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement( 1.2132 + NS_LITERAL_CSTRING("SELECT id, hits FROM moz_startup_pages WHERE " 1.2133 + "uri = :origin;")); 1.2134 + if (!getPage) { 1.2135 + return; 1.2136 + } 1.2137 + mozStorageStatementScoper scopedPage(getPage); 1.2138 + nsresult rv; 1.2139 + 1.2140 + rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), uri.origin); 1.2141 + RETURN_IF_FAILED(rv); 1.2142 + 1.2143 + bool hasRows; 1.2144 + rv = getPage->ExecuteStep(&hasRows); 1.2145 + RETURN_IF_FAILED(rv); 1.2146 + 1.2147 + if (hasRows) { 1.2148 + // We've loaded this page on startup before 1.2149 + int32_t pageId, hitCount; 1.2150 + 1.2151 + rv = getPage->GetInt32(0, &pageId); 1.2152 + RETURN_IF_FAILED(rv); 1.2153 + 1.2154 + rv = getPage->GetInt32(1, &hitCount); 1.2155 + RETURN_IF_FAILED(rv); 1.2156 + 1.2157 + nsCOMPtr<mozIStorageStatement> updatePage = mStatements.GetCachedStatement( 1.2158 + NS_LITERAL_CSTRING("UPDATE moz_startup_pages SET hits = :hit_count, " 1.2159 + "last_hit = :startup_time WHERE id = :page_id;")); 1.2160 + if (!updatePage) { 1.2161 + return; 1.2162 + } 1.2163 + mozStorageStatementScoper scopedUpdate(updatePage); 1.2164 + 1.2165 + rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"), 1.2166 + hitCount + 1); 1.2167 + RETURN_IF_FAILED(rv); 1.2168 + 1.2169 + rv = updatePage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), 1.2170 + mStartupTime); 1.2171 + RETURN_IF_FAILED(rv); 1.2172 + 1.2173 + rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); 1.2174 + RETURN_IF_FAILED(rv); 1.2175 + 1.2176 + updatePage->Execute(); 1.2177 + } else { 1.2178 + // New startup page 1.2179 + nsCOMPtr<mozIStorageStatement> addPage = mStatements.GetCachedStatement( 1.2180 + NS_LITERAL_CSTRING("INSERT INTO moz_startup_pages (uri, hits, " 1.2181 + "last_hit) VALUES (:origin, 1, :startup_time);")); 1.2182 + if (!addPage) { 1.2183 + return; 1.2184 + } 1.2185 + mozStorageStatementScoper scopedAdd(addPage); 1.2186 + rv = addPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), 1.2187 + uri.origin); 1.2188 + RETURN_IF_FAILED(rv); 1.2189 + 1.2190 + rv = addPage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), 1.2191 + mStartupTime); 1.2192 + RETURN_IF_FAILED(rv); 1.2193 + 1.2194 + addPage->Execute(); 1.2195 + } 1.2196 +} 1.2197 + 1.2198 +// Called from the main thread to update the database 1.2199 +NS_IMETHODIMP 1.2200 +Seer::Learn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, 1.2201 + nsILoadContext *loadContext) 1.2202 +{ 1.2203 + MOZ_ASSERT(NS_IsMainThread(), 1.2204 + "Seer interface methods must be called on the main thread"); 1.2205 + 1.2206 + if (!mInitialized) { 1.2207 + return NS_ERROR_NOT_AVAILABLE; 1.2208 + } 1.2209 + 1.2210 + if (!mEnabled) { 1.2211 + return NS_ERROR_NOT_AVAILABLE; 1.2212 + } 1.2213 + 1.2214 + if (loadContext && loadContext->UsePrivateBrowsing()) { 1.2215 + // Don't want to do anything in PB mode 1.2216 + return NS_OK; 1.2217 + } 1.2218 + 1.2219 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.2220 + return NS_ERROR_INVALID_ARG; 1.2221 + } 1.2222 + 1.2223 + switch (reason) { 1.2224 + case nsINetworkSeer::LEARN_LOAD_TOPLEVEL: 1.2225 + case nsINetworkSeer::LEARN_STARTUP: 1.2226 + if (!targetURI || sourceURI) { 1.2227 + return NS_ERROR_INVALID_ARG; 1.2228 + } 1.2229 + break; 1.2230 + case nsINetworkSeer::LEARN_LOAD_REDIRECT: 1.2231 + case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE: 1.2232 + if (!targetURI || !sourceURI) { 1.2233 + return NS_ERROR_INVALID_ARG; 1.2234 + } 1.2235 + break; 1.2236 + default: 1.2237 + return NS_ERROR_INVALID_ARG; 1.2238 + } 1.2239 + 1.2240 + ++mAccumulators->mLearnAttempts; 1.2241 + nsresult rv = ReserveSpaceInQueue(); 1.2242 + if (NS_FAILED(rv)) { 1.2243 + ++mAccumulators->mLearnFullQueue; 1.2244 + return NS_ERROR_NOT_AVAILABLE; 1.2245 + } 1.2246 + 1.2247 + nsRefPtr<SeerLearnEvent> event = new SeerLearnEvent(targetURI, sourceURI, 1.2248 + reason); 1.2249 + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.2250 +} 1.2251 + 1.2252 +// Runnable to clear out the database. Dispatched from the main thread to the 1.2253 +// seer thread 1.2254 +class SeerResetEvent : public nsRunnable 1.2255 +{ 1.2256 +public: 1.2257 + SeerResetEvent() 1.2258 + { } 1.2259 + 1.2260 + NS_IMETHOD Run() MOZ_OVERRIDE 1.2261 + { 1.2262 + MOZ_ASSERT(!NS_IsMainThread(), "Running reset on main thread"); 1.2263 + 1.2264 + gSeer->ResetInternal(); 1.2265 + 1.2266 + return NS_OK; 1.2267 + } 1.2268 +}; 1.2269 + 1.2270 +// Helper that actually does the database wipe. 1.2271 +void 1.2272 +Seer::ResetInternal() 1.2273 +{ 1.2274 + MOZ_ASSERT(!NS_IsMainThread(), "Resetting db on main thread"); 1.2275 + 1.2276 + nsresult rv = EnsureInitStorage(); 1.2277 + RETURN_IF_FAILED(rv); 1.2278 + 1.2279 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_redirects;")); 1.2280 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages;")); 1.2281 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startups;")); 1.2282 + 1.2283 + // These cascade to moz_subresources and moz_subhosts, respectively. 1.2284 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;")); 1.2285 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts;")); 1.2286 + 1.2287 + VacuumDatabase(); 1.2288 + 1.2289 + // Go ahead and ensure this is flushed to disk 1.2290 + CommitTransaction(); 1.2291 + BeginTransaction(); 1.2292 +} 1.2293 + 1.2294 +// Called on the main thread to clear out all our knowledge. Tabula Rasa FTW! 1.2295 +NS_IMETHODIMP 1.2296 +Seer::Reset() 1.2297 +{ 1.2298 + MOZ_ASSERT(NS_IsMainThread(), 1.2299 + "Seer interface methods must be called on the main thread"); 1.2300 + 1.2301 + if (!mInitialized) { 1.2302 + return NS_ERROR_NOT_AVAILABLE; 1.2303 + } 1.2304 + 1.2305 + if (!mEnabled) { 1.2306 + return NS_OK; 1.2307 + } 1.2308 + 1.2309 + nsRefPtr<SeerResetEvent> event = new SeerResetEvent(); 1.2310 + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.2311 +} 1.2312 + 1.2313 +class SeerCleanupEvent : public nsRunnable 1.2314 +{ 1.2315 +public: 1.2316 + NS_IMETHOD Run() MOZ_OVERRIDE 1.2317 + { 1.2318 + gSeer->Cleanup(); 1.2319 + gSeer->mCleanupScheduled = false; 1.2320 + return NS_OK; 1.2321 + } 1.2322 +}; 1.2323 + 1.2324 +// Returns the current size (in bytes) of the db file on disk 1.2325 +int64_t 1.2326 +Seer::GetDBFileSize() 1.2327 +{ 1.2328 + MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSize called on main thread!"); 1.2329 + 1.2330 + nsresult rv = EnsureInitStorage(); 1.2331 + if (NS_FAILED(rv)) { 1.2332 + SEER_LOG(("GetDBFileSize called without db available!")); 1.2333 + return 0; 1.2334 + } 1.2335 + 1.2336 + CommitTransaction(); 1.2337 + 1.2338 + nsCOMPtr<mozIStorageStatement> countStmt = mStatements.GetCachedStatement( 1.2339 + NS_LITERAL_CSTRING("PRAGMA page_count;")); 1.2340 + if (!countStmt) { 1.2341 + return 0; 1.2342 + } 1.2343 + mozStorageStatementScoper scopedCount(countStmt); 1.2344 + bool hasRows; 1.2345 + rv = countStmt->ExecuteStep(&hasRows); 1.2346 + if (NS_FAILED(rv) || !hasRows) { 1.2347 + return 0; 1.2348 + } 1.2349 + int64_t pageCount; 1.2350 + rv = countStmt->GetInt64(0, &pageCount); 1.2351 + if (NS_FAILED(rv)) { 1.2352 + return 0; 1.2353 + } 1.2354 + 1.2355 + nsCOMPtr<mozIStorageStatement> sizeStmt = mStatements.GetCachedStatement( 1.2356 + NS_LITERAL_CSTRING("PRAGMA page_size;")); 1.2357 + if (!sizeStmt) { 1.2358 + return 0; 1.2359 + } 1.2360 + mozStorageStatementScoper scopedSize(sizeStmt); 1.2361 + rv = sizeStmt->ExecuteStep(&hasRows); 1.2362 + if (NS_FAILED(rv) || !hasRows) { 1.2363 + return 0; 1.2364 + } 1.2365 + int64_t pageSize; 1.2366 + rv = sizeStmt->GetInt64(0, &pageSize); 1.2367 + if (NS_FAILED(rv)) { 1.2368 + return 0; 1.2369 + } 1.2370 + 1.2371 + BeginTransaction(); 1.2372 + 1.2373 + return pageCount * pageSize; 1.2374 +} 1.2375 + 1.2376 +// Returns the size (in bytes) that the db file will consume on disk AFTER we 1.2377 +// vacuum the db. 1.2378 +int64_t 1.2379 +Seer::GetDBFileSizeAfterVacuum() 1.2380 +{ 1.2381 + MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSizeAfterVacuum called on main thread!"); 1.2382 + 1.2383 + CommitTransaction(); 1.2384 + 1.2385 + nsCOMPtr<mozIStorageStatement> countStmt = mStatements.GetCachedStatement( 1.2386 + NS_LITERAL_CSTRING("PRAGMA page_count;")); 1.2387 + if (!countStmt) { 1.2388 + return 0; 1.2389 + } 1.2390 + mozStorageStatementScoper scopedCount(countStmt); 1.2391 + bool hasRows; 1.2392 + nsresult rv = countStmt->ExecuteStep(&hasRows); 1.2393 + if (NS_FAILED(rv) || !hasRows) { 1.2394 + return 0; 1.2395 + } 1.2396 + int64_t pageCount; 1.2397 + rv = countStmt->GetInt64(0, &pageCount); 1.2398 + if (NS_FAILED(rv)) { 1.2399 + return 0; 1.2400 + } 1.2401 + 1.2402 + nsCOMPtr<mozIStorageStatement> sizeStmt = mStatements.GetCachedStatement( 1.2403 + NS_LITERAL_CSTRING("PRAGMA page_size;")); 1.2404 + if (!sizeStmt) { 1.2405 + return 0; 1.2406 + } 1.2407 + mozStorageStatementScoper scopedSize(sizeStmt); 1.2408 + rv = sizeStmt->ExecuteStep(&hasRows); 1.2409 + if (NS_FAILED(rv) || !hasRows) { 1.2410 + return 0; 1.2411 + } 1.2412 + int64_t pageSize; 1.2413 + rv = sizeStmt->GetInt64(0, &pageSize); 1.2414 + if (NS_FAILED(rv)) { 1.2415 + return 0; 1.2416 + } 1.2417 + 1.2418 + nsCOMPtr<mozIStorageStatement> freeStmt = mStatements.GetCachedStatement( 1.2419 + NS_LITERAL_CSTRING("PRAGMA freelist_count;")); 1.2420 + if (!freeStmt) { 1.2421 + return 0; 1.2422 + } 1.2423 + mozStorageStatementScoper scopedFree(freeStmt); 1.2424 + rv = freeStmt->ExecuteStep(&hasRows); 1.2425 + if (NS_FAILED(rv) || !hasRows) { 1.2426 + return 0; 1.2427 + } 1.2428 + int64_t freelistCount; 1.2429 + rv = freeStmt->GetInt64(0, &freelistCount); 1.2430 + if (NS_FAILED(rv)) { 1.2431 + return 0; 1.2432 + } 1.2433 + 1.2434 + BeginTransaction(); 1.2435 + 1.2436 + return (pageCount - freelistCount) * pageSize; 1.2437 +} 1.2438 + 1.2439 +void 1.2440 +Seer::MaybeScheduleCleanup() 1.2441 +{ 1.2442 + MOZ_ASSERT(!NS_IsMainThread(), "MaybeScheduleCleanup called on main thread!"); 1.2443 + 1.2444 + // This is a little racy, but it's a nice little shutdown optimization if the 1.2445 + // race works out the right way. 1.2446 + if (!mInitialized) { 1.2447 + return; 1.2448 + } 1.2449 + 1.2450 + if (mCleanupScheduled) { 1.2451 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false); 1.2452 + return; 1.2453 + } 1.2454 + 1.2455 + int64_t dbFileSize = GetDBFileSize(); 1.2456 + if (dbFileSize < mMaxDBSize) { 1.2457 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false); 1.2458 + return; 1.2459 + } 1.2460 + 1.2461 + mCleanupScheduled = true; 1.2462 + 1.2463 + nsRefPtr<SeerCleanupEvent> event = new SeerCleanupEvent(); 1.2464 + nsresult rv = mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.2465 + if (NS_FAILED(rv)) { 1.2466 + mCleanupScheduled = false; 1.2467 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false); 1.2468 + } else { 1.2469 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, true); 1.2470 + } 1.2471 +} 1.2472 + 1.2473 +#ifndef ANDROID 1.2474 +static const long long CLEANUP_CUTOFF = ONE_MONTH; 1.2475 +#else 1.2476 +static const long long CLEANUP_CUTOFF = ONE_WEEK; 1.2477 +#endif 1.2478 + 1.2479 +void 1.2480 +Seer::CleanupOrigins(PRTime now) 1.2481 +{ 1.2482 + PRTime cutoff = now - CLEANUP_CUTOFF; 1.2483 + 1.2484 + nsCOMPtr<mozIStorageStatement> deleteOrigins = mStatements.GetCachedStatement( 1.2485 + NS_LITERAL_CSTRING("DELETE FROM moz_hosts WHERE last_load <= :cutoff")); 1.2486 + if (!deleteOrigins) { 1.2487 + return; 1.2488 + } 1.2489 + mozStorageStatementScoper scopedOrigins(deleteOrigins); 1.2490 + 1.2491 + nsresult rv = deleteOrigins->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"), 1.2492 + cutoff); 1.2493 + RETURN_IF_FAILED(rv); 1.2494 + 1.2495 + deleteOrigins->Execute(); 1.2496 +} 1.2497 + 1.2498 +void 1.2499 +Seer::CleanupStartupPages(PRTime now) 1.2500 +{ 1.2501 + PRTime cutoff = now - ONE_WEEK; 1.2502 + 1.2503 + nsCOMPtr<mozIStorageStatement> deletePages = mStatements.GetCachedStatement( 1.2504 + NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages WHERE " 1.2505 + "last_hit <= :cutoff")); 1.2506 + if (!deletePages) { 1.2507 + return; 1.2508 + } 1.2509 + mozStorageStatementScoper scopedPages(deletePages); 1.2510 + 1.2511 + nsresult rv = deletePages->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"), 1.2512 + cutoff); 1.2513 + RETURN_IF_FAILED(rv); 1.2514 + 1.2515 + deletePages->Execute(); 1.2516 +} 1.2517 + 1.2518 +int32_t 1.2519 +Seer::GetSubresourceCount() 1.2520 +{ 1.2521 + nsCOMPtr<mozIStorageStatement> count = mStatements.GetCachedStatement( 1.2522 + NS_LITERAL_CSTRING("SELECT COUNT(id) FROM moz_subresources")); 1.2523 + if (!count) { 1.2524 + return 0; 1.2525 + } 1.2526 + mozStorageStatementScoper scopedCount(count); 1.2527 + 1.2528 + bool hasRows; 1.2529 + nsresult rv = count->ExecuteStep(&hasRows); 1.2530 + if (NS_FAILED(rv) || !hasRows) { 1.2531 + return 0; 1.2532 + } 1.2533 + 1.2534 + int32_t subresourceCount = 0; 1.2535 + count->GetInt32(0, &subresourceCount); 1.2536 + 1.2537 + return subresourceCount; 1.2538 +} 1.2539 + 1.2540 +void 1.2541 +Seer::Cleanup() 1.2542 +{ 1.2543 + MOZ_ASSERT(!NS_IsMainThread(), "Seer::Cleanup called on main thread!"); 1.2544 + 1.2545 + nsresult rv = EnsureInitStorage(); 1.2546 + if (NS_FAILED(rv)) { 1.2547 + return; 1.2548 + } 1.2549 + 1.2550 + int64_t dbFileSize = GetDBFileSize(); 1.2551 + float preservePercentage = static_cast<float>(mPreservePercentage) / 100.0; 1.2552 + int64_t evictionCutoff = static_cast<int64_t>(mMaxDBSize) * preservePercentage; 1.2553 + if (dbFileSize < evictionCutoff) { 1.2554 + return; 1.2555 + } 1.2556 + 1.2557 + CommitTransaction(); 1.2558 + BeginTransaction(); 1.2559 + 1.2560 + PRTime now = PR_Now(); 1.2561 + if (mLastCleanupTime) { 1.2562 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_DELTA, 1.2563 + (now - mLastCleanupTime) / 1000); 1.2564 + } 1.2565 + mLastCleanupTime = now; 1.2566 + 1.2567 + CleanupOrigins(now); 1.2568 + CleanupStartupPages(now); 1.2569 + 1.2570 + dbFileSize = GetDBFileSizeAfterVacuum(); 1.2571 + if (dbFileSize < evictionCutoff) { 1.2572 + // We've deleted enough stuff, time to free up the disk space and be on 1.2573 + // our way. 1.2574 + VacuumDatabase(); 1.2575 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true); 1.2576 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME, 1.2577 + (PR_Now() - mLastCleanupTime) / 1000); 1.2578 + return; 1.2579 + } 1.2580 + 1.2581 + bool canDelete = true; 1.2582 + while (canDelete && (dbFileSize >= evictionCutoff)) { 1.2583 + int32_t subresourceCount = GetSubresourceCount(); 1.2584 + if (!subresourceCount) { 1.2585 + canDelete = false; 1.2586 + break; 1.2587 + } 1.2588 + 1.2589 + // DB size scales pretty much linearly with the number of rows in 1.2590 + // moz_subresources, so we can guess how many rows we need to delete pretty 1.2591 + // accurately. 1.2592 + float percentNeeded = static_cast<float>(dbFileSize - evictionCutoff) / 1.2593 + static_cast<float>(dbFileSize); 1.2594 + 1.2595 + int32_t subresourcesToDelete = static_cast<int32_t>(percentNeeded * subresourceCount); 1.2596 + if (!subresourcesToDelete) { 1.2597 + // We're getting pretty close to nothing here, anyway, so we may as well 1.2598 + // just trash it all. This delete cascades to moz_subresources, as well. 1.2599 + rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;")); 1.2600 + if (NS_FAILED(rv)) { 1.2601 + canDelete = false; 1.2602 + break; 1.2603 + } 1.2604 + } else { 1.2605 + nsCOMPtr<mozIStorageStatement> deleteStatement = mStatements.GetCachedStatement( 1.2606 + NS_LITERAL_CSTRING("DELETE FROM moz_subresources WHERE id IN " 1.2607 + "(SELECT id FROM moz_subresources ORDER BY " 1.2608 + "last_hit ASC LIMIT :limit);")); 1.2609 + if (!deleteStatement) { 1.2610 + canDelete = false; 1.2611 + break; 1.2612 + } 1.2613 + mozStorageStatementScoper scopedDelete(deleteStatement); 1.2614 + 1.2615 + rv = deleteStatement->BindInt32ByName(NS_LITERAL_CSTRING("limit"), 1.2616 + subresourcesToDelete); 1.2617 + if (NS_FAILED(rv)) { 1.2618 + canDelete = false; 1.2619 + break; 1.2620 + } 1.2621 + 1.2622 + rv = deleteStatement->Execute(); 1.2623 + if (NS_FAILED(rv)) { 1.2624 + canDelete = false; 1.2625 + break; 1.2626 + } 1.2627 + 1.2628 + // Now we clean up pages that no longer reference any subresources 1.2629 + rv = mDB->ExecuteSimpleSQL( 1.2630 + NS_LITERAL_CSTRING("DELETE FROM moz_pages WHERE id NOT IN " 1.2631 + "(SELECT DISTINCT(pid) FROM moz_subresources);")); 1.2632 + if (NS_FAILED(rv)) { 1.2633 + canDelete = false; 1.2634 + break; 1.2635 + } 1.2636 + } 1.2637 + 1.2638 + if (canDelete) { 1.2639 + dbFileSize = GetDBFileSizeAfterVacuum(); 1.2640 + } 1.2641 + } 1.2642 + 1.2643 + if (!canDelete || (dbFileSize >= evictionCutoff)) { 1.2644 + // Last-ditch effort to free up space 1.2645 + ResetInternal(); 1.2646 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, false); 1.2647 + } else { 1.2648 + // We do this to actually free up the space on disk 1.2649 + VacuumDatabase(); 1.2650 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true); 1.2651 + } 1.2652 + Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME, 1.2653 + (PR_Now() - mLastCleanupTime) / 1000); 1.2654 +} 1.2655 + 1.2656 +void 1.2657 +Seer::VacuumDatabase() 1.2658 +{ 1.2659 + MOZ_ASSERT(!NS_IsMainThread(), "VacuumDatabase called on main thread!"); 1.2660 + 1.2661 + CommitTransaction(); 1.2662 + mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;")); 1.2663 + BeginTransaction(); 1.2664 +} 1.2665 + 1.2666 +#ifdef SEER_TESTS 1.2667 +class SeerPrepareForDnsTestEvent : public nsRunnable 1.2668 +{ 1.2669 +public: 1.2670 + SeerPrepareForDnsTestEvent(int64_t timestamp, const char *uri) 1.2671 + :mTimestamp(timestamp) 1.2672 + ,mUri(uri) 1.2673 + { } 1.2674 + 1.2675 + NS_IMETHOD Run() MOZ_OVERRIDE 1.2676 + { 1.2677 + MOZ_ASSERT(!NS_IsMainThread(), "Preparing for DNS Test on main thread!"); 1.2678 + gSeer->PrepareForDnsTestInternal(mTimestamp, mUri); 1.2679 + return NS_OK; 1.2680 + } 1.2681 + 1.2682 +private: 1.2683 + int64_t mTimestamp; 1.2684 + nsAutoCString mUri; 1.2685 +}; 1.2686 + 1.2687 +void 1.2688 +Seer::PrepareForDnsTestInternal(int64_t timestamp, const nsACString &uri) 1.2689 +{ 1.2690 + nsCOMPtr<mozIStorageStatement> update = mStatements.GetCachedStatement( 1.2691 + NS_LITERAL_CSTRING("UPDATE moz_subresources SET last_hit = :timestamp, " 1.2692 + "hits = 2 WHERE uri = :uri;")); 1.2693 + if (!update) { 1.2694 + return; 1.2695 + } 1.2696 + mozStorageStatementScoper scopedUpdate(update); 1.2697 + 1.2698 + nsresult rv = update->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"), 1.2699 + timestamp); 1.2700 + RETURN_IF_FAILED(rv); 1.2701 + 1.2702 + rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("uri"), uri); 1.2703 + RETURN_IF_FAILED(rv); 1.2704 + 1.2705 + update->Execute(); 1.2706 +} 1.2707 +#endif 1.2708 + 1.2709 +NS_IMETHODIMP 1.2710 +Seer::PrepareForDnsTest(int64_t timestamp, const char *uri) 1.2711 +{ 1.2712 +#ifdef SEER_TESTS 1.2713 + MOZ_ASSERT(NS_IsMainThread(), 1.2714 + "Seer interface methods must be called on the main thread"); 1.2715 + 1.2716 + if (!mInitialized) { 1.2717 + return NS_ERROR_NOT_AVAILABLE; 1.2718 + } 1.2719 + 1.2720 + nsRefPtr<SeerPrepareForDnsTestEvent> event = 1.2721 + new SeerPrepareForDnsTestEvent(timestamp, uri); 1.2722 + return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.2723 +#else 1.2724 + return NS_ERROR_NOT_IMPLEMENTED; 1.2725 +#endif 1.2726 +} 1.2727 + 1.2728 +// Helper functions to make using the seer easier from native code 1.2729 + 1.2730 +static nsresult 1.2731 +EnsureGlobalSeer(nsINetworkSeer **aSeer) 1.2732 +{ 1.2733 + nsresult rv; 1.2734 + nsCOMPtr<nsINetworkSeer> seer = do_GetService("@mozilla.org/network/seer;1", 1.2735 + &rv); 1.2736 + NS_ENSURE_SUCCESS(rv, rv); 1.2737 + 1.2738 + NS_IF_ADDREF(*aSeer = seer); 1.2739 + return NS_OK; 1.2740 +} 1.2741 + 1.2742 +nsresult 1.2743 +SeerPredict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason, 1.2744 + nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier) 1.2745 +{ 1.2746 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.2747 + return NS_OK; 1.2748 + } 1.2749 + 1.2750 + nsCOMPtr<nsINetworkSeer> seer; 1.2751 + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); 1.2752 + NS_ENSURE_SUCCESS(rv, rv); 1.2753 + 1.2754 + return seer->Predict(targetURI, sourceURI, reason, loadContext, verifier); 1.2755 +} 1.2756 + 1.2757 +nsresult 1.2758 +SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, 1.2759 + nsILoadContext *loadContext) 1.2760 +{ 1.2761 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.2762 + return NS_OK; 1.2763 + } 1.2764 + 1.2765 + nsCOMPtr<nsINetworkSeer> seer; 1.2766 + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); 1.2767 + NS_ENSURE_SUCCESS(rv, rv); 1.2768 + 1.2769 + return seer->Learn(targetURI, sourceURI, reason, loadContext); 1.2770 +} 1.2771 + 1.2772 +nsresult 1.2773 +SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, 1.2774 + nsILoadGroup *loadGroup) 1.2775 +{ 1.2776 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.2777 + return NS_OK; 1.2778 + } 1.2779 + 1.2780 + nsCOMPtr<nsINetworkSeer> seer; 1.2781 + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); 1.2782 + NS_ENSURE_SUCCESS(rv, rv); 1.2783 + 1.2784 + nsCOMPtr<nsILoadContext> loadContext; 1.2785 + 1.2786 + if (loadGroup) { 1.2787 + nsCOMPtr<nsIInterfaceRequestor> callbacks; 1.2788 + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 1.2789 + if (callbacks) { 1.2790 + loadContext = do_GetInterface(callbacks); 1.2791 + } 1.2792 + } 1.2793 + 1.2794 + return seer->Learn(targetURI, sourceURI, reason, loadContext); 1.2795 +} 1.2796 + 1.2797 +nsresult 1.2798 +SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, 1.2799 + nsIDocument *document) 1.2800 +{ 1.2801 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.2802 + return NS_OK; 1.2803 + } 1.2804 + 1.2805 + nsCOMPtr<nsINetworkSeer> seer; 1.2806 + nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); 1.2807 + NS_ENSURE_SUCCESS(rv, rv); 1.2808 + 1.2809 + nsCOMPtr<nsILoadContext> loadContext; 1.2810 + 1.2811 + if (document) { 1.2812 + loadContext = document->GetLoadContext(); 1.2813 + } 1.2814 + 1.2815 + return seer->Learn(targetURI, sourceURI, reason, loadContext); 1.2816 +} 1.2817 + 1.2818 +nsresult 1.2819 +SeerLearnRedirect(nsIURI *targetURI, nsIChannel *channel, 1.2820 + nsILoadContext *loadContext) 1.2821 +{ 1.2822 + nsCOMPtr<nsIURI> sourceURI; 1.2823 + nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI)); 1.2824 + NS_ENSURE_SUCCESS(rv, rv); 1.2825 + 1.2826 + bool sameUri; 1.2827 + rv = targetURI->Equals(sourceURI, &sameUri); 1.2828 + NS_ENSURE_SUCCESS(rv, rv); 1.2829 + 1.2830 + if (sameUri) { 1.2831 + return NS_OK; 1.2832 + } 1.2833 + 1.2834 + if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { 1.2835 + return NS_OK; 1.2836 + } 1.2837 + 1.2838 + nsCOMPtr<nsINetworkSeer> seer; 1.2839 + rv = EnsureGlobalSeer(getter_AddRefs(seer)); 1.2840 + NS_ENSURE_SUCCESS(rv, rv); 1.2841 + 1.2842 + return seer->Learn(targetURI, sourceURI, 1.2843 + nsINetworkSeer::LEARN_LOAD_REDIRECT, loadContext); 1.2844 +} 1.2845 + 1.2846 +} // ::mozilla::net 1.2847 +} // ::mozilla