services/healthreport/providers.jsm

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /**
michael@0 6 * This file contains metrics data providers for the Firefox Health
michael@0 7 * Report. Ideally each provider in this file exists in separate modules
michael@0 8 * and lives close to the code it is querying. However, because of the
michael@0 9 * overhead of JS compartments (which are created for each module), we
michael@0 10 * currently have all the code in one file. When the overhead of
michael@0 11 * compartments reaches a reasonable level, this file should be split
michael@0 12 * up.
michael@0 13 */
michael@0 14
michael@0 15 "use strict";
michael@0 16
michael@0 17 #ifndef MERGED_COMPARTMENT
michael@0 18
michael@0 19 this.EXPORTED_SYMBOLS = [
michael@0 20 "AddonsProvider",
michael@0 21 "AppInfoProvider",
michael@0 22 #ifdef MOZ_CRASHREPORTER
michael@0 23 "CrashesProvider",
michael@0 24 #endif
michael@0 25 "HealthReportProvider",
michael@0 26 "PlacesProvider",
michael@0 27 "SearchesProvider",
michael@0 28 "SessionsProvider",
michael@0 29 "SysInfoProvider",
michael@0 30 ];
michael@0 31
michael@0 32 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
michael@0 33
michael@0 34 Cu.import("resource://gre/modules/Metrics.jsm");
michael@0 35
michael@0 36 #endif
michael@0 37
michael@0 38 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 39 Cu.import("resource://gre/modules/osfile.jsm");
michael@0 40 Cu.import("resource://gre/modules/Preferences.jsm");
michael@0 41 Cu.import("resource://gre/modules/Services.jsm");
michael@0 42 Cu.import("resource://gre/modules/Task.jsm");
michael@0 43 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 44 Cu.import("resource://services-common/utils.js");
michael@0 45
michael@0 46 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
michael@0 47 "resource://gre/modules/AddonManager.jsm");
michael@0 48 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
michael@0 49 "resource://gre/modules/UpdateChannel.jsm");
michael@0 50 XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
michael@0 51 "resource://gre/modules/PlacesDBUtils.jsm");
michael@0 52
michael@0 53
michael@0 54 const LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_LAST_NUMERIC};
michael@0 55 const LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_LAST_TEXT};
michael@0 56 const DAILY_DISCRETE_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC};
michael@0 57 const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC};
michael@0 58 const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
michael@0 59 const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER};
michael@0 60
michael@0 61 const TELEMETRY_PREF = "toolkit.telemetry.enabled";
michael@0 62
michael@0 63 function isTelemetryEnabled(prefs) {
michael@0 64 return prefs.get(TELEMETRY_PREF, false);
michael@0 65 }
michael@0 66
michael@0 67 /**
michael@0 68 * Represents basic application state.
michael@0 69 *
michael@0 70 * This is roughly a union of nsIXULAppInfo, nsIXULRuntime, with a few extra
michael@0 71 * pieces thrown in.
michael@0 72 */
michael@0 73 function AppInfoMeasurement() {
michael@0 74 Metrics.Measurement.call(this);
michael@0 75 }
michael@0 76
michael@0 77 AppInfoMeasurement.prototype = Object.freeze({
michael@0 78 __proto__: Metrics.Measurement.prototype,
michael@0 79
michael@0 80 name: "appinfo",
michael@0 81 version: 2,
michael@0 82
michael@0 83 fields: {
michael@0 84 vendor: LAST_TEXT_FIELD,
michael@0 85 name: LAST_TEXT_FIELD,
michael@0 86 id: LAST_TEXT_FIELD,
michael@0 87 version: LAST_TEXT_FIELD,
michael@0 88 appBuildID: LAST_TEXT_FIELD,
michael@0 89 platformVersion: LAST_TEXT_FIELD,
michael@0 90 platformBuildID: LAST_TEXT_FIELD,
michael@0 91 os: LAST_TEXT_FIELD,
michael@0 92 xpcomabi: LAST_TEXT_FIELD,
michael@0 93 updateChannel: LAST_TEXT_FIELD,
michael@0 94 distributionID: LAST_TEXT_FIELD,
michael@0 95 distributionVersion: LAST_TEXT_FIELD,
michael@0 96 hotfixVersion: LAST_TEXT_FIELD,
michael@0 97 locale: LAST_TEXT_FIELD,
michael@0 98 isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
michael@0 99 isTelemetryEnabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
michael@0 100 isBlocklistEnabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
michael@0 101 },
michael@0 102 });
michael@0 103
michael@0 104 /**
michael@0 105 * Legacy version of app info before Telemetry was added.
michael@0 106 *
michael@0 107 * The "last" fields have all been removed. We only report the longitudinal
michael@0 108 * field.
michael@0 109 */
michael@0 110 function AppInfoMeasurement1() {
michael@0 111 Metrics.Measurement.call(this);
michael@0 112 }
michael@0 113
michael@0 114 AppInfoMeasurement1.prototype = Object.freeze({
michael@0 115 __proto__: Metrics.Measurement.prototype,
michael@0 116
michael@0 117 name: "appinfo",
michael@0 118 version: 1,
michael@0 119
michael@0 120 fields: {
michael@0 121 isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
michael@0 122 },
michael@0 123 });
michael@0 124
michael@0 125
michael@0 126 function AppVersionMeasurement1() {
michael@0 127 Metrics.Measurement.call(this);
michael@0 128 }
michael@0 129
michael@0 130 AppVersionMeasurement1.prototype = Object.freeze({
michael@0 131 __proto__: Metrics.Measurement.prototype,
michael@0 132
michael@0 133 name: "versions",
michael@0 134 version: 1,
michael@0 135
michael@0 136 fields: {
michael@0 137 version: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
michael@0 138 },
michael@0 139 });
michael@0 140
michael@0 141 // Version 2 added the build ID.
michael@0 142 function AppVersionMeasurement2() {
michael@0 143 Metrics.Measurement.call(this);
michael@0 144 }
michael@0 145
michael@0 146 AppVersionMeasurement2.prototype = Object.freeze({
michael@0 147 __proto__: Metrics.Measurement.prototype,
michael@0 148
michael@0 149 name: "versions",
michael@0 150 version: 2,
michael@0 151
michael@0 152 fields: {
michael@0 153 appVersion: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
michael@0 154 platformVersion: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
michael@0 155 appBuildID: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
michael@0 156 platformBuildID: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
michael@0 157 },
michael@0 158 });
michael@0 159
michael@0 160 /**
michael@0 161 * Holds data on the application update functionality.
michael@0 162 */
michael@0 163 function AppUpdateMeasurement1() {
michael@0 164 Metrics.Measurement.call(this);
michael@0 165 }
michael@0 166
michael@0 167 AppUpdateMeasurement1.prototype = Object.freeze({
michael@0 168 __proto__: Metrics.Measurement.prototype,
michael@0 169
michael@0 170 name: "update",
michael@0 171 version: 1,
michael@0 172
michael@0 173 fields: {
michael@0 174 enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
michael@0 175 autoDownload: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
michael@0 176 },
michael@0 177 });
michael@0 178
michael@0 179 this.AppInfoProvider = function AppInfoProvider() {
michael@0 180 Metrics.Provider.call(this);
michael@0 181
michael@0 182 this._prefs = new Preferences({defaultBranch: null});
michael@0 183 }
michael@0 184 AppInfoProvider.prototype = Object.freeze({
michael@0 185 __proto__: Metrics.Provider.prototype,
michael@0 186
michael@0 187 name: "org.mozilla.appInfo",
michael@0 188
michael@0 189 measurementTypes: [
michael@0 190 AppInfoMeasurement,
michael@0 191 AppInfoMeasurement1,
michael@0 192 AppUpdateMeasurement1,
michael@0 193 AppVersionMeasurement1,
michael@0 194 AppVersionMeasurement2,
michael@0 195 ],
michael@0 196
michael@0 197 pullOnly: true,
michael@0 198
michael@0 199 appInfoFields: {
michael@0 200 // From nsIXULAppInfo.
michael@0 201 vendor: "vendor",
michael@0 202 name: "name",
michael@0 203 id: "ID",
michael@0 204 version: "version",
michael@0 205 appBuildID: "appBuildID",
michael@0 206 platformVersion: "platformVersion",
michael@0 207 platformBuildID: "platformBuildID",
michael@0 208
michael@0 209 // From nsIXULRuntime.
michael@0 210 os: "OS",
michael@0 211 xpcomabi: "XPCOMABI",
michael@0 212 },
michael@0 213
michael@0 214 postInit: function () {
michael@0 215 return Task.spawn(this._postInit.bind(this));
michael@0 216 },
michael@0 217
michael@0 218 _postInit: function () {
michael@0 219 let recordEmptyAppInfo = function () {
michael@0 220 this._setCurrentAppVersion("");
michael@0 221 this._setCurrentPlatformVersion("");
michael@0 222 this._setCurrentAppBuildID("");
michael@0 223 return this._setCurrentPlatformBuildID("");
michael@0 224 }.bind(this);
michael@0 225
michael@0 226 // Services.appInfo should always be defined for any reasonably behaving
michael@0 227 // Gecko app. If it isn't, we insert a empty string sentinel value.
michael@0 228 let ai;
michael@0 229 try {
michael@0 230 ai = Services.appinfo;
michael@0 231 } catch (ex) {
michael@0 232 this._log.error("Could not obtain Services.appinfo: " +
michael@0 233 CommonUtils.exceptionStr(ex));
michael@0 234 yield recordEmptyAppInfo();
michael@0 235 return;
michael@0 236 }
michael@0 237
michael@0 238 if (!ai) {
michael@0 239 this._log.error("Services.appinfo is unavailable.");
michael@0 240 yield recordEmptyAppInfo();
michael@0 241 return;
michael@0 242 }
michael@0 243
michael@0 244 let currentAppVersion = ai.version;
michael@0 245 let currentPlatformVersion = ai.platformVersion;
michael@0 246 let currentAppBuildID = ai.appBuildID;
michael@0 247 let currentPlatformBuildID = ai.platformBuildID;
michael@0 248
michael@0 249 // State's name doesn't contain "app" for historical compatibility.
michael@0 250 let lastAppVersion = yield this.getState("lastVersion");
michael@0 251 let lastPlatformVersion = yield this.getState("lastPlatformVersion");
michael@0 252 let lastAppBuildID = yield this.getState("lastAppBuildID");
michael@0 253 let lastPlatformBuildID = yield this.getState("lastPlatformBuildID");
michael@0 254
michael@0 255 if (currentAppVersion != lastAppVersion) {
michael@0 256 yield this._setCurrentAppVersion(currentAppVersion);
michael@0 257 }
michael@0 258
michael@0 259 if (currentPlatformVersion != lastPlatformVersion) {
michael@0 260 yield this._setCurrentPlatformVersion(currentPlatformVersion);
michael@0 261 }
michael@0 262
michael@0 263 if (currentAppBuildID != lastAppBuildID) {
michael@0 264 yield this._setCurrentAppBuildID(currentAppBuildID);
michael@0 265 }
michael@0 266
michael@0 267 if (currentPlatformBuildID != lastPlatformBuildID) {
michael@0 268 yield this._setCurrentPlatformBuildID(currentPlatformBuildID);
michael@0 269 }
michael@0 270 },
michael@0 271
michael@0 272 _setCurrentAppVersion: function (version) {
michael@0 273 this._log.info("Recording new application version: " + version);
michael@0 274 let m = this.getMeasurement("versions", 2);
michael@0 275 m.addDailyDiscreteText("appVersion", version);
michael@0 276
michael@0 277 // "app" not encoded in key for historical compatibility.
michael@0 278 return this.setState("lastVersion", version);
michael@0 279 },
michael@0 280
michael@0 281 _setCurrentPlatformVersion: function (version) {
michael@0 282 this._log.info("Recording new platform version: " + version);
michael@0 283 let m = this.getMeasurement("versions", 2);
michael@0 284 m.addDailyDiscreteText("platformVersion", version);
michael@0 285 return this.setState("lastPlatformVersion", version);
michael@0 286 },
michael@0 287
michael@0 288 _setCurrentAppBuildID: function (build) {
michael@0 289 this._log.info("Recording new application build ID: " + build);
michael@0 290 let m = this.getMeasurement("versions", 2);
michael@0 291 m.addDailyDiscreteText("appBuildID", build);
michael@0 292 return this.setState("lastAppBuildID", build);
michael@0 293 },
michael@0 294
michael@0 295 _setCurrentPlatformBuildID: function (build) {
michael@0 296 this._log.info("Recording new platform build ID: " + build);
michael@0 297 let m = this.getMeasurement("versions", 2);
michael@0 298 m.addDailyDiscreteText("platformBuildID", build);
michael@0 299 return this.setState("lastPlatformBuildID", build);
michael@0 300 },
michael@0 301
michael@0 302
michael@0 303 collectConstantData: function () {
michael@0 304 return this.storage.enqueueTransaction(this._populateConstants.bind(this));
michael@0 305 },
michael@0 306
michael@0 307 _populateConstants: function () {
michael@0 308 let m = this.getMeasurement(AppInfoMeasurement.prototype.name,
michael@0 309 AppInfoMeasurement.prototype.version);
michael@0 310
michael@0 311 let ai;
michael@0 312 try {
michael@0 313 ai = Services.appinfo;
michael@0 314 } catch (ex) {
michael@0 315 this._log.warn("Could not obtain Services.appinfo: " +
michael@0 316 CommonUtils.exceptionStr(ex));
michael@0 317 throw ex;
michael@0 318 }
michael@0 319
michael@0 320 if (!ai) {
michael@0 321 this._log.warn("Services.appinfo is unavailable.");
michael@0 322 throw ex;
michael@0 323 }
michael@0 324
michael@0 325 for (let [k, v] in Iterator(this.appInfoFields)) {
michael@0 326 try {
michael@0 327 yield m.setLastText(k, ai[v]);
michael@0 328 } catch (ex) {
michael@0 329 this._log.warn("Error obtaining Services.appinfo." + v);
michael@0 330 }
michael@0 331 }
michael@0 332
michael@0 333 try {
michael@0 334 yield m.setLastText("updateChannel", UpdateChannel.get());
michael@0 335 } catch (ex) {
michael@0 336 this._log.warn("Could not obtain update channel: " +
michael@0 337 CommonUtils.exceptionStr(ex));
michael@0 338 }
michael@0 339
michael@0 340 yield m.setLastText("distributionID", this._prefs.get("distribution.id", ""));
michael@0 341 yield m.setLastText("distributionVersion", this._prefs.get("distribution.version", ""));
michael@0 342 yield m.setLastText("hotfixVersion", this._prefs.get("extensions.hotfix.lastVersion", ""));
michael@0 343
michael@0 344 try {
michael@0 345 let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
michael@0 346 .getService(Ci.nsIXULChromeRegistry)
michael@0 347 .getSelectedLocale("global");
michael@0 348 yield m.setLastText("locale", locale);
michael@0 349 } catch (ex) {
michael@0 350 this._log.warn("Could not obtain application locale: " +
michael@0 351 CommonUtils.exceptionStr(ex));
michael@0 352 }
michael@0 353
michael@0 354 // FUTURE this should be retrieved periodically or at upload time.
michael@0 355 yield this._recordIsTelemetryEnabled(m);
michael@0 356 yield this._recordIsBlocklistEnabled(m);
michael@0 357 yield this._recordDefaultBrowser(m);
michael@0 358 },
michael@0 359
michael@0 360 _recordIsTelemetryEnabled: function (m) {
michael@0 361 let enabled = isTelemetryEnabled(this._prefs);
michael@0 362 this._log.debug("Recording telemetry enabled (" + TELEMETRY_PREF + "): " + enabled);
michael@0 363 yield m.setDailyLastNumeric("isTelemetryEnabled", enabled ? 1 : 0);
michael@0 364 },
michael@0 365
michael@0 366 _recordIsBlocklistEnabled: function (m) {
michael@0 367 let enabled = this._prefs.get("extensions.blocklist.enabled", false);
michael@0 368 this._log.debug("Recording blocklist enabled: " + enabled);
michael@0 369 yield m.setDailyLastNumeric("isBlocklistEnabled", enabled ? 1 : 0);
michael@0 370 },
michael@0 371
michael@0 372 _recordDefaultBrowser: function (m) {
michael@0 373 let shellService;
michael@0 374 try {
michael@0 375 shellService = Cc["@mozilla.org/browser/shell-service;1"]
michael@0 376 .getService(Ci.nsIShellService);
michael@0 377 } catch (ex) {
michael@0 378 this._log.warn("Could not obtain shell service: " +
michael@0 379 CommonUtils.exceptionStr(ex));
michael@0 380 }
michael@0 381
michael@0 382 let isDefault = -1;
michael@0 383
michael@0 384 if (shellService) {
michael@0 385 try {
michael@0 386 // This uses the same set of flags used by the pref pane.
michael@0 387 isDefault = shellService.isDefaultBrowser(false, true) ? 1 : 0;
michael@0 388 } catch (ex) {
michael@0 389 this._log.warn("Could not determine if default browser: " +
michael@0 390 CommonUtils.exceptionStr(ex));
michael@0 391 }
michael@0 392 }
michael@0 393
michael@0 394 return m.setDailyLastNumeric("isDefaultBrowser", isDefault);
michael@0 395 },
michael@0 396
michael@0 397 collectDailyData: function () {
michael@0 398 return this.storage.enqueueTransaction(function getDaily() {
michael@0 399 let m = this.getMeasurement(AppUpdateMeasurement1.prototype.name,
michael@0 400 AppUpdateMeasurement1.prototype.version);
michael@0 401
michael@0 402 let enabled = this._prefs.get("app.update.enabled", false);
michael@0 403 yield m.setDailyLastNumeric("enabled", enabled ? 1 : 0);
michael@0 404
michael@0 405 let auto = this._prefs.get("app.update.auto", false);
michael@0 406 yield m.setDailyLastNumeric("autoDownload", auto ? 1 : 0);
michael@0 407 }.bind(this));
michael@0 408 },
michael@0 409 });
michael@0 410
michael@0 411
michael@0 412 function SysInfoMeasurement() {
michael@0 413 Metrics.Measurement.call(this);
michael@0 414 }
michael@0 415
michael@0 416 SysInfoMeasurement.prototype = Object.freeze({
michael@0 417 __proto__: Metrics.Measurement.prototype,
michael@0 418
michael@0 419 name: "sysinfo",
michael@0 420 version: 2,
michael@0 421
michael@0 422 fields: {
michael@0 423 cpuCount: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
michael@0 424 memoryMB: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
michael@0 425 manufacturer: LAST_TEXT_FIELD,
michael@0 426 device: LAST_TEXT_FIELD,
michael@0 427 hardware: LAST_TEXT_FIELD,
michael@0 428 name: LAST_TEXT_FIELD,
michael@0 429 version: LAST_TEXT_FIELD,
michael@0 430 architecture: LAST_TEXT_FIELD,
michael@0 431 isWow64: LAST_NUMERIC_FIELD,
michael@0 432 },
michael@0 433 });
michael@0 434
michael@0 435
michael@0 436 this.SysInfoProvider = function SysInfoProvider() {
michael@0 437 Metrics.Provider.call(this);
michael@0 438 };
michael@0 439
michael@0 440 SysInfoProvider.prototype = Object.freeze({
michael@0 441 __proto__: Metrics.Provider.prototype,
michael@0 442
michael@0 443 name: "org.mozilla.sysinfo",
michael@0 444
michael@0 445 measurementTypes: [SysInfoMeasurement],
michael@0 446
michael@0 447 pullOnly: true,
michael@0 448
michael@0 449 sysInfoFields: {
michael@0 450 cpucount: "cpuCount",
michael@0 451 memsize: "memoryMB",
michael@0 452 manufacturer: "manufacturer",
michael@0 453 device: "device",
michael@0 454 hardware: "hardware",
michael@0 455 name: "name",
michael@0 456 version: "version",
michael@0 457 arch: "architecture",
michael@0 458 isWow64: "isWow64",
michael@0 459 },
michael@0 460
michael@0 461 collectConstantData: function () {
michael@0 462 return this.storage.enqueueTransaction(this._populateConstants.bind(this));
michael@0 463 },
michael@0 464
michael@0 465 _populateConstants: function () {
michael@0 466 let m = this.getMeasurement(SysInfoMeasurement.prototype.name,
michael@0 467 SysInfoMeasurement.prototype.version);
michael@0 468
michael@0 469 let si = Cc["@mozilla.org/system-info;1"]
michael@0 470 .getService(Ci.nsIPropertyBag2);
michael@0 471
michael@0 472 for (let [k, v] in Iterator(this.sysInfoFields)) {
michael@0 473 try {
michael@0 474 if (!si.hasKey(k)) {
michael@0 475 this._log.debug("Property not available: " + k);
michael@0 476 continue;
michael@0 477 }
michael@0 478
michael@0 479 let value = si.getProperty(k);
michael@0 480 let method = "setLastText";
michael@0 481
michael@0 482 if (["cpucount", "memsize"].indexOf(k) != -1) {
michael@0 483 let converted = parseInt(value, 10);
michael@0 484 if (Number.isNaN(converted)) {
michael@0 485 continue;
michael@0 486 }
michael@0 487
michael@0 488 value = converted;
michael@0 489 method = "setLastNumeric";
michael@0 490 }
michael@0 491
michael@0 492 switch (k) {
michael@0 493 case "memsize":
michael@0 494 // Round memory to mebibytes.
michael@0 495 value = Math.round(value / 1048576);
michael@0 496 break;
michael@0 497 case "isWow64":
michael@0 498 // Property is only present on Windows. hasKey() skipping from
michael@0 499 // above ensures undefined or null doesn't creep in here.
michael@0 500 value = value ? 1 : 0;
michael@0 501 method = "setLastNumeric";
michael@0 502 break;
michael@0 503 }
michael@0 504
michael@0 505 yield m[method](v, value);
michael@0 506 } catch (ex) {
michael@0 507 this._log.warn("Error obtaining system info field: " + k + " " +
michael@0 508 CommonUtils.exceptionStr(ex));
michael@0 509 }
michael@0 510 }
michael@0 511 },
michael@0 512 });
michael@0 513
michael@0 514
michael@0 515 /**
michael@0 516 * Holds information about the current/active session.
michael@0 517 *
michael@0 518 * The fields within the current session are moved to daily session fields when
michael@0 519 * the application is shut down.
michael@0 520 *
michael@0 521 * This measurement is backed by the SessionRecorder, not the database.
michael@0 522 */
michael@0 523 function CurrentSessionMeasurement() {
michael@0 524 Metrics.Measurement.call(this);
michael@0 525 }
michael@0 526
michael@0 527 CurrentSessionMeasurement.prototype = Object.freeze({
michael@0 528 __proto__: Metrics.Measurement.prototype,
michael@0 529
michael@0 530 name: "current",
michael@0 531 version: 3,
michael@0 532
michael@0 533 // Storage is in preferences.
michael@0 534 fields: {},
michael@0 535
michael@0 536 /**
michael@0 537 * All data is stored in prefs, so we have a custom implementation.
michael@0 538 */
michael@0 539 getValues: function () {
michael@0 540 let sessions = this.provider.healthReporter.sessionRecorder;
michael@0 541
michael@0 542 let fields = new Map();
michael@0 543 let now = new Date();
michael@0 544 fields.set("startDay", [now, Metrics.dateToDays(sessions.startDate)]);
michael@0 545 fields.set("activeTicks", [now, sessions.activeTicks]);
michael@0 546 fields.set("totalTime", [now, sessions.totalTime]);
michael@0 547 fields.set("main", [now, sessions.main]);
michael@0 548 fields.set("firstPaint", [now, sessions.firstPaint]);
michael@0 549 fields.set("sessionRestored", [now, sessions.sessionRestored]);
michael@0 550
michael@0 551 return CommonUtils.laterTickResolvingPromise({
michael@0 552 days: new Metrics.DailyValues(),
michael@0 553 singular: fields,
michael@0 554 });
michael@0 555 },
michael@0 556
michael@0 557 _serializeJSONSingular: function (data) {
michael@0 558 let result = {"_v": this.version};
michael@0 559
michael@0 560 for (let [field, value] of data) {
michael@0 561 result[field] = value[1];
michael@0 562 }
michael@0 563
michael@0 564 return result;
michael@0 565 },
michael@0 566 });
michael@0 567
michael@0 568 /**
michael@0 569 * Records a history of all application sessions.
michael@0 570 */
michael@0 571 function PreviousSessionsMeasurement() {
michael@0 572 Metrics.Measurement.call(this);
michael@0 573 }
michael@0 574
michael@0 575 PreviousSessionsMeasurement.prototype = Object.freeze({
michael@0 576 __proto__: Metrics.Measurement.prototype,
michael@0 577
michael@0 578 name: "previous",
michael@0 579 version: 3,
michael@0 580
michael@0 581 fields: {
michael@0 582 // Milliseconds of sessions that were properly shut down.
michael@0 583 cleanActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 584 cleanTotalTime: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 585
michael@0 586 // Milliseconds of sessions that were not properly shut down.
michael@0 587 abortedActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 588 abortedTotalTime: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 589
michael@0 590 // Startup times in milliseconds.
michael@0 591 main: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 592 firstPaint: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 593 sessionRestored: DAILY_DISCRETE_NUMERIC_FIELD,
michael@0 594 },
michael@0 595 });
michael@0 596
michael@0 597
michael@0 598 /**
michael@0 599 * Records information about the current browser session.
michael@0 600 *
michael@0 601 * A browser session is defined as an application/process lifetime. We
michael@0 602 * start a new session when the application starts (essentially when
michael@0 603 * this provider is instantiated) and end the session on shutdown.
michael@0 604 *
michael@0 605 * As the application runs, we record basic information about the
michael@0 606 * "activity" of the session. Activity is defined by the presence of
michael@0 607 * physical input into the browser (key press, mouse click, touch, etc).
michael@0 608 *
michael@0 609 * We differentiate between regular sessions and "aborted" sessions. An
michael@0 610 * aborted session is one that does not end expectedly. This is often the
michael@0 611 * result of a crash. We detect aborted sessions by storing the current
michael@0 612 * session separate from completed sessions. We normally move the
michael@0 613 * current session to completed sessions on application shutdown. If a
michael@0 614 * current session is present on application startup, that means that
michael@0 615 * the previous session was aborted.
michael@0 616 */
michael@0 617 this.SessionsProvider = function () {
michael@0 618 Metrics.Provider.call(this);
michael@0 619 };
michael@0 620
michael@0 621 SessionsProvider.prototype = Object.freeze({
michael@0 622 __proto__: Metrics.Provider.prototype,
michael@0 623
michael@0 624 name: "org.mozilla.appSessions",
michael@0 625
michael@0 626 measurementTypes: [CurrentSessionMeasurement, PreviousSessionsMeasurement],
michael@0 627
michael@0 628 pullOnly: true,
michael@0 629
michael@0 630 collectConstantData: function () {
michael@0 631 let previous = this.getMeasurement("previous", 3);
michael@0 632
michael@0 633 return this.storage.enqueueTransaction(this._recordAndPruneSessions.bind(this));
michael@0 634 },
michael@0 635
michael@0 636 _recordAndPruneSessions: function () {
michael@0 637 this._log.info("Moving previous sessions from session recorder to storage.");
michael@0 638 let recorder = this.healthReporter.sessionRecorder;
michael@0 639 let sessions = recorder.getPreviousSessions();
michael@0 640 this._log.debug("Found " + Object.keys(sessions).length + " previous sessions.");
michael@0 641
michael@0 642 let daily = this.getMeasurement("previous", 3);
michael@0 643
michael@0 644 // Please note the coupling here between the session recorder and our state.
michael@0 645 // If the pruned index or the current index of the session recorder is ever
michael@0 646 // deleted or reset to 0, our stored state of a later index would mean that
michael@0 647 // new sessions would never be captured by this provider until the session
michael@0 648 // recorder index catches up to our last session ID. This should not happen
michael@0 649 // under normal circumstances, so we don't worry too much about it. We
michael@0 650 // should, however, consider this as part of implementing bug 841561.
michael@0 651 let lastRecordedSession = yield this.getState("lastSession");
michael@0 652 if (lastRecordedSession === null) {
michael@0 653 lastRecordedSession = -1;
michael@0 654 }
michael@0 655 this._log.debug("The last recorded session was #" + lastRecordedSession);
michael@0 656
michael@0 657 for (let [index, session] in Iterator(sessions)) {
michael@0 658 if (index <= lastRecordedSession) {
michael@0 659 this._log.warn("Already recorded session " + index + ". Did the last " +
michael@0 660 "session crash or have an issue saving the prefs file?");
michael@0 661 continue;
michael@0 662 }
michael@0 663
michael@0 664 let type = session.clean ? "clean" : "aborted";
michael@0 665 let date = session.startDate;
michael@0 666 yield daily.addDailyDiscreteNumeric(type + "ActiveTicks", session.activeTicks, date);
michael@0 667 yield daily.addDailyDiscreteNumeric(type + "TotalTime", session.totalTime, date);
michael@0 668
michael@0 669 for (let field of ["main", "firstPaint", "sessionRestored"]) {
michael@0 670 yield daily.addDailyDiscreteNumeric(field, session[field], date);
michael@0 671 }
michael@0 672
michael@0 673 lastRecordedSession = index;
michael@0 674 }
michael@0 675
michael@0 676 yield this.setState("lastSession", "" + lastRecordedSession);
michael@0 677 recorder.pruneOldSessions(new Date());
michael@0 678 },
michael@0 679 });
michael@0 680
michael@0 681 /**
michael@0 682 * Stores the set of active addons in storage.
michael@0 683 *
michael@0 684 * We do things a little differently than most other measurements. Because
michael@0 685 * addons are difficult to shoehorn into distinct fields, we simply store a
michael@0 686 * JSON blob in storage in a text field.
michael@0 687 */
michael@0 688 function ActiveAddonsMeasurement() {
michael@0 689 Metrics.Measurement.call(this);
michael@0 690
michael@0 691 this._serializers = {};
michael@0 692 this._serializers[this.SERIALIZE_JSON] = {
michael@0 693 singular: this._serializeJSONSingular.bind(this),
michael@0 694 // We don't need a daily serializer because we have none of this data.
michael@0 695 };
michael@0 696 }
michael@0 697
michael@0 698 ActiveAddonsMeasurement.prototype = Object.freeze({
michael@0 699 __proto__: Metrics.Measurement.prototype,
michael@0 700
michael@0 701 name: "addons",
michael@0 702 version: 2,
michael@0 703
michael@0 704 fields: {
michael@0 705 addons: LAST_TEXT_FIELD,
michael@0 706 },
michael@0 707
michael@0 708 _serializeJSONSingular: function (data) {
michael@0 709 if (!data.has("addons")) {
michael@0 710 this._log.warn("Don't have addons info. Weird.");
michael@0 711 return null;
michael@0 712 }
michael@0 713
michael@0 714 // Exceptions are caught in the caller.
michael@0 715 let result = JSON.parse(data.get("addons")[1]);
michael@0 716 result._v = this.version;
michael@0 717 return result;
michael@0 718 },
michael@0 719 });
michael@0 720
michael@0 721 /**
michael@0 722 * Stores the set of active plugins in storage.
michael@0 723 *
michael@0 724 * This stores the data in a JSON blob in a text field similar to the
michael@0 725 * ActiveAddonsMeasurement.
michael@0 726 */
michael@0 727 function ActivePluginsMeasurement() {
michael@0 728 Metrics.Measurement.call(this);
michael@0 729
michael@0 730 this._serializers = {};
michael@0 731 this._serializers[this.SERIALIZE_JSON] = {
michael@0 732 singular: this._serializeJSONSingular.bind(this),
michael@0 733 // We don't need a daily serializer because we have none of this data.
michael@0 734 };
michael@0 735 }
michael@0 736
michael@0 737 ActivePluginsMeasurement.prototype = Object.freeze({
michael@0 738 __proto__: Metrics.Measurement.prototype,
michael@0 739
michael@0 740 name: "plugins",
michael@0 741 version: 1,
michael@0 742
michael@0 743 fields: {
michael@0 744 plugins: LAST_TEXT_FIELD,
michael@0 745 },
michael@0 746
michael@0 747 _serializeJSONSingular: function (data) {
michael@0 748 if (!data.has("plugins")) {
michael@0 749 this._log.warn("Don't have plugins info. Weird.");
michael@0 750 return null;
michael@0 751 }
michael@0 752
michael@0 753 // Exceptions are caught in the caller.
michael@0 754 let result = JSON.parse(data.get("plugins")[1]);
michael@0 755 result._v = this.version;
michael@0 756 return result;
michael@0 757 },
michael@0 758 });
michael@0 759
michael@0 760
michael@0 761 function AddonCountsMeasurement() {
michael@0 762 Metrics.Measurement.call(this);
michael@0 763 }
michael@0 764
michael@0 765 AddonCountsMeasurement.prototype = Object.freeze({
michael@0 766 __proto__: Metrics.Measurement.prototype,
michael@0 767
michael@0 768 name: "counts",
michael@0 769 version: 2,
michael@0 770
michael@0 771 fields: {
michael@0 772 theme: DAILY_LAST_NUMERIC_FIELD,
michael@0 773 lwtheme: DAILY_LAST_NUMERIC_FIELD,
michael@0 774 plugin: DAILY_LAST_NUMERIC_FIELD,
michael@0 775 extension: DAILY_LAST_NUMERIC_FIELD,
michael@0 776 service: DAILY_LAST_NUMERIC_FIELD,
michael@0 777 },
michael@0 778 });
michael@0 779
michael@0 780
michael@0 781 /**
michael@0 782 * Legacy version of addons counts before services was added.
michael@0 783 */
michael@0 784 function AddonCountsMeasurement1() {
michael@0 785 Metrics.Measurement.call(this);
michael@0 786 }
michael@0 787
michael@0 788 AddonCountsMeasurement1.prototype = Object.freeze({
michael@0 789 __proto__: Metrics.Measurement.prototype,
michael@0 790
michael@0 791 name: "counts",
michael@0 792 version: 1,
michael@0 793
michael@0 794 fields: {
michael@0 795 theme: DAILY_LAST_NUMERIC_FIELD,
michael@0 796 lwtheme: DAILY_LAST_NUMERIC_FIELD,
michael@0 797 plugin: DAILY_LAST_NUMERIC_FIELD,
michael@0 798 extension: DAILY_LAST_NUMERIC_FIELD,
michael@0 799 },
michael@0 800 });
michael@0 801
michael@0 802
michael@0 803 this.AddonsProvider = function () {
michael@0 804 Metrics.Provider.call(this);
michael@0 805
michael@0 806 this._prefs = new Preferences({defaultBranch: null});
michael@0 807 };
michael@0 808
michael@0 809 AddonsProvider.prototype = Object.freeze({
michael@0 810 __proto__: Metrics.Provider.prototype,
michael@0 811
michael@0 812 // Whenever these AddonListener callbacks are called, we repopulate
michael@0 813 // and store the set of addons. Note that these events will only fire
michael@0 814 // for restartless add-ons. For actions that require a restart, we
michael@0 815 // will catch the change after restart. The alternative is a lot of
michael@0 816 // state tracking here, which isn't desirable.
michael@0 817 ADDON_LISTENER_CALLBACKS: [
michael@0 818 "onEnabled",
michael@0 819 "onDisabled",
michael@0 820 "onInstalled",
michael@0 821 "onUninstalled",
michael@0 822 ],
michael@0 823
michael@0 824 // Add-on types for which full details are uploaded in the
michael@0 825 // ActiveAddonsMeasurement. All other types are ignored.
michael@0 826 FULL_DETAIL_TYPES: [
michael@0 827 "extension",
michael@0 828 "service",
michael@0 829 ],
michael@0 830
michael@0 831 name: "org.mozilla.addons",
michael@0 832
michael@0 833 measurementTypes: [
michael@0 834 ActiveAddonsMeasurement,
michael@0 835 ActivePluginsMeasurement,
michael@0 836 AddonCountsMeasurement1,
michael@0 837 AddonCountsMeasurement,
michael@0 838 ],
michael@0 839
michael@0 840 postInit: function () {
michael@0 841 let listener = {};
michael@0 842
michael@0 843 for (let method of this.ADDON_LISTENER_CALLBACKS) {
michael@0 844 listener[method] = this._collectAndStoreAddons.bind(this);
michael@0 845 }
michael@0 846
michael@0 847 this._listener = listener;
michael@0 848 AddonManager.addAddonListener(this._listener);
michael@0 849
michael@0 850 return CommonUtils.laterTickResolvingPromise();
michael@0 851 },
michael@0 852
michael@0 853 onShutdown: function () {
michael@0 854 AddonManager.removeAddonListener(this._listener);
michael@0 855 this._listener = null;
michael@0 856
michael@0 857 return CommonUtils.laterTickResolvingPromise();
michael@0 858 },
michael@0 859
michael@0 860 collectConstantData: function () {
michael@0 861 return this._collectAndStoreAddons();
michael@0 862 },
michael@0 863
michael@0 864 _collectAndStoreAddons: function () {
michael@0 865 let deferred = Promise.defer();
michael@0 866
michael@0 867 AddonManager.getAllAddons(function onAllAddons(addons) {
michael@0 868 let data;
michael@0 869 let addonsField;
michael@0 870 let pluginsField;
michael@0 871 try {
michael@0 872 data = this._createDataStructure(addons);
michael@0 873 addonsField = JSON.stringify(data.addons);
michael@0 874 pluginsField = JSON.stringify(data.plugins);
michael@0 875 } catch (ex) {
michael@0 876 this._log.warn("Exception when populating add-ons data structure: " +
michael@0 877 CommonUtils.exceptionStr(ex));
michael@0 878 deferred.reject(ex);
michael@0 879 return;
michael@0 880 }
michael@0 881
michael@0 882 let now = new Date();
michael@0 883 let addons = this.getMeasurement("addons", 2);
michael@0 884 let plugins = this.getMeasurement("plugins", 1);
michael@0 885 let counts = this.getMeasurement(AddonCountsMeasurement.prototype.name,
michael@0 886 AddonCountsMeasurement.prototype.version);
michael@0 887
michael@0 888 this.enqueueStorageOperation(function storageAddons() {
michael@0 889 for (let type in data.counts) {
michael@0 890 try {
michael@0 891 counts.fieldID(type);
michael@0 892 } catch (ex) {
michael@0 893 this._log.warn("Add-on type without field: " + type);
michael@0 894 continue;
michael@0 895 }
michael@0 896
michael@0 897 counts.setDailyLastNumeric(type, data.counts[type], now);
michael@0 898 }
michael@0 899
michael@0 900 return addons.setLastText("addons", addonsField).then(
michael@0 901 function onSuccess() {
michael@0 902 return plugins.setLastText("plugins", pluginsField).then(
michael@0 903 function onSuccess() { deferred.resolve(); },
michael@0 904 function onError(error) { deferred.reject(error); }
michael@0 905 );
michael@0 906 },
michael@0 907 function onError(error) { deferred.reject(error); }
michael@0 908 );
michael@0 909 }.bind(this));
michael@0 910 }.bind(this));
michael@0 911
michael@0 912 return deferred.promise;
michael@0 913 },
michael@0 914
michael@0 915 COPY_ADDON_FIELDS: [
michael@0 916 "userDisabled",
michael@0 917 "appDisabled",
michael@0 918 "name",
michael@0 919 "version",
michael@0 920 "type",
michael@0 921 "scope",
michael@0 922 "description",
michael@0 923 "foreignInstall",
michael@0 924 "hasBinaryComponents",
michael@0 925 ],
michael@0 926
michael@0 927 COPY_PLUGIN_FIELDS: [
michael@0 928 "name",
michael@0 929 "version",
michael@0 930 "description",
michael@0 931 "blocklisted",
michael@0 932 "disabled",
michael@0 933 "clicktoplay",
michael@0 934 ],
michael@0 935
michael@0 936 _createDataStructure: function (addons) {
michael@0 937 let data = {
michael@0 938 addons: {},
michael@0 939 plugins: {},
michael@0 940 counts: {}
michael@0 941 };
michael@0 942
michael@0 943 for (let addon of addons) {
michael@0 944 let type = addon.type;
michael@0 945
michael@0 946 // We count plugins separately below.
michael@0 947 if (addon.type == "plugin")
michael@0 948 continue;
michael@0 949
michael@0 950 data.counts[type] = (data.counts[type] || 0) + 1;
michael@0 951
michael@0 952 if (this.FULL_DETAIL_TYPES.indexOf(addon.type) == -1) {
michael@0 953 continue;
michael@0 954 }
michael@0 955
michael@0 956 let obj = {};
michael@0 957 for (let field of this.COPY_ADDON_FIELDS) {
michael@0 958 obj[field] = addon[field];
michael@0 959 }
michael@0 960
michael@0 961 if (addon.installDate) {
michael@0 962 obj.installDay = this._dateToDays(addon.installDate);
michael@0 963 }
michael@0 964
michael@0 965 if (addon.updateDate) {
michael@0 966 obj.updateDay = this._dateToDays(addon.updateDate);
michael@0 967 }
michael@0 968
michael@0 969 data.addons[addon.id] = obj;
michael@0 970 }
michael@0 971
michael@0 972 let pluginTags = Cc["@mozilla.org/plugin/host;1"].
michael@0 973 getService(Ci.nsIPluginHost).
michael@0 974 getPluginTags({});
michael@0 975
michael@0 976 for (let tag of pluginTags) {
michael@0 977 let obj = {
michael@0 978 mimeTypes: tag.getMimeTypes({}),
michael@0 979 };
michael@0 980
michael@0 981 for (let field of this.COPY_PLUGIN_FIELDS) {
michael@0 982 obj[field] = tag[field];
michael@0 983 }
michael@0 984
michael@0 985 // Plugins need to have a filename and a name, so this can't be empty.
michael@0 986 let id = tag.filename + ":" + tag.name + ":" + tag.version + ":"
michael@0 987 + tag.description;
michael@0 988 data.plugins[id] = obj;
michael@0 989 }
michael@0 990
michael@0 991 data.counts["plugin"] = pluginTags.length;
michael@0 992
michael@0 993 return data;
michael@0 994 },
michael@0 995 });
michael@0 996
michael@0 997 #ifdef MOZ_CRASHREPORTER
michael@0 998
michael@0 999 function DailyCrashesMeasurement1() {
michael@0 1000 Metrics.Measurement.call(this);
michael@0 1001 }
michael@0 1002
michael@0 1003 DailyCrashesMeasurement1.prototype = Object.freeze({
michael@0 1004 __proto__: Metrics.Measurement.prototype,
michael@0 1005
michael@0 1006 name: "crashes",
michael@0 1007 version: 1,
michael@0 1008
michael@0 1009 fields: {
michael@0 1010 pending: DAILY_COUNTER_FIELD,
michael@0 1011 submitted: DAILY_COUNTER_FIELD,
michael@0 1012 },
michael@0 1013 });
michael@0 1014
michael@0 1015 function DailyCrashesMeasurement2() {
michael@0 1016 Metrics.Measurement.call(this);
michael@0 1017 }
michael@0 1018
michael@0 1019 DailyCrashesMeasurement2.prototype = Object.freeze({
michael@0 1020 __proto__: Metrics.Measurement.prototype,
michael@0 1021
michael@0 1022 name: "crashes",
michael@0 1023 version: 2,
michael@0 1024
michael@0 1025 fields: {
michael@0 1026 mainCrash: DAILY_LAST_NUMERIC_FIELD,
michael@0 1027 },
michael@0 1028 });
michael@0 1029
michael@0 1030 this.CrashesProvider = function () {
michael@0 1031 Metrics.Provider.call(this);
michael@0 1032
michael@0 1033 // So we can unit test.
michael@0 1034 this._manager = Services.crashmanager;
michael@0 1035 };
michael@0 1036
michael@0 1037 CrashesProvider.prototype = Object.freeze({
michael@0 1038 __proto__: Metrics.Provider.prototype,
michael@0 1039
michael@0 1040 name: "org.mozilla.crashes",
michael@0 1041
michael@0 1042 measurementTypes: [
michael@0 1043 DailyCrashesMeasurement1,
michael@0 1044 DailyCrashesMeasurement2,
michael@0 1045 ],
michael@0 1046
michael@0 1047 pullOnly: true,
michael@0 1048
michael@0 1049 collectDailyData: function () {
michael@0 1050 return this.storage.enqueueTransaction(this._populateCrashCounts.bind(this));
michael@0 1051 },
michael@0 1052
michael@0 1053 _populateCrashCounts: function () {
michael@0 1054 this._log.info("Grabbing crash counts from crash manager.");
michael@0 1055 let crashCounts = yield this._manager.getCrashCountsByDay();
michael@0 1056 let fields = {
michael@0 1057 "main-crash": "mainCrash",
michael@0 1058 };
michael@0 1059
michael@0 1060 let m = this.getMeasurement("crashes", 2);
michael@0 1061
michael@0 1062 for (let [day, types] of crashCounts) {
michael@0 1063 let date = Metrics.daysToDate(day);
michael@0 1064 for (let [type, count] of types) {
michael@0 1065 if (!(type in fields)) {
michael@0 1066 this._log.warn("Unknown crash type encountered: " + type);
michael@0 1067 continue;
michael@0 1068 }
michael@0 1069
michael@0 1070 yield m.setDailyLastNumeric(fields[type], count, date);
michael@0 1071 }
michael@0 1072 }
michael@0 1073 },
michael@0 1074 });
michael@0 1075
michael@0 1076 #endif
michael@0 1077
michael@0 1078
michael@0 1079 /**
michael@0 1080 * Holds basic statistics about the Places database.
michael@0 1081 */
michael@0 1082 function PlacesMeasurement() {
michael@0 1083 Metrics.Measurement.call(this);
michael@0 1084 }
michael@0 1085
michael@0 1086 PlacesMeasurement.prototype = Object.freeze({
michael@0 1087 __proto__: Metrics.Measurement.prototype,
michael@0 1088
michael@0 1089 name: "places",
michael@0 1090 version: 1,
michael@0 1091
michael@0 1092 fields: {
michael@0 1093 pages: DAILY_LAST_NUMERIC_FIELD,
michael@0 1094 bookmarks: DAILY_LAST_NUMERIC_FIELD,
michael@0 1095 },
michael@0 1096 });
michael@0 1097
michael@0 1098
michael@0 1099 /**
michael@0 1100 * Collects information about Places.
michael@0 1101 */
michael@0 1102 this.PlacesProvider = function () {
michael@0 1103 Metrics.Provider.call(this);
michael@0 1104 };
michael@0 1105
michael@0 1106 PlacesProvider.prototype = Object.freeze({
michael@0 1107 __proto__: Metrics.Provider.prototype,
michael@0 1108
michael@0 1109 name: "org.mozilla.places",
michael@0 1110
michael@0 1111 measurementTypes: [PlacesMeasurement],
michael@0 1112
michael@0 1113 collectDailyData: function () {
michael@0 1114 return this.storage.enqueueTransaction(this._collectData.bind(this));
michael@0 1115 },
michael@0 1116
michael@0 1117 _collectData: function () {
michael@0 1118 let now = new Date();
michael@0 1119 let data = yield this._getDailyValues();
michael@0 1120
michael@0 1121 let m = this.getMeasurement("places", 1);
michael@0 1122
michael@0 1123 yield m.setDailyLastNumeric("pages", data.PLACES_PAGES_COUNT);
michael@0 1124 yield m.setDailyLastNumeric("bookmarks", data.PLACES_BOOKMARKS_COUNT);
michael@0 1125 },
michael@0 1126
michael@0 1127 _getDailyValues: function () {
michael@0 1128 let deferred = Promise.defer();
michael@0 1129
michael@0 1130 PlacesDBUtils.telemetry(null, function onResult(data) {
michael@0 1131 deferred.resolve(data);
michael@0 1132 });
michael@0 1133
michael@0 1134 return deferred.promise;
michael@0 1135 },
michael@0 1136 });
michael@0 1137
michael@0 1138 function SearchCountMeasurement1() {
michael@0 1139 Metrics.Measurement.call(this);
michael@0 1140 }
michael@0 1141
michael@0 1142 SearchCountMeasurement1.prototype = Object.freeze({
michael@0 1143 __proto__: Metrics.Measurement.prototype,
michael@0 1144
michael@0 1145 name: "counts",
michael@0 1146 version: 1,
michael@0 1147
michael@0 1148 // We only record searches for search engines that have partner agreements
michael@0 1149 // with Mozilla.
michael@0 1150 fields: {
michael@0 1151 "amazon.com.abouthome": DAILY_COUNTER_FIELD,
michael@0 1152 "amazon.com.contextmenu": DAILY_COUNTER_FIELD,
michael@0 1153 "amazon.com.searchbar": DAILY_COUNTER_FIELD,
michael@0 1154 "amazon.com.urlbar": DAILY_COUNTER_FIELD,
michael@0 1155 "bing.abouthome": DAILY_COUNTER_FIELD,
michael@0 1156 "bing.contextmenu": DAILY_COUNTER_FIELD,
michael@0 1157 "bing.searchbar": DAILY_COUNTER_FIELD,
michael@0 1158 "bing.urlbar": DAILY_COUNTER_FIELD,
michael@0 1159 "google.abouthome": DAILY_COUNTER_FIELD,
michael@0 1160 "google.contextmenu": DAILY_COUNTER_FIELD,
michael@0 1161 "google.searchbar": DAILY_COUNTER_FIELD,
michael@0 1162 "google.urlbar": DAILY_COUNTER_FIELD,
michael@0 1163 "yahoo.abouthome": DAILY_COUNTER_FIELD,
michael@0 1164 "yahoo.contextmenu": DAILY_COUNTER_FIELD,
michael@0 1165 "yahoo.searchbar": DAILY_COUNTER_FIELD,
michael@0 1166 "yahoo.urlbar": DAILY_COUNTER_FIELD,
michael@0 1167 "other.abouthome": DAILY_COUNTER_FIELD,
michael@0 1168 "other.contextmenu": DAILY_COUNTER_FIELD,
michael@0 1169 "other.searchbar": DAILY_COUNTER_FIELD,
michael@0 1170 "other.urlbar": DAILY_COUNTER_FIELD,
michael@0 1171 },
michael@0 1172 });
michael@0 1173
michael@0 1174 /**
michael@0 1175 * Records search counts per day per engine and where search initiated.
michael@0 1176 *
michael@0 1177 * We want to record granular details for individual locale-specific search
michael@0 1178 * providers, but only if they're Mozilla partners. In order to do this, we
michael@0 1179 * track the nsISearchEngine identifier, which denotes shipped search engines,
michael@0 1180 * and intersect those with our partner list.
michael@0 1181 *
michael@0 1182 * We don't use the search engine name directly, because it is shared across
michael@0 1183 * locales; e.g., eBay-de and eBay both share the name "eBay".
michael@0 1184 */
michael@0 1185 function SearchCountMeasurementBase() {
michael@0 1186 this._fieldSpecs = {};
michael@0 1187 Metrics.Measurement.call(this);
michael@0 1188 }
michael@0 1189
michael@0 1190 SearchCountMeasurementBase.prototype = Object.freeze({
michael@0 1191 __proto__: Metrics.Measurement.prototype,
michael@0 1192
michael@0 1193
michael@0 1194 // Our fields are dynamic.
michael@0 1195 get fields() {
michael@0 1196 return this._fieldSpecs;
michael@0 1197 },
michael@0 1198
michael@0 1199 /**
michael@0 1200 * Override the default behavior: serializers should include every counter
michael@0 1201 * field from the DB, even if we don't currently have it registered.
michael@0 1202 *
michael@0 1203 * Do this so we don't have to register several hundred fields to match
michael@0 1204 * various Firefox locales.
michael@0 1205 *
michael@0 1206 * We use the "provider.type" syntax as a rudimentary check for validity.
michael@0 1207 *
michael@0 1208 * We trust that measurement versioning is sufficient to exclude old provider
michael@0 1209 * data.
michael@0 1210 */
michael@0 1211 shouldIncludeField: function (name) {
michael@0 1212 return name.contains(".");
michael@0 1213 },
michael@0 1214
michael@0 1215 /**
michael@0 1216 * The measurement type mechanism doesn't introspect the DB. Override it
michael@0 1217 * so that we can assume all unknown fields are counters.
michael@0 1218 */
michael@0 1219 fieldType: function (name) {
michael@0 1220 if (name in this.fields) {
michael@0 1221 return this.fields[name].type;
michael@0 1222 }
michael@0 1223
michael@0 1224 // Default to a counter.
michael@0 1225 return Metrics.Storage.FIELD_DAILY_COUNTER;
michael@0 1226 },
michael@0 1227
michael@0 1228 SOURCES: [
michael@0 1229 "abouthome",
michael@0 1230 "contextmenu",
michael@0 1231 "newtab",
michael@0 1232 "searchbar",
michael@0 1233 "urlbar",
michael@0 1234 ],
michael@0 1235 });
michael@0 1236
michael@0 1237 function SearchCountMeasurement2() {
michael@0 1238 SearchCountMeasurementBase.call(this);
michael@0 1239 }
michael@0 1240
michael@0 1241 SearchCountMeasurement2.prototype = Object.freeze({
michael@0 1242 __proto__: SearchCountMeasurementBase.prototype,
michael@0 1243 name: "counts",
michael@0 1244 version: 2,
michael@0 1245 });
michael@0 1246
michael@0 1247 function SearchCountMeasurement3() {
michael@0 1248 SearchCountMeasurementBase.call(this);
michael@0 1249 }
michael@0 1250
michael@0 1251 SearchCountMeasurement3.prototype = Object.freeze({
michael@0 1252 __proto__: SearchCountMeasurementBase.prototype,
michael@0 1253 name: "counts",
michael@0 1254 version: 3,
michael@0 1255
michael@0 1256 getEngines: function () {
michael@0 1257 return Services.search.getEngines();
michael@0 1258 },
michael@0 1259
michael@0 1260 getEngineID: function (engine) {
michael@0 1261 if (!engine) {
michael@0 1262 return "other";
michael@0 1263 }
michael@0 1264 if (engine.identifier) {
michael@0 1265 return engine.identifier;
michael@0 1266 }
michael@0 1267 return "other-" + engine.name;
michael@0 1268 },
michael@0 1269 });
michael@0 1270
michael@0 1271 function SearchEnginesMeasurement1() {
michael@0 1272 Metrics.Measurement.call(this);
michael@0 1273 }
michael@0 1274
michael@0 1275 SearchEnginesMeasurement1.prototype = Object.freeze({
michael@0 1276 __proto__: Metrics.Measurement.prototype,
michael@0 1277
michael@0 1278 name: "engines",
michael@0 1279 version: 1,
michael@0 1280
michael@0 1281 fields: {
michael@0 1282 default: DAILY_LAST_TEXT_FIELD,
michael@0 1283 },
michael@0 1284 });
michael@0 1285
michael@0 1286 this.SearchesProvider = function () {
michael@0 1287 Metrics.Provider.call(this);
michael@0 1288
michael@0 1289 this._prefs = new Preferences({defaultBranch: null});
michael@0 1290 };
michael@0 1291
michael@0 1292 this.SearchesProvider.prototype = Object.freeze({
michael@0 1293 __proto__: Metrics.Provider.prototype,
michael@0 1294
michael@0 1295 name: "org.mozilla.searches",
michael@0 1296 measurementTypes: [
michael@0 1297 SearchCountMeasurement1,
michael@0 1298 SearchCountMeasurement2,
michael@0 1299 SearchCountMeasurement3,
michael@0 1300 SearchEnginesMeasurement1,
michael@0 1301 ],
michael@0 1302
michael@0 1303 /**
michael@0 1304 * Initialize the search service before our measurements are touched.
michael@0 1305 */
michael@0 1306 preInit: function (storage) {
michael@0 1307 // Initialize search service.
michael@0 1308 let deferred = Promise.defer();
michael@0 1309 Services.search.init(function onInitComplete () {
michael@0 1310 deferred.resolve();
michael@0 1311 });
michael@0 1312 return deferred.promise;
michael@0 1313 },
michael@0 1314
michael@0 1315 collectDailyData: function () {
michael@0 1316 return this.storage.enqueueTransaction(function getDaily() {
michael@0 1317 // We currently only record this if Telemetry is enabled.
michael@0 1318 if (!isTelemetryEnabled(this._prefs)) {
michael@0 1319 return;
michael@0 1320 }
michael@0 1321
michael@0 1322 let m = this.getMeasurement(SearchEnginesMeasurement1.prototype.name,
michael@0 1323 SearchEnginesMeasurement1.prototype.version);
michael@0 1324
michael@0 1325 let engine;
michael@0 1326 try {
michael@0 1327 engine = Services.search.defaultEngine;
michael@0 1328 } catch (e) {}
michael@0 1329 let name;
michael@0 1330
michael@0 1331 if (!engine) {
michael@0 1332 name = "NONE";
michael@0 1333 } else if (engine.identifier) {
michael@0 1334 name = engine.identifier;
michael@0 1335 } else if (engine.name) {
michael@0 1336 name = "other-" + engine.name;
michael@0 1337 } else {
michael@0 1338 name = "UNDEFINED";
michael@0 1339 }
michael@0 1340
michael@0 1341 yield m.setDailyLastText("default", name);
michael@0 1342 }.bind(this));
michael@0 1343 },
michael@0 1344
michael@0 1345 /**
michael@0 1346 * Record that a search occurred.
michael@0 1347 *
michael@0 1348 * @param engine
michael@0 1349 * (nsISearchEngine) The search engine used.
michael@0 1350 * @param source
michael@0 1351 * (string) Where the search was initiated from. Must be one of the
michael@0 1352 * SearchCountMeasurement2.SOURCES values.
michael@0 1353 *
michael@0 1354 * @return Promise<>
michael@0 1355 * The promise is resolved when the storage operation completes.
michael@0 1356 */
michael@0 1357 recordSearch: function (engine, source) {
michael@0 1358 let m = this.getMeasurement("counts", 3);
michael@0 1359
michael@0 1360 if (m.SOURCES.indexOf(source) == -1) {
michael@0 1361 throw new Error("Unknown source for search: " + source);
michael@0 1362 }
michael@0 1363
michael@0 1364 let field = m.getEngineID(engine) + "." + source;
michael@0 1365 if (this.storage.hasFieldFromMeasurement(m.id, field,
michael@0 1366 this.storage.FIELD_DAILY_COUNTER)) {
michael@0 1367 let fieldID = this.storage.fieldIDFromMeasurement(m.id, field);
michael@0 1368 return this.enqueueStorageOperation(function recordSearchKnownField() {
michael@0 1369 return this.storage.incrementDailyCounterFromFieldID(fieldID);
michael@0 1370 }.bind(this));
michael@0 1371 }
michael@0 1372
michael@0 1373 // Otherwise, we first need to create the field.
michael@0 1374 return this.enqueueStorageOperation(function recordFieldAndSearch() {
michael@0 1375 // This function has to return a promise.
michael@0 1376 return Task.spawn(function () {
michael@0 1377 let fieldID = yield this.storage.registerField(m.id, field,
michael@0 1378 this.storage.FIELD_DAILY_COUNTER);
michael@0 1379 yield this.storage.incrementDailyCounterFromFieldID(fieldID);
michael@0 1380 }.bind(this));
michael@0 1381 }.bind(this));
michael@0 1382 },
michael@0 1383 });
michael@0 1384
michael@0 1385 function HealthReportSubmissionMeasurement1() {
michael@0 1386 Metrics.Measurement.call(this);
michael@0 1387 }
michael@0 1388
michael@0 1389 HealthReportSubmissionMeasurement1.prototype = Object.freeze({
michael@0 1390 __proto__: Metrics.Measurement.prototype,
michael@0 1391
michael@0 1392 name: "submissions",
michael@0 1393 version: 1,
michael@0 1394
michael@0 1395 fields: {
michael@0 1396 firstDocumentUploadAttempt: DAILY_COUNTER_FIELD,
michael@0 1397 continuationUploadAttempt: DAILY_COUNTER_FIELD,
michael@0 1398 uploadSuccess: DAILY_COUNTER_FIELD,
michael@0 1399 uploadTransportFailure: DAILY_COUNTER_FIELD,
michael@0 1400 uploadServerFailure: DAILY_COUNTER_FIELD,
michael@0 1401 uploadClientFailure: DAILY_COUNTER_FIELD,
michael@0 1402 },
michael@0 1403 });
michael@0 1404
michael@0 1405 function HealthReportSubmissionMeasurement2() {
michael@0 1406 Metrics.Measurement.call(this);
michael@0 1407 }
michael@0 1408
michael@0 1409 HealthReportSubmissionMeasurement2.prototype = Object.freeze({
michael@0 1410 __proto__: Metrics.Measurement.prototype,
michael@0 1411
michael@0 1412 name: "submissions",
michael@0 1413 version: 2,
michael@0 1414
michael@0 1415 fields: {
michael@0 1416 firstDocumentUploadAttempt: DAILY_COUNTER_FIELD,
michael@0 1417 continuationUploadAttempt: DAILY_COUNTER_FIELD,
michael@0 1418 uploadSuccess: DAILY_COUNTER_FIELD,
michael@0 1419 uploadTransportFailure: DAILY_COUNTER_FIELD,
michael@0 1420 uploadServerFailure: DAILY_COUNTER_FIELD,
michael@0 1421 uploadClientFailure: DAILY_COUNTER_FIELD,
michael@0 1422 uploadAlreadyInProgress: DAILY_COUNTER_FIELD,
michael@0 1423 },
michael@0 1424 });
michael@0 1425
michael@0 1426 this.HealthReportProvider = function () {
michael@0 1427 Metrics.Provider.call(this);
michael@0 1428 }
michael@0 1429
michael@0 1430 HealthReportProvider.prototype = Object.freeze({
michael@0 1431 __proto__: Metrics.Provider.prototype,
michael@0 1432
michael@0 1433 name: "org.mozilla.healthreport",
michael@0 1434
michael@0 1435 measurementTypes: [
michael@0 1436 HealthReportSubmissionMeasurement1,
michael@0 1437 HealthReportSubmissionMeasurement2,
michael@0 1438 ],
michael@0 1439
michael@0 1440 recordEvent: function (event, date=new Date()) {
michael@0 1441 let m = this.getMeasurement("submissions", 2);
michael@0 1442 return this.enqueueStorageOperation(function recordCounter() {
michael@0 1443 return m.incrementDailyCounter(event, date);
michael@0 1444 });
michael@0 1445 },
michael@0 1446 });

mercurial