author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Mon, 02 Feb 2015 13:10:48 +0100 | |
changeset 227001 | b955d50b6ec34505f4f75c166fdc56825cea507c |
parent 226999 | 99ae116ac21331e268442842c7bb8537d3705f99 (current diff) |
parent 227000 | e79f5902b0b1c7e39620949882689fb9fbfe0f5d (diff) |
child 227002 | 3bf7ed413e87f086a3f5ede1ddd475f310b117e0 |
push id | 54982 |
push user | cbook@mozilla.com |
push date | Mon, 02 Feb 2015 12:25:45 +0000 |
treeherder | mozilla-inbound@7e188718dea2 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 38.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/toolkit/components/passwordmgr/nsLoginManager.js +++ b/toolkit/components/passwordmgr/nsLoginManager.js @@ -15,16 +15,18 @@ Cu.import("resource://gre/modules/LoginM XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"); +const MS_PER_DAY = 24 * 60 * 60 * 1000; + var debug = false; function log(...pieces) { function generateLogMessage(args) { let strings = ['Login Manager:']; args.forEach(function(arg) { if (typeof arg === 'string') { strings.push(arg); @@ -196,44 +198,75 @@ LoginManager.prototype = { Task.spawn(function () { yield this._pwmgr._storage.terminate(); this._pwmgr._initStorage(); yield this._pwmgr.initializationPromise; Services.obs.notifyObservers(null, "passwordmgr-storage-replace-complete", null); }.bind(this)); } else if (topic == "gather-telemetry") { - this._pwmgr._gatherTelemetry(); + // When testing, the "data" parameter is a string containing the + // reference time in milliseconds for time-based statistics. + this._pwmgr._gatherTelemetry(data ? parseInt(data) + : new Date().getTime()); } else { log("Oops! Unexpected notification:", topic); } } }, - _gatherTelemetry : function() { - let numPasswordsBlocklist = Services.telemetry.getHistogramById("PWMGR_BLOCKLIST_NUM_SITES"); - numPasswordsBlocklist.clear(); - numPasswordsBlocklist.add(this.getAllDisabledHosts({}).length); + /** + * Collects statistics about the current logins and settings. The telemetry + * histograms used here are not accumulated, but are reset each time this + * function is called, since it can be called multiple times in a session. + * + * This function might also not be called at all in the current session. + * + * @param referenceTimeMs + * Current time used to calculate time-based statistics, expressed as + * the number of milliseconds since January 1, 1970, 00:00:00 UTC. + * This is set to a fake value during unit testing. + */ + _gatherTelemetry : function (referenceTimeMs) { + function clearAndGetHistogram(histogramId) { + let histogram = Services.telemetry.getHistogramById(histogramId); + histogram.clear(); + return histogram; + } - let numPasswordsHist = Services.telemetry.getHistogramById("PWMGR_NUM_SAVED_PASSWORDS"); - numPasswordsHist.clear(); - numPasswordsHist.add(this.countLogins("", "", "")); + clearAndGetHistogram("PWMGR_BLOCKLIST_NUM_SITES").add( + this.getAllDisabledHosts({}).length + ); + clearAndGetHistogram("PWMGR_NUM_SAVED_PASSWORDS").add( + this.countLogins("", "", "") + ); - let isPwdSavedEnabledHist = Services.telemetry.getHistogramById("PWMGR_SAVING_ENABLED"); - isPwdSavedEnabledHist.clear(); - isPwdSavedEnabledHist.add(this._remember); + // This is a boolean histogram, and not a flag, because we don't want to + // record any value if _gatherTelemetry is not called. + clearAndGetHistogram("PWMGR_SAVING_ENABLED").add(this._remember); // Don't try to get logins if MP is enabled, since we don't want to show a MP prompt. - if (this.isLoggedIn) { - let logins = this.getAllLogins({}); + if (!this.isLoggedIn) { + return; + } + + let logins = this.getAllLogins({}); + + let usernamePresentHistogram = clearAndGetHistogram("PWMGR_USERNAME_PRESENT"); + let loginLastUsedDaysHistogram = clearAndGetHistogram("PWMGR_LOGIN_LAST_USED_DAYS"); - let usernameHist = Services.telemetry.getHistogramById("PWMGR_USERNAME_PRESENT"); - usernameHist.clear(); - for (let login of logins) { - usernameHist.add(!!login.username); + for (let login of logins) { + usernamePresentHistogram.add(!!login.username); + + login.QueryInterface(Ci.nsILoginMetaInfo); + let timeLastUsedAgeMs = referenceTimeMs - login.timeLastUsed; + if (timeLastUsedAgeMs > 0) { + loginLastUsedDaysHistogram.add( + Math.floor(timeLastUsedAgeMs / MS_PER_DAY) + ); } } },
new file mode 100644 --- /dev/null +++ b/toolkit/components/passwordmgr/test/unit/test_telemetry.js @@ -0,0 +1,183 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the statistics and other counters reported through telemetry. + */ + +"use strict"; + +//////////////////////////////////////////////////////////////////////////////// +//// Globals + +const MS_PER_DAY = 24 * 60 * 60 * 1000; + +// To prevent intermittent failures when the test is executed at a time that is +// very close to a day boundary, we make it deterministic by using a static +// reference date for all the time-based statistics. +const gReferenceTimeMs = new Date("2000-01-01T00:00:00").getTime(); + +// Returns a milliseconds value to use with nsILoginMetaInfo properties, falling +// approximately in the middle of the specified number of days before the +// reference time, where zero days indicates a time within the past 24 hours. +let daysBeforeMs = days => gReferenceTimeMs - (days + 0.5) * MS_PER_DAY; + +/** + * Contains metadata that will be attached to test logins in order to verify + * that the statistics collection is working properly. Most properties of the + * logins are initialized to the default test values already. + * + * If you update this data or any of the telemetry histograms it checks, you'll + * probably need to update the expected statistics in the test below. + */ +const StatisticsTestData = [ + { + timeLastUsed: daysBeforeMs(0), + }, + { + timeLastUsed: daysBeforeMs(1), + }, + { + timeLastUsed: daysBeforeMs(7), + }, + { + username: "", + timeLastUsed: daysBeforeMs(7), + }, + { + username: "", + timeLastUsed: daysBeforeMs(30), + }, + { + username: "", + timeLastUsed: daysBeforeMs(31), + }, + { + timeLastUsed: daysBeforeMs(365), + }, + { + username: "", + timeLastUsed: daysBeforeMs(366), + }, + { + // If the login was saved in the future, it is ignored for statistiscs. + timeLastUsed: daysBeforeMs(-1), + }, + { + timeLastUsed: daysBeforeMs(1000), + }, +]; + +/** + * Triggers the collection of those statistics that are not accumulated each + * time an action is taken, but are a static snapshot of the current state. + */ +function triggerStatisticsCollection() { + Services.obs.notifyObservers(null, "gather-telemetry", "" + gReferenceTimeMs); +} + +/** + * Tests the telemetry histogram with the given ID contains only the specified + * non-zero ranges, expressed in the format { range1: value1, range2: value2 }. + */ +function testHistogram(histogramId, expectedNonZeroRanges) { + let snapshot = Services.telemetry.getHistogramById(histogramId).snapshot(); + + // Compute the actual ranges in the format { range1: value1, range2: value2 }. + let actualNonZeroRanges = {}; + for (let [index, range] of snapshot.ranges.entries()) { + let value = snapshot.counts[index]; + if (value > 0) { + actualNonZeroRanges[range] = value; + } + } + + // These are stringified to visualize the differences between the values. + do_print("Testing histogram: " + histogramId); + do_check_eq(JSON.stringify(actualNonZeroRanges), + JSON.stringify(expectedNonZeroRanges)); +} + +//////////////////////////////////////////////////////////////////////////////// +//// Tests + +/** + * Enable local telemetry recording for the duration of the tests, and prepare + * the test data that will be used by the following tests. + */ +add_task(function test_initialize() { + let oldCanRecord = Services.telemetry.canRecord; + Services.telemetry.canRecord = true; + do_register_cleanup(function () { + Services.telemetry.canRecord = oldCanRecord; + }); + + let uniqueNumber = 1; + for (let loginModifications of StatisticsTestData) { + loginModifications.hostname = `http://${uniqueNumber++}.example.com`; + Services.logins.addLogin(TestData.formLogin(loginModifications)); + } +}); + +/** + * Tests the collection of statistics related to login metadata. + */ +add_task(function test_logins_statistics() { + // Repeat the operation twice to test that histograms are not accumulated. + for (let repeating of [false, true]) { + triggerStatisticsCollection(); + + // Should record 1 in the bucket corresponding to the number of passwords. + testHistogram("PWMGR_NUM_SAVED_PASSWORDS", + { 10: 1 }); + + // For each saved login, should record 1 in the bucket corresponding to the + // age in days since the login was last used. + testHistogram("PWMGR_LOGIN_LAST_USED_DAYS", + { 0: 1, 1: 1, 7: 2, 29: 2, 356: 2, 750: 1 }); + + // Should record the number of logins without a username in bucket 0, and + // the number of logins with a username in bucket 1. + testHistogram("PWMGR_USERNAME_PRESENT", + { 0: 4, 1: 6 }); + } +}); + +/** + * Tests the collection of statistics related to hosts for which passowrd saving + * has been explicitly disabled. + */ +add_task(function test_disabledHosts_statistics() { + // Should record 1 in the bucket corresponding to the number of sites for + // which password saving is disabled. + Services.logins.setLoginSavingEnabled("http://www.example.com", false); + triggerStatisticsCollection(); + testHistogram("PWMGR_BLOCKLIST_NUM_SITES", { 1: 1 }); + + Services.logins.setLoginSavingEnabled("http://www.example.com", true); + triggerStatisticsCollection(); + testHistogram("PWMGR_BLOCKLIST_NUM_SITES", { 0: 1 }); +}); + +/** + * Tests the collection of statistics related to general settings. + */ +add_task(function test_settings_statistics() { + let oldRememberSignons = Services.prefs.getBoolPref("signon.rememberSignons"); + do_register_cleanup(function () { + Services.prefs.setBoolPref("signon.rememberSignons", oldRememberSignons); + }); + + // Repeat the operation twice per value to test that histograms are reset. + for (let remember of [false, true, false, true]) { + // This change should be observed immediately by the login service. + Services.prefs.setBoolPref("signon.rememberSignons", remember); + + triggerStatisticsCollection(); + + // Should record 1 in either bucket 0 or bucket 1 based on the preference. + testHistogram("PWMGR_SAVING_ENABLED", remember ? { 1: 1 } : { 0: 1 }); + } +});
--- a/toolkit/components/passwordmgr/test/unit/xpcshell.ini +++ b/toolkit/components/passwordmgr/test/unit/xpcshell.ini @@ -19,8 +19,9 @@ skip-if = os != "android" [test_legacy_empty_formSubmitURL.js] [test_legacy_validation.js] [test_logins_change.js] [test_logins_decrypt_failure.js] [test_logins_metainfo.js] [test_logins_search.js] [test_notifications.js] [test_storage.js] +[test_telemetry.js]
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7263,17 +7263,24 @@ "description": "The number of sites for which the user has explicitly rejected saving logins" }, "PWMGR_NUM_SAVED_PASSWORDS": { "expires_in_version": "never", "kind": "exponential", "high": 750, "n_buckets" : 50, "extended_statistics_ok": true, - "description": "The number of saved signons in storage" + "description": "Total number of saved logins, including those that cannot be decrypted" + }, + "PWMGR_LOGIN_LAST_USED_DAYS": { + "expires_in_version": "never", + "kind": "exponential", + "high": 750, + "n_buckets" : 40, + "description": "Time in days each saved login was last used" }, "PWMGR_SAVING_ENABLED": { "expires_in_version": "never", "kind": "boolean", "description": "Number of users who have password saving on globally" }, "PWMGR_USERNAME_PRESENT": { "expires_in_version": "never",