Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 169 additions & 27 deletions src/glean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { DEFAULT_TELEMETRY_ENDPOINT } from "./constants";
import { CLIENT_INFO_STORAGE, DEFAULT_TELEMETRY_ENDPOINT, KNOWN_CLIENT_ID } from "./constants";
import Configuration from "config";
import MetricsDatabase from "metrics/database";
import PingsDatabase from "pings/database";
import PingUploader from "upload";
import { isUndefined, sanitizeApplicationId } from "utils";
import { CoreMetrics } from "internal_metrics";
import { Lifetime } from "metrics";
import { DatetimeMetric } from "metrics/types/datetime";

class Glean {
// The Glean singleton.
Expand All @@ -20,8 +21,6 @@ class Glean {
metrics: MetricsDatabase,
pings: PingsDatabase
}
// Whether or not to record metrics.
private _uploadEnabled: boolean;
// Whether or not Glean has been initialized.
private _initialized: boolean;
// Instances of Glean's core metrics.
Expand All @@ -30,8 +29,13 @@ class Glean {
private _pingUploader: PingUploader

// Properties that will only be set on `initialize`.

// The application ID (will be sanitized during initialization).
private _applicationId?: string;
// The server pings are sent to.
private _serverEndpoint?: URL;
// Whether or not to record metrics.
private _uploadEnabled?: boolean;

private constructor() {
if (!isUndefined(Glean._instance)) {
Expand All @@ -47,8 +51,14 @@ class Glean {
metrics: new MetricsDatabase(),
pings: new PingsDatabase(this._pingUploader)
};
// Temporarily setting this to true always, until Bug 1677444 is resolved.
this._uploadEnabled = true;
}

private static get instance(): Glean {
if (!Glean._instance) {
Glean._instance = new Glean();
}

return Glean._instance;
}

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

private static get instance(): Glean {
if (!Glean._instance) {
Glean._instance = new Glean();
}
private static get uploadEnabled(): boolean {
return Glean.instance._uploadEnabled || false;
}

return Glean._instance;
private static set uploadEnabled(value: boolean) {
Glean.instance._uploadEnabled = value;
}

/**
* Handles the changing of state from upload disabled to enabled.
*
* Should only be called when the state actually changes.
*
* The `uploadEnabled` flag is set to true and the core Glean metrics are recreated.
*/
private static async onUploadEnabled(): Promise<void> {
Glean.uploadEnabled = true;
await Glean.coreMetrics.initialize();
}

/**
* Handles the changing of state from upload enabled to disabled.
*
* Should only be called when the state actually changes.
*
* A deletion_request ping is sent, all pending metrics, events and queued
* pings are cleared, and the client_id is set to KNOWN_CLIENT_ID.
* Afterward, the upload_enabled flag is set to false.
*/
private static async onUploadDisabled(): Promise<void> {
Glean.uploadEnabled = false;
await Glean.clearMetrics();
}

/**
* Clears any pending metrics and pings.
*
* This function is only supposed to be called when telemetry is disabled.
*/
private static async clearMetrics(): Promise<void> {
// Stop ongoing uploading jobs and clear pending pings queue.
await Glean.pingUploader.clearPendingPingsQueue();

// There is only one metric that we want to survive after clearing all
// metrics: first_run_date. Here, we store its value
// so we can restore it after clearing the metrics.
//
// TODO: This may throw an exception. Bug 1687475 will resolve this.
const existingFirstRunDate = new DatetimeMetric(
await Glean.metricsDatabase.getMetric(
CLIENT_INFO_STORAGE,
Glean.coreMetrics.firstRunDate
)
);

// Clear the databases.
await Glean.metricsDatabase.clearAll();
await Glean.pingsDatabase.clearAll();

// We need to briefly set upload_enabled to true here so that `set`
// is not a no-op.
//
// Note that we can't provide the same guarantees as glean-core here.
// If by any change another actor attempts to record a metric while
// we are setting the known client id and first run date, they will be allowed to.
//
// TODO: Bug 1687491 might resolve this issue.
Glean.uploadEnabled = true;

// Store a "dummy" KNOWN_CLIENT_ID in the client_id metric. This will
// make it easier to detect if pings were unintentionally sent after
// uploading is disabled.
await Glean.coreMetrics.clientId.set(KNOWN_CLIENT_ID);

// Restore the first_run_date.
await Glean.coreMetrics.firstRunDate.set(existingFirstRunDate.date);

Glean.uploadEnabled = false;
}

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

if (applicationId.length === 0) {
throw new Error("Unable to initialize Glean, applicationId cannot be an empty string.");
}
Glean.instance._applicationId = sanitizeApplicationId(applicationId);

Glean.instance._serverEndpoint = config
? config.serverEndpoint : new URL(DEFAULT_TELEMETRY_ENDPOINT);
await Glean.coreMetrics.initialize(config?.appBuild, config?.appDisplayVersion);

if (uploadEnabled) {
await Glean.onUploadEnabled();
} else {
await Glean.onUploadDisabled();
}

Glean.instance._initialized = true;

Expand Down Expand Up @@ -118,31 +224,66 @@ class Glean {
return Glean.instance._initialized;
}

// TODO: Make the following functions `private` once Bug 1682769 is resolved.
static get uploadEnabled(): boolean {
return Glean.instance._uploadEnabled;
}

static set uploadEnabled(value: boolean) {
Glean.instance._uploadEnabled = value;
}

static get applicationId(): string | undefined {
if (!Glean.instance._initialized) {
console.error("Attempted to access the Glean.applicationId before Glean was initialized.");
console.warn("Attempted to access the Glean.applicationId before Glean was initialized.");
}

return Glean.instance._applicationId;
}

static get serverEndpoint(): URL | undefined {
if (!Glean.instance._initialized) {
console.error("Attempted to access the Glean.serverEndpoint before Glean was initialized.");
console.warn("Attempted to access the Glean.serverEndpoint before Glean was initialized.");
}

return Glean.instance._serverEndpoint;
}

/**
* Determines whether upload is enabled.
*
* When upload is disabled, no data will be recorded.
*
* @returns Whether upload is enabled.
*/
static isUploadEnabled(): boolean {
if (!Glean.instance._initialized) {
console.warn("Attempted to access the Glean.uploadEnabled before Glean was initialized.");
}

return Glean.uploadEnabled;
}

/**
* Sets whether upload is enabled or not.
*
* When uploading is disabled, metrics aren't recorded at all and no
* data is uploaded.
*
* When disabling, all pending metrics, events and queued pings are cleared.
*
* When enabling, the core Glean metrics are recreated.
*
* If the value of this flag is not actually changed, this is a no-op.
*
* @param flag When true, enable metric collection.
*
* @returns Whether the flag was different from the current value,
* and actual work was done to clear or reinstate metrics.
*/
static async setUploadEnabled(flag: boolean): Promise<boolean> {
if (Glean.uploadEnabled !== flag) {
if (flag) {
await Glean.onUploadEnabled();
} else {
await Glean.onUploadDisabled();
}
return true;
}
return false;
}

/**
* **Test-only API**
*
Expand All @@ -163,13 +304,14 @@ class Glean {
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
*
* @param applicationId The application ID (will be sanitized during initialization).
* @param uploadEnabled Determines whether telemetry is enabled.
* If disabled, all persisted metrics, events and queued pings (except
* first_run_date) are cleared. Default to `true`.
* @param config Glean configuration options.
*/
static async testRestGlean(applicationId: string, config?: Configuration): Promise<void> {
static async testRestGlean(applicationId: string, uploadEnabled = true, config?: Configuration): Promise<void> {
// Get back to an uninitialized state.
Glean.instance._initialized = false;
// Reset upload enabled state, not to inerfere with other tests.
Glean.uploadEnabled = true;

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

// Re-Initialize Glean.
await Glean.initialize(applicationId, config);
await Glean.initialize(applicationId, uploadEnabled, config);
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/metrics/types/uuid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

import { KNOWN_CLIENT_ID } from "../../constants";
import { Metric, MetricType, CommonMetricData } from "metrics";
import { isString } from "utils";
import Glean from "glean";
Expand All @@ -17,7 +18,11 @@ export class UUIDMetric extends Metric<string, string> {
if (!isString(v)) {
return false;
}


if (v === KNOWN_CLIENT_ID) {
return true;
}

return UUIDvalidate(v);
}

Expand Down
Loading