--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -1,24 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const myScope = this;
Cu.import("resource://gre/modules/Log.jsm", this);
-Cu.import("resource://gre/modules/osfile.jsm", this)
+Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://services-common/utils.js", this);
+Cu.import("resource://gre/modules/TelemetryController.jsm");
this.EXPORTED_SYMBOLS = [
"CrashManager",
];
/**
* How long to wait after application startup before crash event files are
* automatically aggregated.
@@ -524,16 +526,42 @@ this.CrashManager.prototype = Object.fre
let crashID = lines[0];
let metadata = {};
for (let i = 1; i < lines.length; i++) {
let [key, val] = lines[i].split("=");
metadata[key] = val;
}
store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
crashID, date, metadata);
+
+ // If we have a saved environment, use it. Otherwise report
+ // the current environment.
+ let crashEnvironment = null;
+ let reportMeta = Cu.cloneInto(metadata, myScope);
+ if ('TelemetryEnvironment' in reportMeta) {
+ try {
+ crashEnvironment = JSON.parse(reportMeta.TelemetryEnvironment);
+ } catch(e) {
+ Cu.reportError(e);
+ }
+ delete reportMeta.TelemetryEnvironment;
+ }
+ TelemetryController.submitExternalPing("crash",
+ {
+ version: 1,
+ crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
+ metadata: reportMeta,
+ hasCrashEnvironment: (crashEnvironment !== null),
+ },
+ {
+ retentionDays: 180,
+ addClientId: true,
+ addEnvironment: true,
+ overrideEnvironment: crashEnvironment,
+ });
break;
case "crash.submission.1":
if (lines.length == 3) {
let [crashID, result, remoteID] = lines;
store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
crashID, date);
@@ -768,24 +796,20 @@ CrashStore.prototype = Object.freeze({
// days stored in the payload matches up to actual data.
let actualCounts = new Map();
// In the past, submissions were stored as separate crash records
// with an id of e.g. "someID-submission". If we find IDs ending
// with "-submission", we will need to convert the data to be stored
// as actual submissions.
//
- // TODO: The old way of storing submissions was used from FF33 - FF34.
- // We should drop the conversion code after a few releases. See bug
- // 1056157.
- let hasSubmissionsStoredAsCrashes = false;
-
+ // The old way of storing submissions was used from FF33 - FF34. We
+ // drop this old data on the floor.
for (let id in data.crashes) {
if (id.endsWith("-submission")) {
- hasSubmissionsStoredAsCrashes = true;
continue;
}
let crash = data.crashes[id];
let denormalized = this._denormalize(crash);
denormalized.submissions = new Map();
if (crash.submissions) {
@@ -806,42 +830,16 @@ CrashStore.prototype = Object.freeze({
if (denormalized.metadata &&
denormalized.metadata.OOMAllocationSize) {
let oomKey = key + "-oom";
actualCounts.set(oomKey, (actualCounts.get(oomKey) || 0) + 1);
}
}
- if (hasSubmissionsStoredAsCrashes) {
- for (let id in data.crashes) {
- if (!id.endsWith("-submission")) {
- continue;
- }
-
- // This type of record will contain e.g.:
- // {
- // "id": "crash1-submission",
- // "type": "main-crash-submission-succeeded",
- // "crashDate": "...",
- // }
- let submissionData = this._denormalize(data.crashes[id]);
-
- let crashID = id.replace(/-submission$/, "");
- let result = submissionData.type.endsWith("-succeeded") ?
- CrashManager.prototype.SUBMISSION_RESULT_OK :
- CrashManager.prototype.SUBMISSION_RESULT_FAILED;
-
- this.addSubmissionAttempt(crashID, "converted",
- submissionData.crashDate);
- this.addSubmissionResult(crashID, "converted",
- submissionData.crashDate, result);
- }
- }
-
// The validation in this loop is arguably not necessary. We perform
// it as a defense against unknown bugs.
for (let dayKey in data.countsByDay) {
let day = parseInt(dayKey, 10);
for (let type in data.countsByDay[day]) {
this._ensureCountsForDay(day);
let count = data.countsByDay[day][type];
@@ -1155,73 +1153,69 @@ CrashStore.prototype = Object.freeze({
crashes.push(crash);
}
}
return crashes;
},
/**
- * Obtain a particular crash submission from its ID.
- *
- * @return undefined | submission object
- */
- getSubmission: function (crashID, submissionID) {
- let crash = this._data.crashes.get(crashID);
- if (!crash || !submissionID) {
- return undefined;
- }
-
- return crash.submissions.get(submissionID);
- },
-
- /**
* Ensure the submission record is present in storage.
+ * @returns [submission, crash]
*/
_ensureSubmissionRecord: function (crashID, submissionID) {
let crash = this._data.crashes.get(crashID);
if (!crash || !submissionID) {
return null;
}
if (!crash.submissions.has(submissionID)) {
crash.submissions.set(submissionID, {
requestDate: null,
responseDate: null,
result: null,
});
}
- return crash.submissions.get(submissionID);
+ return [crash.submissions.get(submissionID), crash];
},
/**
* @return boolean True if the attempt was recorded.
*/
addSubmissionAttempt: function (crashID, submissionID, date) {
- let submission = this._ensureSubmissionRecord(crashID, submissionID);
+ let [submission, crash] =
+ this._ensureSubmissionRecord(crashID, submissionID);
if (!submission) {
return false;
}
submission.requestDate = date;
+ Services.telemetry.getKeyedHistogramById("PROCESS_CRASH_SUBMIT_ATTEMPT")
+ .add(crash.type, 1);
return true;
},
/**
* @return boolean True if the response was recorded.
*/
addSubmissionResult: function (crashID, submissionID, date, result) {
- let submission = this.getSubmission(crashID, submissionID);
+ let crash = this._data.crashes.get(crashID);
+ if (!crash || !submissionID) {
+ return false;
+ }
+ let submission = crash.submissions.get(submissionID);
if (!submission) {
return false;
}
submission.responseDate = date;
submission.result = result;
+ Services.telemetry.getKeyedHistogramById("PROCESS_CRASH_SUBMIT_SUCCESS")
+ .add(crash.type, result == "ok");
return true;
},
/**
* @return boolean True if the classifications were set.
*/
setCrashClassifications: function (crashID, classifications) {
let crash = this._data.crashes.get(crashID);
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -4,28 +4,31 @@
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
let bsp = Cu.import("resource://gre/modules/CrashManager.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
Cu.import("resource://testing-common/CrashManagerTest.jsm", this);
+Cu.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
const DUMMY_DATE = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000);
DUMMY_DATE.setMilliseconds(0);
const DUMMY_DATE_2 = new Date(Date.now() - 20 * 24 * 60 * 60 * 1000);
DUMMY_DATE_2.setMilliseconds(0);
function run_test() {
do_get_profile();
configureLogging();
+ TelemetryArchiveTesting.setup();
run_next_test();
}
add_task(function* test_constructor_ok() {
let m = new CrashManager({
pendingDumpsDir: "/foo",
submittedDumpsDir: "/bar",
eventsDirs: [],
@@ -202,28 +205,69 @@ add_task(function* test_schedule_mainten
yield m.scheduleMaintenance(25);
let crashes = yield m.getCrashes();
Assert.equal(crashes.length, 1);
Assert.equal(crashes[0].id, "id1");
});
add_task(function* test_main_crash_event_file() {
+ let ac = new TelemetryArchiveTesting.Checker();
+ yield ac.promiseInit();
+ let theEnvironment = TelemetryEnvironment.currentEnvironment;
+
let m = yield getManager();
- yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1\nk1=v1\nk2=v2");
+ yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1\nk1=v1\nk2=v2\nTelemetryEnvironment=" + JSON.stringify(theEnvironment));
let count = yield m.aggregateEventsFiles();
Assert.equal(count, 1);
let crashes = yield m.getCrashes();
Assert.equal(crashes.length, 1);
Assert.equal(crashes[0].id, "id1");
Assert.equal(crashes[0].type, "main-crash");
- Assert.deepEqual(crashes[0].metadata, { k1: "v1", k2: "v2"});
+ Assert.equal(crashes[0].metadata.k1, "v1");
+ Assert.equal(crashes[0].metadata.k2, "v2");
+ Assert.ok(crashes[0].metadata.TelemetryEnvironment);
+ Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 3);
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
+ let found = yield ac.promiseFindPing("crash", [
+ [["payload", "hasCrashEnvironment"], true],
+ [["payload", "metadata", "k1"], "v1"],
+ ]);
+ Assert.ok(found, "Telemetry ping submitted for found crash");
+ Assert.deepEqual(found.environment, theEnvironment, "The saved environment should be present");
+
+ count = yield m.aggregateEventsFiles();
+ Assert.equal(count, 0);
+});
+
+add_task(function* test_main_crash_event_file_noenv() {
+ let ac = new TelemetryArchiveTesting.Checker();
+ yield ac.promiseInit();
+
+ let m = yield getManager();
+ yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1\nk1=v3\nk2=v2");
+ let count = yield m.aggregateEventsFiles();
+ Assert.equal(count, 1);
+
+ let crashes = yield m.getCrashes();
+ Assert.equal(crashes.length, 1);
+ Assert.equal(crashes[0].id, "id1");
+ Assert.equal(crashes[0].type, "main-crash");
+ Assert.deepEqual(crashes[0].metadata, { k1: "v3", k2: "v2"});
+ Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
+
+ let found = yield ac.promiseFindPing("crash", [
+ [["payload", "hasCrashEnvironment"], false],
+ [["payload", "metadata", "k1"], "v3"],
+ ]);
+ Assert.ok(found, "Telemetry ping submitted for found crash");
+ Assert.ok(found.environment, "There is an environment");
+
count = yield m.aggregateEventsFiles();
Assert.equal(count, 0);
});
add_task(function* test_crash_submission_event_file() {
let m = yield getManager();
yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "crash1");
yield m.createEventsFile("1-submission", "crash.submission.1", DUMMY_DATE_2,
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
@@ -487,97 +487,52 @@ add_task(function* test_high_water() {
Assert.equal(s._countsByDay.get(day1).
get(PROCESS_TYPE_PLUGIN + "-" + CRASH_TYPE_CRASH),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
Assert.equal(s._countsByDay.get(day1).
get(PROCESS_TYPE_PLUGIN + "-" + CRASH_TYPE_HANG),
s.HIGH_WATER_DAILY_THRESHOLD + 1);
});
-add_task(function* test_getSubmission() {
- let s = yield getStore();
-
- Assert.equal(s.getSubmission("crash1", "sub1"), undefined);
- Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "crash1",
- DUMMY_DATE));
- Assert.equal(s.getSubmission("crash1", "sub1"), undefined);
- Assert.ok(s.addSubmissionAttempt("crash1", "sub1", DUMMY_DATE));
- Assert.ok(s.getSubmission("crash1", "sub1"));
-});
-
add_task(function* test_addSubmission() {
let s = yield getStore();
Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "crash1",
DUMMY_DATE));
Assert.ok(s.addSubmissionAttempt("crash1", "sub1", DUMMY_DATE));
- let submission = s.getSubmission("crash1", "sub1");
+ let crash = s.getCrash("crash1");
+ let submission = crash.submissions.get("sub1");
Assert.ok(!!submission);
Assert.equal(submission.requestDate.getTime(), DUMMY_DATE.getTime());
Assert.equal(submission.responseDate, null);
Assert.equal(submission.result, null);
Assert.ok(s.addSubmissionResult("crash1", "sub1", DUMMY_DATE_2,
SUBMISSION_RESULT_FAILED));
- submission = s.getSubmission("crash1", "sub1");
+ crash = s.getCrash("crash1");
+ Assert.equal(crash.submissions.size, 1);
+ submission = crash.submissions.get("sub1");
Assert.ok(!!submission);
Assert.equal(submission.requestDate.getTime(), DUMMY_DATE.getTime());
Assert.equal(submission.responseDate.getTime(), DUMMY_DATE_2.getTime());
Assert.equal(submission.result, SUBMISSION_RESULT_FAILED);
Assert.ok(s.addSubmissionAttempt("crash1", "sub2", DUMMY_DATE));
Assert.ok(s.addSubmissionResult("crash1", "sub2", DUMMY_DATE_2,
SUBMISSION_RESULT_OK));
- submission = s.getSubmission("crash1", "sub2");
+ Assert.equal(crash.submissions.size, 2);
+ submission = crash.submissions.get("sub2");
Assert.ok(!!submission);
Assert.equal(submission.result, SUBMISSION_RESULT_OK);
});
-add_task(function* test_convertSubmissionsStoredAsCrashes() {
- let s = yield getStore();
-
- let addSubmissionAsCrash = (processType, crashType, succeeded, id, date) => {
- id = id + "-submission";
- let process = processType + "-" + crashType + "-submission";
- let submissionType = succeeded ? "succeeded" : "failed";
- return s.addCrash(process, submissionType, id, date);
- };
-
- Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "crash1",
- new Date()));
- Assert.ok(addSubmissionAsCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, true,
- "crash1", DUMMY_DATE));
-
- Assert.ok(s.addCrash(PROCESS_TYPE_PLUGIN, CRASH_TYPE_HANG, "hang1",
- new Date()));
- Assert.ok(addSubmissionAsCrash(PROCESS_TYPE_PLUGIN, CRASH_TYPE_HANG, false,
- "hang1", DUMMY_DATE_2));
-
- Assert.equal(s.crashes.length, 4);
- yield s.save();
- yield s.load();
- Assert.equal(s.crashes.length, 2);
-
- let submission = s.getSubmission("crash1", "converted");
- Assert.ok(!!submission);
- Assert.equal(submission.result, SUBMISSION_RESULT_OK);
- Assert.equal(submission.requestDate.getTime(), DUMMY_DATE.getTime());
- Assert.equal(submission.responseDate.getTime(), DUMMY_DATE.getTime());
-
- submission = s.getSubmission("hang1", "converted");
- Assert.ok(!!submission);
- Assert.equal(submission.result, SUBMISSION_RESULT_FAILED);
- Assert.equal(submission.requestDate.getTime(), DUMMY_DATE_2.getTime());
- Assert.equal(submission.responseDate.getTime(), DUMMY_DATE_2.getTime());
-});
-
add_task(function* test_setCrashClassification() {
let s = yield getStore();
Assert.ok(s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "crash1",
new Date()));
let classifications = s.crashes[0].classifications;
Assert.ok(!!classifications);
Assert.equal(classifications.length, 0);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7620,16 +7620,30 @@
"description": "Counts of plugin/content process abnormal shutdown, whether or not a crash report was available."
},
"SUBPROCESS_CRASHES_WITH_DUMP": {
"expires_in_version": "never",
"kind": "count",
"keyed": true,
"description": "Counts of plugin and content process crashes which are reported with a crash dump."
},
+ "PROCESS_CRASH_SUBMIT_ATTEMPT": {
+ "expires_in_version": "never",
+ "kind": "count",
+ "keyed": true,
+ "releaseChannelCollection": "opt-out",
+ "description": "An attempt to submit a crash. Keyed on the CrashManager Crash.type."
+ },
+ "PROCESS_CRASH_SUBMIT_SUCCESS": {
+ "expires_in_version": "never",
+ "kind": "boolean",
+ "keyed": true,
+ "releaseChannelCollection": "opt-out",
+ "description": "The submission status when main/plugin/content crashes are submitted. 1 is success, 0 is failure. Keyed on the CrashManager Crash.type."
+ },
"STUMBLER_TIME_BETWEEN_UPLOADS_SEC": {
"expires_in_version": "45",
"kind": "exponential",
"n_buckets": 50,
"high": 259200,
"description": "Stumbler: The time in seconds between uploads."
},
"STUMBLER_VOLUME_BYTES_UPLOADED_PER_SEC": {
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -891,23 +891,23 @@ EnvironmentCache.prototype = {
},
/**
* Get the build data in object form.
* @return Object containing the build data.
*/
_getBuild: function () {
let buildData = {
- applicationId: Services.appinfo.ID,
- applicationName: Services.appinfo.name,
+ applicationId: Services.appinfo.ID || null,
+ applicationName: Services.appinfo.name || null,
architecture: Services.sysinfo.get("arch"),
- buildId: Services.appinfo.appBuildID,
- version: Services.appinfo.version,
- vendor: Services.appinfo.vendor,
- platformVersion: Services.appinfo.platformVersion,
+ buildId: Services.appinfo.appBuildID || null,
+ version: Services.appinfo.version || null,
+ vendor: Services.appinfo.vendor || null,
+ platformVersion: Services.appinfo.platformVersion || null,
xpcomAbi: Services.appinfo.XPCOMABI,
hotfixVersion: Preferences.get(PREF_HOTFIX_LASTVERSION, null),
};
// Add |architecturesInBinary| only for Mac Universal builds.
if ("@mozilla.org/xpcom/mac-utils;1" in Cc) {
let macUtils = Cc["@mozilla.org/xpcom/mac-utils;1"].getService(Ci.nsIMacUtils);
if (macUtils && macUtils.isUniversalBinary) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/crash-ping.rst
@@ -0,0 +1,24 @@
+
+"crash" ping
+============
+
+This ping is captured after the main Firefox process crashes, whether or not the crash report is submitted to crash-stats.mozilla.org. It includes non-identifying metadata about the crash.
+
+The environment block that is sent with this ping varies: if Firefox was running long enough to record the environment block before the crash, then the environment at the time of the crash will be recorded and ``hasCrashEnvironment`` will be true. If Firefox crashed before the environment was recorded, ``hasCrashEnvironment`` will be false and the recorded environment will be the environment at time of submission.
+
+The client ID is submitted with this ping.
+
+Structure::
+
+ {
+ version: 1,
+ type: "crash",
+ ... common ping data
+ clientId: <UUID>,
+ environment: { ... },
+ payload: {
+ crashDate: "YYYY-MM-DD",
+ metadata: {...}, // Annotations saved while Firefox was running. See nsExceptionHandler.cpp for more information
+ hasCrashEnvironment: bool
+ }
+ }
--- a/toolkit/components/telemetry/docs/pings.rst
+++ b/toolkit/components/telemetry/docs/pings.rst
@@ -24,17 +24,18 @@ The telemetry server team is working tow
* `2XX` - success, don't resubmit
* `4XX` - there was some problem with the request - the client should not try to resubmit as it would just receive the same response
* `5XX` - there was a server-side error, the client should try to resubmit later
Ping types
==========
* :doc:`main <main-ping>` - contains the information collected by Telemetry (Histograms, hang stacks, ...)
-* :doc:`saved-session <main-ping>` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side.
+* :doc:`saved-session <main-ping>` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side. This is temporary and will be removed soon.
+* :doc:`crash <crash-ping>` - a ping that is captured and sent after Firefox crashes.
* ``activation`` - *planned* - sent right after installation or profile creation
* ``upgrade`` - *planned* - sent right after an upgrade
* ``deletion`` - *planned* - on opt-out we may have to tell the server to delete user data
Archiving
=========
When archiving is enabled through the relative preference, pings submitted to ``TelemetryController`` are also stored locally in the user profile directory, in `<profile-dir>/datareporting/archived`.
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -40,16 +40,20 @@ EXTRA_JS_MODULES += [
]
EXTRA_PP_JS_MODULES += [
'TelemetryController.jsm',
'TelemetryEnvironment.jsm',
'TelemetrySession.jsm',
]
+TESTING_JS_MODULES += [
+ 'tests/unit/TelemetryArchiveTesting.jsm',
+]
+
FAIL_ON_WARNINGS = True
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
GENERATED_FILES = [
'TelemetryHistogramData.inc',
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/TelemetryArchiveTesting.jsm
@@ -0,0 +1,89 @@
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/TelemetryArchive.jsm");
+Cu.import("resource://testing-common/Assert.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
+
+this.EXPORTED_SYMBOLS = [
+ "TelemetryArchiveTesting",
+];
+
+function checkForProperties(ping, expected) {
+ for (let [props, val] of expected) {
+ let test = ping;
+ for (let prop of props) {
+ test = test[prop];
+ if (test === undefined) {
+ return false;
+ }
+ }
+ if (test !== val) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * A helper object that allows test code to check whether a telemetry ping
+ * was properly saved. To use, first initialize to collect the starting pings
+ * and then check for new ping data.
+ */
+function Checker() {
+}
+Checker.prototype = {
+ promiseInit: function() {
+ this._pingMap = new Map();
+ return TelemetryArchive.promiseArchivedPingList().then((plist) => {
+ for (let ping of plist) {
+ this._pingMap.set(ping.id, ping);
+ }
+ });
+ },
+
+ /**
+ * Find and return a new ping with certain properties.
+ *
+ * @param expected: an array of [['prop'...], 'value'] to check
+ * For example:
+ * [
+ * [['environment', 'build', 'applicationId'], '20150101010101'],
+ * [['version'], 1],
+ * [['metadata', 'OOMAllocationSize'], 123456789],
+ * ]
+ * @returns a matching ping if found, or null
+ */
+ promiseFindPing: Task.async(function*(type, expected) {
+ let candidates = [];
+ let plist = yield TelemetryArchive.promiseArchivedPingList();
+ for (let ping of plist) {
+ if (this._pingMap.has(ping.id)) {
+ continue;
+ }
+ if (ping.type == type) {
+ candidates.push(ping);
+ }
+ }
+
+ for (let candidate of candidates) {
+ let ping = yield TelemetryArchive.promiseArchivedPingById(candidate.id);
+ if (checkForProperties(ping, expected)) {
+ return ping;
+ }
+ }
+ return null;
+ }),
+};
+
+const TelemetryArchiveTesting = {
+ setup: function() {
+ Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
+ Services.prefs.setBoolPref("datareporting.healthreport.service.enabled", true);
+ Services.prefs.setCharPref("toolkit.telemetry.log.level", "Trace");
+ Services.prefs.setBoolPref("toolkit.telemetry.archive.enabled", true);
+ TelemetryController.initLogging();
+ },
+
+ Checker: Checker,
+};