about:startup - add the code that will display extension events as marks on the graph. also adds some boilerplate to make serializing async code easy again and uses it to serialize different queries to the database
authorDaniel Brooks <db48x@db48x.net>
Mon, 13 Sep 2010 07:35:50 -0400
changeset 58839 e08982b4328f9341ba72fd7c148efff637ef1458
parent 58838 ea4f08e2bd70678fcfe0dc7734b3427cbe374481
child 58840 4a6800b88bbc440c40c26e9cc43e4dfd641cb14c
push idunknown
push userunknown
push dateunknown
milestone2.0b6pre
about:startup - add the code that will display extension events as marks on the graph. also adds some boilerplate to make serializing async code easy again and uses it to serialize different queries to the database
toolkit/components/startup/src/nsAppStartup.cpp
toolkit/content/aboutStartup.js
toolkit/locales/en-US/chrome/global/aboutStartup.properties
--- a/toolkit/components/startup/src/nsAppStartup.cpp
+++ b/toolkit/components/startup/src/nsAppStartup.cpp
@@ -539,17 +539,17 @@ nsresult RecordStartupDuration()
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = EnsureTable(db,
                    NS_LITERAL_CSTRING("duration"),
                    NS_LITERAL_CSTRING("timestamp INTEGER, launch INTEGER, startup INTEGER, appVersion TEXT, appBuild TEXT, platformVersion TEXT, platformBuild TEXT"));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = EnsureTable(db,
                    NS_LITERAL_CSTRING("events"),
-                   NS_LITERAL_CSTRING("timestamp INTEGER, extensionID TEXT, extensionVersion TEXT"));
+                   NS_LITERAL_CSTRING("timestamp INTEGER, extensionID TEXT, extensionVersion TEXT, action TEXT"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<mozIStorageStatement> statement;
   rv = db->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO duration VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"),
                            getter_AddRefs(statement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIXULRuntime> runtime = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
--- a/toolkit/content/aboutStartup.js
+++ b/toolkit/content/aboutStartup.js
@@ -1,45 +1,68 @@
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 
+let stringsvc = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                          .getService(Components.interfaces.nsIStringBundleService);
+let strings = stringsvc.createBundle("chrome://global/locale/aboutStartup.properties");
+let branding = stringsvc.createBundle("chrome://branding/locale/brand.properties");
+
+function displayTimestamp(id, µs) document.getElementById(id).textContent = formatstamp(µs);
+function displayDuration(id, µs) document.getElementById(id).nextSibling.textContent = formatms(msFromµs(µs));
+
+function formatStr(str, args) strings.formatStringFromName("about.startup."+ str, args, args.length);
+function appVersion(version, build) formatStr("appVersion",
+//                                              [branding.getStringFromName("brandShortName"),
+                                              ["Firefox",
+                                               version, build]);
+function formatExtension(str, id, version) formatStr(str, [id, version]);
+
+function msFromµs(µs) µs / 1000;
+function formatstamp(µs) new Date(msFromµs(µs));
+function formatµs(µs) µs + " µs";
+function formatms(ms) formatStr("milliseconds", [ms]);
+
+function point(stamp, µs, v, b) [msFromµs(stamp), msFromµs(µs), { appVersion: v, appBuild: b }];
+function range(a, b) ({ from: msFromµs(a), to: msFromµs(b || a) });
+function mark(x, y) { var r = {}; x && (r.xaxis = x); y && (r.yaxis = y); return r };
+function major(r) { r.color = "#444"; return r; }
+function minor(r) { r.color = "#AAA"; return r; }
+function label(r, l) { r.label = l; return r; }
+function majorMark(x, l) label(major(mark(range(x))), l);
+function minorMark(x, l) label(minor(mark(range(x))), l);
+function extensionMark(x, l) label(mark(range(x)), l);
+
+///// First, display the timings from the current startup
 let launched, startup, restored;
-
 let runtime = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime);
 
 try {
   displayTimestamp("launched", launched = runtime.launchTimestamp);
 } catch(x) { }
 
 displayTimestamp("started", startup = runtime.startupTimestamp);
 if (launched)
   displayDuration("started", startup - launched);
 
 let ss = Cc["@mozilla.org/browser/sessionstartup;1"].getService(Ci.nsISessionStartup);
 displayTimestamp("restored", restored = ss.restoredTimestamp);
 displayDuration("restored", restored - startup);
 
-function displayTimestamp(id, µs) document.getElementById(id).textContent = formatstamp(µs);
-function displayDuration(id, µs) document.getElementById(id).nextSibling.textContent = formatms(msFromµs(µs));
-
-function msFromµs(µs) µs / 1000;
-function formatstamp(µs) new Date(msFromµs(µs)) +" ("+ formatms(msFromµs(µs)) +")";
-function formatµs(µs) µs + " µs";
-function formatms(ms) ms + " ms";
+///// Next, load the database
+var file = Components.classes["@mozilla.org/file/directory_service;1"]
+                     .getService(Components.interfaces.nsIProperties)
+                     .get("ProfD", Components.interfaces.nsIFile);
+file.append("startup.sqlite");
 
-function point(stamp, µs, v, b) [msFromµs(stamp), msFromµs(µs), { appVersion: v, appBuild: b }];
-function range(a, b) ({ from: msFromµs(a), to: msFromµs(b || a) });
-function mark(x, y) { var r = {}; x && (r.xaxis = x); y && (r.yaxis = y); return r };
-function major(r) { r.color = "#444"; return r; }
-function minor(r) { r.color = "#AAA"; return r; }
-function label(r, l) { r.label = l; return r; }
-function majorMark(x, l) label(major(mark(range(x))), l);
-function minorMark(x, l) label(minor(mark(range(x))), l);
-function extensionMark(x, l) label(mark(range(x)), l);
+var svc = Components.classes["@mozilla.org/storage/service;1"]
+                    .getService(Components.interfaces.mozIStorageService);
+var db = svc.openDatabase(file);
 
+///// set up the graph options
 var graph, overview;
 var options = { legend: { show: true, position: "ne", margin: 10, labelBoxBorderColor: "transparent" },
                 xaxis: { mode: "time" },
                 yaxis: { min: 0, tickFormatter: formatms },
                 selection: { mode: "xy", color: "#00A" },
                 grid: { show: true, borderWidth: 0, markings: [], aboveData: true, tickColor: "white" },
                 series: { lines: { show: true, fill: true },
                           points: { show: true, fill: true },
@@ -59,88 +82,16 @@ var overviewOpts = $.extend(true, {}, op
 
 var series = [{ label: "Launch Time",
                 data: []
               },
               { label: "Startup Time",
                 data: []
               }
              ];
-var file = Components.classes["@mozilla.org/file/directory_service;1"]
-                     .getService(Components.interfaces.nsIProperties)
-                     .get("ProfD", Components.interfaces.nsIFile);
-file.append("startup.sqlite");
-
-var svc = Components.classes["@mozilla.org/storage/service;1"]
-                    .getService(Components.interfaces.mozIStorageService);
-var db = svc.openDatabase(file);
-var query = db.createStatement("SELECT timestamp, launch, startup, appVersion, appBuild, platformVersion, platformBuild FROM duration");
-var lastver, lastbuild;
-query.executeAsync({
-  handleResult: function(results)
-  {
-    let hasresults = false;
-    let table = document.getElementById("duration-table");
-    for (let row = results.getNextRow(); row; row = results.getNextRow())
-    {
-      hasresults = true;
-      let stamp = row.getResultByName("timestamp");
-      let version = row.getResultByName("appVersion");
-      let build = row.getResultByName("appBuild");
-      if (lastver != version)
-      {
-        options.grid.markings.push(majorMark(stamp, "Firefox "+ version +" ("+ build +")"));
-      }
-      else
-        if (lastbuild != build)
-          options.grid.markings.push(minorMark(stamp, "Firefox "+ version +" ("+ build +")"));
-
-      lastver = version;
-      lastbuild = build;
-      let l = row.getResultByName("launch"),
-          s = row.getResultByName("startup");
-      series[1].data.push(point(stamp, l, version, build));
-      series[0].data.push(point(stamp, l + s, version, build));
-      table.appendChild(tr(td(formatstamp(stamp)),
-                           td(formatms(msFromµs(l))),
-                           td(formatms(msFromµs(s))),
-                           td(formatms(msFromµs((l + s)))),
-                           td(version),
-                           td(build),
-                           td(row.getResultByName("platformVersion")),
-                           td(row.getResultByName("platformBuild"))));
-    }
-    if (hasresults)
-      $("#duration-table > .empty").hide();
-  },
-  handleError: function(error)
-  {
-    $("#duration-table").appendChild(tr(td("Error: "+ error.message +" ("+ error.result +")")));
-  },
-  handleCompletion: function()
-  {
-    var table = $("table");
-    var height = $(window).height() - (table.offset().top + table.outerHeight(true)) - 110;
-    $("#graph").height(Math.max(350, height));
-
-    options.xaxis.min = Date.now() - 604800000; // 7 days in milliseconds
-    var max = 0;
-    for each (let [stamp, d] in series[0].data)
-      if (stamp >= options.xaxis.min && d > max)
-        max = d;
-    options.yaxis.max = max;
-
-    graph = $.plot($("#graph"), series, options);
-
-    var offset = graph.getPlotOffset().left;
-    $("#overview").width($("#overview").width() - offset);
-    $("#overview").css("margin-left", offset);
-    overview = $.plot($("#overview"), series, overviewOpts);
-  },
-});
 
 $("#graph").bind("plotselected", function (event, ranges)
 {
   // do the zooming
   graph = $.plot($("#graph"),
                  series,
                  $.extend(true, {}, options,
                           { xaxis: { min: ranges.xaxis.from,
@@ -154,16 +105,146 @@ query.executeAsync({
   // don't fire event on the overview to prevent eternal loop
   overview.setSelection(ranges, true);
 });
 
 $("#overview").bind("plotselected", function (event, ranges) {
   graph.setSelection(ranges);
 });
 
+///// read everything from the database and graph it
+let work = (function()
+{
+  populateMeasurements();
+  yield;
+  populateEvents();
+  yield;
+})();
+
+function go()
+{
+  try {
+    work.next();
+  } catch (x) {
+    // harmless
+  }
+}
+
+go();
+
+function populateMeasurements()
+{
+  var query = db.createStatement("SELECT timestamp, launch, startup, appVersion, appBuild, platformVersion, platformBuild FROM duration");
+  var lastver, lastbuild;
+  query.executeAsync({
+    handleResult: function(results)
+    {
+      let hasresults = false;
+      let table = document.getElementById("duration-table");
+      for (let row = results.getNextRow(); row; row = results.getNextRow())
+      {
+        hasresults = true;
+        let stamp = row.getResultByName("timestamp");
+        let version = row.getResultByName("appVersion");
+        let build = row.getResultByName("appBuild");
+        if (lastver != version)
+        {
+          options.grid.markings.push(majorMark(stamp, appVersion(version, build)));
+        }
+        else
+          if (lastbuild != build)
+            options.grid.markings.push(minorMark(stamp, appVersion(version, build)));
+
+        lastver = version;
+        lastbuild = build;
+        let l = row.getResultByName("launch"),
+            s = row.getResultByName("startup");
+        series[1].data.push(point(stamp, l, version, build));
+        series[0].data.push(point(stamp, l + s, version, build));
+        table.appendChild(tr(td(formatstamp(stamp)),
+                             td(formatms(msFromµs(l))),
+                             td(formatms(msFromµs(s))),
+                             td(formatms(msFromµs((l + s)))),
+                             td(version),
+                             td(build),
+                             td(row.getResultByName("platformVersion")),
+                             td(row.getResultByName("platformBuild"))));
+      }
+      if (hasresults)
+        $("#duration-table > .empty").hide();
+    },
+    handleError: function(error)
+    {
+      $("#duration-table").appendChild(tr(td("Error: "+ error.message +" ("+ error.result +")")));
+    },
+    handleCompletion: function()
+    {
+      var table = $("table");
+      var height = $(window).height() - (table.offset().top + table.outerHeight(true)) - 110;
+      $("#graph").height(Math.max(350, height));
+
+      options.xaxis.min = Date.now() - 604800000; // 7 days in milliseconds
+      var max = 0;
+      for each (let [stamp, d] in series[0].data)
+        if (stamp >= options.xaxis.min && d > max)
+          max = d;
+      options.yaxis.max = max;
+
+      graph = $.plot($("#graph"), series, options);
+
+      var offset = graph.getPlotOffset().left;
+      $("#overview").width($("#overview").width() - offset);
+      $("#overview").css("margin-left", offset);
+      overview = $.plot($("#overview"), series, overviewOpts);
+      go();
+    },
+  });
+}
+
+function populateEvents()
+{
+  var query = db.createStatement("SELECT timestamp, launch, startup, appVersion, appBuild, platformVersion, platformBuild FROM duration");
+  let lastver, lastbuild;
+  let hasresults;
+
+  query.executeAsync({
+    handleResult: function(results)
+    {
+      let table = document.getElementById("events-table");
+      for (let row = results.getNextRow(); row; row = results.getNextRow())
+      {
+        hasresults = true;
+        let stamp = row.getResultByName("timestamp"),
+            id = row.getResultByName("extensionID"),
+            name = id,
+            version = row.getResultByName("extensionVersion"),
+            action = row.getResultByName("action");
+
+        options.grid.markings.push(extensionMark(stamp, formatExtension(action, name, version)));
+
+        table.appendChild(tr(td(formatstamp(stamp)),
+                             td(id),
+                             td(name),
+                             td(version),
+                             td(action)));
+      }
+      if (hasresults)
+        $("#events-table > .empty").hide();
+    },
+    handleError: function(error)
+    {
+      $("#events-table").appendChild(tr(td("Error: "+ error.message +" ("+ error.result +")")));
+    },
+    handleCompletion: function()
+    {
+      graph = $.plot($("#graph"), series, options);
+      go();
+    },
+  });
+}
 
 function td(str)
 {
   let cell = document.createElement("td");
   cell.innerHTML = str;
   return cell;
 }
 
--- a/toolkit/locales/en-US/chrome/global/aboutStartup.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutStartup.properties
@@ -1,15 +1,18 @@
 # LOCALIZATION NOTE (about.startup.appVersion): %1$S will be
 # &brandShortName;, %2$S will be the version number, and %$S3 will be
 # the Gecko build id. ex: "Firefox 4.0b6pre (20100909051952)"
 about.startup.appVersion=%1$S %2$S (%3$S)
 # LOCALIZATION NOTE (about.startup.extensionInstalled): %1$S will be
 # the name of an extension, and %2$S will be its version number
 about.startup.extensionInstalled=%1$S %1$2 installed
+# LOCALIZATION NOTE (about.startup.extensionUninstalled): %1$S will be
+# the name of an extension, and %2$S will be its version number
+about.startup.extensionUninstalled=%1$S %1$2 uninstalled
 # LOCALIZATION NOTE (about.startup.extensionUpgraded): %1$S will be
 # the name of an extension, and %2$S will be its version number
 about.startup.extensionUpgraded=%1$S upgraded to %1$2
 # LOCALIZATION NOTE (about.startup.extensionEnabled): %1$S will be the
 # name of an extension, and %2$S will be its version number
 about.startup.extensionEnabled=%1$S %1$2 enabled
 # LOCALIZATION NOTE (about.startup.extensionDisabled): %1$S will be
 # the name of an extension, and %2$S will be its version number