Bug 599641 - Run ANALYZE after expiration
authorShawn Wilsher <me@shawnwilsher.com>
Mon, 11 Apr 2011 11:15:58 -0700
changeset 67861 f548afb7caca
parent 67849 31631f4fab61
child 67862 e72813d821b8
push id19445
push usersdwilsh@shawnwilsher.com
push date2011-04-11 21:01 +0000
treeherdermozilla-central@832de5e41bd2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs599641
milestone2.2a1pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 599641 - Run ANALYZE after expiration This runs the ANALYZE command on select tables after we expire results from them so that the query planner can plan better next time the application starts. r=mak
toolkit/components/places/nsPlacesExpiration.js
toolkit/components/places/tests/expiration/test_analyze_runs.js
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -189,23 +189,25 @@ const LIMIT = {
 const STATUS = {
   CLEAN: 0,
   DIRTY: 1,
   UNKNOWN: 2,
 };
 
 // Represents actions on which a query will run.
 const ACTION = {
-  TIMED: 1 << 0,
-  CLEAR_HISTORY: 1 << 1,
-  SHUTDOWN: 1 << 2,
-  CLEAN_SHUTDOWN: 1 << 3,
-  IDLE: 1 << 4,
-  DEBUG: 1 << 5,
-  TIMED_OVERLIMIT: 1 << 6,
+  TIMED: 1 << 0, // happens every this._interval
+  CLEAR_HISTORY: 1 << 1, // happens when history is cleared
+  SHUTDOWN: 1 << 2, // happens at shutdown when the db has a DIRTY state
+  CLEAN_SHUTDOWN: 1 << 3,  // happens at shutdown when the db has a CLEAN or
+                           // UNKNOWN state
+  IDLE: 1 << 4, // happens once on idle
+  DEBUG: 1 << 5, // happens whenever TOPIC_DEBUG_START_EXPIRATION is dispatched
+  TIMED_OVERLIMIT: 1 << 6, // just like TIMED, but also when we have too much
+                           // history
 };
 
 // The queries we use to expire.
 const EXPIRATION_QUERIES = {
 
   // Finds visits to be expired.  Will return nothing if we are not over the
   // unique URIs limit.
   QUERY_FIND_VISITS_TO_EXPIRE: {
@@ -395,17 +397,41 @@ const EXPIRATION_QUERIES = {
              ACTION.IDLE | ACTION.DEBUG
   },
 
   // Empty the notifications table.
   QUERY_DELETE_NOTIFICATIONS: {
     sql: "DELETE FROM expiration_notify",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN |
              ACTION.IDLE | ACTION.DEBUG
-  }
+  },
+
+  // The following queries are used to adjust the sqlite_stat1 table to help the
+  // query planner create better queries.  These should always be run LAST, and
+  // are therefore at the end of the object.
+
+  QUERY_ANALYZE_MOZ_PLACES: {
+    sql: "ANALYZE moz_places",
+    actions: ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | ACTION.IDLE |
+             ACTION.DEBUG
+  },
+  QUERY_ANALYZE_MOZ_BOOKMARKS: {
+    sql: "ANALYZE moz_bookmarks",
+    actions: ACTION.IDLE | ACTION.DEBUG
+  },
+  QUERY_ANALYZE_MOZ_HISTORYVISITS: {
+    sql: "ANALYZE moz_historyvisits",
+    actions: ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | ACTION.IDLE |
+             ACTION.DEBUG
+  },
+  QUERY_ANALYZE_MOZ_INPUTHISTORY: {
+    sql: "ANALYZE moz_inputhistory",
+    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+             ACTION.IDLE | ACTION.DEBUG
+  },
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsPlacesExpiration definition
 
 function nsPlacesExpiration()
 {
   //////////////////////////////////////////////////////////////////////////////
@@ -730,18 +756,21 @@ nsPlacesExpiration.prototype = {
     catch (e) {}
     if (this._interval <= 0)
       this._interval = PREF_INTERVAL_SECONDS_NOTSET;
   },
 
   /**
    * Execute async statements to expire with the specified queries.
    *
-   * @param aQueryNames
-   *        The names of the queries to use for this expiration step.
+   * @param aAction
+   *        The ACTION we are expiring for.  See the ACTION const for values.
+   * @param aLimit
+   *        Whether to use small, large or no limits when expiring.  See the
+   *        LIMIT const for values.
    */
   _expireWithActionAndLimit:
   function PEX__expireWithActionAndLimit(aAction, aLimit)
   {
     // Skip expiration during batch mode.
     if (this._inBatchMode)
       return;
 
@@ -860,19 +889,19 @@ nsPlacesExpiration.prototype = {
         params.expire_session = Ci.nsIAnnotationService.EXPIRE_SESSION;
         break;
     }
 
     return stmt;
   },
 
   /**
-   * Creates a new timer based on this._syncInterval.
+   * Creates a new timer based on this._interval.
    *
-   * @return a REPEATING_SLACK nsITimer that runs every this._syncInterval.
+   * @return a REPEATING_SLACK nsITimer that runs every this._interval.
    */
   _newTimer: function PEX__newTimer()
   {
     if (this._timer)
       this._timer.cancel();
     if (this._shuttingDown)
       return;
     let interval = this.status != STATUS.DIRTY ?
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/expiration/test_analyze_runs.js
@@ -0,0 +1,170 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING = "autocomplete-will-enter-text";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+/**
+ * Ensures that we have no data in the tables created by ANALYZE.
+ */
+function clearAnalyzeData()
+{
+  let db = DBConn();
+  if (!db.tableExists("sqlite_stat1")) {
+    return;
+  }
+  db.executeSimpleSQL("DELETE FROM sqlite_stat1");
+}
+
+/**
+ * Checks that we ran ANALYZE on the specified table.
+ *
+ * @param aTableName
+ *        The table to check if ANALYZE was ran.
+ * @param aRan
+ *        True if it was expected to run, false otherwise
+ */
+function do_check_analyze_ran(aTableName, aRan)
+{
+  let db = DBConn();
+  do_check_true(db.tableExists("sqlite_stat1"));
+  let stmt = db.createStatement("SELECT idx FROM sqlite_stat1 WHERE tbl = :table");
+  stmt.params.table = aTableName;
+  try {
+    if (aRan) {
+      do_check_true(stmt.executeStep());
+      do_check_neq(stmt.row.idx, null);
+    }
+    else {
+      do_check_false(stmt.executeStep());
+    }
+  }
+  finally {
+    stmt.finalize();
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+function test_timed()
+{
+  clearAnalyzeData();
+
+  let observer = function(aSubject, aTopic, aData) {
+    Services.obs.removeObserver(observer,
+                                PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+    setInterval(3600);
+    do_check_analyze_ran("moz_places", false);
+    do_check_analyze_ran("moz_bookmarks", false);
+    do_check_analyze_ran("moz_historyvisits", false);
+    do_check_analyze_ran("moz_inputhistory", true);
+    run_next_test();
+  };
+
+  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED,
+                           false);
+  // Set a low interval and wait for the timed expiration to start.
+  setInterval(3);
+}
+
+function test_debug()
+{
+  clearAnalyzeData();
+
+  let observer = function(aSubject, aTopic, aData) {
+    Services.obs.removeObserver(observer,
+                                PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+    do_check_analyze_ran("moz_places", true);
+    do_check_analyze_ran("moz_bookmarks", true);
+    do_check_analyze_ran("moz_historyvisits", true);
+    do_check_analyze_ran("moz_inputhistory", true);
+    run_next_test();
+  };
+
+  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED,
+                           false);
+  force_expiration_step(1);
+}
+
+function test_clear_history()
+{
+  clearAnalyzeData();
+
+  let observer = function(aSubject, aTopic, aData) {
+    Services.obs.removeObserver(observer,
+                                PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+    do_check_analyze_ran("moz_places", true);
+    do_check_analyze_ran("moz_bookmarks", false);
+    do_check_analyze_ran("moz_historyvisits", true);
+    do_check_analyze_ran("moz_inputhistory", true);
+    run_next_test();
+  };
+
+  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED,
+                           false);
+  let listener = Cc["@mozilla.org/places/expiration;1"].
+                 getService(Ci.nsINavHistoryObserver);
+  listener.onClearHistory();
+}
+
+function test_shutdown()
+{
+  clearAnalyzeData();
+
+  let observer = function(aSubject, aTopic, aData) {
+    Services.obs.removeObserver(observer,
+                                PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+    do_check_analyze_ran("moz_places", false);
+    do_check_analyze_ran("moz_bookmarks", false);
+    do_check_analyze_ran("moz_historyvisits", false);
+    do_check_analyze_ran("moz_inputhistory", false);
+    run_next_test();
+  };
+
+  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED,
+                           false);
+  shutdownExpiration();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Harness
+
+let gTests = [
+  test_timed,
+  test_debug,
+  test_clear_history,
+  test_shutdown,
+];
+
+function run_test()
+{
+  const TEST_URI = NetUtil.newURI("http://mozilla.org/");
+  const TEST_TITLE = "This is a test";
+  let bs = PlacesUtils.bookmarks;
+  bs.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI,
+                    bs.DEFAULT_INDEX, TEST_TITLE);
+  let hs = PlacesUtils.history;
+  hs.addVisit(TEST_URI, Date.now() * 1000, null, hs.TRANSITION_TYPED, false, 0);
+
+  let thing = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput,
+                                           Ci.nsIAutoCompletePopup,
+                                           Ci.nsIAutoCompleteController]),
+    get popup() { return thing; },
+    get controller() { return thing; },
+    popupOpen: true,
+    selectedIndex: 0,
+    getValueAt: function() { return TEST_URI.spec; },
+    searchString: TEST_TITLE,
+  };
+  Services.obs.notifyObservers(thing, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING,
+                               null);
+
+  run_next_test();
+}