netwerk/base/src/Seer.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:a4cfb2e366d3
1 /* vim: set ts=2 sts=2 et sw=2: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include <algorithm>
7
8 #include "Seer.h"
9
10 #include "nsAppDirectoryServiceDefs.h"
11 #include "nsICancelable.h"
12 #include "nsIChannel.h"
13 #include "nsIDNSListener.h"
14 #include "nsIDNSService.h"
15 #include "nsIDocument.h"
16 #include "nsIFile.h"
17 #include "nsILoadContext.h"
18 #include "nsILoadGroup.h"
19 #include "nsINetworkSeerVerifier.h"
20 #include "nsIObserverService.h"
21 #include "nsIPrefBranch.h"
22 #include "nsIPrefService.h"
23 #include "nsISpeculativeConnect.h"
24 #include "nsITimer.h"
25 #include "nsIURI.h"
26 #include "nsNetUtil.h"
27 #include "nsServiceManagerUtils.h"
28 #include "nsTArray.h"
29 #include "nsThreadUtils.h"
30 #ifdef MOZ_NUWA_PROCESS
31 #include "ipc/Nuwa.h"
32 #endif
33 #include "prlog.h"
34
35 #include "mozIStorageConnection.h"
36 #include "mozIStorageService.h"
37 #include "mozIStorageStatement.h"
38 #include "mozStorageHelper.h"
39
40 #include "mozilla/Preferences.h"
41 #include "mozilla/storage.h"
42 #include "mozilla/Telemetry.h"
43
44 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
45 #include "nsIPropertyBag2.h"
46 static const int32_t ANDROID_23_VERSION = 10;
47 #endif
48
49 using namespace mozilla;
50
51 namespace mozilla {
52 namespace net {
53
54 #define RETURN_IF_FAILED(_rv) \
55 do { \
56 if (NS_FAILED(_rv)) { \
57 return; \
58 } \
59 } while (0)
60
61 const char SEER_ENABLED_PREF[] = "network.seer.enabled";
62 const char SEER_SSL_HOVER_PREF[] = "network.seer.enable-hover-on-ssl";
63
64 const char SEER_PAGE_DELTA_DAY_PREF[] = "network.seer.page-degradation.day";
65 const int SEER_PAGE_DELTA_DAY_DEFAULT = 0;
66 const char SEER_PAGE_DELTA_WEEK_PREF[] = "network.seer.page-degradation.week";
67 const int SEER_PAGE_DELTA_WEEK_DEFAULT = 5;
68 const char SEER_PAGE_DELTA_MONTH_PREF[] = "network.seer.page-degradation.month";
69 const int SEER_PAGE_DELTA_MONTH_DEFAULT = 10;
70 const char SEER_PAGE_DELTA_YEAR_PREF[] = "network.seer.page-degradation.year";
71 const int SEER_PAGE_DELTA_YEAR_DEFAULT = 25;
72 const char SEER_PAGE_DELTA_MAX_PREF[] = "network.seer.page-degradation.max";
73 const int SEER_PAGE_DELTA_MAX_DEFAULT = 50;
74 const char SEER_SUB_DELTA_DAY_PREF[] =
75 "network.seer.subresource-degradation.day";
76 const int SEER_SUB_DELTA_DAY_DEFAULT = 1;
77 const char SEER_SUB_DELTA_WEEK_PREF[] =
78 "network.seer.subresource-degradation.week";
79 const int SEER_SUB_DELTA_WEEK_DEFAULT = 10;
80 const char SEER_SUB_DELTA_MONTH_PREF[] =
81 "network.seer.subresource-degradation.month";
82 const int SEER_SUB_DELTA_MONTH_DEFAULT = 25;
83 const char SEER_SUB_DELTA_YEAR_PREF[] =
84 "network.seer.subresource-degradation.year";
85 const int SEER_SUB_DELTA_YEAR_DEFAULT = 50;
86 const char SEER_SUB_DELTA_MAX_PREF[] =
87 "network.seer.subresource-degradation.max";
88 const int SEER_SUB_DELTA_MAX_DEFAULT = 100;
89
90 const char SEER_PRECONNECT_MIN_PREF[] =
91 "network.seer.preconnect-min-confidence";
92 const int PRECONNECT_MIN_DEFAULT = 90;
93 const char SEER_PRERESOLVE_MIN_PREF[] =
94 "network.seer.preresolve-min-confidence";
95 const int PRERESOLVE_MIN_DEFAULT = 60;
96 const char SEER_REDIRECT_LIKELY_PREF[] =
97 "network.seer.redirect-likely-confidence";
98 const int REDIRECT_LIKELY_DEFAULT = 75;
99
100 const char SEER_MAX_QUEUE_SIZE_PREF[] = "network.seer.max-queue-size";
101 const uint32_t SEER_MAX_QUEUE_SIZE_DEFAULT = 50;
102
103 const char SEER_MAX_DB_SIZE_PREF[] = "network.seer.max-db-size";
104 const int32_t SEER_MAX_DB_SIZE_DEFAULT_BYTES = 150 * 1024 * 1024;
105 const char SEER_PRESERVE_PERCENTAGE_PREF[] = "network.seer.preserve";
106 const int32_t SEER_PRESERVE_PERCENTAGE_DEFAULT = 80;
107
108 // All these time values are in usec
109 const long long ONE_DAY = 86400LL * 1000000LL;
110 const long long ONE_WEEK = 7LL * ONE_DAY;
111 const long long ONE_MONTH = 30LL * ONE_DAY;
112 const long long ONE_YEAR = 365LL * ONE_DAY;
113
114 const long STARTUP_WINDOW = 5L * 60L * 1000000L; // 5min
115
116 // Version for the database schema
117 static const int32_t SEER_SCHEMA_VERSION = 1;
118
119 struct SeerTelemetryAccumulators {
120 Telemetry::AutoCounter<Telemetry::SEER_PREDICT_ATTEMPTS> mPredictAttempts;
121 Telemetry::AutoCounter<Telemetry::SEER_LEARN_ATTEMPTS> mLearnAttempts;
122 Telemetry::AutoCounter<Telemetry::SEER_PREDICT_FULL_QUEUE> mPredictFullQueue;
123 Telemetry::AutoCounter<Telemetry::SEER_LEARN_FULL_QUEUE> mLearnFullQueue;
124 Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PREDICTIONS> mTotalPredictions;
125 Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRECONNECTS> mTotalPreconnects;
126 Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRERESOLVES> mTotalPreresolves;
127 Telemetry::AutoCounter<Telemetry::SEER_PREDICTIONS_CALCULATED> mPredictionsCalculated;
128 Telemetry::AutoCounter<Telemetry::SEER_LOAD_COUNT_IS_ZERO> mLoadCountZeroes;
129 Telemetry::AutoCounter<Telemetry::SEER_LOAD_COUNT_OVERFLOWS> mLoadCountOverflows;
130 Telemetry::AutoCounter<Telemetry::SEER_STARTUP_COUNT_IS_ZERO> mStartupCountZeroes;
131 Telemetry::AutoCounter<Telemetry::SEER_STARTUP_COUNT_OVERFLOWS> mStartupCountOverflows;
132 };
133
134 // Listener for the speculative DNS requests we'll fire off, which just ignores
135 // the result (since we're just trying to warm the cache). This also exists to
136 // reduce round-trips to the main thread, by being something threadsafe the Seer
137 // can use.
138
139 class SeerDNSListener : public nsIDNSListener
140 {
141 public:
142 NS_DECL_THREADSAFE_ISUPPORTS
143 NS_DECL_NSIDNSLISTENER
144
145 SeerDNSListener()
146 { }
147
148 virtual ~SeerDNSListener()
149 { }
150 };
151
152 NS_IMPL_ISUPPORTS(SeerDNSListener, nsIDNSListener);
153
154 NS_IMETHODIMP
155 SeerDNSListener::OnLookupComplete(nsICancelable *request,
156 nsIDNSRecord *rec,
157 nsresult status)
158 {
159 return NS_OK;
160 }
161
162 // Are you ready for the fun part? Because here comes the fun part. The seer,
163 // which will do awesome stuff as you browse to make your browsing experience
164 // faster.
165
166 static Seer *gSeer = nullptr;
167
168 #if defined(PR_LOGGING)
169 static PRLogModuleInfo *gSeerLog = nullptr;
170 #define SEER_LOG(args) PR_LOG(gSeerLog, 4, args)
171 #else
172 #define SEER_LOG(args)
173 #endif
174
175 NS_IMPL_ISUPPORTS(Seer,
176 nsINetworkSeer,
177 nsIObserver,
178 nsISpeculativeConnectionOverrider,
179 nsIInterfaceRequestor)
180
181 Seer::Seer()
182 :mInitialized(false)
183 ,mEnabled(true)
184 ,mEnableHoverOnSSL(false)
185 ,mPageDegradationDay(SEER_PAGE_DELTA_DAY_DEFAULT)
186 ,mPageDegradationWeek(SEER_PAGE_DELTA_WEEK_DEFAULT)
187 ,mPageDegradationMonth(SEER_PAGE_DELTA_MONTH_DEFAULT)
188 ,mPageDegradationYear(SEER_PAGE_DELTA_YEAR_DEFAULT)
189 ,mPageDegradationMax(SEER_PAGE_DELTA_MAX_DEFAULT)
190 ,mSubresourceDegradationDay(SEER_SUB_DELTA_DAY_DEFAULT)
191 ,mSubresourceDegradationWeek(SEER_SUB_DELTA_WEEK_DEFAULT)
192 ,mSubresourceDegradationMonth(SEER_SUB_DELTA_MONTH_DEFAULT)
193 ,mSubresourceDegradationYear(SEER_SUB_DELTA_YEAR_DEFAULT)
194 ,mSubresourceDegradationMax(SEER_SUB_DELTA_MAX_DEFAULT)
195 ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT)
196 ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT)
197 ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT)
198 ,mMaxQueueSize(SEER_MAX_QUEUE_SIZE_DEFAULT)
199 ,mStatements(mDB)
200 ,mLastStartupTime(0)
201 ,mStartupCount(0)
202 ,mQueueSize(0)
203 ,mQueueSizeLock("Seer.mQueueSizeLock")
204 ,mCleanupScheduled(false)
205 ,mMaxDBSize(SEER_MAX_DB_SIZE_DEFAULT_BYTES)
206 ,mPreservePercentage(SEER_PRESERVE_PERCENTAGE_DEFAULT)
207 ,mLastCleanupTime(0)
208 {
209 #if defined(PR_LOGGING)
210 gSeerLog = PR_NewLogModule("NetworkSeer");
211 #endif
212
213 MOZ_ASSERT(!gSeer, "multiple Seer instances!");
214 gSeer = this;
215 }
216
217 Seer::~Seer()
218 {
219 if (mInitialized)
220 Shutdown();
221
222 RemoveObserver();
223
224 gSeer = nullptr;
225 }
226
227 // Seer::nsIObserver
228
229 nsresult
230 Seer::InstallObserver()
231 {
232 MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
233
234 nsresult rv = NS_OK;
235 nsCOMPtr<nsIObserverService> obs =
236 mozilla::services::GetObserverService();
237 if (!obs) {
238 return NS_ERROR_NOT_AVAILABLE;
239 }
240
241 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
242 NS_ENSURE_SUCCESS(rv, rv);
243
244 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
245 if (!prefs) {
246 return NS_ERROR_NOT_AVAILABLE;
247 }
248
249 Preferences::AddBoolVarCache(&mEnabled, SEER_ENABLED_PREF, true);
250 Preferences::AddBoolVarCache(&mEnableHoverOnSSL, SEER_SSL_HOVER_PREF, false);
251 Preferences::AddIntVarCache(&mPageDegradationDay, SEER_PAGE_DELTA_DAY_PREF,
252 SEER_PAGE_DELTA_DAY_DEFAULT);
253 Preferences::AddIntVarCache(&mPageDegradationWeek, SEER_PAGE_DELTA_WEEK_PREF,
254 SEER_PAGE_DELTA_WEEK_DEFAULT);
255 Preferences::AddIntVarCache(&mPageDegradationMonth,
256 SEER_PAGE_DELTA_MONTH_PREF,
257 SEER_PAGE_DELTA_MONTH_DEFAULT);
258 Preferences::AddIntVarCache(&mPageDegradationYear, SEER_PAGE_DELTA_YEAR_PREF,
259 SEER_PAGE_DELTA_YEAR_DEFAULT);
260 Preferences::AddIntVarCache(&mPageDegradationMax, SEER_PAGE_DELTA_MAX_PREF,
261 SEER_PAGE_DELTA_MAX_DEFAULT);
262
263 Preferences::AddIntVarCache(&mSubresourceDegradationDay,
264 SEER_SUB_DELTA_DAY_PREF,
265 SEER_SUB_DELTA_DAY_DEFAULT);
266 Preferences::AddIntVarCache(&mSubresourceDegradationWeek,
267 SEER_SUB_DELTA_WEEK_PREF,
268 SEER_SUB_DELTA_WEEK_DEFAULT);
269 Preferences::AddIntVarCache(&mSubresourceDegradationMonth,
270 SEER_SUB_DELTA_MONTH_PREF,
271 SEER_SUB_DELTA_MONTH_DEFAULT);
272 Preferences::AddIntVarCache(&mSubresourceDegradationYear,
273 SEER_SUB_DELTA_YEAR_PREF,
274 SEER_SUB_DELTA_YEAR_DEFAULT);
275 Preferences::AddIntVarCache(&mSubresourceDegradationMax,
276 SEER_SUB_DELTA_MAX_PREF,
277 SEER_SUB_DELTA_MAX_DEFAULT);
278
279 Preferences::AddIntVarCache(&mPreconnectMinConfidence,
280 SEER_PRECONNECT_MIN_PREF,
281 PRECONNECT_MIN_DEFAULT);
282 Preferences::AddIntVarCache(&mPreresolveMinConfidence,
283 SEER_PRERESOLVE_MIN_PREF,
284 PRERESOLVE_MIN_DEFAULT);
285 Preferences::AddIntVarCache(&mRedirectLikelyConfidence,
286 SEER_REDIRECT_LIKELY_PREF,
287 REDIRECT_LIKELY_DEFAULT);
288
289 Preferences::AddIntVarCache(&mMaxQueueSize, SEER_MAX_QUEUE_SIZE_PREF,
290 SEER_MAX_QUEUE_SIZE_DEFAULT);
291
292 Preferences::AddIntVarCache(&mMaxDBSize, SEER_MAX_DB_SIZE_PREF,
293 SEER_MAX_DB_SIZE_DEFAULT_BYTES);
294 Preferences::AddIntVarCache(&mPreservePercentage,
295 SEER_PRESERVE_PERCENTAGE_PREF,
296 SEER_PRESERVE_PERCENTAGE_DEFAULT);
297
298 return rv;
299 }
300
301 void
302 Seer::RemoveObserver()
303 {
304 MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
305
306 nsCOMPtr<nsIObserverService> obs =
307 mozilla::services::GetObserverService();
308 if (obs) {
309 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
310 }
311 }
312
313 static const uint32_t COMMIT_TIMER_DELTA_MS = 5 * 1000;
314
315 class SeerCommitTimerInitEvent : public nsRunnable
316 {
317 public:
318 NS_IMETHOD Run() MOZ_OVERRIDE
319 {
320 nsresult rv = NS_OK;
321
322 if (!gSeer->mCommitTimer) {
323 gSeer->mCommitTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
324 } else {
325 gSeer->mCommitTimer->Cancel();
326 }
327 if (NS_SUCCEEDED(rv)) {
328 gSeer->mCommitTimer->Init(gSeer, COMMIT_TIMER_DELTA_MS,
329 nsITimer::TYPE_ONE_SHOT);
330 }
331
332 return NS_OK;
333 }
334 };
335
336 class SeerNewTransactionEvent : public nsRunnable
337 {
338 NS_IMETHODIMP Run() MOZ_OVERRIDE
339 {
340 gSeer->CommitTransaction();
341 gSeer->BeginTransaction();
342 gSeer->MaybeScheduleCleanup();
343 nsRefPtr<SeerCommitTimerInitEvent> event = new SeerCommitTimerInitEvent();
344 NS_DispatchToMainThread(event);
345 return NS_OK;
346 }
347 };
348
349 NS_IMETHODIMP
350 Seer::Observe(nsISupports *subject, const char *topic,
351 const char16_t *data_unicode)
352 {
353 nsresult rv = NS_OK;
354 MOZ_ASSERT(NS_IsMainThread(), "Seer observing something off main thread!");
355
356 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
357 Shutdown();
358 } else if (!strcmp(NS_TIMER_CALLBACK_TOPIC, topic)) {
359 if (mInitialized) { // Can't access io thread if we're not initialized!
360 nsRefPtr<SeerNewTransactionEvent> event = new SeerNewTransactionEvent();
361 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
362 }
363 }
364
365 return rv;
366 }
367
368 // Seer::nsISpeculativeConnectionOverrider
369
370 NS_IMETHODIMP
371 Seer::GetIgnoreIdle(bool *ignoreIdle)
372 {
373 *ignoreIdle = true;
374 return NS_OK;
375 }
376
377 NS_IMETHODIMP
378 Seer::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections)
379 {
380 *ignorePossibleSpdyConnections = true;
381 return NS_OK;
382 }
383
384 NS_IMETHODIMP
385 Seer::GetParallelSpeculativeConnectLimit(
386 uint32_t *parallelSpeculativeConnectLimit)
387 {
388 *parallelSpeculativeConnectLimit = 6;
389 return NS_OK;
390 }
391
392 // Seer::nsIInterfaceRequestor
393
394 NS_IMETHODIMP
395 Seer::GetInterface(const nsIID &iid, void **result)
396 {
397 return QueryInterface(iid, result);
398 }
399
400 #ifdef MOZ_NUWA_PROCESS
401 class NuwaMarkSeerThreadRunner : public nsRunnable
402 {
403 NS_IMETHODIMP Run() MOZ_OVERRIDE
404 {
405 if (IsNuwaProcess()) {
406 NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
407 "NuwaMarkCurrentThread is undefined!");
408 NuwaMarkCurrentThread(nullptr, nullptr);
409 }
410 return NS_OK;
411 }
412 };
413 #endif
414
415 // Seer::nsINetworkSeer
416
417 nsresult
418 Seer::Init()
419 {
420 if (!NS_IsMainThread()) {
421 MOZ_ASSERT(false, "Seer::Init called off the main thread!");
422 return NS_ERROR_UNEXPECTED;
423 }
424
425 nsresult rv = NS_OK;
426
427 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
428 // This is an ugly hack to disable the seer on android < 2.3, as it doesn't
429 // play nicely with those android versions, at least on our infra. Causes
430 // timeouts in reftests. See bug 881804 comment 86.
431 nsCOMPtr<nsIPropertyBag2> infoService =
432 do_GetService("@mozilla.org/system-info;1");
433 if (infoService) {
434 int32_t androidVersion = -1;
435 rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"),
436 &androidVersion);
437 if (NS_SUCCEEDED(rv) && (androidVersion < ANDROID_23_VERSION)) {
438 return NS_ERROR_NOT_AVAILABLE;
439 }
440 }
441 #endif
442
443 mStartupTime = PR_Now();
444
445 mAccumulators = new SeerTelemetryAccumulators();
446
447 rv = InstallObserver();
448 NS_ENSURE_SUCCESS(rv, rv);
449
450 if (!mDNSListener) {
451 mDNSListener = new SeerDNSListener();
452 }
453
454 rv = NS_NewNamedThread("Network Seer", getter_AddRefs(mIOThread));
455 NS_ENSURE_SUCCESS(rv, rv);
456
457 #ifdef MOZ_NUWA_PROCESS
458 nsCOMPtr<nsIRunnable> runner = new NuwaMarkSeerThreadRunner();
459 mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL);
460 #endif
461
462 mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
463 NS_ENSURE_SUCCESS(rv, rv);
464
465 mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
466 NS_ENSURE_SUCCESS(rv, rv);
467
468 mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
469 NS_ENSURE_SUCCESS(rv, rv);
470
471 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
472 getter_AddRefs(mDBFile));
473 NS_ENSURE_SUCCESS(rv, rv);
474 rv = mDBFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
475 NS_ENSURE_SUCCESS(rv, rv);
476
477 mInitialized = true;
478
479 return rv;
480 }
481
482 void
483 Seer::CheckForAndDeleteOldDBFile()
484 {
485 nsCOMPtr<nsIFile> oldDBFile;
486 nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
487 RETURN_IF_FAILED(rv);
488
489 rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
490 RETURN_IF_FAILED(rv);
491
492 bool oldFileExists = false;
493 rv = oldDBFile->Exists(&oldFileExists);
494 if (NS_FAILED(rv) || !oldFileExists) {
495 return;
496 }
497
498 oldDBFile->Remove(false);
499 }
500
501 // Make sure that our sqlite storage is all set up with all the tables we need
502 // to do the work. It isn't the end of the world if this fails, since this is
503 // all an optimization, anyway.
504
505 nsresult
506 Seer::EnsureInitStorage()
507 {
508 MOZ_ASSERT(!NS_IsMainThread(), "Initializing seer storage on main thread");
509
510 if (mDB) {
511 return NS_OK;
512 }
513
514 nsresult rv;
515
516 CheckForAndDeleteOldDBFile();
517
518 rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB));
519 if (NS_FAILED(rv)) {
520 // Retry once by trashing the file and trying to open again. If this fails,
521 // we can just bail, and hope for better luck next time.
522 rv = mDBFile->Remove(false);
523 NS_ENSURE_SUCCESS(rv, rv);
524
525 rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB));
526 NS_ENSURE_SUCCESS(rv, rv);
527 }
528
529 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
530 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA foreign_keys = ON;"));
531
532 BeginTransaction();
533
534 // A table to make sure we're working with the database layout we expect
535 rv = mDB->ExecuteSimpleSQL(
536 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_seer_version (\n"
537 " version INTEGER NOT NULL\n"
538 ");\n"));
539 NS_ENSURE_SUCCESS(rv, rv);
540
541 nsCOMPtr<mozIStorageStatement> stmt;
542 rv = mDB->CreateStatement(
543 NS_LITERAL_CSTRING("SELECT version FROM moz_seer_version;\n"),
544 getter_AddRefs(stmt));
545 NS_ENSURE_SUCCESS(rv, rv);
546
547 bool hasRows;
548 rv = stmt->ExecuteStep(&hasRows);
549 NS_ENSURE_SUCCESS(rv, rv);
550 if (hasRows) {
551 int32_t currentVersion;
552 rv = stmt->GetInt32(0, &currentVersion);
553 NS_ENSURE_SUCCESS(rv, rv);
554
555 // This is what we do while we only have one schema version. Later, we'll
556 // have to change this to actually upgrade things as appropriate.
557 MOZ_ASSERT(currentVersion == SEER_SCHEMA_VERSION,
558 "Invalid seer schema version!");
559 if (currentVersion != SEER_SCHEMA_VERSION) {
560 return NS_ERROR_UNEXPECTED;
561 }
562 } else {
563 stmt = nullptr;
564 rv = mDB->CreateStatement(
565 NS_LITERAL_CSTRING("INSERT INTO moz_seer_version (version) VALUES "
566 "(:seer_version);"),
567 getter_AddRefs(stmt));
568 NS_ENSURE_SUCCESS(rv, rv);
569
570 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("seer_version"),
571 SEER_SCHEMA_VERSION);
572 NS_ENSURE_SUCCESS(rv, rv);
573
574 stmt->Execute();
575 }
576
577 stmt = nullptr;
578
579 // This table keeps track of the hosts we've seen at the top level of a
580 // pageload so we can map them to hosts used for subresources of a pageload.
581 rv = mDB->ExecuteSimpleSQL(
582 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_hosts (\n"
583 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
584 " origin TEXT NOT NULL,\n"
585 " loads INTEGER DEFAULT 0,\n"
586 " last_load INTEGER DEFAULT 0\n"
587 ");\n"));
588 NS_ENSURE_SUCCESS(rv, rv);
589
590 rv = mDB->ExecuteSimpleSQL(
591 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_id_origin_index "
592 "ON moz_hosts (id, origin);"));
593 NS_ENSURE_SUCCESS(rv, rv);
594
595 rv = mDB->ExecuteSimpleSQL(
596 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_origin_index "
597 "ON moz_hosts (origin);"));
598 NS_ENSURE_SUCCESS(rv, rv);
599
600 rv = mDB->ExecuteSimpleSQL(
601 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_load_index "
602 "ON moz_hosts (last_load);"));
603 NS_ENSURE_SUCCESS(rv, rv);
604
605 // And this is the table that keeps track of the hosts for subresources of a
606 // pageload.
607 rv = mDB->ExecuteSimpleSQL(
608 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subhosts (\n"
609 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
610 " hid INTEGER NOT NULL,\n"
611 " origin TEXT NOT NULL,\n"
612 " hits INTEGER DEFAULT 0,\n"
613 " last_hit INTEGER DEFAULT 0,\n"
614 " FOREIGN KEY(hid) REFERENCES moz_hosts(id) ON DELETE CASCADE\n"
615 ");\n"));
616 NS_ENSURE_SUCCESS(rv, rv);
617
618 rv = mDB->ExecuteSimpleSQL(
619 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_hid_origin_index "
620 "ON moz_subhosts (hid, origin);"));
621 NS_ENSURE_SUCCESS(rv, rv);
622
623 rv = mDB->ExecuteSimpleSQL(
624 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_id_index "
625 "ON moz_subhosts (id);"));
626 NS_ENSURE_SUCCESS(rv, rv);
627
628 // Table to keep track of how many times we've started up, and when the last
629 // time was.
630 rv = mDB->ExecuteSimpleSQL(
631 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startups (\n"
632 " startups INTEGER,\n"
633 " last_startup INTEGER\n"
634 ");\n"));
635 NS_ENSURE_SUCCESS(rv, rv);
636
637 rv = mDB->CreateStatement(
638 NS_LITERAL_CSTRING("SELECT startups, last_startup FROM moz_startups;\n"),
639 getter_AddRefs(stmt));
640 NS_ENSURE_SUCCESS(rv, rv);
641
642 // We'll go ahead and keep track of our startup count here, since we can
643 // (mostly) equate "the service was created and asked to do stuff" with
644 // "the browser was started up".
645 rv = stmt->ExecuteStep(&hasRows);
646 NS_ENSURE_SUCCESS(rv, rv);
647 if (hasRows) {
648 // We've started up before. Update our startup statistics
649 stmt->GetInt32(0, &mStartupCount);
650 stmt->GetInt64(1, &mLastStartupTime);
651
652 // This finalizes the statement
653 stmt = nullptr;
654
655 rv = mDB->CreateStatement(
656 NS_LITERAL_CSTRING("UPDATE moz_startups SET startups = :startup_count, "
657 "last_startup = :startup_time;\n"),
658 getter_AddRefs(stmt));
659 NS_ENSURE_SUCCESS(rv, rv);
660
661 int32_t newStartupCount = mStartupCount + 1;
662 if (newStartupCount <= 0) {
663 SEER_LOG(("Seer::EnsureInitStorage startup count overflow\n"));
664 newStartupCount = mStartupCount;
665 ++mAccumulators->mStartupCountOverflows;
666 }
667
668 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("startup_count"),
669 mStartupCount + 1);
670 NS_ENSURE_SUCCESS(rv, rv);
671
672 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
673 mStartupTime);
674 NS_ENSURE_SUCCESS(rv, rv);
675
676 stmt->Execute();
677 } else {
678 // This is our first startup, so let's go ahead and mark it as such
679 mStartupCount = 1;
680
681 rv = mDB->CreateStatement(
682 NS_LITERAL_CSTRING("INSERT INTO moz_startups (startups, last_startup) "
683 "VALUES (1, :startup_time);\n"),
684 getter_AddRefs(stmt));
685 NS_ENSURE_SUCCESS(rv, rv);
686
687 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
688 mStartupTime);
689 NS_ENSURE_SUCCESS(rv, rv);
690
691 stmt->Execute();
692 }
693
694 // This finalizes the statement
695 stmt = nullptr;
696
697 // This table lists URIs loaded at startup, along with how many startups
698 // they've been loaded during, and when the last time was.
699 rv = mDB->ExecuteSimpleSQL(
700 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startup_pages (\n"
701 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
702 " uri TEXT NOT NULL,\n"
703 " hits INTEGER DEFAULT 0,\n"
704 " last_hit INTEGER DEFAULT 0\n"
705 ");\n"));
706 NS_ENSURE_SUCCESS(rv, rv);
707
708 rv = mDB->ExecuteSimpleSQL(
709 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_uri_index "
710 "ON moz_startup_pages (uri);"));
711 NS_ENSURE_SUCCESS(rv, rv);
712
713 rv = mDB->ExecuteSimpleSQL(
714 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_hit_index "
715 "ON moz_startup_pages (last_hit);"));
716 NS_ENSURE_SUCCESS(rv, rv);
717
718 // This table is similar to moz_hosts above, but uses full URIs instead of
719 // hosts so that we can get more specific predictions for URIs that people
720 // visit often (such as their email or social network home pages).
721 rv = mDB->ExecuteSimpleSQL(
722 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_pages (\n"
723 " id integer PRIMARY KEY AUTOINCREMENT,\n"
724 " uri TEXT NOT NULL,\n"
725 " loads INTEGER DEFAULT 0,\n"
726 " last_load INTEGER DEFAULT 0\n"
727 ");\n"));
728 NS_ENSURE_SUCCESS(rv, rv);
729
730 rv = mDB->ExecuteSimpleSQL(
731 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_id_uri_index "
732 "ON moz_pages (id, uri);"));
733 NS_ENSURE_SUCCESS(rv, rv);
734
735 rv = mDB->ExecuteSimpleSQL(
736 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_uri_index "
737 "ON moz_pages (uri);"));
738 NS_ENSURE_SUCCESS(rv, rv);
739
740 // This table is similar to moz_subhosts above, but is instead related to
741 // moz_pages for finer granularity.
742 rv = mDB->ExecuteSimpleSQL(
743 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subresources (\n"
744 " id integer PRIMARY KEY AUTOINCREMENT,\n"
745 " pid INTEGER NOT NULL,\n"
746 " uri TEXT NOT NULL,\n"
747 " hits INTEGER DEFAULT 0,\n"
748 " last_hit INTEGER DEFAULT 0,\n"
749 " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n"
750 ");\n"));
751 NS_ENSURE_SUCCESS(rv, rv);
752
753 rv = mDB->ExecuteSimpleSQL(
754 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_pid_uri_index "
755 "ON moz_subresources (pid, uri);"));
756 NS_ENSURE_SUCCESS(rv, rv);
757
758 rv = mDB->ExecuteSimpleSQL(
759 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_id_index "
760 "ON moz_subresources (id);"));
761 NS_ENSURE_SUCCESS(rv, rv);
762
763 // This table keeps track of URIs and what they end up finally redirecting to
764 // so we can handle redirects in a sane fashion, as well.
765 rv = mDB->ExecuteSimpleSQL(
766 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_redirects (\n"
767 " id integer PRIMARY KEY AUTOINCREMENT,\n"
768 " pid integer NOT NULL,\n"
769 " uri TEXT NOT NULL,\n"
770 " origin TEXT NOT NULL,\n"
771 " hits INTEGER DEFAULT 0,\n"
772 " last_hit INTEGER DEFAULT 0,\n"
773 " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n"
774 ");\n"));
775 NS_ENSURE_SUCCESS(rv, rv);
776
777 rv = mDB->ExecuteSimpleSQL(
778 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_pid_uri_index "
779 "ON moz_redirects (pid, uri);"));
780 NS_ENSURE_SUCCESS(rv, rv);
781
782 rv = mDB->ExecuteSimpleSQL(
783 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_id_index "
784 "ON moz_redirects (id);"));
785 NS_ENSURE_SUCCESS(rv, rv);
786
787 CommitTransaction();
788 BeginTransaction();
789
790 nsRefPtr<SeerCommitTimerInitEvent> event = new SeerCommitTimerInitEvent();
791 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
792
793 return NS_OK;
794 }
795
796 class SeerThreadShutdownRunner : public nsRunnable
797 {
798 public:
799 SeerThreadShutdownRunner(nsIThread *ioThread)
800 :mIOThread(ioThread)
801 { }
802
803 NS_IMETHODIMP Run() MOZ_OVERRIDE
804 {
805 MOZ_ASSERT(NS_IsMainThread(), "Shut down seer io thread off main thread");
806 mIOThread->Shutdown();
807 return NS_OK;
808 }
809
810 private:
811 nsCOMPtr<nsIThread> mIOThread;
812 };
813
814 class SeerDBShutdownRunner : public nsRunnable
815 {
816 public:
817 SeerDBShutdownRunner(nsIThread *ioThread, nsINetworkSeer *seer)
818 :mIOThread(ioThread)
819 {
820 mSeer = new nsMainThreadPtrHolder<nsINetworkSeer>(seer);
821 }
822
823 NS_IMETHODIMP Run() MOZ_OVERRIDE
824 {
825 MOZ_ASSERT(!NS_IsMainThread(), "Shutting down DB on main thread");
826
827 // Ensure everything is written to disk before we shut down the db
828 gSeer->CommitTransaction();
829
830 gSeer->mStatements.FinalizeStatements();
831 gSeer->mDB->Close();
832 gSeer->mDB = nullptr;
833
834 nsRefPtr<SeerThreadShutdownRunner> runner =
835 new SeerThreadShutdownRunner(mIOThread);
836 NS_DispatchToMainThread(runner);
837
838 return NS_OK;
839 }
840
841 private:
842 nsCOMPtr<nsIThread> mIOThread;
843
844 // Death grip to keep seer alive while we cleanly close its DB connection
845 nsMainThreadPtrHandle<nsINetworkSeer> mSeer;
846 };
847
848 void
849 Seer::Shutdown()
850 {
851 if (!NS_IsMainThread()) {
852 MOZ_ASSERT(false, "Seer::Shutdown called off the main thread!");
853 return;
854 }
855
856 mInitialized = false;
857
858 if (mCommitTimer) {
859 mCommitTimer->Cancel();
860 }
861
862 if (mIOThread) {
863 if (mDB) {
864 nsRefPtr<SeerDBShutdownRunner> runner =
865 new SeerDBShutdownRunner(mIOThread, this);
866 mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL);
867 } else {
868 nsRefPtr<SeerThreadShutdownRunner> runner =
869 new SeerThreadShutdownRunner(mIOThread);
870 NS_DispatchToMainThread(runner);
871 }
872 }
873 }
874
875 nsresult
876 Seer::Create(nsISupports *aOuter, const nsIID& aIID,
877 void **aResult)
878 {
879 nsresult rv;
880
881 if (aOuter != nullptr) {
882 return NS_ERROR_NO_AGGREGATION;
883 }
884
885 nsRefPtr<Seer> svc = new Seer();
886
887 rv = svc->Init();
888 if (NS_FAILED(rv)) {
889 SEER_LOG(("Failed to initialize seer, seer will be a noop"));
890 }
891
892 // We treat init failure the same as the service being disabled, since this
893 // is all an optimization anyway. No need to freak people out. That's why we
894 // gladly continue on QI'ing here.
895 rv = svc->QueryInterface(aIID, aResult);
896
897 return rv;
898 }
899
900 // Get the full origin (scheme, host, port) out of a URI (maybe should be part
901 // of nsIURI instead?)
902 static void
903 ExtractOrigin(nsIURI *uri, nsAutoCString &s)
904 {
905 s.Truncate();
906
907 nsAutoCString scheme;
908 nsresult rv = uri->GetScheme(scheme);
909 RETURN_IF_FAILED(rv);
910
911 nsAutoCString host;
912 rv = uri->GetAsciiHost(host);
913 RETURN_IF_FAILED(rv);
914
915 int32_t port;
916 rv = uri->GetPort(&port);
917 RETURN_IF_FAILED(rv);
918
919 s.Assign(scheme);
920 s.AppendLiteral("://");
921 s.Append(host);
922 if (port != -1) {
923 s.AppendLiteral(":");
924 s.AppendInt(port);
925 }
926 }
927
928 // An event to do the work for a prediction that needs to hit the sqlite
929 // database. These events should be created on the main thread, and run on
930 // the seer thread.
931 class SeerPredictionEvent : public nsRunnable
932 {
933 public:
934 SeerPredictionEvent(nsIURI *targetURI, nsIURI *sourceURI,
935 SeerPredictReason reason,
936 nsINetworkSeerVerifier *verifier)
937 :mReason(reason)
938 {
939 MOZ_ASSERT(NS_IsMainThread(), "Creating prediction event off main thread");
940
941 mEnqueueTime = TimeStamp::Now();
942
943 if (verifier) {
944 mVerifier = new nsMainThreadPtrHolder<nsINetworkSeerVerifier>(verifier);
945 }
946 if (targetURI) {
947 targetURI->GetAsciiSpec(mTargetURI.spec);
948 ExtractOrigin(targetURI, mTargetURI.origin);
949 }
950 if (sourceURI) {
951 sourceURI->GetAsciiSpec(mSourceURI.spec);
952 ExtractOrigin(sourceURI, mSourceURI.origin);
953 }
954 }
955
956 NS_IMETHOD Run() MOZ_OVERRIDE
957 {
958 MOZ_ASSERT(!NS_IsMainThread(), "Running prediction event on main thread");
959
960 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WAIT_TIME,
961 mEnqueueTime);
962
963 TimeStamp startTime = TimeStamp::Now();
964
965 nsresult rv = NS_OK;
966
967 switch (mReason) {
968 case nsINetworkSeer::PREDICT_LOAD:
969 gSeer->PredictForPageload(mTargetURI, mVerifier, 0, mEnqueueTime);
970 break;
971 case nsINetworkSeer::PREDICT_STARTUP:
972 gSeer->PredictForStartup(mVerifier, mEnqueueTime);
973 break;
974 default:
975 MOZ_ASSERT(false, "Got unexpected value for predict reason");
976 rv = NS_ERROR_UNEXPECTED;
977 }
978
979 gSeer->FreeSpaceInQueue();
980
981 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WORK_TIME,
982 startTime);
983
984 gSeer->MaybeScheduleCleanup();
985
986 return rv;
987 }
988
989 private:
990 Seer::UriInfo mTargetURI;
991 Seer::UriInfo mSourceURI;
992 SeerPredictReason mReason;
993 SeerVerifierHandle mVerifier;
994 TimeStamp mEnqueueTime;
995 };
996
997 // Predicting for a link is easy, and doesn't require the round-trip to the
998 // seer thread and back to the main thread, since we don't have to hit the db
999 // for that.
1000 void
1001 Seer::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
1002 nsINetworkSeerVerifier *verifier)
1003 {
1004 MOZ_ASSERT(NS_IsMainThread(), "Predicting for link off main thread");
1005
1006 if (!mSpeculativeService) {
1007 return;
1008 }
1009
1010 if (!mEnableHoverOnSSL) {
1011 bool isSSL = false;
1012 sourceURI->SchemeIs("https", &isSSL);
1013 if (isSSL) {
1014 // We don't want to predict from an HTTPS page, to avoid info leakage
1015 SEER_LOG(("Not predicting for link hover - on an SSL page"));
1016 return;
1017 }
1018 }
1019
1020 mSpeculativeService->SpeculativeConnect(targetURI, nullptr);
1021 if (verifier) {
1022 verifier->OnPredictPreconnect(targetURI);
1023 }
1024 }
1025
1026 // This runnable runs on the main thread, and is responsible for actually
1027 // firing off predictive actions (such as TCP/TLS preconnects and DNS lookups)
1028 class SeerPredictionRunner : public nsRunnable
1029 {
1030 public:
1031 SeerPredictionRunner(SeerVerifierHandle &verifier, TimeStamp predictStartTime)
1032 :mVerifier(verifier)
1033 ,mPredictStartTime(predictStartTime)
1034 { }
1035
1036 void AddPreconnect(const nsACString &uri)
1037 {
1038 mPreconnects.AppendElement(uri);
1039 }
1040
1041 void AddPreresolve(const nsACString &uri)
1042 {
1043 mPreresolves.AppendElement(uri);
1044 }
1045
1046 bool HasWork() const
1047 {
1048 return !(mPreconnects.IsEmpty() && mPreresolves.IsEmpty());
1049 }
1050
1051 NS_IMETHOD Run() MOZ_OVERRIDE
1052 {
1053 MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
1054
1055 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_ACTION,
1056 mPredictStartTime);
1057
1058 uint32_t len, i;
1059
1060 len = mPreconnects.Length();
1061 for (i = 0; i < len; ++i) {
1062 nsCOMPtr<nsIURI> uri;
1063 nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreconnects[i]);
1064 if (NS_FAILED(rv)) {
1065 continue;
1066 }
1067
1068 ++gSeer->mAccumulators->mTotalPredictions;
1069 ++gSeer->mAccumulators->mTotalPreconnects;
1070 gSeer->mSpeculativeService->SpeculativeConnect(uri, gSeer);
1071 if (mVerifier) {
1072 mVerifier->OnPredictPreconnect(uri);
1073 }
1074 }
1075
1076 len = mPreresolves.Length();
1077 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
1078 for (i = 0; i < len; ++i) {
1079 nsCOMPtr<nsIURI> uri;
1080 nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreresolves[i]);
1081 if (NS_FAILED(rv)) {
1082 continue;
1083 }
1084
1085 ++gSeer->mAccumulators->mTotalPredictions;
1086 ++gSeer->mAccumulators->mTotalPreresolves;
1087 nsAutoCString hostname;
1088 uri->GetAsciiHost(hostname);
1089 nsCOMPtr<nsICancelable> tmpCancelable;
1090 gSeer->mDnsService->AsyncResolve(hostname,
1091 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1092 nsIDNSService::RESOLVE_SPECULATE),
1093 gSeer->mDNSListener, nullptr,
1094 getter_AddRefs(tmpCancelable));
1095 if (mVerifier) {
1096 mVerifier->OnPredictDNS(uri);
1097 }
1098 }
1099
1100 mPreconnects.Clear();
1101 mPreresolves.Clear();
1102
1103 return NS_OK;
1104 }
1105
1106 private:
1107 nsTArray<nsCString> mPreconnects;
1108 nsTArray<nsCString> mPreresolves;
1109 SeerVerifierHandle mVerifier;
1110 TimeStamp mPredictStartTime;
1111 };
1112
1113 // This calculates how much to degrade our confidence in our data based on
1114 // the last time this top-level resource was loaded. This "global degradation"
1115 // applies to *all* subresources we have associated with the top-level
1116 // resource. This will be in addition to any reduction in confidence we have
1117 // associated with a particular subresource.
1118 int
1119 Seer::CalculateGlobalDegradation(PRTime now, PRTime lastLoad)
1120 {
1121 int globalDegradation;
1122 PRTime delta = now - lastLoad;
1123 if (delta < ONE_DAY) {
1124 globalDegradation = mPageDegradationDay;
1125 } else if (delta < ONE_WEEK) {
1126 globalDegradation = mPageDegradationWeek;
1127 } else if (delta < ONE_MONTH) {
1128 globalDegradation = mPageDegradationMonth;
1129 } else if (delta < ONE_YEAR) {
1130 globalDegradation = mPageDegradationYear;
1131 } else {
1132 globalDegradation = mPageDegradationMax;
1133 }
1134
1135 Telemetry::Accumulate(Telemetry::SEER_GLOBAL_DEGRADATION, globalDegradation);
1136 return globalDegradation;
1137 }
1138
1139 // This calculates our overall confidence that a particular subresource will be
1140 // loaded as part of a top-level load.
1141 // @param baseConfidence - the basic confidence we have for this subresource,
1142 // which is the percentage of time this top-level load
1143 // loads the subresource in question
1144 // @param lastHit - the timestamp of the last time we loaded this subresource as
1145 // part of this top-level load
1146 // @param lastPossible - the timestamp of the last time we performed this
1147 // top-level load
1148 // @param globalDegradation - the degradation for this top-level load as
1149 // determined by CalculateGlobalDegradation
1150 int
1151 Seer::CalculateConfidence(int baseConfidence, PRTime lastHit,
1152 PRTime lastPossible, int globalDegradation)
1153 {
1154 ++mAccumulators->mPredictionsCalculated;
1155
1156 int maxConfidence = 100;
1157 int confidenceDegradation = 0;
1158
1159 if (lastHit < lastPossible) {
1160 // We didn't load this subresource the last time this top-level load was
1161 // performed, so let's not bother preconnecting (at the very least).
1162 maxConfidence = mPreconnectMinConfidence - 1;
1163
1164 // Now calculate how much we want to degrade our confidence based on how
1165 // long it's been between the last time we did this top-level load and the
1166 // last time this top-level load included this subresource.
1167 PRTime delta = lastPossible - lastHit;
1168 if (delta == 0) {
1169 confidenceDegradation = 0;
1170 } else if (delta < ONE_DAY) {
1171 confidenceDegradation = mSubresourceDegradationDay;
1172 } else if (delta < ONE_WEEK) {
1173 confidenceDegradation = mSubresourceDegradationWeek;
1174 } else if (delta < ONE_MONTH) {
1175 confidenceDegradation = mSubresourceDegradationMonth;
1176 } else if (delta < ONE_YEAR) {
1177 confidenceDegradation = mSubresourceDegradationYear;
1178 } else {
1179 confidenceDegradation = mSubresourceDegradationMax;
1180 maxConfidence = 0;
1181 }
1182 }
1183
1184 // Calculate our confidence and clamp it to between 0 and maxConfidence
1185 // (<= 100)
1186 int confidence = baseConfidence - confidenceDegradation - globalDegradation;
1187 confidence = std::max(confidence, 0);
1188 confidence = std::min(confidence, maxConfidence);
1189
1190 Telemetry::Accumulate(Telemetry::SEER_BASE_CONFIDENCE, baseConfidence);
1191 Telemetry::Accumulate(Telemetry::SEER_SUBRESOURCE_DEGRADATION,
1192 confidenceDegradation);
1193 Telemetry::Accumulate(Telemetry::SEER_CONFIDENCE, confidence);
1194 return confidence;
1195 }
1196
1197 // (Maybe) adds a predictive action to the prediction runner, based on our
1198 // calculated confidence for the subresource in question.
1199 void
1200 Seer::SetupPrediction(int confidence, const nsACString &uri,
1201 SeerPredictionRunner *runner)
1202 {
1203 if (confidence >= mPreconnectMinConfidence) {
1204 runner->AddPreconnect(uri);
1205 } else if (confidence >= mPreresolveMinConfidence) {
1206 runner->AddPreresolve(uri);
1207 }
1208 }
1209
1210 // This gets the data about the top-level load from our database, either from
1211 // the pages table (which is specific to a particular URI), or from the hosts
1212 // table (which is for a particular origin).
1213 bool
1214 Seer::LookupTopLevel(QueryType queryType, const nsACString &key,
1215 TopLevelInfo &info)
1216 {
1217 MOZ_ASSERT(!NS_IsMainThread(), "LookupTopLevel called on main thread.");
1218
1219 nsCOMPtr<mozIStorageStatement> stmt;
1220 if (queryType == QUERY_PAGE) {
1221 stmt = mStatements.GetCachedStatement(
1222 NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_pages WHERE "
1223 "uri = :key;"));
1224 } else {
1225 stmt = mStatements.GetCachedStatement(
1226 NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_hosts WHERE "
1227 "origin = :key;"));
1228 }
1229 NS_ENSURE_TRUE(stmt, false);
1230 mozStorageStatementScoper scope(stmt);
1231
1232 nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
1233 NS_ENSURE_SUCCESS(rv, false);
1234
1235 bool hasRows;
1236 rv = stmt->ExecuteStep(&hasRows);
1237 NS_ENSURE_SUCCESS(rv, false);
1238
1239 if (!hasRows) {
1240 return false;
1241 }
1242
1243 rv = stmt->GetInt32(0, &info.id);
1244 NS_ENSURE_SUCCESS(rv, false);
1245
1246 rv = stmt->GetInt32(1, &info.loadCount);
1247 NS_ENSURE_SUCCESS(rv, false);
1248
1249 rv = stmt->GetInt64(2, &info.lastLoad);
1250 NS_ENSURE_SUCCESS(rv, false);
1251
1252 return true;
1253 }
1254
1255 // Insert data about either a top-level page or a top-level origin into
1256 // the database.
1257 void
1258 Seer::AddTopLevel(QueryType queryType, const nsACString &key, PRTime now)
1259 {
1260 MOZ_ASSERT(!NS_IsMainThread(), "AddTopLevel called on main thread.");
1261
1262 nsCOMPtr<mozIStorageStatement> stmt;
1263 if (queryType == QUERY_PAGE) {
1264 stmt = mStatements.GetCachedStatement(
1265 NS_LITERAL_CSTRING("INSERT INTO moz_pages (uri, loads, last_load) "
1266 "VALUES (:key, 1, :now);"));
1267 } else {
1268 stmt = mStatements.GetCachedStatement(
1269 NS_LITERAL_CSTRING("INSERT INTO moz_hosts (origin, loads, last_load) "
1270 "VALUES (:key, 1, :now);"));
1271 }
1272 if (!stmt) {
1273 return;
1274 }
1275 mozStorageStatementScoper scope(stmt);
1276
1277 // Loading a page implicitly makes the seer learn about the page,
1278 // so since we don't have it already, let's add it.
1279 nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
1280 RETURN_IF_FAILED(rv);
1281
1282 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
1283 RETURN_IF_FAILED(rv);
1284
1285 rv = stmt->Execute();
1286 }
1287
1288 // Update data about either a top-level page or a top-level origin in the
1289 // database.
1290 void
1291 Seer::UpdateTopLevel(QueryType queryType, const TopLevelInfo &info, PRTime now)
1292 {
1293 MOZ_ASSERT(!NS_IsMainThread(), "UpdateTopLevel called on main thread.");
1294
1295 nsCOMPtr<mozIStorageStatement> stmt;
1296 if (queryType == QUERY_PAGE) {
1297 stmt = mStatements.GetCachedStatement(
1298 NS_LITERAL_CSTRING("UPDATE moz_pages SET loads = :load_count, "
1299 "last_load = :now WHERE id = :id;"));
1300 } else {
1301 stmt = mStatements.GetCachedStatement(
1302 NS_LITERAL_CSTRING("UPDATE moz_hosts SET loads = :load_count, "
1303 "last_load = :now WHERE id = :id;"));
1304 }
1305 if (!stmt) {
1306 return;
1307 }
1308 mozStorageStatementScoper scope(stmt);
1309
1310 int32_t newLoadCount = info.loadCount + 1;
1311 if (newLoadCount <= 0) {
1312 SEER_LOG(("Seer::UpdateTopLevel type %d id %d load count overflow\n",
1313 queryType, info.id));
1314 newLoadCount = info.loadCount;
1315 ++mAccumulators->mLoadCountOverflows;
1316 }
1317
1318 // First, let's update the page in the database, since loading a page
1319 // implicitly learns about the page.
1320 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("load_count"),
1321 newLoadCount);
1322 RETURN_IF_FAILED(rv);
1323
1324 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
1325 RETURN_IF_FAILED(rv);
1326
1327 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
1328 RETURN_IF_FAILED(rv);
1329
1330 rv = stmt->Execute();
1331 }
1332
1333 // Tries to predict for a top-level load (either page-based or origin-based).
1334 // Returns false if it failed to predict at all, true if it did some sort of
1335 // prediction.
1336 // @param queryType - whether to predict based on page or origin
1337 // @param info - the db info about the top-level resource
1338 bool
1339 Seer::TryPredict(QueryType queryType, const TopLevelInfo &info, PRTime now,
1340 SeerVerifierHandle &verifier, TimeStamp &predictStartTime)
1341 {
1342 MOZ_ASSERT(!NS_IsMainThread(), "TryPredict called on main thread.");
1343
1344 if (!info.loadCount) {
1345 SEER_LOG(("Seer::TryPredict info.loadCount is zero!\n"));
1346 ++mAccumulators->mLoadCountZeroes;
1347 return false;
1348 }
1349
1350 int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad);
1351
1352 // Now let's look up the subresources we know about for this page
1353 nsCOMPtr<mozIStorageStatement> stmt;
1354 if (queryType == QUERY_PAGE) {
1355 stmt = mStatements.GetCachedStatement(
1356 NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_subresources "
1357 "WHERE pid = :id;"));
1358 } else {
1359 stmt = mStatements.GetCachedStatement(
1360 NS_LITERAL_CSTRING("SELECT origin, hits, last_hit FROM moz_subhosts "
1361 "WHERE hid = :id;"));
1362 }
1363 NS_ENSURE_TRUE(stmt, false);
1364 mozStorageStatementScoper scope(stmt);
1365
1366 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
1367 NS_ENSURE_SUCCESS(rv, false);
1368
1369 bool hasRows;
1370 rv = stmt->ExecuteStep(&hasRows);
1371 if (NS_FAILED(rv) || !hasRows) {
1372 return false;
1373 }
1374
1375 nsRefPtr<SeerPredictionRunner> runner =
1376 new SeerPredictionRunner(verifier, predictStartTime);
1377
1378 while (hasRows) {
1379 int32_t hitCount;
1380 PRTime lastHit;
1381 nsAutoCString subresource;
1382 int baseConfidence, confidence;
1383
1384 // We use goto nextrow here instead of just failling, because we want
1385 // to do some sort of prediction if at all possible. Of course, it's
1386 // probably unlikely that subsequent rows will succeed if one fails, but
1387 // it's worth a shot.
1388
1389 rv = stmt->GetUTF8String(0, subresource);
1390 if NS_FAILED(rv) {
1391 goto nextrow;
1392 }
1393
1394 rv = stmt->GetInt32(1, &hitCount);
1395 if (NS_FAILED(rv)) {
1396 goto nextrow;
1397 }
1398
1399 rv = stmt->GetInt64(2, &lastHit);
1400 if (NS_FAILED(rv)) {
1401 goto nextrow;
1402 }
1403
1404 baseConfidence = (hitCount * 100) / info.loadCount;
1405 confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad,
1406 globalDegradation);
1407 SetupPrediction(confidence, subresource, runner);
1408
1409 nextrow:
1410 rv = stmt->ExecuteStep(&hasRows);
1411 NS_ENSURE_SUCCESS(rv, false);
1412 }
1413
1414 bool predicted = false;
1415
1416 if (runner->HasWork()) {
1417 NS_DispatchToMainThread(runner);
1418 predicted = true;
1419 }
1420
1421 return predicted;
1422 }
1423
1424 // Find out if a top-level page is likely to redirect.
1425 bool
1426 Seer::WouldRedirect(const TopLevelInfo &info, PRTime now, UriInfo &newUri)
1427 {
1428 MOZ_ASSERT(!NS_IsMainThread(), "WouldRedirect called on main thread.");
1429
1430 if (!info.loadCount) {
1431 SEER_LOG(("Seer::WouldRedirect info.loadCount is zero!\n"));
1432 ++mAccumulators->mLoadCountZeroes;
1433 return false;
1434 }
1435
1436 nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement(
1437 NS_LITERAL_CSTRING("SELECT uri, origin, hits, last_hit "
1438 "FROM moz_redirects WHERE pid = :id;"));
1439 NS_ENSURE_TRUE(stmt, false);
1440 mozStorageStatementScoper scope(stmt);
1441
1442 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
1443 NS_ENSURE_SUCCESS(rv, false);
1444
1445 bool hasRows;
1446 rv = stmt->ExecuteStep(&hasRows);
1447 if (NS_FAILED(rv) || !hasRows) {
1448 return false;
1449 }
1450
1451 rv = stmt->GetUTF8String(0, newUri.spec);
1452 NS_ENSURE_SUCCESS(rv, false);
1453
1454 rv = stmt->GetUTF8String(1, newUri.origin);
1455 NS_ENSURE_SUCCESS(rv, false);
1456
1457 int32_t hitCount;
1458 rv = stmt->GetInt32(2, &hitCount);
1459 NS_ENSURE_SUCCESS(rv, false);
1460
1461 PRTime lastHit;
1462 rv = stmt->GetInt64(3, &lastHit);
1463 NS_ENSURE_SUCCESS(rv, false);
1464
1465 int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad);
1466 int baseConfidence = (hitCount * 100) / info.loadCount;
1467 int confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad,
1468 globalDegradation);
1469
1470 if (confidence > mRedirectLikelyConfidence) {
1471 return true;
1472 }
1473
1474 return false;
1475 }
1476
1477 // This will add a page to our list of startup pages if it's being loaded
1478 // before our startup window has expired.
1479 void
1480 Seer::MaybeLearnForStartup(const UriInfo &uri, const PRTime now)
1481 {
1482 MOZ_ASSERT(!NS_IsMainThread(), "MaybeLearnForStartup called on main thread.");
1483
1484 if ((now - mStartupTime) < STARTUP_WINDOW) {
1485 LearnForStartup(uri);
1486 }
1487 }
1488
1489 const int MAX_PAGELOAD_DEPTH = 10;
1490
1491 // This is the driver for prediction based on a new pageload.
1492 void
1493 Seer::PredictForPageload(const UriInfo &uri, SeerVerifierHandle &verifier,
1494 int stackCount, TimeStamp &predictStartTime)
1495 {
1496 MOZ_ASSERT(!NS_IsMainThread(), "PredictForPageload called on main thread.");
1497
1498 if (stackCount > MAX_PAGELOAD_DEPTH) {
1499 SEER_LOG(("Too deep into pageload prediction"));
1500 return;
1501 }
1502
1503 if (NS_FAILED(EnsureInitStorage())) {
1504 return;
1505 }
1506
1507 PRTime now = PR_Now();
1508
1509 MaybeLearnForStartup(uri, now);
1510
1511 TopLevelInfo pageInfo;
1512 TopLevelInfo originInfo;
1513 bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo);
1514 bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo);
1515
1516 if (!havePage) {
1517 AddTopLevel(QUERY_PAGE, uri.spec, now);
1518 } else {
1519 UpdateTopLevel(QUERY_PAGE, pageInfo, now);
1520 }
1521
1522 if (!haveOrigin) {
1523 AddTopLevel(QUERY_ORIGIN, uri.origin, now);
1524 } else {
1525 UpdateTopLevel(QUERY_ORIGIN, originInfo, now);
1526 }
1527
1528 UriInfo newUri;
1529 if (havePage && WouldRedirect(pageInfo, now, newUri)) {
1530 nsRefPtr<SeerPredictionRunner> runner =
1531 new SeerPredictionRunner(verifier, predictStartTime);
1532 runner->AddPreconnect(newUri.spec);
1533 NS_DispatchToMainThread(runner);
1534 PredictForPageload(newUri, verifier, stackCount + 1, predictStartTime);
1535 return;
1536 }
1537
1538 bool predicted = false;
1539
1540 // We always try to be as specific as possible in our predictions, so try
1541 // to predict based on the full URI before we fall back to the origin.
1542 if (havePage) {
1543 predicted = TryPredict(QUERY_PAGE, pageInfo, now, verifier,
1544 predictStartTime);
1545 }
1546
1547 if (!predicted && haveOrigin) {
1548 predicted = TryPredict(QUERY_ORIGIN, originInfo, now, verifier,
1549 predictStartTime);
1550 }
1551
1552 if (!predicted) {
1553 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION,
1554 predictStartTime);
1555 }
1556 }
1557
1558 // This is the driver for predicting at browser startup time based on pages that
1559 // have previously been loaded close to startup.
1560 void
1561 Seer::PredictForStartup(SeerVerifierHandle &verifier,
1562 TimeStamp &predictStartTime)
1563 {
1564 MOZ_ASSERT(!NS_IsMainThread(), "PredictForStartup called on main thread");
1565
1566 if (!mStartupCount) {
1567 SEER_LOG(("Seer::PredictForStartup mStartupCount is zero!\n"));
1568 ++mAccumulators->mStartupCountZeroes;
1569 return;
1570 }
1571
1572 if (NS_FAILED(EnsureInitStorage())) {
1573 return;
1574 }
1575
1576 nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement(
1577 NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_startup_pages;"));
1578 if (!stmt) {
1579 return;
1580 }
1581 mozStorageStatementScoper scope(stmt);
1582 nsresult rv;
1583 bool hasRows;
1584
1585 nsRefPtr<SeerPredictionRunner> runner =
1586 new SeerPredictionRunner(verifier, predictStartTime);
1587
1588 rv = stmt->ExecuteStep(&hasRows);
1589 RETURN_IF_FAILED(rv);
1590
1591 while (hasRows) {
1592 nsAutoCString uri;
1593 int32_t hitCount;
1594 PRTime lastHit;
1595 int baseConfidence, confidence;
1596
1597 // We use goto nextrow here instead of just failling, because we want
1598 // to do some sort of prediction if at all possible. Of course, it's
1599 // probably unlikely that subsequent rows will succeed if one fails, but
1600 // it's worth a shot.
1601
1602 rv = stmt->GetUTF8String(0, uri);
1603 if (NS_FAILED(rv)) {
1604 goto nextrow;
1605 }
1606
1607 rv = stmt->GetInt32(1, &hitCount);
1608 if (NS_FAILED(rv)) {
1609 goto nextrow;
1610 }
1611
1612 rv = stmt->GetInt64(2, &lastHit);
1613 if (NS_FAILED(rv)) {
1614 goto nextrow;
1615 }
1616
1617 baseConfidence = (hitCount * 100) / mStartupCount;
1618 confidence = CalculateConfidence(baseConfidence, lastHit,
1619 mLastStartupTime, 0);
1620 SetupPrediction(confidence, uri, runner);
1621
1622 nextrow:
1623 rv = stmt->ExecuteStep(&hasRows);
1624 RETURN_IF_FAILED(rv);
1625 }
1626
1627 if (runner->HasWork()) {
1628 NS_DispatchToMainThread(runner);
1629 } else {
1630 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION,
1631 predictStartTime);
1632 }
1633 }
1634
1635 // All URIs we get passed *must* be http or https if they're not null. This
1636 // helps ensure that.
1637 static bool
1638 IsNullOrHttp(nsIURI *uri)
1639 {
1640 if (!uri) {
1641 return true;
1642 }
1643
1644 bool isHTTP = false;
1645 uri->SchemeIs("http", &isHTTP);
1646 if (!isHTTP) {
1647 uri->SchemeIs("https", &isHTTP);
1648 }
1649
1650 return isHTTP;
1651 }
1652
1653 nsresult
1654 Seer::ReserveSpaceInQueue()
1655 {
1656 MutexAutoLock lock(mQueueSizeLock);
1657
1658 if (mQueueSize >= mMaxQueueSize) {
1659 SEER_LOG(("Not enqueuing event - queue too large"));
1660 return NS_ERROR_NOT_AVAILABLE;
1661 }
1662
1663 mQueueSize++;
1664 return NS_OK;
1665 }
1666
1667 void
1668 Seer::FreeSpaceInQueue()
1669 {
1670 MutexAutoLock lock(mQueueSizeLock);
1671 MOZ_ASSERT(mQueueSize > 0, "unexpected mQueueSize");
1672 mQueueSize--;
1673 }
1674
1675 // Called from the main thread to initiate predictive actions
1676 NS_IMETHODIMP
1677 Seer::Predict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason,
1678 nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier)
1679 {
1680 MOZ_ASSERT(NS_IsMainThread(),
1681 "Seer interface methods must be called on the main thread");
1682
1683 if (!mInitialized) {
1684 return NS_ERROR_NOT_AVAILABLE;
1685 }
1686
1687 if (!mEnabled) {
1688 return NS_ERROR_NOT_AVAILABLE;
1689 }
1690
1691 if (loadContext && loadContext->UsePrivateBrowsing()) {
1692 // Don't want to do anything in PB mode
1693 return NS_OK;
1694 }
1695
1696 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1697 // Nothing we can do for non-HTTP[S] schemes
1698 return NS_OK;
1699 }
1700
1701 // Ensure we've been given the appropriate arguments for the kind of
1702 // prediction we're being asked to do
1703 switch (reason) {
1704 case nsINetworkSeer::PREDICT_LINK:
1705 if (!targetURI || !sourceURI) {
1706 return NS_ERROR_INVALID_ARG;
1707 }
1708 // Link hover is a special case where we can predict without hitting the
1709 // db, so let's go ahead and fire off that prediction here.
1710 PredictForLink(targetURI, sourceURI, verifier);
1711 return NS_OK;
1712 case nsINetworkSeer::PREDICT_LOAD:
1713 if (!targetURI || sourceURI) {
1714 return NS_ERROR_INVALID_ARG;
1715 }
1716 break;
1717 case nsINetworkSeer::PREDICT_STARTUP:
1718 if (targetURI || sourceURI) {
1719 return NS_ERROR_INVALID_ARG;
1720 }
1721 break;
1722 default:
1723 return NS_ERROR_INVALID_ARG;
1724 }
1725
1726 ++mAccumulators->mPredictAttempts;
1727 nsresult rv = ReserveSpaceInQueue();
1728 if (NS_FAILED(rv)) {
1729 ++mAccumulators->mPredictFullQueue;
1730 return NS_ERROR_NOT_AVAILABLE;
1731 }
1732
1733 nsRefPtr<SeerPredictionEvent> event = new SeerPredictionEvent(targetURI,
1734 sourceURI,
1735 reason,
1736 verifier);
1737 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
1738 }
1739
1740 // A runnable for updating our information in the database. This must always
1741 // be dispatched to the seer thread.
1742 class SeerLearnEvent : public nsRunnable
1743 {
1744 public:
1745 SeerLearnEvent(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason)
1746 :mReason(reason)
1747 {
1748 MOZ_ASSERT(NS_IsMainThread(), "Creating learn event off main thread");
1749
1750 mEnqueueTime = TimeStamp::Now();
1751
1752 targetURI->GetAsciiSpec(mTargetURI.spec);
1753 ExtractOrigin(targetURI, mTargetURI.origin);
1754 if (sourceURI) {
1755 sourceURI->GetAsciiSpec(mSourceURI.spec);
1756 ExtractOrigin(sourceURI, mSourceURI.origin);
1757 }
1758 }
1759
1760 NS_IMETHOD Run() MOZ_OVERRIDE
1761 {
1762 MOZ_ASSERT(!NS_IsMainThread(), "Running learn off main thread");
1763
1764 nsresult rv = NS_OK;
1765
1766 Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WAIT_TIME,
1767 mEnqueueTime);
1768
1769 TimeStamp startTime = TimeStamp::Now();
1770
1771 switch (mReason) {
1772 case nsINetworkSeer::LEARN_LOAD_TOPLEVEL:
1773 gSeer->LearnForToplevel(mTargetURI);
1774 break;
1775 case nsINetworkSeer::LEARN_LOAD_REDIRECT:
1776 gSeer->LearnForRedirect(mTargetURI, mSourceURI);
1777 break;
1778 case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE:
1779 gSeer->LearnForSubresource(mTargetURI, mSourceURI);
1780 break;
1781 case nsINetworkSeer::LEARN_STARTUP:
1782 gSeer->LearnForStartup(mTargetURI);
1783 break;
1784 default:
1785 MOZ_ASSERT(false, "Got unexpected value for learn reason");
1786 rv = NS_ERROR_UNEXPECTED;
1787 }
1788
1789 gSeer->FreeSpaceInQueue();
1790
1791 Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WORK_TIME, startTime);
1792
1793 gSeer->MaybeScheduleCleanup();
1794
1795 return rv;
1796 }
1797 private:
1798 Seer::UriInfo mTargetURI;
1799 Seer::UriInfo mSourceURI;
1800 SeerLearnReason mReason;
1801 TimeStamp mEnqueueTime;
1802 };
1803
1804 void
1805 Seer::LearnForToplevel(const UriInfo &uri)
1806 {
1807 MOZ_ASSERT(!NS_IsMainThread(), "LearnForToplevel called on main thread.");
1808
1809 if (NS_FAILED(EnsureInitStorage())) {
1810 return;
1811 }
1812
1813 PRTime now = PR_Now();
1814
1815 MaybeLearnForStartup(uri, now);
1816
1817 TopLevelInfo pageInfo;
1818 TopLevelInfo originInfo;
1819 bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo);
1820 bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo);
1821
1822 if (!havePage) {
1823 AddTopLevel(QUERY_PAGE, uri.spec, now);
1824 } else {
1825 UpdateTopLevel(QUERY_PAGE, pageInfo, now);
1826 }
1827
1828 if (!haveOrigin) {
1829 AddTopLevel(QUERY_ORIGIN, uri.origin, now);
1830 } else {
1831 UpdateTopLevel(QUERY_ORIGIN, originInfo, now);
1832 }
1833 }
1834
1835 // Queries to look up information about a *specific* subresource associated
1836 // with a *specific* top-level load.
1837 bool
1838 Seer::LookupSubresource(QueryType queryType, const int32_t parentId,
1839 const nsACString &key, SubresourceInfo &info)
1840 {
1841 MOZ_ASSERT(!NS_IsMainThread(), "LookupSubresource called on main thread.");
1842
1843 nsCOMPtr<mozIStorageStatement> stmt;
1844 if (queryType == QUERY_PAGE) {
1845 stmt = mStatements.GetCachedStatement(
1846 NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subresources "
1847 "WHERE pid = :parent_id AND uri = :key;"));
1848 } else {
1849 stmt = mStatements.GetCachedStatement(
1850 NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subhosts WHERE "
1851 "hid = :parent_id AND origin = :key;"));
1852 }
1853 NS_ENSURE_TRUE(stmt, false);
1854 mozStorageStatementScoper scope(stmt);
1855
1856 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"),
1857 parentId);
1858 NS_ENSURE_SUCCESS(rv, false);
1859
1860 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
1861 NS_ENSURE_SUCCESS(rv, false);
1862
1863 bool hasRows;
1864 rv = stmt->ExecuteStep(&hasRows);
1865 NS_ENSURE_SUCCESS(rv, false);
1866 if (!hasRows) {
1867 return false;
1868 }
1869
1870 rv = stmt->GetInt32(0, &info.id);
1871 NS_ENSURE_SUCCESS(rv, false);
1872
1873 rv = stmt->GetInt32(1, &info.hitCount);
1874 NS_ENSURE_SUCCESS(rv, false);
1875
1876 rv = stmt->GetInt64(2, &info.lastHit);
1877 NS_ENSURE_SUCCESS(rv, false);
1878
1879 return true;
1880 }
1881
1882 // Add information about a new subresource associated with a top-level load.
1883 void
1884 Seer::AddSubresource(QueryType queryType, const int32_t parentId,
1885 const nsACString &key, const PRTime now)
1886 {
1887 MOZ_ASSERT(!NS_IsMainThread(), "AddSubresource called on main thread.");
1888
1889 nsCOMPtr<mozIStorageStatement> stmt;
1890 if (queryType == QUERY_PAGE) {
1891 stmt = mStatements.GetCachedStatement(
1892 NS_LITERAL_CSTRING("INSERT INTO moz_subresources "
1893 "(pid, uri, hits, last_hit) VALUES "
1894 "(:parent_id, :key, 1, :now);"));
1895 } else {
1896 stmt = mStatements.GetCachedStatement(
1897 NS_LITERAL_CSTRING("INSERT INTO moz_subhosts "
1898 "(hid, origin, hits, last_hit) VALUES "
1899 "(:parent_id, :key, 1, :now);"));
1900 }
1901 if (!stmt) {
1902 return;
1903 }
1904 mozStorageStatementScoper scope(stmt);
1905
1906 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"),
1907 parentId);
1908 RETURN_IF_FAILED(rv);
1909
1910 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
1911 RETURN_IF_FAILED(rv);
1912
1913 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
1914 RETURN_IF_FAILED(rv);
1915
1916 rv = stmt->Execute();
1917 }
1918
1919 // Update the information about a particular subresource associated with a
1920 // top-level load
1921 void
1922 Seer::UpdateSubresource(QueryType queryType, const SubresourceInfo &info,
1923 const PRTime now)
1924 {
1925 MOZ_ASSERT(!NS_IsMainThread(), "UpdateSubresource called on main thread.");
1926
1927 nsCOMPtr<mozIStorageStatement> stmt;
1928 if (queryType == QUERY_PAGE) {
1929 stmt = mStatements.GetCachedStatement(
1930 NS_LITERAL_CSTRING("UPDATE moz_subresources SET hits = :hit_count, "
1931 "last_hit = :now WHERE id = :id;"));
1932 } else {
1933 stmt = mStatements.GetCachedStatement(
1934 NS_LITERAL_CSTRING("UPDATE moz_subhosts SET hits = :hit_count, "
1935 "last_hit = :now WHERE id = :id;"));
1936 }
1937 if (!stmt) {
1938 return;
1939 }
1940 mozStorageStatementScoper scope(stmt);
1941
1942 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"),
1943 info.hitCount + 1);
1944 RETURN_IF_FAILED(rv);
1945
1946 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
1947 RETURN_IF_FAILED(rv);
1948
1949 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
1950 RETURN_IF_FAILED(rv);
1951
1952 rv = stmt->Execute();
1953 }
1954
1955 // Called when a subresource has been hit from a top-level load. Uses the two
1956 // helper functions above to update the database appropriately.
1957 void
1958 Seer::LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI)
1959 {
1960 MOZ_ASSERT(!NS_IsMainThread(), "LearnForSubresource called on main thread.");
1961
1962 if (NS_FAILED(EnsureInitStorage())) {
1963 return;
1964 }
1965
1966 TopLevelInfo pageInfo, originInfo;
1967 bool havePage = LookupTopLevel(QUERY_PAGE, sourceURI.spec, pageInfo);
1968 bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, sourceURI.origin,
1969 originInfo);
1970
1971 if (!havePage && !haveOrigin) {
1972 // Nothing to do, since we know nothing about the top level resource
1973 return;
1974 }
1975
1976 SubresourceInfo resourceInfo;
1977 bool haveResource = false;
1978 if (havePage) {
1979 haveResource = LookupSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec,
1980 resourceInfo);
1981 }
1982
1983 SubresourceInfo hostInfo;
1984 bool haveHost = false;
1985 if (haveOrigin) {
1986 haveHost = LookupSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin,
1987 hostInfo);
1988 }
1989
1990 PRTime now = PR_Now();
1991
1992 if (haveResource) {
1993 UpdateSubresource(QUERY_PAGE, resourceInfo, now);
1994 } else if (havePage) {
1995 AddSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, now);
1996 }
1997 // Can't add a subresource to a page we don't have in our db.
1998
1999 if (haveHost) {
2000 UpdateSubresource(QUERY_ORIGIN, hostInfo, now);
2001 } else if (haveOrigin) {
2002 AddSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, now);
2003 }
2004 // Can't add a subhost to a host we don't have in our db
2005 }
2006
2007 // This is called when a top-level loaded ended up redirecting to a different
2008 // URI so we can keep track of that fact.
2009 void
2010 Seer::LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI)
2011 {
2012 MOZ_ASSERT(!NS_IsMainThread(), "LearnForRedirect called on main thread.");
2013
2014 if (NS_FAILED(EnsureInitStorage())) {
2015 return;
2016 }
2017
2018 PRTime now = PR_Now();
2019 nsresult rv;
2020
2021 nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement(
2022 NS_LITERAL_CSTRING("SELECT id FROM moz_pages WHERE uri = :spec;"));
2023 if (!getPage) {
2024 return;
2025 }
2026 mozStorageStatementScoper scopedPage(getPage);
2027
2028 // look up source in moz_pages
2029 rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
2030 sourceURI.spec);
2031 RETURN_IF_FAILED(rv);
2032
2033 bool hasRows;
2034 rv = getPage->ExecuteStep(&hasRows);
2035 if (NS_FAILED(rv) || !hasRows) {
2036 return;
2037 }
2038
2039 int32_t pageId;
2040 rv = getPage->GetInt32(0, &pageId);
2041 RETURN_IF_FAILED(rv);
2042
2043 nsCOMPtr<mozIStorageStatement> getRedirect = mStatements.GetCachedStatement(
2044 NS_LITERAL_CSTRING("SELECT id, hits FROM moz_redirects WHERE "
2045 "pid = :page_id AND uri = :spec;"));
2046 if (!getRedirect) {
2047 return;
2048 }
2049 mozStorageStatementScoper scopedRedirect(getRedirect);
2050
2051 rv = getRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
2052 RETURN_IF_FAILED(rv);
2053
2054 rv = getRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
2055 targetURI.spec);
2056 RETURN_IF_FAILED(rv);
2057
2058 rv = getRedirect->ExecuteStep(&hasRows);
2059 RETURN_IF_FAILED(rv);
2060
2061 if (!hasRows) {
2062 // This is the first time we've seen this top-level redirect to this URI
2063 nsCOMPtr<mozIStorageStatement> addRedirect = mStatements.GetCachedStatement(
2064 NS_LITERAL_CSTRING("INSERT INTO moz_redirects "
2065 "(pid, uri, origin, hits, last_hit) VALUES "
2066 "(:page_id, :spec, :origin, 1, :now);"));
2067 if (!addRedirect) {
2068 return;
2069 }
2070 mozStorageStatementScoper scopedAdd(addRedirect);
2071
2072 rv = addRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
2073 RETURN_IF_FAILED(rv);
2074
2075 rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
2076 targetURI.spec);
2077 RETURN_IF_FAILED(rv);
2078
2079 rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"),
2080 targetURI.origin);
2081 RETURN_IF_FAILED(rv);
2082
2083 rv = addRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
2084 RETURN_IF_FAILED(rv);
2085
2086 rv = addRedirect->Execute();
2087 } else {
2088 // We've seen this redirect before
2089 int32_t redirId, hits;
2090 rv = getRedirect->GetInt32(0, &redirId);
2091 RETURN_IF_FAILED(rv);
2092
2093 rv = getRedirect->GetInt32(1, &hits);
2094 RETURN_IF_FAILED(rv);
2095
2096 nsCOMPtr<mozIStorageStatement> updateRedirect =
2097 mStatements.GetCachedStatement(
2098 NS_LITERAL_CSTRING("UPDATE moz_redirects SET hits = :hits, "
2099 "last_hit = :now WHERE id = :redir;"));
2100 if (!updateRedirect) {
2101 return;
2102 }
2103 mozStorageStatementScoper scopedUpdate(updateRedirect);
2104
2105 rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("hits"), hits + 1);
2106 RETURN_IF_FAILED(rv);
2107
2108 rv = updateRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
2109 RETURN_IF_FAILED(rv);
2110
2111 rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("redir"), redirId);
2112 RETURN_IF_FAILED(rv);
2113
2114 updateRedirect->Execute();
2115 }
2116 }
2117
2118 // Add information about a top-level load to our list of startup pages
2119 void
2120 Seer::LearnForStartup(const UriInfo &uri)
2121 {
2122 MOZ_ASSERT(!NS_IsMainThread(), "LearnForStartup called on main thread.");
2123
2124 if (NS_FAILED(EnsureInitStorage())) {
2125 return;
2126 }
2127
2128 nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement(
2129 NS_LITERAL_CSTRING("SELECT id, hits FROM moz_startup_pages WHERE "
2130 "uri = :origin;"));
2131 if (!getPage) {
2132 return;
2133 }
2134 mozStorageStatementScoper scopedPage(getPage);
2135 nsresult rv;
2136
2137 rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), uri.origin);
2138 RETURN_IF_FAILED(rv);
2139
2140 bool hasRows;
2141 rv = getPage->ExecuteStep(&hasRows);
2142 RETURN_IF_FAILED(rv);
2143
2144 if (hasRows) {
2145 // We've loaded this page on startup before
2146 int32_t pageId, hitCount;
2147
2148 rv = getPage->GetInt32(0, &pageId);
2149 RETURN_IF_FAILED(rv);
2150
2151 rv = getPage->GetInt32(1, &hitCount);
2152 RETURN_IF_FAILED(rv);
2153
2154 nsCOMPtr<mozIStorageStatement> updatePage = mStatements.GetCachedStatement(
2155 NS_LITERAL_CSTRING("UPDATE moz_startup_pages SET hits = :hit_count, "
2156 "last_hit = :startup_time WHERE id = :page_id;"));
2157 if (!updatePage) {
2158 return;
2159 }
2160 mozStorageStatementScoper scopedUpdate(updatePage);
2161
2162 rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"),
2163 hitCount + 1);
2164 RETURN_IF_FAILED(rv);
2165
2166 rv = updatePage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
2167 mStartupTime);
2168 RETURN_IF_FAILED(rv);
2169
2170 rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
2171 RETURN_IF_FAILED(rv);
2172
2173 updatePage->Execute();
2174 } else {
2175 // New startup page
2176 nsCOMPtr<mozIStorageStatement> addPage = mStatements.GetCachedStatement(
2177 NS_LITERAL_CSTRING("INSERT INTO moz_startup_pages (uri, hits, "
2178 "last_hit) VALUES (:origin, 1, :startup_time);"));
2179 if (!addPage) {
2180 return;
2181 }
2182 mozStorageStatementScoper scopedAdd(addPage);
2183 rv = addPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"),
2184 uri.origin);
2185 RETURN_IF_FAILED(rv);
2186
2187 rv = addPage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
2188 mStartupTime);
2189 RETURN_IF_FAILED(rv);
2190
2191 addPage->Execute();
2192 }
2193 }
2194
2195 // Called from the main thread to update the database
2196 NS_IMETHODIMP
2197 Seer::Learn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
2198 nsILoadContext *loadContext)
2199 {
2200 MOZ_ASSERT(NS_IsMainThread(),
2201 "Seer interface methods must be called on the main thread");
2202
2203 if (!mInitialized) {
2204 return NS_ERROR_NOT_AVAILABLE;
2205 }
2206
2207 if (!mEnabled) {
2208 return NS_ERROR_NOT_AVAILABLE;
2209 }
2210
2211 if (loadContext && loadContext->UsePrivateBrowsing()) {
2212 // Don't want to do anything in PB mode
2213 return NS_OK;
2214 }
2215
2216 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2217 return NS_ERROR_INVALID_ARG;
2218 }
2219
2220 switch (reason) {
2221 case nsINetworkSeer::LEARN_LOAD_TOPLEVEL:
2222 case nsINetworkSeer::LEARN_STARTUP:
2223 if (!targetURI || sourceURI) {
2224 return NS_ERROR_INVALID_ARG;
2225 }
2226 break;
2227 case nsINetworkSeer::LEARN_LOAD_REDIRECT:
2228 case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE:
2229 if (!targetURI || !sourceURI) {
2230 return NS_ERROR_INVALID_ARG;
2231 }
2232 break;
2233 default:
2234 return NS_ERROR_INVALID_ARG;
2235 }
2236
2237 ++mAccumulators->mLearnAttempts;
2238 nsresult rv = ReserveSpaceInQueue();
2239 if (NS_FAILED(rv)) {
2240 ++mAccumulators->mLearnFullQueue;
2241 return NS_ERROR_NOT_AVAILABLE;
2242 }
2243
2244 nsRefPtr<SeerLearnEvent> event = new SeerLearnEvent(targetURI, sourceURI,
2245 reason);
2246 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
2247 }
2248
2249 // Runnable to clear out the database. Dispatched from the main thread to the
2250 // seer thread
2251 class SeerResetEvent : public nsRunnable
2252 {
2253 public:
2254 SeerResetEvent()
2255 { }
2256
2257 NS_IMETHOD Run() MOZ_OVERRIDE
2258 {
2259 MOZ_ASSERT(!NS_IsMainThread(), "Running reset on main thread");
2260
2261 gSeer->ResetInternal();
2262
2263 return NS_OK;
2264 }
2265 };
2266
2267 // Helper that actually does the database wipe.
2268 void
2269 Seer::ResetInternal()
2270 {
2271 MOZ_ASSERT(!NS_IsMainThread(), "Resetting db on main thread");
2272
2273 nsresult rv = EnsureInitStorage();
2274 RETURN_IF_FAILED(rv);
2275
2276 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_redirects;"));
2277 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages;"));
2278 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startups;"));
2279
2280 // These cascade to moz_subresources and moz_subhosts, respectively.
2281 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;"));
2282 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts;"));
2283
2284 VacuumDatabase();
2285
2286 // Go ahead and ensure this is flushed to disk
2287 CommitTransaction();
2288 BeginTransaction();
2289 }
2290
2291 // Called on the main thread to clear out all our knowledge. Tabula Rasa FTW!
2292 NS_IMETHODIMP
2293 Seer::Reset()
2294 {
2295 MOZ_ASSERT(NS_IsMainThread(),
2296 "Seer interface methods must be called on the main thread");
2297
2298 if (!mInitialized) {
2299 return NS_ERROR_NOT_AVAILABLE;
2300 }
2301
2302 if (!mEnabled) {
2303 return NS_OK;
2304 }
2305
2306 nsRefPtr<SeerResetEvent> event = new SeerResetEvent();
2307 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
2308 }
2309
2310 class SeerCleanupEvent : public nsRunnable
2311 {
2312 public:
2313 NS_IMETHOD Run() MOZ_OVERRIDE
2314 {
2315 gSeer->Cleanup();
2316 gSeer->mCleanupScheduled = false;
2317 return NS_OK;
2318 }
2319 };
2320
2321 // Returns the current size (in bytes) of the db file on disk
2322 int64_t
2323 Seer::GetDBFileSize()
2324 {
2325 MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSize called on main thread!");
2326
2327 nsresult rv = EnsureInitStorage();
2328 if (NS_FAILED(rv)) {
2329 SEER_LOG(("GetDBFileSize called without db available!"));
2330 return 0;
2331 }
2332
2333 CommitTransaction();
2334
2335 nsCOMPtr<mozIStorageStatement> countStmt = mStatements.GetCachedStatement(
2336 NS_LITERAL_CSTRING("PRAGMA page_count;"));
2337 if (!countStmt) {
2338 return 0;
2339 }
2340 mozStorageStatementScoper scopedCount(countStmt);
2341 bool hasRows;
2342 rv = countStmt->ExecuteStep(&hasRows);
2343 if (NS_FAILED(rv) || !hasRows) {
2344 return 0;
2345 }
2346 int64_t pageCount;
2347 rv = countStmt->GetInt64(0, &pageCount);
2348 if (NS_FAILED(rv)) {
2349 return 0;
2350 }
2351
2352 nsCOMPtr<mozIStorageStatement> sizeStmt = mStatements.GetCachedStatement(
2353 NS_LITERAL_CSTRING("PRAGMA page_size;"));
2354 if (!sizeStmt) {
2355 return 0;
2356 }
2357 mozStorageStatementScoper scopedSize(sizeStmt);
2358 rv = sizeStmt->ExecuteStep(&hasRows);
2359 if (NS_FAILED(rv) || !hasRows) {
2360 return 0;
2361 }
2362 int64_t pageSize;
2363 rv = sizeStmt->GetInt64(0, &pageSize);
2364 if (NS_FAILED(rv)) {
2365 return 0;
2366 }
2367
2368 BeginTransaction();
2369
2370 return pageCount * pageSize;
2371 }
2372
2373 // Returns the size (in bytes) that the db file will consume on disk AFTER we
2374 // vacuum the db.
2375 int64_t
2376 Seer::GetDBFileSizeAfterVacuum()
2377 {
2378 MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSizeAfterVacuum called on main thread!");
2379
2380 CommitTransaction();
2381
2382 nsCOMPtr<mozIStorageStatement> countStmt = mStatements.GetCachedStatement(
2383 NS_LITERAL_CSTRING("PRAGMA page_count;"));
2384 if (!countStmt) {
2385 return 0;
2386 }
2387 mozStorageStatementScoper scopedCount(countStmt);
2388 bool hasRows;
2389 nsresult rv = countStmt->ExecuteStep(&hasRows);
2390 if (NS_FAILED(rv) || !hasRows) {
2391 return 0;
2392 }
2393 int64_t pageCount;
2394 rv = countStmt->GetInt64(0, &pageCount);
2395 if (NS_FAILED(rv)) {
2396 return 0;
2397 }
2398
2399 nsCOMPtr<mozIStorageStatement> sizeStmt = mStatements.GetCachedStatement(
2400 NS_LITERAL_CSTRING("PRAGMA page_size;"));
2401 if (!sizeStmt) {
2402 return 0;
2403 }
2404 mozStorageStatementScoper scopedSize(sizeStmt);
2405 rv = sizeStmt->ExecuteStep(&hasRows);
2406 if (NS_FAILED(rv) || !hasRows) {
2407 return 0;
2408 }
2409 int64_t pageSize;
2410 rv = sizeStmt->GetInt64(0, &pageSize);
2411 if (NS_FAILED(rv)) {
2412 return 0;
2413 }
2414
2415 nsCOMPtr<mozIStorageStatement> freeStmt = mStatements.GetCachedStatement(
2416 NS_LITERAL_CSTRING("PRAGMA freelist_count;"));
2417 if (!freeStmt) {
2418 return 0;
2419 }
2420 mozStorageStatementScoper scopedFree(freeStmt);
2421 rv = freeStmt->ExecuteStep(&hasRows);
2422 if (NS_FAILED(rv) || !hasRows) {
2423 return 0;
2424 }
2425 int64_t freelistCount;
2426 rv = freeStmt->GetInt64(0, &freelistCount);
2427 if (NS_FAILED(rv)) {
2428 return 0;
2429 }
2430
2431 BeginTransaction();
2432
2433 return (pageCount - freelistCount) * pageSize;
2434 }
2435
2436 void
2437 Seer::MaybeScheduleCleanup()
2438 {
2439 MOZ_ASSERT(!NS_IsMainThread(), "MaybeScheduleCleanup called on main thread!");
2440
2441 // This is a little racy, but it's a nice little shutdown optimization if the
2442 // race works out the right way.
2443 if (!mInitialized) {
2444 return;
2445 }
2446
2447 if (mCleanupScheduled) {
2448 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false);
2449 return;
2450 }
2451
2452 int64_t dbFileSize = GetDBFileSize();
2453 if (dbFileSize < mMaxDBSize) {
2454 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false);
2455 return;
2456 }
2457
2458 mCleanupScheduled = true;
2459
2460 nsRefPtr<SeerCleanupEvent> event = new SeerCleanupEvent();
2461 nsresult rv = mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
2462 if (NS_FAILED(rv)) {
2463 mCleanupScheduled = false;
2464 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false);
2465 } else {
2466 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, true);
2467 }
2468 }
2469
2470 #ifndef ANDROID
2471 static const long long CLEANUP_CUTOFF = ONE_MONTH;
2472 #else
2473 static const long long CLEANUP_CUTOFF = ONE_WEEK;
2474 #endif
2475
2476 void
2477 Seer::CleanupOrigins(PRTime now)
2478 {
2479 PRTime cutoff = now - CLEANUP_CUTOFF;
2480
2481 nsCOMPtr<mozIStorageStatement> deleteOrigins = mStatements.GetCachedStatement(
2482 NS_LITERAL_CSTRING("DELETE FROM moz_hosts WHERE last_load <= :cutoff"));
2483 if (!deleteOrigins) {
2484 return;
2485 }
2486 mozStorageStatementScoper scopedOrigins(deleteOrigins);
2487
2488 nsresult rv = deleteOrigins->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"),
2489 cutoff);
2490 RETURN_IF_FAILED(rv);
2491
2492 deleteOrigins->Execute();
2493 }
2494
2495 void
2496 Seer::CleanupStartupPages(PRTime now)
2497 {
2498 PRTime cutoff = now - ONE_WEEK;
2499
2500 nsCOMPtr<mozIStorageStatement> deletePages = mStatements.GetCachedStatement(
2501 NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages WHERE "
2502 "last_hit <= :cutoff"));
2503 if (!deletePages) {
2504 return;
2505 }
2506 mozStorageStatementScoper scopedPages(deletePages);
2507
2508 nsresult rv = deletePages->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"),
2509 cutoff);
2510 RETURN_IF_FAILED(rv);
2511
2512 deletePages->Execute();
2513 }
2514
2515 int32_t
2516 Seer::GetSubresourceCount()
2517 {
2518 nsCOMPtr<mozIStorageStatement> count = mStatements.GetCachedStatement(
2519 NS_LITERAL_CSTRING("SELECT COUNT(id) FROM moz_subresources"));
2520 if (!count) {
2521 return 0;
2522 }
2523 mozStorageStatementScoper scopedCount(count);
2524
2525 bool hasRows;
2526 nsresult rv = count->ExecuteStep(&hasRows);
2527 if (NS_FAILED(rv) || !hasRows) {
2528 return 0;
2529 }
2530
2531 int32_t subresourceCount = 0;
2532 count->GetInt32(0, &subresourceCount);
2533
2534 return subresourceCount;
2535 }
2536
2537 void
2538 Seer::Cleanup()
2539 {
2540 MOZ_ASSERT(!NS_IsMainThread(), "Seer::Cleanup called on main thread!");
2541
2542 nsresult rv = EnsureInitStorage();
2543 if (NS_FAILED(rv)) {
2544 return;
2545 }
2546
2547 int64_t dbFileSize = GetDBFileSize();
2548 float preservePercentage = static_cast<float>(mPreservePercentage) / 100.0;
2549 int64_t evictionCutoff = static_cast<int64_t>(mMaxDBSize) * preservePercentage;
2550 if (dbFileSize < evictionCutoff) {
2551 return;
2552 }
2553
2554 CommitTransaction();
2555 BeginTransaction();
2556
2557 PRTime now = PR_Now();
2558 if (mLastCleanupTime) {
2559 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_DELTA,
2560 (now - mLastCleanupTime) / 1000);
2561 }
2562 mLastCleanupTime = now;
2563
2564 CleanupOrigins(now);
2565 CleanupStartupPages(now);
2566
2567 dbFileSize = GetDBFileSizeAfterVacuum();
2568 if (dbFileSize < evictionCutoff) {
2569 // We've deleted enough stuff, time to free up the disk space and be on
2570 // our way.
2571 VacuumDatabase();
2572 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true);
2573 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME,
2574 (PR_Now() - mLastCleanupTime) / 1000);
2575 return;
2576 }
2577
2578 bool canDelete = true;
2579 while (canDelete && (dbFileSize >= evictionCutoff)) {
2580 int32_t subresourceCount = GetSubresourceCount();
2581 if (!subresourceCount) {
2582 canDelete = false;
2583 break;
2584 }
2585
2586 // DB size scales pretty much linearly with the number of rows in
2587 // moz_subresources, so we can guess how many rows we need to delete pretty
2588 // accurately.
2589 float percentNeeded = static_cast<float>(dbFileSize - evictionCutoff) /
2590 static_cast<float>(dbFileSize);
2591
2592 int32_t subresourcesToDelete = static_cast<int32_t>(percentNeeded * subresourceCount);
2593 if (!subresourcesToDelete) {
2594 // We're getting pretty close to nothing here, anyway, so we may as well
2595 // just trash it all. This delete cascades to moz_subresources, as well.
2596 rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;"));
2597 if (NS_FAILED(rv)) {
2598 canDelete = false;
2599 break;
2600 }
2601 } else {
2602 nsCOMPtr<mozIStorageStatement> deleteStatement = mStatements.GetCachedStatement(
2603 NS_LITERAL_CSTRING("DELETE FROM moz_subresources WHERE id IN "
2604 "(SELECT id FROM moz_subresources ORDER BY "
2605 "last_hit ASC LIMIT :limit);"));
2606 if (!deleteStatement) {
2607 canDelete = false;
2608 break;
2609 }
2610 mozStorageStatementScoper scopedDelete(deleteStatement);
2611
2612 rv = deleteStatement->BindInt32ByName(NS_LITERAL_CSTRING("limit"),
2613 subresourcesToDelete);
2614 if (NS_FAILED(rv)) {
2615 canDelete = false;
2616 break;
2617 }
2618
2619 rv = deleteStatement->Execute();
2620 if (NS_FAILED(rv)) {
2621 canDelete = false;
2622 break;
2623 }
2624
2625 // Now we clean up pages that no longer reference any subresources
2626 rv = mDB->ExecuteSimpleSQL(
2627 NS_LITERAL_CSTRING("DELETE FROM moz_pages WHERE id NOT IN "
2628 "(SELECT DISTINCT(pid) FROM moz_subresources);"));
2629 if (NS_FAILED(rv)) {
2630 canDelete = false;
2631 break;
2632 }
2633 }
2634
2635 if (canDelete) {
2636 dbFileSize = GetDBFileSizeAfterVacuum();
2637 }
2638 }
2639
2640 if (!canDelete || (dbFileSize >= evictionCutoff)) {
2641 // Last-ditch effort to free up space
2642 ResetInternal();
2643 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, false);
2644 } else {
2645 // We do this to actually free up the space on disk
2646 VacuumDatabase();
2647 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true);
2648 }
2649 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME,
2650 (PR_Now() - mLastCleanupTime) / 1000);
2651 }
2652
2653 void
2654 Seer::VacuumDatabase()
2655 {
2656 MOZ_ASSERT(!NS_IsMainThread(), "VacuumDatabase called on main thread!");
2657
2658 CommitTransaction();
2659 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
2660 BeginTransaction();
2661 }
2662
2663 #ifdef SEER_TESTS
2664 class SeerPrepareForDnsTestEvent : public nsRunnable
2665 {
2666 public:
2667 SeerPrepareForDnsTestEvent(int64_t timestamp, const char *uri)
2668 :mTimestamp(timestamp)
2669 ,mUri(uri)
2670 { }
2671
2672 NS_IMETHOD Run() MOZ_OVERRIDE
2673 {
2674 MOZ_ASSERT(!NS_IsMainThread(), "Preparing for DNS Test on main thread!");
2675 gSeer->PrepareForDnsTestInternal(mTimestamp, mUri);
2676 return NS_OK;
2677 }
2678
2679 private:
2680 int64_t mTimestamp;
2681 nsAutoCString mUri;
2682 };
2683
2684 void
2685 Seer::PrepareForDnsTestInternal(int64_t timestamp, const nsACString &uri)
2686 {
2687 nsCOMPtr<mozIStorageStatement> update = mStatements.GetCachedStatement(
2688 NS_LITERAL_CSTRING("UPDATE moz_subresources SET last_hit = :timestamp, "
2689 "hits = 2 WHERE uri = :uri;"));
2690 if (!update) {
2691 return;
2692 }
2693 mozStorageStatementScoper scopedUpdate(update);
2694
2695 nsresult rv = update->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"),
2696 timestamp);
2697 RETURN_IF_FAILED(rv);
2698
2699 rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("uri"), uri);
2700 RETURN_IF_FAILED(rv);
2701
2702 update->Execute();
2703 }
2704 #endif
2705
2706 NS_IMETHODIMP
2707 Seer::PrepareForDnsTest(int64_t timestamp, const char *uri)
2708 {
2709 #ifdef SEER_TESTS
2710 MOZ_ASSERT(NS_IsMainThread(),
2711 "Seer interface methods must be called on the main thread");
2712
2713 if (!mInitialized) {
2714 return NS_ERROR_NOT_AVAILABLE;
2715 }
2716
2717 nsRefPtr<SeerPrepareForDnsTestEvent> event =
2718 new SeerPrepareForDnsTestEvent(timestamp, uri);
2719 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
2720 #else
2721 return NS_ERROR_NOT_IMPLEMENTED;
2722 #endif
2723 }
2724
2725 // Helper functions to make using the seer easier from native code
2726
2727 static nsresult
2728 EnsureGlobalSeer(nsINetworkSeer **aSeer)
2729 {
2730 nsresult rv;
2731 nsCOMPtr<nsINetworkSeer> seer = do_GetService("@mozilla.org/network/seer;1",
2732 &rv);
2733 NS_ENSURE_SUCCESS(rv, rv);
2734
2735 NS_IF_ADDREF(*aSeer = seer);
2736 return NS_OK;
2737 }
2738
2739 nsresult
2740 SeerPredict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason,
2741 nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier)
2742 {
2743 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2744 return NS_OK;
2745 }
2746
2747 nsCOMPtr<nsINetworkSeer> seer;
2748 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
2749 NS_ENSURE_SUCCESS(rv, rv);
2750
2751 return seer->Predict(targetURI, sourceURI, reason, loadContext, verifier);
2752 }
2753
2754 nsresult
2755 SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
2756 nsILoadContext *loadContext)
2757 {
2758 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2759 return NS_OK;
2760 }
2761
2762 nsCOMPtr<nsINetworkSeer> seer;
2763 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
2764 NS_ENSURE_SUCCESS(rv, rv);
2765
2766 return seer->Learn(targetURI, sourceURI, reason, loadContext);
2767 }
2768
2769 nsresult
2770 SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
2771 nsILoadGroup *loadGroup)
2772 {
2773 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2774 return NS_OK;
2775 }
2776
2777 nsCOMPtr<nsINetworkSeer> seer;
2778 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
2779 NS_ENSURE_SUCCESS(rv, rv);
2780
2781 nsCOMPtr<nsILoadContext> loadContext;
2782
2783 if (loadGroup) {
2784 nsCOMPtr<nsIInterfaceRequestor> callbacks;
2785 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
2786 if (callbacks) {
2787 loadContext = do_GetInterface(callbacks);
2788 }
2789 }
2790
2791 return seer->Learn(targetURI, sourceURI, reason, loadContext);
2792 }
2793
2794 nsresult
2795 SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
2796 nsIDocument *document)
2797 {
2798 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2799 return NS_OK;
2800 }
2801
2802 nsCOMPtr<nsINetworkSeer> seer;
2803 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
2804 NS_ENSURE_SUCCESS(rv, rv);
2805
2806 nsCOMPtr<nsILoadContext> loadContext;
2807
2808 if (document) {
2809 loadContext = document->GetLoadContext();
2810 }
2811
2812 return seer->Learn(targetURI, sourceURI, reason, loadContext);
2813 }
2814
2815 nsresult
2816 SeerLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
2817 nsILoadContext *loadContext)
2818 {
2819 nsCOMPtr<nsIURI> sourceURI;
2820 nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
2821 NS_ENSURE_SUCCESS(rv, rv);
2822
2823 bool sameUri;
2824 rv = targetURI->Equals(sourceURI, &sameUri);
2825 NS_ENSURE_SUCCESS(rv, rv);
2826
2827 if (sameUri) {
2828 return NS_OK;
2829 }
2830
2831 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2832 return NS_OK;
2833 }
2834
2835 nsCOMPtr<nsINetworkSeer> seer;
2836 rv = EnsureGlobalSeer(getter_AddRefs(seer));
2837 NS_ENSURE_SUCCESS(rv, rv);
2838
2839 return seer->Learn(targetURI, sourceURI,
2840 nsINetworkSeer::LEARN_LOAD_REDIRECT, loadContext);
2841 }
2842
2843 } // ::mozilla::net
2844 } // ::mozilla

mercurial