зеркало из https://github.com/mozilla/gecko-dev.git
Bug 875562 - Part 6: Implement initial crash events; r=bsmedberg
Support for main process crashes, plugin crashes, and plugin hangs is added to the crash manager. This includes a JS API for reporting them as well as support for reading the event files. There is still an issue of unbound growth on the store. This will be addressed in a subsequent patch. --HG-- extra : rebase_source : e714bf5f9c2fd9c50f2e40659c3b1a89591f3b1a
This commit is contained in:
Родитель
6c3be1e5b5
Коммит
df0a8cce6d
|
@ -346,19 +346,62 @@ this.CrashManager.prototype = Object.freeze({
|
|||
let decoder = new TextDecoder();
|
||||
data = decoder.decode(data);
|
||||
|
||||
let sepIndex = data.indexOf("\n");
|
||||
if (sepIndex == -1) {
|
||||
return this.EVENT_FILE_ERROR_MALFORMED;
|
||||
let type, time, payload;
|
||||
let start = 0;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let index = data.indexOf("\n", start);
|
||||
if (index == -1) {
|
||||
return this.EVENT_FILE_ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
let sub = data.substring(start, index);
|
||||
switch (i) {
|
||||
case 0:
|
||||
type = sub;
|
||||
break;
|
||||
case 1:
|
||||
time = sub;
|
||||
try {
|
||||
time = parseInt(time, 10);
|
||||
} catch (ex) {
|
||||
return this.EVENT_FILE_ERROR_MALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
start = index + 1;
|
||||
}
|
||||
let date = new Date(time * 1000);
|
||||
let payload = data.substring(start);
|
||||
|
||||
let type = data.substring(0, sepIndex);
|
||||
let payload = data.substring(sepIndex + 1);
|
||||
|
||||
return this._handleEventFilePayload(entry, type, payload);
|
||||
return this._handleEventFilePayload(store, entry, type, date, payload);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_handleEventFilePayload: function (entry, type, payload) {
|
||||
_handleEventFilePayload: function (store, entry, type, date, payload) {
|
||||
// The payload types and formats are documented in docs/crash-events.rst.
|
||||
// Do not change the format of an existing type. Instead, invent a new
|
||||
// type.
|
||||
|
||||
let eventMap = {
|
||||
"crash.main.1": "addMainProcessCrash",
|
||||
"crash.plugin.1": "addPluginCrash",
|
||||
"hang.plugin.1": "addPluginHang",
|
||||
};
|
||||
|
||||
if (type in eventMap) {
|
||||
let lines = payload.split("\n");
|
||||
if (lines.length > 1) {
|
||||
this._log.warn("Multiple lines unexpected in payload for " +
|
||||
entry.path);
|
||||
return this.EVENT_FILE_ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
store[eventMap[type]](payload, date);
|
||||
return this.EVENT_FILE_SUCCESS;
|
||||
}
|
||||
|
||||
// DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
|
||||
|
||||
return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
|
||||
},
|
||||
|
||||
|
@ -494,6 +537,10 @@ function CrashStore(storeDir, telemetrySizeKey) {
|
|||
}
|
||||
|
||||
CrashStore.prototype = Object.freeze({
|
||||
TYPE_MAIN_CRASH: "main-crash",
|
||||
TYPE_PLUGIN_CRASH: "plugin-crash",
|
||||
TYPE_PLUGIN_HANG: "plugin-hang",
|
||||
|
||||
/**
|
||||
* Load data from disk.
|
||||
*
|
||||
|
@ -681,6 +728,87 @@ CrashStore.prototype = Object.freeze({
|
|||
|
||||
return null;
|
||||
},
|
||||
|
||||
_ensureCrashRecord: function (id) {
|
||||
if (!this._data.crashes.has(id)) {
|
||||
this._data.crashes.set(id, {
|
||||
id: id,
|
||||
type: null,
|
||||
crashDate: null,
|
||||
});
|
||||
}
|
||||
|
||||
return this._data.crashes.get(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Record the occurrence of a crash in the main process.
|
||||
*
|
||||
* @param id (string) Crash ID. Likely a UUID.
|
||||
* @param date (Date) When the crash occurred.
|
||||
*/
|
||||
addMainProcessCrash: function (id, date) {
|
||||
let r = this._ensureCrashRecord(id);
|
||||
r.type = this.TYPE_MAIN_CRASH;
|
||||
r.crashDate = date;
|
||||
},
|
||||
|
||||
/**
|
||||
* Record the occurrence of a crash in a plugin process.
|
||||
*
|
||||
* @param id (string) Crash ID. Likely a UUID.
|
||||
* @param date (Date) When the crash occurred.
|
||||
*/
|
||||
addPluginCrash: function (id, date) {
|
||||
let r = this._ensureCrashRecord(id);
|
||||
r.type = this.TYPE_PLUGIN_CRASH;
|
||||
r.crashDate = date;
|
||||
},
|
||||
|
||||
/**
|
||||
* Record the occurrence of a hang in a plugin process.
|
||||
*
|
||||
* @param id (string) Crash ID. Likely a UUID.
|
||||
* @param date (Date) When the hang was reported.
|
||||
*/
|
||||
addPluginHang: function (id, date) {
|
||||
let r = this._ensureCrashRecord(id);
|
||||
r.type = this.TYPE_PLUGIN_HANG;
|
||||
r.crashDate = date;
|
||||
},
|
||||
|
||||
get mainProcessCrashes() {
|
||||
let crashes = [];
|
||||
for (let crash of this.crashes) {
|
||||
if (crash.isMainProcessCrash) {
|
||||
crashes.push(crash);
|
||||
}
|
||||
}
|
||||
|
||||
return crashes;
|
||||
},
|
||||
|
||||
get pluginCrashes() {
|
||||
let crashes = [];
|
||||
for (let crash of this.crashes) {
|
||||
if (crash.isPluginCrash) {
|
||||
crashes.push(crash);
|
||||
}
|
||||
}
|
||||
|
||||
return crashes;
|
||||
},
|
||||
|
||||
get pluginHangs() {
|
||||
let crashes = [];
|
||||
for (let crash of this.crashes) {
|
||||
if (crash.isPluginHang) {
|
||||
crashes.push(crash);
|
||||
}
|
||||
}
|
||||
|
||||
return crashes;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -718,6 +846,22 @@ CrashRecord.prototype = Object.freeze({
|
|||
// We currently only have 1 date, so this is easy.
|
||||
return this._o.crashDate;
|
||||
},
|
||||
|
||||
get type() {
|
||||
return this._o.type;
|
||||
},
|
||||
|
||||
get isMainProcessCrash() {
|
||||
return this._o.type == CrashStore.prototype.TYPE_MAIN_CRASH;
|
||||
},
|
||||
|
||||
get isPluginCrash() {
|
||||
return this._o.type == CrashStore.prototype.TYPE_PLUGIN_CRASH;
|
||||
},
|
||||
|
||||
get isPluginHang() {
|
||||
return this._o.type == CrashStore.prototype.TYPE_PLUGIN_HANG;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,10 +87,12 @@ this.TestingCrashManager.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
createEventsFile: function (filename, name, content, index=0, date=new Date()) {
|
||||
createEventsFile: function (filename, type, date, content, index=0) {
|
||||
let path = OS.Path.join(this._eventsDirs[index], filename);
|
||||
|
||||
let data = name + "\n" + content;
|
||||
let data = type + "\n" +
|
||||
Math.floor(date.getTime() / 1000) + "\n" +
|
||||
content;
|
||||
let encoder = new TextEncoder();
|
||||
let array = encoder.encode(data);
|
||||
|
||||
|
@ -105,21 +107,22 @@ this.TestingCrashManager.prototype = {
|
|||
*
|
||||
* We can probably delete this once we have actual events defined.
|
||||
*/
|
||||
_handleEventFilePayload: function (entry, type, payload) {
|
||||
_handleEventFilePayload: function (store, entry, type, date, payload) {
|
||||
if (type == "test.1") {
|
||||
if (payload == "malformed") {
|
||||
return this.EVENT_FILE_ERROR_MALFORMED;
|
||||
} else if (payload == "success") {
|
||||
return this.EVENT_FILE_SUCCESS;
|
||||
} else {
|
||||
// Payload is crash ID. Create a duommy record.
|
||||
this._store._data.crashes.set(payload, {id: payload, crashDate: entry.date});
|
||||
|
||||
return this.EVENT_FILE_SUCCESS;
|
||||
return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
return CrashManager.prototype._handleEventFilePayload.call(this, type,
|
||||
return CrashManager.prototype._handleEventFilePayload.call(this,
|
||||
store,
|
||||
entry,
|
||||
type,
|
||||
date,
|
||||
payload);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/osfile.jsm", this);
|
|||
|
||||
Cu.import("resource://testing-common/CrashManagerTest.jsm", this);
|
||||
|
||||
const DUMMY_DATE = new Date(1391043519000);
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
run_next_test();
|
||||
|
@ -111,9 +113,9 @@ add_task(function* test_store_expires() {
|
|||
// Ensure discovery of unprocessed events files works.
|
||||
add_task(function* test_unprocessed_events_files() {
|
||||
let m = yield getManager();
|
||||
yield m.createEventsFile("1", "test.1", "foo", 0);
|
||||
yield m.createEventsFile("2", "test.1", "bar", 0);
|
||||
yield m.createEventsFile("1", "test.1", "baz", 1);
|
||||
yield m.createEventsFile("1", "test.1", new Date(), "foo", 0);
|
||||
yield m.createEventsFile("2", "test.1", new Date(), "bar", 0);
|
||||
yield m.createEventsFile("1", "test.1", new Date(), "baz", 1);
|
||||
|
||||
let paths = yield m._getUnprocessedEventsFiles();
|
||||
Assert.equal(paths.length, 3);
|
||||
|
@ -133,7 +135,7 @@ add_task(function* test_aggregate_events_locking() {
|
|||
add_task(function* test_malformed_files_deleted() {
|
||||
let m = yield getManager();
|
||||
|
||||
yield m.createEventsFile("1", "test.1", "malformed");
|
||||
yield m.createEventsFile("1", "crash.main.1", new Date(), "foo\nbar");
|
||||
|
||||
let count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 1);
|
||||
|
@ -148,8 +150,8 @@ add_task(function* test_malformed_files_deleted() {
|
|||
add_task(function* test_aggregate_ignore_unknown_events() {
|
||||
let m = yield getManager();
|
||||
|
||||
yield m.createEventsFile("1", "test.1", "success");
|
||||
yield m.createEventsFile("2", "foobar.1", "dummy");
|
||||
yield m.createEventsFile("1", "crash.main.1", DUMMY_DATE, "id1");
|
||||
yield m.createEventsFile("2", "foobar.1", new Date(), "dummy");
|
||||
|
||||
let count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 2);
|
||||
|
@ -165,8 +167,8 @@ add_task(function* test_prune_old() {
|
|||
let m = yield getManager();
|
||||
let oldDate = new Date(Date.now() - 86400000);
|
||||
let newDate = new Date(Date.now() - 10000);
|
||||
yield m.createEventsFile("1", "test.1", "id1", 0, oldDate);
|
||||
yield m.createEventsFile("2", "test.1", "id2", 0, newDate);
|
||||
yield m.createEventsFile("1", "crash.main.1", oldDate, "id1");
|
||||
yield m.createEventsFile("2", "crash.plugin.1", newDate, "id2");
|
||||
|
||||
yield m.aggregateEventsFiles();
|
||||
|
||||
|
@ -190,13 +192,69 @@ add_task(function* test_prune_old() {
|
|||
|
||||
add_task(function* test_schedule_maintenance() {
|
||||
let m = yield getManager();
|
||||
yield m.createEventsFile("1", "test.1", "id1");
|
||||
yield m.createEventsFile("1", "crash.main.1", DUMMY_DATE, "id1");
|
||||
|
||||
let oldDate = new Date(Date.now() - m.PURGE_OLDER_THAN_DAYS * 2 * 24 * 60 * 60 * 1000);
|
||||
yield m.createEventsFile("2", "test.1", "id2", 0, oldDate);
|
||||
yield m.createEventsFile("2", "crash.main.1", oldDate, "id2");
|
||||
|
||||
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 m = yield getManager();
|
||||
yield m.createEventsFile("1", "crash.main.1", DUMMY_DATE, "id1");
|
||||
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].crashDate, DUMMY_DATE);
|
||||
|
||||
count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 0);
|
||||
});
|
||||
|
||||
add_task(function* test_multiline_crash_id_rejected() {
|
||||
let m = yield getManager();
|
||||
yield m.createEventsFile("1", "crash.main.1", DUMMY_DATE, "id1\nid2");
|
||||
yield m.aggregateEventsFiles();
|
||||
let crashes = yield m.getCrashes();
|
||||
Assert.equal(crashes.length, 0);
|
||||
});
|
||||
|
||||
add_task(function* test_plugin_crash_event_file() {
|
||||
let m = yield getManager();
|
||||
yield m.createEventsFile("1", "crash.plugin.1", DUMMY_DATE, "id1");
|
||||
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, "plugin-crash");
|
||||
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
|
||||
|
||||
count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 0);
|
||||
});
|
||||
|
||||
add_task(function* test_plugin_hang_event_file() {
|
||||
let m = yield getManager();
|
||||
yield m.createEventsFile("1", "hang.plugin.1", DUMMY_DATE, "id1");
|
||||
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, "plugin-hang");
|
||||
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
|
||||
|
||||
count = yield m.aggregateEventsFiles();
|
||||
Assert.equal(count, 0);
|
||||
});
|
||||
|
|
|
@ -25,10 +25,6 @@ function getStore() {
|
|||
yield OS.File.makeDir(storeDir, {unixMode: OS.Constants.libc.S_IRWXU});
|
||||
|
||||
let s = new CrashStore(storeDir);
|
||||
s._addCrash = (id, date) => {
|
||||
s._data.crashes.set(id, {id: id, crashDate: date});
|
||||
}
|
||||
|
||||
yield s.load();
|
||||
|
||||
return s;
|
||||
|
@ -49,8 +45,7 @@ add_task(function test_add_crash() {
|
|||
|
||||
Assert.equal(s.crashesCount, 0);
|
||||
let d = new Date(Date.now() - 5000);
|
||||
// TODO use official APIs once they are implemented.
|
||||
s._addCrash("id1", d);
|
||||
s.addMainProcessCrash("id1", d);
|
||||
|
||||
Assert.equal(s.crashesCount, 1);
|
||||
|
||||
|
@ -61,7 +56,7 @@ add_task(function test_add_crash() {
|
|||
Assert.equal(c.id, "id1", "ID set properly.");
|
||||
Assert.equal(c.crashDate.getTime(), d.getTime(), "Date set.");
|
||||
|
||||
s._addCrash("id2", new Date());
|
||||
s.addMainProcessCrash("id2", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
});
|
||||
|
||||
|
@ -72,8 +67,8 @@ add_task(function test_save_load() {
|
|||
|
||||
let d1 = new Date();
|
||||
let d2 = new Date(d1.getTime() - 10000);
|
||||
s._addCrash("id1", d1);
|
||||
s._addCrash("id2", d2);
|
||||
s.addMainProcessCrash("id1", d1);
|
||||
s.addMainProcessCrash("id2", d2);
|
||||
|
||||
yield s.save();
|
||||
|
||||
|
@ -102,3 +97,87 @@ add_task(function test_corrupt_json() {
|
|||
Assert.ok(s.corruptDate);
|
||||
Assert.equal(date.getTime(), s.corruptDate.getTime());
|
||||
});
|
||||
|
||||
add_task(function* test_add_main_crash() {
|
||||
let s = yield getStore();
|
||||
|
||||
s.addMainProcessCrash("id1", new Date());
|
||||
Assert.equal(s.crashesCount, 1);
|
||||
|
||||
let c = s.crashes[0];
|
||||
Assert.ok(c.crashDate);
|
||||
Assert.equal(c.type, bsp.CrashStore.prototype.TYPE_MAIN_CRASH);
|
||||
Assert.ok(c.isMainProcessCrash);
|
||||
|
||||
s.addMainProcessCrash("id2", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
|
||||
// Duplicate.
|
||||
s.addMainProcessCrash("id1", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
|
||||
Assert.equal(s.mainProcessCrashes.length, 2);
|
||||
});
|
||||
|
||||
add_task(function* test_add_plugin_crash() {
|
||||
let s = yield getStore();
|
||||
|
||||
s.addPluginCrash("id1", new Date());
|
||||
Assert.equal(s.crashesCount, 1);
|
||||
|
||||
let c = s.crashes[0];
|
||||
Assert.ok(c.crashDate);
|
||||
Assert.equal(c.type, bsp.CrashStore.prototype.TYPE_PLUGIN_CRASH);
|
||||
Assert.ok(c.isPluginCrash);
|
||||
|
||||
s.addPluginCrash("id2", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
|
||||
s.addPluginCrash("id1", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
|
||||
Assert.equal(s.pluginCrashes.length, 2);
|
||||
});
|
||||
|
||||
add_task(function* test_add_plugin_hang() {
|
||||
let s = yield getStore();
|
||||
|
||||
s.addPluginHang("id1", new Date());
|
||||
Assert.equal(s.crashesCount, 1);
|
||||
|
||||
let c = s.crashes[0];
|
||||
Assert.ok(c.crashDate);
|
||||
Assert.equal(c.type, bsp.CrashStore.prototype.TYPE_PLUGIN_HANG);
|
||||
Assert.ok(c.isPluginHang);
|
||||
|
||||
s.addPluginHang("id2", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
|
||||
s.addPluginHang("id1", new Date());
|
||||
Assert.equal(s.crashesCount, 2);
|
||||
|
||||
Assert.equal(s.pluginHangs.length, 2);
|
||||
});
|
||||
|
||||
add_task(function* test_add_mixed_types() {
|
||||
let s = yield getStore();
|
||||
|
||||
s.addMainProcessCrash("main", new Date());
|
||||
s.addPluginCrash("pcrash", new Date());
|
||||
s.addPluginHang("phang", new Date());
|
||||
|
||||
Assert.equal(s.crashesCount, 3);
|
||||
|
||||
yield s.save();
|
||||
|
||||
s._data.crashes.clear();
|
||||
Assert.equal(s.crashesCount, 0);
|
||||
|
||||
yield s.load();
|
||||
|
||||
Assert.equal(s.crashesCount, 3);
|
||||
|
||||
Assert.equal(s.mainProcessCrashes.length, 1);
|
||||
Assert.equal(s.pluginCrashes.length, 1);
|
||||
Assert.equal(s.pluginHangs.length, 1);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче