Bug 1599567 - Add telemetry for when logins store is unusable because of missing or corrupt logins.json file. r=MattN

Differential Revision: https://phabricator.services.mozilla.com/D78477
This commit is contained in:
prathiksha 2020-06-25 16:29:25 +00:00
parent 1c03261951
commit a7eff183d5
5 changed files with 111 additions and 0 deletions

View File

@ -101,6 +101,12 @@ add_task(async function setup_head() {
// Ignore warnings and non-errors.
return;
}
if (msg.errorMessage.includes('Unknown event: ["jsonfile", "load"')) {
// Ignore telemetry errors from JSONFile.jsm.
return;
}
if (
msg.errorMessage == "Refreshing device list failed." ||
msg.errorMessage == "Skipping device list refresh; not signed in"

View File

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
ChromeUtils.defineModuleGetter(
this,
"LoginStore",
"resource://gre/modules/LoginStore.jsm"
);
const { TelemetryTestUtils } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);
/**
* Tests collecting telemetry when login store is missing.
*/
add_task(async function test_login_store_missing_telemetry() {
// Clear events recorded in the jsonfile category during startup.
Services.telemetry.clearEvents();
// Check that logins.json does not exist.
let loginsStorePath = OS.Path.join(
OS.Constants.Path.profileDir,
"logins.json"
);
Assert.equal(false, await OS.File.exists(loginsStorePath));
// Create a LoginStore.jsm object and try to load logins.json.
let store = new LoginStore(loginsStorePath);
await store.load();
TelemetryTestUtils.assertEvents(
[["jsonfile", "load", "logins"]],
{},
{ clear: true }
);
});
/**
* Tests collecting telemetry when the login store is corrupt.
*/
add_task(async function test_login_store_corrupt_telemetry() {
let loginsStorePath = OS.Path.join(
OS.Constants.Path.profileDir,
"logins.json"
);
let store = new LoginStore(loginsStorePath);
let string = '{"logins":[{"hostname":"http://www.example.com","id":1,';
await OS.File.writeAtomic(store.path, new TextEncoder().encode(string));
await store.load();
// A .corrupt file should have been created.
Assert.ok(await OS.File.exists(store.path + ".corrupt"));
TelemetryTestUtils.assertEvents(
[
["jsonfile", "load", "logins", ""],
["jsonfile", "load", "logins", "invalid_json"],
],
{},
{ clear: true }
);
// Clean up.
await OS.File.remove(store.path + ".corrupt");
await OS.File.remove(store.path);
});

View File

@ -8,6 +8,8 @@ support-files = data/**
skip-if = os == "android"
[test_module_LoginStore.js]
skip-if = os == "android"
[test_module_LoginStoreTelemetry.js]
skip-if = os == "android"
# The following tests apply to any storage back-end that supports add/modify/remove.
[test_context_menu.js]

View File

@ -780,6 +780,18 @@ pwmgr:
- "firefox"
record_in_processes: [content, main]
jsonfile:
load:
description: >
Records when JSONFile.jsm consumers are trying to access a missing or corrupt json file.
For example, Login Store trying to access logins.json when it has gone missing or corrupt.
objects: ["logins", "autofillprofiles"]
bug_numbers: [1599567]
expiry_version: never
products: ["firefox"]
record_in_processes: [main]
notification_emails: ["prathiksha@mozilla.com", "passwords-dev@mozilla.org"]
fxa:
connect:
objects: ["account"]

View File

@ -72,6 +72,12 @@ const FileInputStream = Components.Constructor(
"init"
);
ChromeUtils.defineModuleGetter(
this,
"Services",
"resource://gre/modules/Services.jsm"
);
/**
* Delay between a change to the data and the related save operation.
*/
@ -130,6 +136,8 @@ function JSONFile(config) {
"JSON store: writing data",
this._finalizeInternalBound
);
Services.telemetry.setEventRecordingEnabled("jsonfile", true);
}
JSONFile.prototype = {
@ -213,6 +221,16 @@ JSONFile.prototype = {
// just start with new data. Other errors may indicate that the file is
// corrupt, thus we move it to a backup location before allowing it to
// be overwritten by an empty file.
let cleansedBasename = OS.Path.basename(this.path)
.replace(/\.json$/, "")
.replaceAll(/[^a-zA-Z0-9_.]/g, "");
let errorNo = ex.winLastError || ex.unixErrno;
Services.telemetry.recordEvent(
"jsonfile",
"load",
cleansedBasename,
errorNo ? errorNo.toString() : ""
);
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
Cu.reportError(ex);
@ -223,6 +241,12 @@ JSONFile.prototype = {
});
await openInfo.file.close();
await OS.File.move(this.path, openInfo.path);
Services.telemetry.recordEvent(
"jsonfile",
"load",
cleansedBasename,
"invalid_json"
);
} catch (e2) {
Cu.reportError(e2);
}