mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-07 03:38:51 +02:00
1109 lines
33 KiB
JavaScript
1109 lines
33 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/
|
|
*/
|
|
|
|
ChromeUtils.defineESModuleGetters(this, {
|
|
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
|
});
|
|
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
|
);
|
|
|
|
const PRERELEASE_CHANNELS = Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS;
|
|
const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS;
|
|
|
|
function checkEventFormat(events) {
|
|
Assert.ok(Array.isArray(events), "Events should be serialized to an array.");
|
|
for (let e of events) {
|
|
Assert.ok(Array.isArray(e), "Event should be an array.");
|
|
Assert.greaterOrEqual(
|
|
e.length,
|
|
4,
|
|
"Event should have at least 4 elements."
|
|
);
|
|
Assert.lessOrEqual(e.length, 6, "Event should have at most 6 elements.");
|
|
|
|
Assert.equal(typeof e[0], "number", "Element 0 should be a number.");
|
|
Assert.equal(typeof e[1], "string", "Element 1 should be a string.");
|
|
Assert.equal(typeof e[2], "string", "Element 2 should be a string.");
|
|
Assert.equal(typeof e[3], "string", "Element 3 should be a string.");
|
|
|
|
if (e.length > 4) {
|
|
Assert.ok(
|
|
e[4] === null || typeof e[4] == "string",
|
|
"Event element 4 should be null or a string."
|
|
);
|
|
}
|
|
if (e.length > 5) {
|
|
Assert.ok(
|
|
e[5] === null || typeof e[5] == "object",
|
|
"Event element 5 should be null or an object."
|
|
);
|
|
}
|
|
|
|
let extra = e[5];
|
|
if (extra) {
|
|
Assert.ok(
|
|
Object.keys(extra).every(k => typeof k == "string"),
|
|
"All extra keys should be strings."
|
|
);
|
|
Assert.ok(
|
|
Object.values(extra).every(v => typeof v == "string"),
|
|
"All extra values should be strings."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param summaries is of the form
|
|
* [{process, [event category, event object, event method], count}]
|
|
* @param clearScalars - true if you want to clear the scalars
|
|
*/
|
|
function checkEventSummary(summaries, clearScalars) {
|
|
let scalars = Telemetry.getSnapshotForKeyedScalars("main", clearScalars);
|
|
|
|
for (let [process, [category, eObject, method], count] of summaries) {
|
|
let uniqueEventName = `${category}#${eObject}#${method}`;
|
|
let summaryCount;
|
|
if (process === "dynamic") {
|
|
summaryCount =
|
|
scalars.dynamic["telemetry.dynamic_event_counts"][uniqueEventName];
|
|
} else {
|
|
summaryCount =
|
|
scalars[process]["telemetry.event_counts"][uniqueEventName];
|
|
}
|
|
Assert.equal(
|
|
summaryCount,
|
|
count,
|
|
`${uniqueEventName} had wrong summary count`
|
|
);
|
|
}
|
|
}
|
|
|
|
function checkRegistrationFailure(failureType) {
|
|
let snapshot = Telemetry.getSnapshotForHistograms("main", true);
|
|
Assert.ok(
|
|
"parent" in snapshot,
|
|
"There should be at least one parent histogram when checking for registration failures."
|
|
);
|
|
Assert.ok(
|
|
"TELEMETRY_EVENT_REGISTRATION_ERROR" in snapshot.parent,
|
|
"TELEMETRY_EVENT_REGISTRATION_ERROR should exist when checking for registration failures."
|
|
);
|
|
let values = snapshot.parent.TELEMETRY_EVENT_REGISTRATION_ERROR.values;
|
|
Assert.ok(
|
|
!!values,
|
|
"TELEMETRY_EVENT_REGISTRATION_ERROR's values should exist when checking for registration failures."
|
|
);
|
|
Assert.equal(
|
|
values[failureType],
|
|
1,
|
|
`Event registration ought to have failed due to type ${failureType}`
|
|
);
|
|
}
|
|
|
|
function checkRecordingFailure(failureType) {
|
|
let snapshot = Telemetry.getSnapshotForHistograms("main", true);
|
|
Assert.ok(
|
|
"parent" in snapshot,
|
|
"There should be at least one parent histogram when checking for recording failures."
|
|
);
|
|
Assert.ok(
|
|
"TELEMETRY_EVENT_RECORDING_ERROR" in snapshot.parent,
|
|
"TELEMETRY_EVENT_RECORDING_ERROR should exist when checking for recording failures."
|
|
);
|
|
let values = snapshot.parent.TELEMETRY_EVENT_RECORDING_ERROR.values;
|
|
Assert.ok(
|
|
!!values,
|
|
"TELEMETRY_EVENT_RECORDING_ERROR's values should exist when checking for recording failures."
|
|
);
|
|
Assert.equal(
|
|
values[failureType],
|
|
1,
|
|
`Event recording ought to have failed due to type ${failureType}`
|
|
);
|
|
}
|
|
|
|
add_task(async function test_event_summary_limit() {
|
|
Telemetry.clearEvents();
|
|
Telemetry.clearScalars();
|
|
|
|
const limit = 500; // matches kMaxEventSummaryKeys in TelemetryScalar.cpp.
|
|
let objects = [];
|
|
for (let i = 0; i < limit + 1; i++) {
|
|
objects.push("object" + i);
|
|
}
|
|
// Using "telemetry.test.dynamic" as using "telemetry.test" will enable
|
|
// the "telemetry.test" category.
|
|
Telemetry.registerEvents("telemetry.test.dynamic", {
|
|
test_method: {
|
|
methods: ["testMethod"],
|
|
objects,
|
|
record_on_release: true,
|
|
},
|
|
});
|
|
for (let object of objects) {
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "testMethod", object);
|
|
}
|
|
|
|
TelemetryTestUtils.assertNumberOfEvents(
|
|
limit + 1,
|
|
{},
|
|
{ process: "dynamic" }
|
|
);
|
|
let scalarSnapshot = Telemetry.getSnapshotForKeyedScalars("main", true);
|
|
Assert.equal(
|
|
Object.keys(scalarSnapshot.dynamic["telemetry.dynamic_event_counts"])
|
|
.length,
|
|
limit,
|
|
"Should not have recorded more than `limit` events"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_recording_state() {
|
|
Telemetry.clearEvents();
|
|
Telemetry.clearScalars();
|
|
|
|
const events = [
|
|
["telemetry.test", "test1", "object1"],
|
|
["telemetry.test.second", "test", "object1"],
|
|
];
|
|
|
|
// Both test categories should be off by default.
|
|
events.forEach(e => Telemetry.recordEvent(...e));
|
|
TelemetryTestUtils.assertEvents([]);
|
|
checkEventSummary(
|
|
events.map(e => ["parent", e, 1]),
|
|
true
|
|
);
|
|
|
|
// Enable one test category and see that we record correctly.
|
|
Telemetry.setEventRecordingEnabled("telemetry.test", true);
|
|
events.forEach(e => Telemetry.recordEvent(...e));
|
|
TelemetryTestUtils.assertEvents([events[0]]);
|
|
checkEventSummary(
|
|
events.map(e => ["parent", e, 1]),
|
|
true
|
|
);
|
|
|
|
// Also enable the other test category and see that we record correctly.
|
|
Telemetry.setEventRecordingEnabled("telemetry.test.second", true);
|
|
events.forEach(e => Telemetry.recordEvent(...e));
|
|
TelemetryTestUtils.assertEvents(events);
|
|
checkEventSummary(
|
|
events.map(e => ["parent", e, 1]),
|
|
true
|
|
);
|
|
|
|
// Now turn of one category again and check that this works as expected.
|
|
Telemetry.setEventRecordingEnabled("telemetry.test", false);
|
|
events.forEach(e => Telemetry.recordEvent(...e));
|
|
TelemetryTestUtils.assertEvents([events[1]]);
|
|
checkEventSummary(
|
|
events.map(e => ["parent", e, 1]),
|
|
true
|
|
);
|
|
});
|
|
|
|
add_task(async function recording_setup() {
|
|
// Make sure both test categories are enabled for the remaining tests.
|
|
// Otherwise their event recording won't work.
|
|
Telemetry.setEventRecordingEnabled("telemetry.test", true);
|
|
Telemetry.setEventRecordingEnabled("telemetry.test.second", true);
|
|
});
|
|
|
|
add_task(async function test_recording() {
|
|
Telemetry.clearScalars();
|
|
Telemetry.clearEvents();
|
|
|
|
// Record some events.
|
|
let expected = [
|
|
{ optout: false, event: ["telemetry.test", "test1", "object1"] },
|
|
{ optout: false, event: ["telemetry.test", "test2", "object2"] },
|
|
|
|
{ optout: false, event: ["telemetry.test", "test1", "object1", "value"] },
|
|
{
|
|
optout: false,
|
|
event: ["telemetry.test", "test1", "object1", "value", null],
|
|
},
|
|
{
|
|
optout: false,
|
|
event: ["telemetry.test", "test1", "object1", null, { key1: "value1" }],
|
|
},
|
|
{
|
|
optout: false,
|
|
event: [
|
|
"telemetry.test",
|
|
"test1",
|
|
"object1",
|
|
"value",
|
|
{ key1: "value1", key2: "value2" },
|
|
],
|
|
},
|
|
|
|
{ optout: true, event: ["telemetry.test", "optout", "object1"] },
|
|
{ optout: false, event: ["telemetry.test.second", "test", "object1"] },
|
|
{
|
|
optout: false,
|
|
event: [
|
|
"telemetry.test.second",
|
|
"test",
|
|
"object1",
|
|
null,
|
|
{ key1: "value1" },
|
|
],
|
|
},
|
|
];
|
|
|
|
for (let entry of expected) {
|
|
entry.tsBefore = Math.floor(Telemetry.msSinceProcessStart());
|
|
try {
|
|
Telemetry.recordEvent(...entry.event);
|
|
} catch (ex) {
|
|
Assert.ok(
|
|
false,
|
|
`Failed to record event ${JSON.stringify(entry.event)}: ${ex}`
|
|
);
|
|
}
|
|
entry.tsAfter = Math.floor(Telemetry.msSinceProcessStart());
|
|
}
|
|
|
|
// Strip off trailing null values to match the serialized events.
|
|
for (let entry of expected) {
|
|
let e = entry.event;
|
|
while (e.length >= 3 && e[e.length - 1] === null) {
|
|
e.pop();
|
|
}
|
|
}
|
|
|
|
// Check that the events were summarized properly.
|
|
let summaries = {};
|
|
expected.forEach(({ optout, event }) => {
|
|
let [category, eObject, method] = event;
|
|
let uniqueEventName = `${category}#${eObject}#${method}`;
|
|
if (!(uniqueEventName in summaries)) {
|
|
summaries[uniqueEventName] = ["parent", event, 1];
|
|
} else {
|
|
summaries[uniqueEventName][2]++;
|
|
}
|
|
});
|
|
checkEventSummary(Object.values(summaries), true);
|
|
|
|
// The following should not result in any recorded events.
|
|
Telemetry.recordEvent("unknown.category", "test1", "object1");
|
|
checkRecordingFailure(0 /* UnknownEvent */);
|
|
Telemetry.recordEvent("telemetry.test", "unknown", "object1");
|
|
checkRecordingFailure(0 /* UnknownEvent */);
|
|
Telemetry.recordEvent("telemetry.test", "test1", "unknown");
|
|
checkRecordingFailure(0 /* UnknownEvent */);
|
|
|
|
let checkEvents = (events, expectedEvents) => {
|
|
checkEventFormat(events);
|
|
Assert.equal(
|
|
events.length,
|
|
expectedEvents.length,
|
|
"Snapshot should have the right number of events."
|
|
);
|
|
|
|
for (let i = 0; i < events.length; ++i) {
|
|
let { tsBefore, tsAfter } = expectedEvents[i];
|
|
let ts = events[i][0];
|
|
Assert.greaterOrEqual(
|
|
ts,
|
|
tsBefore,
|
|
"The recorded timestamp should be greater than the one before recording."
|
|
);
|
|
Assert.lessOrEqual(
|
|
ts,
|
|
tsAfter,
|
|
"The recorded timestamp should be less than the one after recording."
|
|
);
|
|
|
|
let recordedData = events[i].slice(1);
|
|
let expectedData = expectedEvents[i].event.slice();
|
|
Assert.deepEqual(
|
|
recordedData,
|
|
expectedData,
|
|
"The recorded event data should match."
|
|
);
|
|
}
|
|
};
|
|
|
|
// Check that the expected events were recorded.
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, false);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
checkEvents(snapshot.parent, expected);
|
|
|
|
// Check serializing only opt-out events.
|
|
snapshot = Telemetry.snapshotEvents(ALL_CHANNELS, false);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
let filtered = expected.filter(e => !!e.optout);
|
|
checkEvents(snapshot.parent, filtered);
|
|
});
|
|
|
|
add_task(async function test_clear() {
|
|
Telemetry.clearEvents();
|
|
|
|
const COUNT = 10;
|
|
for (let i = 0; i < COUNT; ++i) {
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1");
|
|
Telemetry.recordEvent("telemetry.test.second", "test", "object1");
|
|
}
|
|
|
|
// Check that events were recorded.
|
|
// The events are cleared by passing the respective flag.
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
Assert.equal(
|
|
snapshot.parent.length,
|
|
2 * COUNT,
|
|
`Should have recorded ${2 * COUNT} events.`
|
|
);
|
|
|
|
// Now the events should be cleared.
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, false);
|
|
Assert.equal(
|
|
Object.keys(snapshot).length,
|
|
0,
|
|
`Should have cleared the events.`
|
|
);
|
|
|
|
for (let i = 0; i < COUNT; ++i) {
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1");
|
|
Telemetry.recordEvent("telemetry.test.second", "test", "object1");
|
|
}
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true, 5);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
Assert.equal(snapshot.parent.length, 5, "Should have returned 5 events");
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, false);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
Assert.equal(
|
|
snapshot.parent.length,
|
|
2 * COUNT - 5,
|
|
`Should have returned ${2 * COUNT - 5} events`
|
|
);
|
|
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1");
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, false, 5);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
Assert.equal(snapshot.parent.length, 5, "Should have returned 5 events");
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
Assert.equal(
|
|
snapshot.parent.length,
|
|
2 * COUNT - 5 + 1,
|
|
`Should have returned ${2 * COUNT - 5 + 1} events`
|
|
);
|
|
});
|
|
|
|
add_task(async function test_expiry() {
|
|
Telemetry.clearEvents();
|
|
|
|
// Recording call with event that is expired by version.
|
|
Telemetry.recordEvent("telemetry.test", "expired_version", "object1");
|
|
checkRecordingFailure(1 /* Expired */);
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
Object.keys(snapshot).length,
|
|
0,
|
|
"Should not record event with expired version."
|
|
);
|
|
|
|
// Recording call with event that has expiry_version set into the future.
|
|
Telemetry.recordEvent("telemetry.test", "not_expired_optout", "object1");
|
|
TelemetryTestUtils.assertNumberOfEvents(1);
|
|
});
|
|
|
|
add_task(async function test_invalidParams() {
|
|
Telemetry.clearEvents();
|
|
|
|
// Recording call with wrong type for value argument.
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", 1);
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
Object.keys(snapshot).length,
|
|
0,
|
|
"Should not record event when value argument with invalid type is passed."
|
|
);
|
|
checkRecordingFailure(3 /* Value */);
|
|
|
|
// Recording call with wrong type for extra argument.
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, "invalid");
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
Object.keys(snapshot).length,
|
|
0,
|
|
"Should not record event when extra argument with invalid type is passed."
|
|
);
|
|
checkRecordingFailure(4 /* Extra */);
|
|
|
|
// Recording call with unknown extra key.
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {
|
|
key3: "x",
|
|
});
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
Object.keys(snapshot).length,
|
|
0,
|
|
"Should not record event when extra argument with invalid key is passed."
|
|
);
|
|
checkRecordingFailure(2 /* ExtraKey */);
|
|
|
|
// Recording call with invalid value type.
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {
|
|
key3: 1,
|
|
});
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
Object.keys(snapshot).length,
|
|
0,
|
|
"Should not record event when extra argument with invalid value type is passed."
|
|
);
|
|
checkRecordingFailure(4 /* Extra */);
|
|
});
|
|
|
|
add_task(async function test_storageLimit() {
|
|
Telemetry.clearEvents();
|
|
|
|
let limitReached = TestUtils.topicObserved(
|
|
"event-telemetry-storage-limit-reached"
|
|
);
|
|
// Record more events than the storage limit allows.
|
|
let LIMIT = 1000;
|
|
let COUNT = LIMIT + 10;
|
|
for (let i = 0; i < COUNT; ++i) {
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", String(i));
|
|
}
|
|
|
|
await limitReached;
|
|
Assert.ok(true, "Topic was notified when event limit was reached");
|
|
|
|
// Check that the right events were recorded.
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.ok("parent" in snapshot, "Should have entry for main process.");
|
|
let events = snapshot.parent;
|
|
Assert.equal(
|
|
events.length,
|
|
COUNT,
|
|
`Should have only recorded all ${COUNT} events`
|
|
);
|
|
Assert.ok(
|
|
events.every((e, idx) => e[4] === String(idx)),
|
|
"Should have recorded all events."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_valueLimits() {
|
|
Telemetry.clearEvents();
|
|
|
|
// Record values that are at or over the limits for string lengths.
|
|
let LIMIT = 80;
|
|
let expected = [
|
|
["telemetry.test", "test1", "object1", "a".repeat(LIMIT - 10), null],
|
|
["telemetry.test", "test1", "object1", "a".repeat(LIMIT), null],
|
|
["telemetry.test", "test1", "object1", "a".repeat(LIMIT + 1), null],
|
|
["telemetry.test", "test1", "object1", "a".repeat(LIMIT + 10), null],
|
|
|
|
[
|
|
"telemetry.test",
|
|
"test1",
|
|
"object1",
|
|
null,
|
|
{ key1: "a".repeat(LIMIT - 10) },
|
|
],
|
|
["telemetry.test", "test1", "object1", null, { key1: "a".repeat(LIMIT) }],
|
|
[
|
|
"telemetry.test",
|
|
"test1",
|
|
"object1",
|
|
null,
|
|
{ key1: "a".repeat(LIMIT + 1) },
|
|
],
|
|
[
|
|
"telemetry.test",
|
|
"test1",
|
|
"object1",
|
|
null,
|
|
{ key1: "a".repeat(LIMIT + 10) },
|
|
],
|
|
];
|
|
|
|
for (let event of expected) {
|
|
Telemetry.recordEvent(...event);
|
|
if (event[3]) {
|
|
event[3] = event[3].substr(0, LIMIT);
|
|
} else {
|
|
event[3] = undefined;
|
|
}
|
|
if (event[4]) {
|
|
event[4].key1 = event[4].key1.substr(0, LIMIT);
|
|
}
|
|
}
|
|
|
|
// Strip off trailing null values to match the serialized events.
|
|
for (let e of expected) {
|
|
while (e.length >= 3 && e[e.length - 1] === null) {
|
|
e.pop();
|
|
}
|
|
}
|
|
|
|
// Check that the right events were recorded.
|
|
TelemetryTestUtils.assertEvents(expected);
|
|
});
|
|
|
|
add_task(async function test_unicodeValues() {
|
|
Telemetry.clearEvents();
|
|
|
|
// Record string values containing unicode characters.
|
|
let value = "漢語";
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", value);
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {
|
|
key1: value,
|
|
});
|
|
|
|
// Check that the values were correctly recorded.
|
|
TelemetryTestUtils.assertEvents([{ value }, { extra: { key1: value } }]);
|
|
});
|
|
|
|
add_task(async function test_dynamicEvents() {
|
|
Telemetry.clearEvents();
|
|
Telemetry.clearScalars();
|
|
Telemetry.canRecordExtended = true;
|
|
|
|
// Register some test events.
|
|
Telemetry.registerEvents("telemetry.test.dynamic", {
|
|
// Event with only required fields.
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
},
|
|
// Event with extra_keys.
|
|
test2: {
|
|
methods: ["test2", "test2b"],
|
|
objects: ["object1"],
|
|
extra_keys: ["key1", "key2"],
|
|
},
|
|
// Expired event.
|
|
test3: {
|
|
methods: ["test3"],
|
|
objects: ["object1"],
|
|
expired: true,
|
|
},
|
|
// A release-channel recording event.
|
|
test4: {
|
|
methods: ["test4"],
|
|
objects: ["object1"],
|
|
record_on_release: true,
|
|
},
|
|
});
|
|
|
|
// Record some valid events.
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1");
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test2", "object1", null, {
|
|
key1: "foo",
|
|
key2: "bar",
|
|
});
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test2b", "object1", null, {
|
|
key1: "foo",
|
|
key2: "bar",
|
|
});
|
|
Telemetry.recordEvent(
|
|
"telemetry.test.dynamic",
|
|
"test3",
|
|
"object1",
|
|
"some value"
|
|
);
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test4", "object1", null);
|
|
|
|
// Test recording an unknown event.
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "unknown", "unknown");
|
|
checkRecordingFailure(0 /* UnknownEvent */);
|
|
|
|
// Now check that the snapshot contains the expected data.
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, false);
|
|
Assert.ok(
|
|
"dynamic" in snapshot,
|
|
"Should have dynamic events in the snapshot."
|
|
);
|
|
|
|
let expected = [
|
|
["telemetry.test.dynamic", "test1", "object1"],
|
|
[
|
|
"telemetry.test.dynamic",
|
|
"test2",
|
|
"object1",
|
|
null,
|
|
{ key1: "foo", key2: "bar" },
|
|
],
|
|
[
|
|
"telemetry.test.dynamic",
|
|
"test2b",
|
|
"object1",
|
|
null,
|
|
{ key1: "foo", key2: "bar" },
|
|
],
|
|
// "test3" is epxired, so it should not be recorded.
|
|
["telemetry.test.dynamic", "test4", "object1"],
|
|
];
|
|
let events = snapshot.dynamic;
|
|
Assert.equal(
|
|
events.length,
|
|
expected.length,
|
|
"Should have recorded the right amount of events."
|
|
);
|
|
for (let i = 0; i < expected.length; ++i) {
|
|
Assert.deepEqual(
|
|
events[i].slice(1),
|
|
expected[i],
|
|
"Should have recorded the expected event data."
|
|
);
|
|
}
|
|
|
|
// Check that we've summarized the recorded events
|
|
checkEventSummary(
|
|
expected.map(ev => ["dynamic", ev, 1]),
|
|
true
|
|
);
|
|
|
|
// Check that the opt-out snapshot contains only the one expected event.
|
|
snapshot = Telemetry.snapshotEvents(ALL_CHANNELS, false);
|
|
Assert.ok(
|
|
"dynamic" in snapshot,
|
|
"Should have dynamic events in the snapshot."
|
|
);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
1,
|
|
"Should have one opt-out event in the snapshot."
|
|
);
|
|
expected = ["telemetry.test.dynamic", "test4", "object1"];
|
|
Assert.deepEqual(snapshot.dynamic[0].slice(1), expected);
|
|
|
|
// Recording with unknown extra keys should be ignored and print an error.
|
|
Telemetry.clearEvents();
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1", null, {
|
|
key1: "foo",
|
|
});
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test2", "object1", null, {
|
|
key1: "foo",
|
|
unknown: "bar",
|
|
});
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.ok(
|
|
!("dynamic" in snapshot),
|
|
"Should have not recorded dynamic events with unknown extra keys."
|
|
);
|
|
|
|
// Other built-in events should not show up in the "dynamic" bucket of the snapshot.
|
|
Telemetry.recordEvent("telemetry.test", "test1", "object1");
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.ok(
|
|
!("dynamic" in snapshot),
|
|
"Should have not recorded built-in event into dynamic bucket."
|
|
);
|
|
|
|
// Test that recording opt-in and opt-out events works as expected.
|
|
Telemetry.clearEvents();
|
|
Telemetry.canRecordExtended = false;
|
|
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1");
|
|
Telemetry.recordEvent("telemetry.test.dynamic", "test4", "object1");
|
|
|
|
expected = [
|
|
// Only "test4" should have been recorded.
|
|
["telemetry.test.dynamic", "test4", "object1"],
|
|
];
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
1,
|
|
"Should have one opt-out event in the snapshot."
|
|
);
|
|
Assert.deepEqual(
|
|
snapshot.dynamic.map(e => e.slice(1)),
|
|
expected
|
|
);
|
|
});
|
|
|
|
add_task(async function test_dynamicEventRegistrationValidation() {
|
|
Telemetry.canRecordExtended = true;
|
|
Telemetry.clearEvents();
|
|
|
|
// Test registration of invalid categories.
|
|
Telemetry.getSnapshotForHistograms("main", true); // Clear histograms before we begin.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry+test+dynamic", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
},
|
|
}),
|
|
/Category parameter should match the identifier pattern\./,
|
|
"Should throw when registering category names with invalid characters."
|
|
);
|
|
checkRegistrationFailure(2 /* Category */);
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents(
|
|
"telemetry.test.test.test.test.test.test.test.test",
|
|
{
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
},
|
|
}
|
|
),
|
|
/Category parameter should match the identifier pattern\./,
|
|
"Should throw when registering overly long category names."
|
|
);
|
|
checkRegistrationFailure(2 /* Category */);
|
|
|
|
// Test registration of invalid event names.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic1", {
|
|
"test?1": {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
},
|
|
}),
|
|
/Event names should match the identifier pattern\./,
|
|
"Should throw when registering event names with invalid characters."
|
|
);
|
|
checkRegistrationFailure(1 /* Name */);
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic2", {
|
|
test1test1test1test1test1test1test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
},
|
|
}),
|
|
/Event names should match the identifier pattern\./,
|
|
"Should throw when registering overly long event names."
|
|
);
|
|
checkRegistrationFailure(1 /* Name */);
|
|
|
|
// Test registration of invalid method names.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic3", {
|
|
test1: {
|
|
methods: ["test?1"],
|
|
objects: ["object1"],
|
|
},
|
|
}),
|
|
/Method names should match the identifier pattern\./,
|
|
"Should throw when registering method names with invalid characters."
|
|
);
|
|
checkRegistrationFailure(3 /* Method */);
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic", {
|
|
test1: {
|
|
methods: ["test1test1test1test1test1test1test1"],
|
|
objects: ["object1"],
|
|
},
|
|
}),
|
|
/Method names should match the identifier pattern\./,
|
|
"Should throw when registering overly long method names."
|
|
);
|
|
checkRegistrationFailure(3 /* Method */);
|
|
|
|
// Test registration of invalid object names.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic4", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object?1"],
|
|
},
|
|
}),
|
|
/Object names should match the identifier pattern\./,
|
|
"Should throw when registering object names with invalid characters."
|
|
);
|
|
checkRegistrationFailure(4 /* Object */);
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic5", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1object1object1object1object1object1"],
|
|
},
|
|
}),
|
|
/Object names should match the identifier pattern\./,
|
|
"Should throw when registering overly long object names."
|
|
);
|
|
checkRegistrationFailure(4 /* Object */);
|
|
|
|
// Test validation of invalid key names.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic6", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
extra_keys: ["a?1"],
|
|
},
|
|
}),
|
|
/Extra key names should match the identifier pattern\./,
|
|
"Should throw when registering extra key names with invalid characters."
|
|
);
|
|
checkRegistrationFailure(5 /* ExtraKeys */);
|
|
|
|
// Test validation of key names that are too long - we allow a maximum of 15 characters.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic7", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
extra_keys: ["a012345678901234"],
|
|
},
|
|
}),
|
|
/Extra key names should match the identifier pattern\./,
|
|
"Should throw when registering extra key names which are too long."
|
|
);
|
|
checkRegistrationFailure(5 /* ExtraKeys */);
|
|
Telemetry.registerEvents("telemetry.test.dynamic8", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
extra_keys: ["a01234567890123"],
|
|
},
|
|
});
|
|
|
|
// Test validation of extra key count - we only allow 10.
|
|
Assert.throws(
|
|
() =>
|
|
Telemetry.registerEvents("telemetry.test.dynamic9", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
extra_keys: [
|
|
"a1",
|
|
"a2",
|
|
"a3",
|
|
"a4",
|
|
"a5",
|
|
"a6",
|
|
"a7",
|
|
"a8",
|
|
"a9",
|
|
"a10",
|
|
"a11",
|
|
],
|
|
},
|
|
}),
|
|
/No more than 10 extra keys can be registered\./,
|
|
"Should throw when registering too many extra keys."
|
|
);
|
|
checkRegistrationFailure(5 /* ExtraKeys */);
|
|
Telemetry.registerEvents("telemetry.test.dynamic10", {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
extra_keys: ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10"],
|
|
},
|
|
});
|
|
});
|
|
|
|
// When add-ons update, they may re-register some of the dynamic events.
|
|
// Test through some possible scenarios.
|
|
add_task(async function test_dynamicEventRegisterAgain() {
|
|
Telemetry.canRecordExtended = true;
|
|
Telemetry.clearEvents();
|
|
|
|
const category = "telemetry.test.register.again";
|
|
let events = {
|
|
test1: {
|
|
methods: ["test1"],
|
|
objects: ["object1"],
|
|
},
|
|
};
|
|
|
|
// First register the initial event and make sure it can be recorded.
|
|
Telemetry.registerEvents(category, events);
|
|
let expected = [[category, "test1", "object1"]];
|
|
expected.forEach(e => Telemetry.recordEvent(...e));
|
|
|
|
let snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
expected.length,
|
|
"Should have right number of events in the snapshot."
|
|
);
|
|
Assert.deepEqual(
|
|
snapshot.dynamic.map(e => e.slice(1)),
|
|
expected
|
|
);
|
|
|
|
// Register the same event again and make sure it can still be recorded.
|
|
Telemetry.registerEvents(category, events);
|
|
Telemetry.recordEvent(category, "test1", "object1");
|
|
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
expected.length,
|
|
"Should have right number of events in the snapshot."
|
|
);
|
|
Assert.deepEqual(
|
|
snapshot.dynamic.map(e => e.slice(1)),
|
|
expected
|
|
);
|
|
|
|
// Now register another event in the same category and make sure both events can be recorded.
|
|
events.test2 = {
|
|
methods: ["test2"],
|
|
objects: ["object2"],
|
|
};
|
|
Telemetry.registerEvents(category, events);
|
|
|
|
expected = [
|
|
[category, "test1", "object1"],
|
|
[category, "test2", "object2"],
|
|
];
|
|
expected.forEach(e => Telemetry.recordEvent(...e));
|
|
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
expected.length,
|
|
"Should have right number of events in the snapshot."
|
|
);
|
|
Assert.deepEqual(
|
|
snapshot.dynamic.map(e => e.slice(1)),
|
|
expected
|
|
);
|
|
|
|
// Check that adding a new object to an event entry works.
|
|
events.test1.methods = ["test1a"];
|
|
events.test2.objects = ["object2", "object2a"];
|
|
Telemetry.registerEvents(category, events);
|
|
|
|
expected = [
|
|
[category, "test1", "object1"],
|
|
[category, "test2", "object2"],
|
|
[category, "test1a", "object1"],
|
|
[category, "test2", "object2a"],
|
|
];
|
|
expected.forEach(e => Telemetry.recordEvent(...e));
|
|
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
expected.length,
|
|
"Should have right number of events in the snapshot."
|
|
);
|
|
Assert.deepEqual(
|
|
snapshot.dynamic.map(e => e.slice(1)),
|
|
expected
|
|
);
|
|
|
|
// Make sure that we can expire events that are already registered.
|
|
events.test2.expired = true;
|
|
Telemetry.registerEvents(category, events);
|
|
|
|
expected = [[category, "test1", "object1"]];
|
|
expected.forEach(e => Telemetry.recordEvent(...e));
|
|
|
|
snapshot = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true);
|
|
Assert.equal(
|
|
snapshot.dynamic.length,
|
|
expected.length,
|
|
"Should have right number of events in the snapshot."
|
|
);
|
|
Assert.deepEqual(
|
|
snapshot.dynamic.map(e => e.slice(1)),
|
|
expected
|
|
);
|
|
});
|
|
|
|
add_task(
|
|
{
|
|
skip_if: () => gIsAndroid,
|
|
},
|
|
async function test_productSpecificEvents() {
|
|
const EVENT_CATEGORY = "telemetry.test";
|
|
const DEFAULT_PRODUCTS_EVENT = "default_products";
|
|
const DESKTOP_ONLY_EVENT = "desktop_only";
|
|
const MULTIPRODUCT_EVENT = "multiproduct";
|
|
const MOBILE_ONLY_EVENT = "mobile_only";
|
|
|
|
Telemetry.clearEvents();
|
|
|
|
// Try to record the desktop and multiproduct event
|
|
Telemetry.recordEvent(EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1");
|
|
Telemetry.recordEvent(EVENT_CATEGORY, DESKTOP_ONLY_EVENT, "object1");
|
|
Telemetry.recordEvent(EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1");
|
|
|
|
// Try to record the mobile-only event
|
|
Telemetry.recordEvent(EVENT_CATEGORY, MOBILE_ONLY_EVENT, "object1");
|
|
|
|
let events = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true).parent;
|
|
|
|
let expected = [
|
|
[EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1"],
|
|
[EVENT_CATEGORY, DESKTOP_ONLY_EVENT, "object1"],
|
|
[EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1"],
|
|
];
|
|
Assert.equal(
|
|
events.length,
|
|
expected.length,
|
|
"Should have recorded the right amount of events."
|
|
);
|
|
for (let i = 0; i < expected.length; ++i) {
|
|
Assert.deepEqual(
|
|
events[i].slice(1),
|
|
expected[i],
|
|
"Should have recorded the expected event data."
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
{
|
|
skip_if: () => !gIsAndroid,
|
|
},
|
|
async function test_mobileSpecificEvents() {
|
|
const EVENT_CATEGORY = "telemetry.test";
|
|
const DEFAULT_PRODUCTS_EVENT = "default_products";
|
|
const DESKTOP_ONLY_EVENT = "desktop_only";
|
|
const MULTIPRODUCT_EVENT = "multiproduct";
|
|
const MOBILE_ONLY_EVENT = "mobile_only";
|
|
|
|
Telemetry.clearEvents();
|
|
|
|
// Try to record the mobile-only and multiproduct event
|
|
Telemetry.recordEvent(EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1");
|
|
Telemetry.recordEvent(EVENT_CATEGORY, MOBILE_ONLY_EVENT, "object1");
|
|
Telemetry.recordEvent(EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1");
|
|
|
|
// Try to record the mobile-only event
|
|
Telemetry.recordEvent(EVENT_CATEGORY, DESKTOP_ONLY_EVENT, "object1");
|
|
|
|
let events = Telemetry.snapshotEvents(PRERELEASE_CHANNELS, true).parent;
|
|
|
|
let expected = [
|
|
[EVENT_CATEGORY, DEFAULT_PRODUCTS_EVENT, "object1"],
|
|
[EVENT_CATEGORY, MOBILE_ONLY_EVENT, "object1"],
|
|
[EVENT_CATEGORY, MULTIPRODUCT_EVENT, "object1"],
|
|
];
|
|
Assert.equal(
|
|
events.length,
|
|
expected.length,
|
|
"Should have recorded the right amount of events."
|
|
);
|
|
for (let i = 0; i < expected.length; ++i) {
|
|
Assert.deepEqual(
|
|
events[i].slice(1),
|
|
expected[i],
|
|
"Should have recorded the expected event data."
|
|
);
|
|
}
|
|
}
|
|
);
|