Skip to content

Commit 693c2f5

Browse files
author
Beatriz Rizental
authored
Bug 1677443 - Implement initialize and core metrics (#23)
1 parent c8473c2 commit 693c2f5

File tree

14 files changed

+375
-13
lines changed

14 files changed

+375
-13
lines changed

src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ export const PING_INFO_STORAGE = "glean_ping_info";
2929
//
3030
// See: https://mozilla.github.io/glean/book/dev/core/internal/reserved-ping-names.html
3131
export const CLIENT_INFO_STORAGE = "glean_client_info";
32+
33+
// We will set the client id to this client id in case upload is disabled.
34+
export const KNOWN_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0";

src/glean.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import MetricsDatabase from "metrics/database";
66
import PingsDatabase from "pings/database";
77
import { isUndefined, sanitizeApplicationId } from "utils";
8+
import { CoreMetrics } from "internal_metrics";
9+
import { Lifetime } from "metrics";
810

911
class Glean {
1012
// The Glean singleton.
@@ -19,6 +21,8 @@ class Glean {
1921
private _uploadEnabled: boolean;
2022
// Whether or not Glean has been initialized.
2123
private _initialized: boolean;
24+
// Instances of Glean's core metrics.
25+
private _coreMetrics: CoreMetrics;
2226

2327
// Properties that will only be set on `initialize`.
2428
private _applicationId?: string;
@@ -30,6 +34,7 @@ class Glean {
3034
Use Glean.instance instead to access the Glean singleton.`);
3135
}
3236

37+
this._coreMetrics = new CoreMetrics();
3338
this._initialized = false;
3439
this._db = {
3540
metrics: new MetricsDatabase(),
@@ -39,6 +44,10 @@ class Glean {
3944
this._uploadEnabled = true;
4045
}
4146

47+
private static get coreMetrics(): CoreMetrics {
48+
return Glean.instance._coreMetrics;
49+
}
50+
4251
private static get instance(): Glean {
4352
if (!Glean._instance) {
4453
Glean._instance = new Glean();
@@ -51,14 +60,19 @@ class Glean {
5160
* Initialize Glean. This method should only be called once, subsequent calls will be no-op.
5261
*
5362
* @param applicationId The application ID (will be sanitized during initialization).
63+
* @param appBuild The build identifier generated by the CI system (e.g. "1234/A").
64+
* @param appDisplayVersion The user visible version string fro the application running Glean.js.
5465
*/
55-
static initialize(applicationId: string): void {
66+
static async initialize(applicationId: string, appBuild?: string, appDisplayVersion?: string): Promise<void> {
5667
if (Glean.instance._initialized) {
5768
console.warn("Attempted to initialize Glean, but it has already been initialized. Ignoring.");
5869
return;
5970
}
6071

6172
Glean.instance._applicationId = sanitizeApplicationId(applicationId);
73+
await Glean.coreMetrics.initialize(appBuild, appDisplayVersion);
74+
75+
Glean.instance._initialized = true;
6276
}
6377

6478
/**
@@ -79,6 +93,15 @@ class Glean {
7993
return Glean.instance._db.pings;
8094
}
8195

96+
/**
97+
* Gets this Glean's instance initialization status.
98+
*
99+
* @returns Whether or not the Glean singleton has been initialized.
100+
*/
101+
static get initialized(): boolean {
102+
return Glean.instance._initialized;
103+
}
104+
82105
// TODO: Make the following functions `private` once Bug 1682769 is resolved.
83106
static get uploadEnabled(): boolean {
84107
return Glean.instance._uploadEnabled;
@@ -99,16 +122,36 @@ class Glean {
99122
/**
100123
* **Test-only API**
101124
*
102-
* Resets the Glean singleton to its initial state.
125+
* Resets the Glean to an uninitialized state and clear app lifetime metrics.
103126
*
104127
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
105128
*/
106-
static async testRestGlean(): Promise<void> {
129+
static async testUninitialize(): Promise<void> {
130+
Glean.instance._initialized = false;
131+
await Glean.metricsDatabase.clear(Lifetime.Application);
132+
}
133+
134+
/**
135+
* **Test-only API**
136+
*
137+
* Resets the Glean singleton to its initial state and re-initializes it.
138+
*
139+
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
140+
*
141+
* @param applicationId The application ID (will be sanitized during initialization).
142+
* @param optionalInitializeArgs Optional arguments to be passed to `initialize`.
143+
*/
144+
static async testRestGlean(applicationId: string, ...optionalInitializeArgs: never[]): Promise<void> {
145+
// Get back to an uninitialized state.
146+
Glean.instance._initialized = false;
107147
// Reset upload enabled state, not to inerfere with other tests.
108148
Glean.uploadEnabled = true;
109149
// Clear the databases.
110150
await Glean.metricsDatabase.clearAll();
111151
await Glean.pingsDatabase.clearAll();
152+
153+
// Initialize Glean.
154+
await Glean.initialize(applicationId, ...optionalInitializeArgs);
112155
}
113156
}
114157

src/internal_metrics.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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 { KNOWN_CLIENT_ID, CLIENT_INFO_STORAGE } from "./constants";
6+
import UUIDMetricType from "metrics/types/uuid";
7+
import DatetimeMetricType from "metrics/types/datetime";
8+
import StringMetricType from "metrics/types/string";
9+
import { createMetric } from "metrics/utils";
10+
import TimeUnit from "metrics/time_unit";
11+
import { Lifetime } from "metrics";
12+
import Glean from "glean";
13+
import PlatformInfo from "platform_info";
14+
15+
export class CoreMetrics {
16+
readonly clientId: UUIDMetricType;
17+
readonly firstRunDate: DatetimeMetricType;
18+
readonly os: StringMetricType;
19+
readonly osVersion: StringMetricType;
20+
readonly architecture: StringMetricType;
21+
readonly locale: StringMetricType;
22+
// Provided by the user
23+
readonly appBuild: StringMetricType;
24+
readonly appDisplayVersion: StringMetricType;
25+
26+
constructor() {
27+
this.clientId = new UUIDMetricType({
28+
name: "client_id",
29+
category: "",
30+
sendInPings: ["glean_client_info"],
31+
lifetime: Lifetime.User,
32+
disabled: false,
33+
});
34+
35+
this.firstRunDate = new DatetimeMetricType({
36+
name: "first_run_date",
37+
category: "",
38+
sendInPings: ["glean_client_info"],
39+
lifetime: Lifetime.User,
40+
disabled: false,
41+
}, TimeUnit.Day);
42+
43+
this.os = new StringMetricType({
44+
name: "os",
45+
category: "",
46+
sendInPings: ["glean_client_info"],
47+
lifetime: Lifetime.Application,
48+
disabled: false,
49+
});
50+
51+
this.osVersion = new StringMetricType({
52+
name: "os_version",
53+
category: "",
54+
sendInPings: ["glean_client_info"],
55+
lifetime: Lifetime.Application,
56+
disabled: false,
57+
});
58+
59+
this.architecture = new StringMetricType({
60+
name: "architecture",
61+
category: "",
62+
sendInPings: ["glean_client_info"],
63+
lifetime: Lifetime.Application,
64+
disabled: false,
65+
});
66+
67+
this.locale = new StringMetricType({
68+
name: "locale",
69+
category: "",
70+
sendInPings: ["glean_client_info"],
71+
lifetime: Lifetime.Application,
72+
disabled: false,
73+
});
74+
75+
this.appBuild = new StringMetricType({
76+
name: "app_build",
77+
category: "",
78+
sendInPings: ["glean_client_info"],
79+
lifetime: Lifetime.Application,
80+
disabled: false,
81+
});
82+
83+
this.appDisplayVersion = new StringMetricType({
84+
name: "app_display_version",
85+
category: "",
86+
sendInPings: ["glean_client_info"],
87+
lifetime: Lifetime.Application,
88+
disabled: false,
89+
});
90+
}
91+
92+
async initialize(appBuild?: string, appDisplayVersion?: string): Promise<void> {
93+
await this.initializeClientId();
94+
await this.initializeFirstRunDate();
95+
await this.initializeOs();
96+
await this.initializeOsVersion();
97+
await this.initializeArchitecture();
98+
await this.initializeLocale();
99+
await this.appBuild.set(appBuild || "Unknown");
100+
await this.appDisplayVersion.set(appDisplayVersion || "Unkown");
101+
}
102+
103+
/**
104+
* Generates and sets the client_id if it is not set,
105+
* or if the current value is currepted.
106+
*/
107+
private async initializeClientId(): Promise<void> {
108+
let needNewClientId = false;
109+
const clientIdData = await Glean.metricsDatabase.getMetric(CLIENT_INFO_STORAGE, this.clientId);
110+
if (clientIdData) {
111+
try {
112+
const currentClientId = createMetric("uuid", clientIdData);
113+
if (currentClientId.payload() === KNOWN_CLIENT_ID) {
114+
needNewClientId = true;
115+
}
116+
} catch {
117+
console.warn("Unexpected value found for Glean clientId. Ignoring.");
118+
needNewClientId = true;
119+
}
120+
} else {
121+
needNewClientId = true;
122+
}
123+
124+
if (needNewClientId) {
125+
await this.clientId.generateAndSet();
126+
}
127+
}
128+
129+
/**
130+
* Generates and sets the first_run_date if it is not set.
131+
*/
132+
private async initializeFirstRunDate(): Promise<void> {
133+
const firstRunDate = await Glean.metricsDatabase.getMetric(
134+
CLIENT_INFO_STORAGE,
135+
this.firstRunDate
136+
);
137+
138+
if (!firstRunDate) {
139+
await this.firstRunDate.set();
140+
}
141+
}
142+
143+
/**
144+
* Gets and sets the os.
145+
*/
146+
async initializeOs(): Promise<void> {
147+
await this.os.set(await PlatformInfo.os());
148+
}
149+
150+
/**
151+
* Gets and sets the os.
152+
*/
153+
async initializeOsVersion(): Promise<void> {
154+
await this.osVersion.set(await PlatformInfo.osVersion());
155+
}
156+
157+
/**
158+
* Gets and sets the system architecture.
159+
*/
160+
async initializeArchitecture(): Promise<void> {
161+
await this.architecture.set(await PlatformInfo.arch());
162+
}
163+
164+
/**
165+
* Gets and sets the system / browser locale.
166+
*/
167+
async initializeLocale(): Promise<void> {
168+
await this.locale.set(await PlatformInfo.locale());
169+
}
170+
}

src/pings/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ class PingType {
4545
* @returns Whether or not the ping was successfully submitted.
4646
*/
4747
async submit(reason?: string): Promise<boolean> {
48+
if (!Glean.initialized) {
49+
console.info("Glean must be initialized before submitting pings.");
50+
return false;
51+
}
52+
4853
if (!Glean.uploadEnabled) {
4954
console.info("Glean disabled: not submitting any pings.");
5055
return false;

src/platform_info/index.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
// Must be up to date with https://github.com/mozilla/glean/blob/main/glean-core/src/system.rs
6+
export const enum KnownOperatingSystems {
7+
Android = "Android",
8+
iOS = "iOS",
9+
Linux = "Linux",
10+
MacOS = "Darwin",
11+
Windows = "Windows",
12+
FreeBSD = "FreeBSD",
13+
NetBSD = "NetBSD",
14+
OpenBSD = "OpenBSD",
15+
Solaris = "Solaris",
16+
// ChromeOS is not listed in the Glean SDK because it is not a possibility there.
17+
ChromeOS = "ChromeOS",
18+
Unknown = "Unknown",
19+
}
20+
21+
export interface PlatformInfo {
22+
/**
23+
* Gets and returns the current OS system.
24+
*
25+
* @returns The current OS.
26+
*/
27+
os(): Promise<KnownOperatingSystems>;
28+
29+
/**
30+
* Gets and returns the current OS system version.
31+
*
32+
* @returns The current OS version.
33+
*/
34+
osVersion(): Promise<string>;
35+
36+
/**
37+
* Gets and returnst the current system architecture.
38+
*
39+
* @returns The current system architecture.
40+
*/
41+
arch(): Promise<string>;
42+
43+
/**
44+
* Gets and returnst the current system / browser locale.
45+
*
46+
* @returns The current system / browser locale.
47+
*/
48+
locale(): Promise<string>;
49+
}
50+
51+
// Default export for tests sake.
52+
const MockPlatformInfo: PlatformInfo = {
53+
os(): Promise<KnownOperatingSystems> {
54+
return Promise.resolve(KnownOperatingSystems.Unknown);
55+
},
56+
57+
osVersion(): Promise<string> {
58+
return Promise.resolve("Unknown");
59+
},
60+
61+
arch(): Promise<string> {
62+
return Promise.resolve("Unknown");
63+
},
64+
65+
locale(): Promise<string> {
66+
return Promise.resolve("Unknown");
67+
},
68+
};
69+
export default MockPlatformInfo;

0 commit comments

Comments
 (0)