author | Gregory Szorc <gps@mozilla.com> |
Sun, 06 Jan 2013 14:47:18 -0800 | |
changeset 117849 | 03759402e43327ca3031e522d3917637986b02ac |
parent 117848 | 1bbcfe1c66087835db2a9e4eed9b8e8a5db69b48 |
child 117850 | eaeae002559b1910eb84eb0e715dd08bed1588fb |
push id | 24116 |
push user | gszorc@mozilla.com |
push date | Mon, 07 Jan 2013 08:22:48 +0000 |
treeherder | mozilla-central@66d595814554 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | rnewman |
bugs | 808126 |
milestone | 20.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/services/healthreport/HealthReportComponents.manifest +++ b/services/healthreport/HealthReportComponents.manifest @@ -6,12 +6,13 @@ # metro browser: {99bceaaa-e3c6-48c1-b981-ef9b46b67d60} component {e354c59b-b252-4040-b6dd-b71864e3e35c} HealthReportService.js contract @mozilla.org/healthreport/service;1 {e354c59b-b252-4040-b6dd-b71864e3e35c} category app-startup HealthReportService service,@mozilla.org/healthreport/service;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} category healthreport-js-provider AddonsProvider resource://gre/modules/services/healthreport/providers.jsm category healthreport-js-provider AppInfoProvider resource://gre/modules/services/healthreport/providers.jsm +category healthreport-js-provider CrashesProvider resource://gre/modules/services/healthreport/providers.jsm category healthreport-js-provider SysInfoProvider resource://gre/modules/services/healthreport/providers.jsm category healthreport-js-provider ProfileMetadataProvider resource://gre/modules/services/healthreport/profile.jsm category healthreport-js-provider SessionsProvider resource://gre/modules/services/healthreport/providers.jsm
--- a/services/healthreport/modules-testing/utils.jsm +++ b/services/healthreport/modules-testing/utils.jsm @@ -2,22 +2,29 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; this.EXPORTED_SYMBOLS = [ "getAppInfo", "updateAppInfo", + "makeFakeAppDir", + "createFakeCrash", ]; -const {interfaces: Ci, results: Cr, utils: Cu} = Components; +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; +Cu.import("resource://gre/modules/commonjs/promise/core.js"); +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/services-common/utils.js"); + let APP_INFO = { vendor: "Mozilla", name: "xpcshell", ID: "xpcshell@tests.mozilla.org", version: "1", appBuildID: "20121107", platformVersion: "p-ver", @@ -66,8 +73,138 @@ this.updateAppInfo = function (obj) { } return obj.QueryInterface(iid); }, }; registrar.registerFactory(id, "XULAppInfo", cid, factory); }; + +// Reference needed in order for fake app dir provider to be active. +let gFakeAppDirectoryProvider; + +/** + * Installs a fake UAppData directory. + * + * This is needed by tests because a UAppData directory typically isn't + * present in the test environment. + * + * This function is suitable for use in different components. If we ever + * establish a central location for convenient test helpers, this should + * go there. + * + * We create the new UAppData directory under the profile's directory + * because the profile directory is automatically cleaned as part of + * test shutdown. + * + * This returns a promise that will be resolved once the new directory + * is created and installed. + */ +this.makeFakeAppDir = function () { + let dirMode = OS.Constants.libc.S_IRWXU; + let dirService = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + let baseFile = dirService.get("ProfD", Ci.nsIFile); + let appD = baseFile.clone(); + appD.append("UAppData"); + + if (gFakeAppDirectoryProvider) { + return Promise.resolve(appD.path); + } + + function makeDir(f) { + if (f.exists()) { + return; + } + + dump("Creating directory: " + f.path + "\n"); + f.create(Ci.nsIFile.DIRECTORY_TYPE, dirMode); + } + + makeDir(appD); + + let reportsD = appD.clone(); + reportsD.append("Crash Reports"); + + let pendingD = reportsD.clone(); + pendingD.append("pending"); + let submittedD = reportsD.clone(); + submittedD.append("submitted"); + + makeDir(reportsD); + makeDir(pendingD); + makeDir(submittedD); + + let provider = { + getFile: function (prop, persistent) { + persistent.value = true; + if (prop == "UAppData") { + return appD.clone(); + } + + throw Cr.NS_ERROR_FAILURE; + }, + + QueryInterace: function (iid) { + if (iid.equals(Ci.nsIDirectoryServiceProvider) || + iid.equals(Ci.nsISupports)) { + return this; + } + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + }; + + // Register the new provider. + dirService.QueryInterface(Ci.nsIDirectoryService) + .registerProvider(provider); + + // And undefine the old one. + try { + dirService.undefine("UAppData"); + } catch (ex) {}; + + gFakeAppDirectoryProvider = provider; + + dump("Successfully installed fake UAppDir\n"); + return Promise.resolve(appD.path); +}; + + +/** + * Creates a fake crash in the Crash Reports directory. + * + * Currently, we just create a dummy file. A more robust implementation would + * create something that actually resembles a crash report file. + * + * This is very similar to code in crashreporter/tests/browser/head.js. + * + * FUTURE consolidate code in a shared JSM. + */ +this.createFakeCrash = function (submitted=false, date=new Date()) { + let id = CommonUtils.generateUUID(); + let filename; + + let paths = ["Crash Reports"]; + let mode; + + if (submitted) { + paths.push("submitted"); + filename = "bp-" + id + ".txt"; + mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR | + OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IROTH; + } else { + paths.push("pending"); + filename = id + ".dmp"; + mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR; + } + + paths.push(filename); + + let file = FileUtils.getFile("UAppData", paths, true); + file.create(file.NORMAL_FILE_TYPE, mode); + file.lastModifiedTime = date.getTime(); + dump("Created fake crash: " + id + "\n"); + + return id; +}; +
--- a/services/healthreport/providers.jsm +++ b/services/healthreport/providers.jsm @@ -12,23 +12,26 @@ * up. */ "use strict"; this.EXPORTED_SYMBOLS = [ "AddonsProvider", "AppInfoProvider", + "CrashDirectoryService", + "CrashesProvider", "SessionsProvider", "SysInfoProvider", ]; const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/commonjs/promise/core.js"); +Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Metrics.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://services-common/preferences.js"); Cu.import("resource://services-common/utils.js"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", @@ -806,8 +809,161 @@ AddonsProvider.prototype = Object.freeze let type = addon.type; data.counts[type] = (data.counts[type] || 0) + 1; } return data; }, }); + +function DailyCrashesMeasurement() { + Metrics.Measurement.call(this); +} + +DailyCrashesMeasurement.prototype = Object.freeze({ + __proto__: Metrics.Measurement.prototype, + + name: "crashes", + version: 1, + + configureStorage: function () { + this.registerStorageField("pending", this.storage.FIELD_DAILY_COUNTER); + this.registerStorageField("submitted", this.storage.FIELD_DAILY_COUNTER); + }, +}); + +this.CrashesProvider = function () { + Metrics.Provider.call(this); +}; + +CrashesProvider.prototype = Object.freeze({ + __proto__: Metrics.Provider.prototype, + + name: "org.mozilla.crashes", + + measurementTypes: [DailyCrashesMeasurement], + + collectConstantData: function () { + return Task.spawn(this._populateCrashCounts.bind(this)); + }, + + _populateCrashCounts: function () { + let now = new Date(); + let service = new CrashDirectoryService(); + + let pending = yield service.getPendingFiles(); + let submitted = yield service.getSubmittedFiles(); + + let lastCheck = yield this.getState("lastCheck"); + if (!lastCheck) { + lastCheck = 0; + } else { + lastCheck = parseInt(lastCheck, 10); + if (Number.isNaN(lastCheck)) { + lastCheck = 0; + } + } + + let m = this.getMeasurement("crashes", 1); + + // FUTURE detect mtimes in the future and react more intelligently. + for (let filename in pending) { + let modified = pending[filename].modified; + + if (modified.getTime() < lastCheck) { + continue; + } + + yield m.incrementDailyCounter("pending", modified); + } + + for (let filename in submitted) { + let modified = submitted[filename].modified; + + if (modified.getTime() < lastCheck) { + continue; + } + + yield m.incrementDailyCounter("submitted", modified); + } + + yield this.setState("lastCheck", "" + now.getTime()); + }, +}); + + +/** + * Helper for interacting with the crashes directory. + * + * FUTURE Extract to JSM alongside crashreporter. Use in about:crashes. + */ +this.CrashDirectoryService = function () { + let base = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("UAppData", Ci.nsIFile); + + let cr = base.clone(); + cr.append("Crash Reports"); + + let submitted = cr.clone(); + submitted.append("submitted"); + + let pending = cr.clone(); + pending.append("pending"); + + this._baseDir = base.path; + this._submittedDir = submitted.path; + this._pendingDir = pending.path; +}; + +CrashDirectoryService.prototype = Object.freeze({ + RE_SUBMITTED_FILENAME: /^bp-.+\.txt$/, + RE_PENDING_FILENAME: /^.+\.dmp$/, + + getPendingFiles: function () { + return this._getDirectoryEntries(this._pendingDir, + this.RE_PENDING_FILENAME); + }, + + getSubmittedFiles: function () { + return this._getDirectoryEntries(this._submittedDir, + this.RE_SUBMITTED_FILENAME); + }, + + _getDirectoryEntries: function (path, re) { + let files = {}; + + return Task.spawn(function iterateDirectory() { + if (!(yield OS.File.exists(path))) { + throw new Task.Result(files); + } + + let iterator = new OS.File.DirectoryIterator(path); + try { + while (true) { + let entry; + try { + entry = yield iterator.next(); + } catch (ex if ex == StopIteration) { + break; + } + + if (!entry.name.match(re)) { + continue; + } + + let info = yield OS.File.stat(entry.path); + files[entry.name] = { + created: info.creationDate, + modified: info.lastModificationDate, + size: info.size, + }; + } + + throw new Task.Result(files); + } finally { + iterator.close(); + } + }); + }, +}); +
new file mode 100644 --- /dev/null +++ b/services/healthreport/tests/xpcshell/test_provider_crashes.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {utils: Cu} = Components; + + +Cu.import("resource://gre/modules/Metrics.jsm"); +Cu.import("resource://gre/modules/services/healthreport/providers.jsm"); +Cu.import("resource://testing-common/services/healthreport/utils.jsm"); + + +const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + + +function run_test() { + makeFakeAppDir().then(run_next_test, do_throw); +} + +let gPending = {}; +let gSubmitted = {}; + +add_task(function test_directory_service() { + let d = new CrashDirectoryService(); + + let entries = yield d.getPendingFiles(); + do_check_eq(typeof(entries), "object"); + do_check_eq(Object.keys(entries).length, 0); + + entries = yield d.getSubmittedFiles(); + do_check_eq(typeof(entries), "object"); + do_check_eq(Object.keys(entries).length, 0); + + let now = new Date(); + + // We lose granularity when writing to filesystem. + now.setUTCMilliseconds(0); + let dates = []; + for (let i = 0; i < 10; i++) { + dates.push(new Date(now.getTime() - i * MILLISECONDS_PER_DAY)); + } + + let pending = {}; + let submitted = {}; + for (let date of dates) { + pending[createFakeCrash(false, date)] = date; + submitted[createFakeCrash(true, date)] = date; + } + + entries = yield d.getPendingFiles(); + do_check_eq(Object.keys(entries).length, Object.keys(pending).length); + for (let id in pending) { + let filename = id + ".dmp"; + do_check_true(filename in entries); + do_check_eq(entries[filename].modified.getTime(), pending[id].getTime()); + } + + entries = yield d.getSubmittedFiles(); + do_check_eq(Object.keys(entries).length, Object.keys(submitted).length); + for (let id in submitted) { + let filename = "bp-" + id + ".txt"; + do_check_true(filename in entries); + do_check_eq(entries[filename].modified.getTime(), submitted[id].getTime()); + } + + gPending = pending; + gSubmitted = submitted; +}); + +add_test(function test_constructor() { + let provider = new CrashesProvider(); + + run_next_test(); +}); + +add_task(function test_init() { + let storage = yield Metrics.Storage("init"); + let provider = new CrashesProvider(); + yield provider.init(storage); + yield provider.shutdown(); + + yield storage.close(); +}); + +add_task(function test_collect() { + let storage = yield Metrics.Storage("collect"); + let provider = new CrashesProvider(); + yield provider.init(storage); + + // FUTURE Don't rely on state from previous test. + yield provider.collectConstantData(); + + let m = provider.getMeasurement("crashes", 1); + let values = yield m.getValues(); + do_check_eq(values.days.size, Object.keys(gPending).length); + for each (let date in gPending) { + do_check_true(values.days.hasDay(date)); + + let value = values.days.getDay(date); + do_check_true(value.has("pending")); + do_check_true(value.has("submitted")); + do_check_eq(value.get("pending"), 1); + do_check_eq(value.get("submitted"), 1); + } + + let currentState = yield provider.getState("lastCheck"); + do_check_eq(typeof(currentState), "string"); + do_check_true(currentState.length > 0); + let lastState = currentState; + + // If we collect again, we should get no new data. + yield provider.collectConstantData(); + values = yield m.getValues(); + for each (let date in gPending) { + let day = values.days.getDay(date); + do_check_eq(day.get("pending"), 1); + do_check_eq(day.get("submitted"), 1); + } + + currentState = yield provider.getState("lastCheck"); + do_check_neq(currentState, lastState); + do_check_true(currentState > lastState); + + let now = new Date(); + let tomorrow = new Date(now.getTime() + MILLISECONDS_PER_DAY); + let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY); + + let yesterdayID = createFakeCrash(false, yesterday); + let tomorrowID = createFakeCrash(false, tomorrow); + + yield provider.collectConstantData(); + values = yield m.getValues(); + do_check_eq(values.days.size, 11); + do_check_eq(values.days.getDay(tomorrow).get("pending"), 1); + + for each (let date in gPending) { + let day = values.days.getDay(date); + do_check_eq(day.get("pending"), 1); + do_check_eq(day.get("submitted"), 1); + } + + yield provider.shutdown(); + yield storage.close(); +}); +
--- a/services/healthreport/tests/xpcshell/xpcshell.ini +++ b/services/healthreport/tests/xpcshell/xpcshell.ini @@ -3,11 +3,12 @@ head = head.js tail = [test_load_modules.js] [test_profile.js] [test_policy.js] [test_healthreporter.js] [test_provider_addons.js] [test_provider_appinfo.js] +[test_provider_crashes.js] [test_provider_sysinfo.js] [test_provider_sessions.js]