Tue, 06 Jan 2015 21:39:09 +0100
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 | }); |