Skip to content

Commit ee00a7a

Browse files
author
Beatriz Rizental
authored
Merge pull request #1267 from brizental/1690253-events-ping
Bug 1690253 - Implement the 'events' ping
2 parents 136098a + 00132b7 commit ee00a7a

File tree

17 files changed

+316
-84
lines changed

17 files changed

+316
-84
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* [#1233](https://github.com/mozilla/glean.js/pull/1233): Add optional `buildDate` argument to `initialize` configuration. The build date can be generated by glean_parser.
66
* [#1233](https://github.com/mozilla/glean.js/pull/1233): Update glean_parser to version 5.1.0.
77
* [#1217](https://github.com/mozilla/glean.js/pull/1217): Record `InvalidType` error when incorrectly type values are passed to metric recording functions.
8+
* [#1267](https://github.com/mozilla/glean.js/pull/1267): Implement the 'events' ping.
89

910
# v0.32.0 (2022-03-01)
1011

glean/src/core/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import { Context } from "./context.js";
1111

1212
const LOG_TAG = "core.Config";
1313

14+
// The default maximum number of events Glean will store before submitting the events ping.
15+
// If the maximum is hit, the events ping is sent immediately.
16+
const DEFAULT_MAX_EVENTS = 500;
17+
1418
/**
1519
* Lists Glean's debug options.
1620
*/
@@ -35,6 +39,8 @@ export interface ConfigurationInterface {
3539
readonly appDisplayVersion?: string,
3640
// The server pings are sent to.
3741
readonly serverEndpoint?: string,
42+
// The maximum number of events to store before submitting the events ping.
43+
readonly maxEvents?: number,
3844
// Optional list of plugins to include in current Glean instance.
3945
plugins?: Plugin[],
4046
// The HTTP client implementation to use for uploading pings.
@@ -61,6 +67,7 @@ export class Configuration implements ConfigurationInterface {
6167
readonly architecture?: string;
6268
readonly osVersion?: string;
6369
readonly buildDate?: Date;
70+
readonly maxEvents: number;
6471

6572
// Debug configuration.
6673
debug: DebugOptions;
@@ -74,6 +81,7 @@ export class Configuration implements ConfigurationInterface {
7481
this.architecture = config?.architecture;
7582
this.osVersion = config?.osVersion;
7683
this.buildDate = config?.buildDate;
84+
this.maxEvents = config?.maxEvents || DEFAULT_MAX_EVENTS;
7785

7886
this.debug = {};
7987

glean/src/core/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export const DEFAULT_TELEMETRY_ENDPOINT = "https://incoming.telemetry.mozilla.or
3737
// The name of the deletion-request ping.
3838
export const DELETION_REQUEST_PING_NAME = "deletion-request";
3939

40+
// The name of the events ping.
41+
export const EVENTS_PING_NAME = "events";
42+
4043
// The maximum amount of source tags a user can set.
4144
export const GLEAN_MAX_SOURCE_TAGS = 5;
4245

glean/src/core/context.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import type { JSONValue } from "./utils.js";
1212
import Dispatcher from "./dispatcher.js";
1313
import log, { LoggingLevel } from "./log.js";
1414
import type { Configuration } from "./config.js";
15+
import type CorePings from "./internal_pings.js";
16+
import type { CoreMetrics } from "./internal_metrics.js";
1517

1618
const LOG_TAG = "core.Context";
1719

@@ -32,6 +34,8 @@ export class Context {
3234

3335
private _dispatcher: Dispatcher;
3436
private _platform!: Platform;
37+
private _corePings!: CorePings;
38+
private _coreMetrics!: CoreMetrics;
3539

3640
// The following group of properties are all set on Glean.initialize
3741
// Attempting to get them before they are set will log an error.
@@ -230,6 +234,22 @@ export class Context {
230234
Context.instance._testing = flag;
231235
}
232236

237+
static get corePings(): CorePings {
238+
return Context.instance._corePings;
239+
}
240+
241+
static set corePings(pings: CorePings) {
242+
Context.instance._corePings = pings;
243+
}
244+
245+
static get coreMetrics(): CoreMetrics {
246+
return Context.instance._coreMetrics;
247+
}
248+
249+
static set coreMetrics(metrics: CoreMetrics) {
250+
Context.instance._coreMetrics = metrics;
251+
}
252+
233253
static set platform(platform: Platform) {
234254
Context.instance._platform = platform;
235255
}

glean/src/core/glean.ts

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ import log, { LoggingLevel } from "./log.js";
2323
const LOG_TAG = "core.Glean";
2424

2525
namespace Glean {
26-
// The below properties are exported for testing purposes.
27-
//
28-
// Instances of Glean's core metrics.
29-
//
30-
// Disabling the lint, because we will actually re-assign this variable in the testInitializeGlean API.
31-
// eslint-disable-next-line prefer-const
32-
export let coreMetrics = new CoreMetrics();
33-
// Instances of Glean's core pings.
34-
export const corePings = new CorePings();
3526
// An instance of the ping uploader.
3627
export let pingUploader: PingUploadManager;
3728

@@ -51,7 +42,7 @@ namespace Glean {
5142
*/
5243
async function onUploadEnabled(): Promise<void> {
5344
Context.uploadEnabled = true;
54-
await coreMetrics.initialize();
45+
await Context.coreMetrics.initialize();
5546
}
5647

5748
/**
@@ -70,7 +61,7 @@ namespace Glean {
7061
// We need to use an undispatched submission to guarantee that the
7162
// ping is collected before metric are cleared, otherwise we end up
7263
// with malformed pings.
73-
await corePings.deletionRequest.submitUndispatched();
64+
await Context.corePings.deletionRequest.submitUndispatched();
7465
await clearMetrics();
7566
}
7667

@@ -97,7 +88,7 @@ namespace Glean {
9788
firstRunDate = new DatetimeMetric(
9889
await Context.metricsDatabase.getMetric(
9990
CLIENT_INFO_STORAGE,
100-
coreMetrics.firstRunDate
91+
Context.coreMetrics.firstRunDate
10192
)
10293
).date;
10394
} catch {
@@ -124,10 +115,10 @@ namespace Glean {
124115
// Store a "dummy" KNOWN_CLIENT_ID in the client_id metric. This will
125116
// make it easier to detect if pings were unintentionally sent after
126117
// uploading is disabled.
127-
await coreMetrics.clientId.setUndispatched(KNOWN_CLIENT_ID);
118+
await Context.coreMetrics.clientId.setUndispatched(KNOWN_CLIENT_ID);
128119

129120
// Restore the first_run_date.
130-
await coreMetrics.firstRunDate.setUndispatched(firstRunDate);
121+
await Context.coreMetrics.firstRunDate.setUndispatched(firstRunDate);
131122

132123
Context.uploadEnabled = false;
133124
}
@@ -194,6 +185,9 @@ namespace Glean {
194185
return;
195186
}
196187

188+
Context.coreMetrics = new CoreMetrics();
189+
Context.corePings = new CorePings();
190+
197191
Context.applicationId = sanitizeApplicationId(applicationId);
198192

199193
// The configuration constructor will throw in case config has any incorrect prop.
@@ -224,14 +218,20 @@ namespace Glean {
224218
//
225219
// The dispatcher will catch and log any exceptions.
226220
Context.dispatcher.flushInit(async () => {
227-
// We need to mark Glean as initialized before dealing with the upload status,
228-
// otherwise we will not be able to submit deletion-request pings if necessary.
229-
//
230-
// This is fine, we are inside a dispatched task that is guaranteed to run before any
231-
// other task. No external API call will be executed before we leave this task.
232221
Context.initialized = true;
233222

234223
Context.uploadEnabled = uploadEnabled;
224+
225+
// Initialize the events database.
226+
//
227+
// It's important this happens _after_ the upload state is set,
228+
// because initializing the events database may record the execution_counter and
229+
// glean.restarted metrics. If the upload state is not defined these metrics cannot be recorded.
230+
//
231+
// This may also submit an 'events' ping,
232+
// so it also needs to happen before application lifetime metrics are cleared.
233+
await Context.eventsDatabase.initialize();
234+
235235
// The upload enabled flag may have changed since the last run, for
236236
// example by the changing of a config file.
237237
if (uploadEnabled) {
@@ -259,7 +259,7 @@ namespace Glean {
259259
// deletion request ping.
260260
const clientId = await Context.metricsDatabase.getMetric(
261261
CLIENT_INFO_STORAGE,
262-
coreMetrics.clientId
262+
Context.coreMetrics.clientId
263263
);
264264

265265
if (clientId) {
@@ -273,13 +273,6 @@ namespace Glean {
273273
}
274274
}
275275

276-
// Initialize the events database.
277-
//
278-
// It's important this happens _after_ the upload state is dealt with,
279-
// because initializing the events database may record the execution_counter and
280-
// glean.restarted metrics. If the upload state is not defined these metrics can't be recorded.
281-
await Context.eventsDatabase.initialize();
282-
283276
// We only scan the pendings pings **after** dealing with the upload state.
284277
// If upload is disabled, pending pings files are deleted
285278
// so we need to know that state **before** scanning the pending pings

glean/src/core/internal_pings.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
import { DELETION_REQUEST_PING_NAME } from "./constants.js";
5+
import { DELETION_REQUEST_PING_NAME, EVENTS_PING_NAME } from "./constants.js";
66
import { InternalPingType as PingType} from "./pings/ping_type.js";
77

88
/**
@@ -16,13 +16,22 @@ class CorePings {
1616
// that the user wishes to have their reported Telemetry data deleted.
1717
// As such it attempts to send itself at the moment the user opts out of data collection.
1818
readonly deletionRequest: PingType;
19+
// The events ping's purpose is to transport event metric information.
20+
readonly events: PingType;
1921

2022
constructor() {
2123
this.deletionRequest = new PingType({
2224
name: DELETION_REQUEST_PING_NAME,
2325
includeClientId: true,
2426
sendIfEmpty: true,
2527
});
28+
29+
this.events = new PingType({
30+
name: EVENTS_PING_NAME,
31+
includeClientId: true,
32+
sendIfEmpty: false,
33+
reasonCodes: ["startup", "max_capacity"]
34+
});
2635
}
2736
}
2837

glean/src/core/metrics/database.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class MetricsDatabase {
262262
if (!validateMetricInternalRepresentation(metricType, metrics[metricIdentifier])) {
263263
log(
264264
LOG_TAG,
265-
`Invalid value found in storage for metric "${metricIdentifier}". Deleting.`,
265+
`Invalid value "${JSON.stringify(metrics[metricIdentifier])}" found in storage for metric "${metricIdentifier}". Deleting.`,
266266
LoggingLevel.Debug
267267
);
268268

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { generateReservedMetricIdentifiers } from "../database.js";
1515
import type { ExtraValues , Event } from "./recorded_event.js";
1616
import { RecordedEvent } from "./recorded_event.js";
1717
import {
18+
EVENTS_PING_NAME,
1819
GLEAN_EXECUTION_COUNTER_EXTRA_KEY,
1920
GLEAN_REFERENCE_TIME_EXTRA_KEY
2021
} from "../../constants.js";
@@ -47,12 +48,14 @@ function createDateObject(str?: ExtraValues): Date {
4748
* Creates an execution counter metric.
4849
*
4950
* @param sendInPings The list of pings this metric is sent in.
51+
* Note: The 'events' ping should not contain glean.restarted events,
52+
* so this ping will be filtered out from the 'sendInPings' array.
5053
* @returns A metric type instance.
5154
*/
5255
function getExecutionCounterMetric(sendInPings: string[]): CounterMetricType {
5356
return new CounterMetricType({
5457
...generateReservedMetricIdentifiers("execution_counter"),
55-
sendInPings: sendInPings,
58+
sendInPings: sendInPings.filter(name => name !== EVENTS_PING_NAME),
5659
lifetime: Lifetime.Ping,
5760
disabled: false
5861
});
@@ -62,13 +65,15 @@ function getExecutionCounterMetric(sendInPings: string[]): CounterMetricType {
6265
* Creates an `glean.restarted` event metric.
6366
*
6467
* @param sendInPings The list of pings this metric is sent in.
68+
* Note: The 'events' ping should not contain glean.restarted events,
69+
* so this ping will be filtered out from the 'sendInPings' array.
6570
* @returns A metric type instance.
6671
*/
6772
export function getGleanRestartedEventMetric(sendInPings: string[]): EventMetricType {
6873
return new EventMetricType({
6974
category: "glean",
7075
name: "restarted",
71-
sendInPings: sendInPings,
76+
sendInPings: sendInPings.filter(name => name !== EVENTS_PING_NAME),
7277
lifetime: Lifetime.Ping,
7378
disabled: false
7479
}, [ GLEAN_REFERENCE_TIME_EXTRA_KEY ]);
@@ -140,6 +145,14 @@ class EventsDatabase {
140145
}
141146

142147
const storeNames = await this.getAvailableStoreNames();
148+
// Submit the events ping in case there are _any_ events unsubmitted from the previous run
149+
if (storeNames.includes(EVENTS_PING_NAME)) {
150+
const storedEvents = (await this.eventsStore.get([EVENTS_PING_NAME]) as JSONArray) ?? [];
151+
if (storedEvents.length > 0) {
152+
await Context.corePings.events.submitUndispatched("startup");
153+
}
154+
}
155+
143156
// Increment the execution counter for known stores.
144157
// !IMPORTANT! This must happen before any event is recorded for this run.
145158
await getExecutionCounterMetric(storeNames).addUndispatched(1);
@@ -177,12 +190,18 @@ class EventsDatabase {
177190
}
178191
value.addExtra(GLEAN_EXECUTION_COUNTER_EXTRA_KEY, currentExecutionCount);
179192

193+
let numEvents = 0;
180194
const transformFn = (v?: JSONValue): JSONArray => {
181-
const existing: JSONArray = (v as JSONArray) ?? [];
182-
existing.push(value.get());
183-
return existing;
195+
const events: JSONArray = (v as JSONArray) ?? [];
196+
events.push(value.get());
197+
numEvents = events.length;
198+
return events;
184199
};
200+
185201
await this.eventsStore.update([ping], transformFn);
202+
if (ping === EVENTS_PING_NAME && numEvents >= Context.config.maxEvents) {
203+
await Context.corePings.events.submitUndispatched("max_capacity");
204+
}
186205
}
187206
}
188207

glean/src/core/metrics/utils.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { isInteger, isString } from "../utils.js";
1111
import { LabeledMetric } from "./types/labeled.js";
1212
import { Context } from "../context.js";
1313
import { ErrorType } from "../error/error_type.js";
14+
import log, { LoggingLevel } from "../log.js";
1415

15-
16+
const LOG_TAG = "Glean.core.Metrics.utils";
1617

1718
/**
1819
* A metric factory function.
@@ -31,7 +32,7 @@ export function createMetric(type: string, v: unknown): Metric<JSONValue, JSONVa
3132

3233
const ctor = Context.getSupportedMetric(type);
3334
if (!ctor) {
34-
throw new Error(`Unable to create metric of unknown type ${type}`);
35+
throw new Error(`Unable to create metric of unknown type "${type}".`);
3536
}
3637

3738
return new ctor(v);
@@ -52,7 +53,8 @@ export function validateMetricInternalRepresentation<T extends JSONValue>(
5253
try {
5354
createMetric(type, v);
5455
return true;
55-
} catch {
56+
} catch(e) {
57+
log(LOG_TAG, (e as Error).message, LoggingLevel.Error);
5658
return false;
5759
}
5860
}

glean/src/core/testing/utils.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type { ConfigurationInterface } from "../config.js";
77
import { Context } from "../context.js";
88
import { testResetEvents } from "../events/utils.js";
99
import Glean from "../glean.js";
10-
import { CoreMetrics } from "../internal_metrics.js";
1110

1211
/**
1312
* Test-only API
@@ -27,10 +26,6 @@ export async function testInitializeGlean(
2726
uploadEnabled = true,
2827
config?: ConfigurationInterface
2928
): Promise<void> {
30-
// Core metrics need to be re-initialized so that
31-
// the supportedMetrics map is re-created.
32-
Glean.coreMetrics = new CoreMetrics();
33-
3429
Context.testing = true;
3530

3631
Glean.setPlatform(TestPlatform);

0 commit comments

Comments
 (0)