Skip to content

Commit f2b2880

Browse files
authored
Merge pull request #1452 from rosahbruno/1723090-delete-trailing-restart-events
Bug 1723090 - Delete trailing glean.restarted events
2 parents ac4c56a + b5fe0a1 commit f2b2880

File tree

7 files changed

+177
-50
lines changed

7 files changed

+177
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
[Full changelog](https://github.com/mozilla/glean.js/compare/v1.1.0...main)
44

5+
* [#1452](https://github.com/mozilla/glean.js/pull/1452): Remove `glean.restarted` trailing events from events list.
56
* [#1450](https://github.com/mozilla/glean.js/pull/1450): Update `ts-node` to 10.8.0 to resolve ESM issues when running tests.
67
* [#1449](https://github.com/mozilla/glean.js/pull/1449): BUGFIX: Add missing quotes to `prepare-release` script to fix issues with version numbers in Qt sample README & circle ci config.
78
* [#1449](https://github.com/mozilla/glean.js/pull/1449): Update Qt sample project docs to include note about problems with different version numbers of Qt commands.

docs/reference/metrics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ In addition to those built-in metrics, the following metrics are added to the pi
2525
| glean.error.invalid_overflow |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Counts the number of times a metric was set a value that overflowed. The labels are the `category.name` identifier of the metric. |[Bug 1591912](https://bugzilla.mozilla.org/show_bug.cgi?id=1591912#c3)||never |1 |
2626
| glean.error.invalid_state |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Counts the number of times a timing metric was used incorrectly. The labels are the `category.name` identifier of the metric. |[Bug 1499761](https://bugzilla.mozilla.org/show_bug.cgi?id=1499761#c5)||never |1 |
2727
| glean.error.invalid_value |[labeled_counter](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html) |Counts the number of times a metric was set to an invalid value. The labels are the `category.name` identifier of the metric. |[Bug 1499761](https://bugzilla.mozilla.org/show_bug.cgi?id=1499761#c5)||never |1 |
28-
| glean.restarted |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |Event that signals an application restart. **This event is included in all pings (custom or otherwise) that contain events.** |[mozilla/glean.js#526](https://github.com/mozilla/glean.js/pull/526#issuecomment-889892100)||never |1 |
28+
| glean.restarted |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |Event that signals an application restart, leading and trailing restarted events will be omitted. **This event is included in all pings (custom or otherwise) that contain events.** |[mozilla/glean.js#526](https://github.com/mozilla/glean.js/pull/526#issuecomment-889892100)||never |1 |
2929

3030
## deletion-request
3131

glean/src/core/metrics/events_database/index.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ class EventsDatabase {
303303
* 1. Sorts event by execution counter and timestamp;
304304
* 2. Applies offset to events timestamps;
305305
* 3. Removes the first event if it is a `glean.restarted` event;
306-
* 4. Removes reserved extra keys and stringifies all extras values.
306+
* 4. Removes reserved extra keys and stringifies all extras values;
307+
* 5. Removes all trailing `glean.restarted` events (if they exists);
307308
*
308309
* @param pingName The name of the ping for which the payload is being prepared.
309310
* @param pingData An unsorted list of events.
@@ -314,7 +315,7 @@ class EventsDatabase {
314315
pingData: RecordedEvent[]
315316
): Promise<JSONArray> {
316317
// Sort events by execution counter and by timestamp.
317-
const sortedEvents = pingData.sort((a, b) => {
318+
let sortedEvents = pingData.sort((a, b) => {
318319
const executionCounterA = Number(a.get().extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY]);
319320
const executionCounterB = Number(b.get().extra?.[GLEAN_EXECUTION_COUNTER_EXTRA_KEY]);
320321
// Sort by execution counter, in case they are different.
@@ -393,6 +394,9 @@ class EventsDatabase {
393394
});
394395
}
395396

397+
// There is no additional context in trailing `glean.restarted` events, they can be removed.
398+
sortedEvents = this.removeTrailingRestartedEvents(sortedEvents);
399+
396400
return sortedEvents.map((e) => e.payload());
397401
}
398402

@@ -402,6 +406,30 @@ class EventsDatabase {
402406
async clearAll(): Promise<void> {
403407
await this.eventsStore.delete([]);
404408
}
409+
410+
private isRestartedEvent(event: RecordedEvent): boolean {
411+
// This extra key will only exist for `glean.restarted` events. If at some
412+
// point that is no longer the case, this can be updated to check the event
413+
// `name` and `category` instead.
414+
return !!event?.get()?.extra?.[GLEAN_REFERENCE_TIME_EXTRA_KEY];
415+
}
416+
417+
/**
418+
* Removes all trailing `glean.restarted` events. We will continue to check
419+
* the last element of the array until there are no longer any elements OR
420+
* the event is not a `glean.restarted` event.
421+
*
422+
* @param sortedEvents Before this is called, events should already be sorted
423+
* which includes removing the leading `glean.restarted` event.
424+
* @returns The input array without any trailing `glean.restarted` events.
425+
*/
426+
private removeTrailingRestartedEvents(sortedEvents: RecordedEvent[]): RecordedEvent[] {
427+
while (!!sortedEvents.length && this.isRestartedEvent(sortedEvents[sortedEvents.length - 1])) {
428+
sortedEvents.pop();
429+
}
430+
431+
return sortedEvents;
432+
}
405433
}
406434

407435
export default EventsDatabase;

glean/src/core/testing/events.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { Context } from "../context.js";
6+
import EventsDatabase from "../metrics/events_database/index.js";
7+
8+
/**
9+
* Test-only API
10+
*
11+
* Fakes a restart by creating a new instance of the events database which will
12+
* log a `glean.restarted` event.
13+
*
14+
* If you do not move the clock forward, an error will be logged.
15+
*
16+
* @param timeOffset How far ahead to move clock before restarting. Some events just need
17+
* a future time, others need a specific future time. The default time is 1 minute.
18+
* @returns New instance of `EventsDatabase` since we "restarted" it.
19+
*/
20+
export async function testRestartGlean(timeOffset: number = 1000 * 60): Promise<EventsDatabase> {
21+
// Move the clock to look like Glean was really restarted.
22+
Context.startTime.setTime(Context.startTime.getTime() + timeOffset);
23+
24+
// Fake a restart.
25+
const db = new EventsDatabase();
26+
await db.initialize();
27+
return db;
28+
}

glean/src/core/testing/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ export async function testResetGlean(
2929
await testUninitializeGlean(clearStores);
3030
await testInitializeGlean(applicationId, uploadEnabled, config);
3131
}
32+
33+
export * from "./utils.js";
34+
export * from "./events.js";

glean/tests/unit/core/metrics/events_database.spec.ts

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@ import type { SinonFakeTimers } from "sinon";
77
import sinon from "sinon";
88

99
import { Lifetime } from "../../../../src/core/metrics/lifetime";
10-
import EventsDatabase, { getGleanRestartedEventMetric } from "../../../../src/core/metrics/events_database";
11-
import { InternalEventMetricType as EventMetricType} from "../../../../src/core/metrics/types/event";
10+
import EventsDatabase, {
11+
getGleanRestartedEventMetric,
12+
} from "../../../../src/core/metrics/events_database";
13+
import { InternalEventMetricType as EventMetricType } from "../../../../src/core/metrics/types/event";
1214
import type { JSONObject } from "../../../../src/core/utils";
1315
import CounterMetricType from "../../../../src/core/metrics/types/counter";
1416
import { generateReservedMetricIdentifiers } from "../../../../src/core/metrics/database";
15-
import { InternalPingType as PingType} from "../../../../src/core/pings/ping_type";
17+
import { InternalPingType as PingType } from "../../../../src/core/pings/ping_type";
1618
import { Context } from "../../../../src/core/context";
1719
import { RecordedEvent } from "../../../../src/core/metrics/events_database/recorded_event";
18-
import { EVENTS_PING_NAME, GLEAN_EXECUTION_COUNTER_EXTRA_KEY } from "../../../../src/core/constants";
20+
import {
21+
EVENTS_PING_NAME,
22+
GLEAN_EXECUTION_COUNTER_EXTRA_KEY,
23+
} from "../../../../src/core/constants";
1924
import { collectPing } from "../../../../src/core/pings/maker";
2025
import { ErrorType } from "../../../../src/core/error/error_type";
2126
import { testResetGlean } from "../../../../src/core/testing";
2227
import type { Event } from "../../../../src/core/metrics/events_database/recorded_event";
23-
import { testInitializeGlean, testUninitializeGlean } from "../../../../src/core/testing/utils";
28+
import {
29+
testInitializeGlean,
30+
testRestartGlean,
31+
testUninitializeGlean,
32+
} from "../../../../src/core/testing";
2433
import { WaitableUploader } from "../../../utils";
2534
import type { PingPayload } from "../../../../src/core/pings/ping_payload";
2635

@@ -334,19 +343,14 @@ describe("EventsDatabase", function() {
334343
}));
335344

336345
// Move the clock forward by one minute to look like Glean was really restarted.
337-
Context.startTime.setTime(Context.startTime.getTime() + 1000 * 60);
338-
// Fake a re-start.
339-
const db2 = new EventsDatabase();
340-
await db2.initialize();
346+
const db2 = await testRestartGlean();
341347

342348
for (const store of stores) {
343349
const snapshot = await db2.getPingEvents(store, true);
344350
assert.ok(snapshot);
345-
assert.strictEqual(2, snapshot.length);
351+
assert.strictEqual(1, snapshot.length);
346352
assert.strictEqual("test", (snapshot[0] as JSONObject)["category"]);
347353
assert.strictEqual("event_injection", (snapshot[0] as JSONObject)["name"]);
348-
assert.strictEqual("glean", (snapshot[1] as JSONObject)["category"]);
349-
assert.strictEqual("restarted", (snapshot[1] as JSONObject)["name"]);
350354

351355
// Check that no errors were recorded for the `glean.restarted` metric.
352356
const restartedMetric = getGleanRestartedEventMetric([store]);
@@ -375,10 +379,7 @@ describe("EventsDatabase", function() {
375379
}));
376380

377381
// Move the clock forward by one minute.
378-
Context.startTime.setTime(Context.startTime.getTime() + 1000 * 60);
379-
// Fake a re-start.
380-
db = new EventsDatabase();
381-
await db.initialize();
382+
db = await testRestartGlean();
382383
}
383384

384385
const snapshot = await db.getPingEvents("store", true);
@@ -396,12 +397,21 @@ describe("EventsDatabase", function() {
396397
prevTime = e.timestamp;
397398
}
398399

399-
// Make sure the found events are the expected events.
400+
// Make sure the found events are the expected events. This array consists of
401+
// a user created event followed by a restarted event and repeats.
400402
for (let i = 0; i < 10; i++) {
401-
assert.strictEqual("test", (snapshot[i * 2] as JSONObject)["category"]);
402-
assert.strictEqual(`stichting_test_${i}`, (snapshot[i * 2] as JSONObject)["name"]);
403-
assert.strictEqual("glean", (snapshot[(i * 2) + 1] as JSONObject)["category"]);
404-
assert.strictEqual("restarted", (snapshot[(i * 2) + 1] as JSONObject)["name"]);
403+
const userEvent = snapshot[i * 2] as JSONObject;
404+
const restartedEvent = snapshot[(i * 2) + 1] as JSONObject;
405+
406+
assert.strictEqual("test", userEvent["category"]);
407+
assert.strictEqual(`stichting_test_${i}`, userEvent["name"]);
408+
409+
// We no longer keep trailing restarted events, so in this scenario, we need to ignore
410+
// the final element of the snapshot since it previously had a restarted event.
411+
if (restartedEvent) {
412+
assert.strictEqual("glean", restartedEvent["category"]);
413+
assert.strictEqual("restarted", restartedEvent["name"]);
414+
}
405415

406416
// Check that no errors were recorded for the `glean.restarted` metric.
407417
const restartedMetric = getGleanRestartedEventMetric(["store"]);
@@ -451,12 +461,21 @@ describe("EventsDatabase", function() {
451461
prevTime = e.timestamp;
452462
}
453463

454-
// Make sure the found events are the expected events.
464+
// Make sure the found events are the expected events. This array consists of
465+
// a user created event followed by a restarted event and repeats.
455466
for (let i = 0; i < 10; i++) {
456-
assert.strictEqual("test", (snapshot[i * 2] as JSONObject)["category"]);
457-
assert.strictEqual(`time_travel_${i}`, (snapshot[i * 2] as JSONObject)["name"]);
458-
assert.strictEqual("glean", (snapshot[(i * 2) + 1] as JSONObject)["category"]);
459-
assert.strictEqual("restarted", (snapshot[(i * 2) + 1] as JSONObject)["name"]);
467+
const userEvent = snapshot[i * 2] as JSONObject;
468+
const restartedEvent = snapshot[(i * 2) + 1] as JSONObject;
469+
470+
assert.strictEqual("test", userEvent["category"]);
471+
assert.strictEqual(`time_travel_${i}`, userEvent["name"]);
472+
473+
// We no longer keep trailing restarted events, so in this scenario, we need to ignore
474+
// the final element of the snapshot since it previously had a restarted event.
475+
if (restartedEvent) {
476+
assert.strictEqual("glean", restartedEvent["category"]);
477+
assert.strictEqual("restarted", restartedEvent["name"]);
478+
}
460479
}
461480

462481
// Time went backwards, so errors must have been recorded.
@@ -505,12 +524,21 @@ describe("EventsDatabase", function() {
505524
prevTime = e.timestamp;
506525
}
507526

508-
// Make sure the found events are the expected events.
527+
// Make sure the found events are the expected events. This array consists of
528+
// a user created event followed by a restarted event and repeats.
509529
for (let i = 0; i < 10; i++) {
510-
assert.strictEqual("test", (snapshot[i * 2] as JSONObject)["category"]);
511-
assert.strictEqual(`time_travel_${i}`, (snapshot[i * 2] as JSONObject)["name"]);
512-
assert.strictEqual("glean", (snapshot[(i * 2) + 1] as JSONObject)["category"]);
513-
assert.strictEqual("restarted", (snapshot[(i * 2) + 1] as JSONObject)["name"]);
530+
const userEvent = snapshot[i * 2] as JSONObject;
531+
const restartedEvent = snapshot[(i * 2) + 1] as JSONObject;
532+
533+
assert.strictEqual("test", userEvent["category"]);
534+
assert.strictEqual(`time_travel_${i}`, userEvent["name"]);
535+
536+
// We no longer keep trailing restarted events, so in this scenario, we need to ignore
537+
// the final element of the snapshot since it previously had a restarted event.
538+
if (restartedEvent) {
539+
assert.strictEqual("glean", restartedEvent["category"]);
540+
assert.strictEqual("restarted", restartedEvent["name"]);
541+
}
514542
}
515543

516544
// Time stood still, so an error must have been recorded.
@@ -552,10 +580,7 @@ describe("EventsDatabase", function() {
552580

553581
// Move the clock forward by one hour.
554582
const restartedTimeOffset = 1000 * 60 * 60;
555-
Context.startTime.setTime(firstStartTime.getTime() + restartedTimeOffset);
556-
// Product is rebooted.
557-
db = new EventsDatabase();
558-
await db.initialize();
583+
db = await testRestartGlean(restartedTimeOffset);
559584

560585
// New events are recorded.
561586
await db.record(event, new RecordedEvent({
@@ -622,10 +647,7 @@ describe("EventsDatabase", function() {
622647

623648
// Move the clock forward by one hour.
624649
const restartedTimeOffset = 1000 * 60 * 60;
625-
Context.startTime.setTime(firstStartTime.getTime() + restartedTimeOffset);
626-
// Product is rebooted.
627-
db = new EventsDatabase();
628-
await db.initialize();
650+
db = await testRestartGlean(restartedTimeOffset);
629651

630652
// New set of events are recorded
631653
await db.record(event, new RecordedEvent({
@@ -640,10 +662,7 @@ describe("EventsDatabase", function() {
640662
}));
641663

642664
// Move the clock forward by one more hour.
643-
Context.startTime.setTime(firstStartTime.getTime() + restartedTimeOffset * 2);
644-
// Product is rebooted.
645-
db = new EventsDatabase();
646-
await db.initialize();
665+
db = await testRestartGlean(restartedTimeOffset);
647666

648667
// New set of events are recorded
649668
await db.record(event, new RecordedEvent({

glean/tests/unit/core/pings/maker.spec.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@
55
import assert from "assert";
66
import sinon from "sinon";
77

8-
import { InternalPingType as PingType} from "../../../../src/core/pings/ping_type";
8+
import { InternalPingType as PingType } from "../../../../src/core/pings/ping_type";
99
import * as PingMaker from "../../../../src/core/pings/maker";
1010
import Glean from "../../../../src/core/glean";
1111
import CoreEvents from "../../../../src/core/events";
1212
import Plugin from "../../../../src/plugins";
13-
import type { JSONObject } from "../../../../src/core/utils";
13+
import type { JSONArray, JSONObject } from "../../../../src/core/utils";
1414
import { Context } from "../../../../src/core/context";
1515
import { stopGleanUploader } from "../../../utils";
1616
import EventMetricType from "../../../../src/core/metrics/types/event";
1717
import { Lifetime } from "../../../../src/core/metrics/lifetime";
18-
import { testInitializeGlean, testUninitializeGlean } from "../../../../src/core/testing/utils";
19-
import { testResetGlean } from "../../../../src/core/testing";
18+
import {
19+
testInitializeGlean,
20+
testResetGlean,
21+
testRestartGlean,
22+
testUninitializeGlean,
23+
} from "../../../../src/core/testing";
24+
import type { ExtraMap } from "../../../../src/core/metrics/events_database/recorded_event";
2025

2126
const sandbox = sinon.createSandbox();
2227

@@ -273,4 +278,47 @@ describe("PingMaker", function() {
273278
labeled_counter: { "glean.error.invalid_value": { "glean.restarted": 1 } }
274279
});
275280
});
281+
282+
it("should delete trailing restarted events", async function() {
283+
const ping = new PingType({
284+
name: "aPing",
285+
includeClientId: true,
286+
sendIfEmpty: true,
287+
});
288+
const event = new EventMetricType({
289+
category: "test",
290+
name: "aEvent",
291+
sendInPings: ["aPing"],
292+
lifetime: Lifetime.Ping,
293+
disabled: false
294+
});
295+
296+
const triggerCustomEvent = async (event: EventMetricType<ExtraMap>) => {
297+
event.record();
298+
// Wait for recording action to complete
299+
await event.testGetValue();
300+
};
301+
302+
const triggerRestartedEvent = async () => {
303+
await testRestartGlean();
304+
};
305+
306+
// Record events
307+
await triggerCustomEvent(event);
308+
309+
await triggerRestartedEvent();
310+
await triggerRestartedEvent();
311+
await triggerRestartedEvent();
312+
313+
await triggerCustomEvent(event);
314+
315+
await triggerRestartedEvent();
316+
await triggerRestartedEvent();
317+
318+
await PingMaker.collectAndStorePing("ident", ping);
319+
const allPings = Object.fromEntries(await Context.pingsDatabase.getAllPings());
320+
const eventsArray = allPings["ident"]["payload"]["events"] as JSONArray;
321+
322+
assert.equal(5, eventsArray.length);
323+
});
276324
});

0 commit comments

Comments
 (0)