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" ;
66import Configuration from "config" ;
77import MetricsDatabase from "metrics/database" ;
88import PingsDatabase from "pings/database" ;
99import PingUploader from "upload" ;
1010import { isUndefined , sanitizeApplicationId } from "utils" ;
1111import { CoreMetrics } from "internal_metrics" ;
1212import { Lifetime } from "metrics" ;
13+ import { DatetimeMetric } from "metrics/types/datetime" ;
1314
1415class 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
0 commit comments