Skip to content

Commit b110d64

Browse files
author
Beatriz Rizental
authored
Bug 1677444 - Implement upload status toggling logic (#26)
1 parent f587679 commit b110d64

File tree

9 files changed

+358
-35
lines changed

9 files changed

+358
-35
lines changed

src/glean.ts

Lines changed: 169 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
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 { DEFAULT_TELEMETRY_ENDPOINT } from "./constants";
5+
import { CLIENT_INFO_STORAGE, DEFAULT_TELEMETRY_ENDPOINT, KNOWN_CLIENT_ID } from "./constants";
66
import Configuration from "config";
77
import MetricsDatabase from "metrics/database";
88
import PingsDatabase from "pings/database";
99
import PingUploader from "upload";
1010
import { isUndefined, sanitizeApplicationId } from "utils";
1111
import { CoreMetrics } from "internal_metrics";
1212
import { Lifetime } from "metrics";
13+
import { DatetimeMetric } from "metrics/types/datetime";
1314

1415
class Glean {
1516
// The Glean singleton.
@@ -20,8 +21,6 @@ class Glean {
2021
metrics: MetricsDatabase,
2122
pings: PingsDatabase
2223
}
23-
// Whether or not to record metrics.
24-
private _uploadEnabled: boolean;
2524
// Whether or not Glean has been initialized.
2625
private _initialized: boolean;
2726
// Instances of Glean's core metrics.
@@ -30,8 +29,13 @@ class Glean {
3029
private _pingUploader: PingUploader
3130

3231
// Properties that will only be set on `initialize`.
32+
33+
// The application ID (will be sanitized during initialization).
3334
private _applicationId?: string;
35+
// The server pings are sent to.
3436
private _serverEndpoint?: URL;
37+
// Whether or not to record metrics.
38+
private _uploadEnabled?: boolean;
3539

3640
private constructor() {
3741
if (!isUndefined(Glean._instance)) {
@@ -47,8 +51,14 @@ class Glean {
4751
metrics: new MetricsDatabase(),
4852
pings: new PingsDatabase(this._pingUploader)
4953
};
50-
// Temporarily setting this to true always, until Bug 1677444 is resolved.
51-
this._uploadEnabled = true;
54+
}
55+
56+
private static get instance(): Glean {
57+
if (!Glean._instance) {
58+
Glean._instance = new Glean();
59+
}
60+
61+
return Glean._instance;
5262
}
5363

5464
private static get pingUploader(): PingUploader {
@@ -59,30 +69,126 @@ class Glean {
5969
return Glean.instance._coreMetrics;
6070
}
6171

62-
private static get instance(): Glean {
63-
if (!Glean._instance) {
64-
Glean._instance = new Glean();
65-
}
72+
private static get uploadEnabled(): boolean {
73+
return Glean.instance._uploadEnabled || false;
74+
}
6675

67-
return Glean._instance;
76+
private static set uploadEnabled(value: boolean) {
77+
Glean.instance._uploadEnabled = value;
78+
}
79+
80+
/**
81+
* Handles the changing of state from upload disabled to enabled.
82+
*
83+
* Should only be called when the state actually changes.
84+
*
85+
* The `uploadEnabled` flag is set to true and the core Glean metrics are recreated.
86+
*/
87+
private static async onUploadEnabled(): Promise<void> {
88+
Glean.uploadEnabled = true;
89+
await Glean.coreMetrics.initialize();
90+
}
91+
92+
/**
93+
* Handles the changing of state from upload enabled to disabled.
94+
*
95+
* Should only be called when the state actually changes.
96+
*
97+
* A deletion_request ping is sent, all pending metrics, events and queued
98+
* pings are cleared, and the client_id is set to KNOWN_CLIENT_ID.
99+
* Afterward, the upload_enabled flag is set to false.
100+
*/
101+
private static async onUploadDisabled(): Promise<void> {
102+
Glean.uploadEnabled = false;
103+
await Glean.clearMetrics();
104+
}
105+
106+
/**
107+
* Clears any pending metrics and pings.
108+
*
109+
* This function is only supposed to be called when telemetry is disabled.
110+
*/
111+
private static async clearMetrics(): Promise<void> {
112+
// Stop ongoing uploading jobs and clear pending pings queue.
113+
await Glean.pingUploader.clearPendingPingsQueue();
114+
115+
// There is only one metric that we want to survive after clearing all
116+
// metrics: first_run_date. Here, we store its value
117+
// so we can restore it after clearing the metrics.
118+
//
119+
// TODO: This may throw an exception. Bug 1687475 will resolve this.
120+
const existingFirstRunDate = new DatetimeMetric(
121+
await Glean.metricsDatabase.getMetric(
122+
CLIENT_INFO_STORAGE,
123+
Glean.coreMetrics.firstRunDate
124+
)
125+
);
126+
127+
// Clear the databases.
128+
await Glean.metricsDatabase.clearAll();
129+
await Glean.pingsDatabase.clearAll();
130+
131+
// We need to briefly set upload_enabled to true here so that `set`
132+
// is not a no-op.
133+
//
134+
// Note that we can't provide the same guarantees as glean-core here.
135+
// If by any change another actor attempts to record a metric while
136+
// we are setting the known client id and first run date, they will be allowed to.
137+
//
138+
// TODO: Bug 1687491 might resolve this issue.
139+
Glean.uploadEnabled = true;
140+
141+
// Store a "dummy" KNOWN_CLIENT_ID in the client_id metric. This will
142+
// make it easier to detect if pings were unintentionally sent after
143+
// uploading is disabled.
144+
await Glean.coreMetrics.clientId.set(KNOWN_CLIENT_ID);
145+
146+
// Restore the first_run_date.
147+
await Glean.coreMetrics.firstRunDate.set(existingFirstRunDate.date);
148+
149+
Glean.uploadEnabled = false;
68150
}
69151

70152
/**
71153
* Initialize Glean. This method should only be called once, subsequent calls will be no-op.
72154
*
155+
* # Note
156+
*
157+
* Before this method is called Glean will not be able to upload pings or record metrics,
158+
* all such operations will be no-op.
159+
*
160+
* This is _not_ the way glean-core deals with this. It will record tasks performed before init
161+
* and flush them on init. We have a bug to figure out how to do that for Glean.js, Bug 1687491.
162+
*
73163
* @param applicationId The application ID (will be sanitized during initialization).
164+
* @param uploadEnabled Determines whether telemetry is enabled.
165+
* If disabled, all persisted metrics, events and queued pings (except
166+
* first_run_date) are cleared.
74167
* @param config Glean configuration options.
75168
*/
76-
static async initialize(applicationId: string, config?: Configuration): Promise<void> {
169+
static async initialize(
170+
applicationId: string,
171+
uploadEnabled: boolean,
172+
config?: Configuration
173+
): Promise<void> {
77174
if (Glean.instance._initialized) {
78175
console.warn("Attempted to initialize Glean, but it has already been initialized. Ignoring.");
79176
return;
80177
}
81178

179+
if (applicationId.length === 0) {
180+
throw new Error("Unable to initialize Glean, applicationId cannot be an empty string.");
181+
}
82182
Glean.instance._applicationId = sanitizeApplicationId(applicationId);
183+
83184
Glean.instance._serverEndpoint = config
84185
? config.serverEndpoint : new URL(DEFAULT_TELEMETRY_ENDPOINT);
85-
await Glean.coreMetrics.initialize(config?.appBuild, config?.appDisplayVersion);
186+
187+
if (uploadEnabled) {
188+
await Glean.onUploadEnabled();
189+
} else {
190+
await Glean.onUploadDisabled();
191+
}
86192

87193
Glean.instance._initialized = true;
88194

@@ -118,31 +224,66 @@ class Glean {
118224
return Glean.instance._initialized;
119225
}
120226

121-
// TODO: Make the following functions `private` once Bug 1682769 is resolved.
122-
static get uploadEnabled(): boolean {
123-
return Glean.instance._uploadEnabled;
124-
}
125-
126-
static set uploadEnabled(value: boolean) {
127-
Glean.instance._uploadEnabled = value;
128-
}
129-
130227
static get applicationId(): string | undefined {
131228
if (!Glean.instance._initialized) {
132-
console.error("Attempted to access the Glean.applicationId before Glean was initialized.");
229+
console.warn("Attempted to access the Glean.applicationId before Glean was initialized.");
133230
}
134231

135232
return Glean.instance._applicationId;
136233
}
137234

138235
static get serverEndpoint(): URL | undefined {
139236
if (!Glean.instance._initialized) {
140-
console.error("Attempted to access the Glean.serverEndpoint before Glean was initialized.");
237+
console.warn("Attempted to access the Glean.serverEndpoint before Glean was initialized.");
141238
}
142239

143240
return Glean.instance._serverEndpoint;
144241
}
145242

243+
/**
244+
* Determines whether upload is enabled.
245+
*
246+
* When upload is disabled, no data will be recorded.
247+
*
248+
* @returns Whether upload is enabled.
249+
*/
250+
static isUploadEnabled(): boolean {
251+
if (!Glean.instance._initialized) {
252+
console.warn("Attempted to access the Glean.uploadEnabled before Glean was initialized.");
253+
}
254+
255+
return Glean.uploadEnabled;
256+
}
257+
258+
/**
259+
* Sets whether upload is enabled or not.
260+
*
261+
* When uploading is disabled, metrics aren't recorded at all and no
262+
* data is uploaded.
263+
*
264+
* When disabling, all pending metrics, events and queued pings are cleared.
265+
*
266+
* When enabling, the core Glean metrics are recreated.
267+
*
268+
* If the value of this flag is not actually changed, this is a no-op.
269+
*
270+
* @param flag When true, enable metric collection.
271+
*
272+
* @returns Whether the flag was different from the current value,
273+
* and actual work was done to clear or reinstate metrics.
274+
*/
275+
static async setUploadEnabled(flag: boolean): Promise<boolean> {
276+
if (Glean.uploadEnabled !== flag) {
277+
if (flag) {
278+
await Glean.onUploadEnabled();
279+
} else {
280+
await Glean.onUploadDisabled();
281+
}
282+
return true;
283+
}
284+
return false;
285+
}
286+
146287
/**
147288
* **Test-only API**
148289
*
@@ -163,13 +304,14 @@ class Glean {
163304
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
164305
*
165306
* @param applicationId The application ID (will be sanitized during initialization).
307+
* @param uploadEnabled Determines whether telemetry is enabled.
308+
* If disabled, all persisted metrics, events and queued pings (except
309+
* first_run_date) are cleared. Default to `true`.
166310
* @param config Glean configuration options.
167311
*/
168-
static async testRestGlean(applicationId: string, config?: Configuration): Promise<void> {
312+
static async testRestGlean(applicationId: string, uploadEnabled = true, config?: Configuration): Promise<void> {
169313
// Get back to an uninitialized state.
170314
Glean.instance._initialized = false;
171-
// Reset upload enabled state, not to inerfere with other tests.
172-
Glean.uploadEnabled = true;
173315

174316
// Stop ongoing jobs and clear pending pings queue.
175317
await Glean.pingUploader.clearPendingPingsQueue();
@@ -179,7 +321,7 @@ class Glean {
179321
await Glean.pingsDatabase.clearAll();
180322

181323
// Re-Initialize Glean.
182-
await Glean.initialize(applicationId, config);
324+
await Glean.initialize(applicationId, uploadEnabled, config);
183325
}
184326
}
185327

src/metrics/types/uuid.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { v4 as UUIDv4, validate as UUIDvalidate } from "uuid";
66

7+
import { KNOWN_CLIENT_ID } from "../../constants";
78
import { Metric, MetricType, CommonMetricData } from "metrics";
89
import { isString } from "utils";
910
import Glean from "glean";
@@ -17,7 +18,11 @@ export class UUIDMetric extends Metric<string, string> {
1718
if (!isString(v)) {
1819
return false;
1920
}
20-
21+
22+
if (v === KNOWN_CLIENT_ID) {
23+
return true;
24+
}
25+
2126
return UUIDvalidate(v);
2227
}
2328

0 commit comments

Comments
 (0)