Skip to content

Commit 66eedb5

Browse files
committed
Address review feedback
1 parent 8d8dd25 commit 66eedb5

File tree

7 files changed

+63
-76
lines changed

7 files changed

+63
-76
lines changed

src/metrics/events_database.ts

Lines changed: 36 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
// * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
import { Store } from "storage";
6-
import WeakStore from "storage/weak";
7-
import { MetricType } from "metrics";
6+
import PersistentStore from "storage/persistent";
87
import { isUndefined, JSONArray, JSONObject, JSONValue } from "utils";
8+
import EventMetricType from "./types/event";
99

1010
export interface Metrics {
1111
[aMetricType: string]: {
@@ -14,21 +14,28 @@ export interface Metrics {
1414
}
1515

1616
// An helper type for the 'extra' map.
17-
export type ExtraMap = { [name: string]: string };
17+
export type ExtraMap = Record<string, string>;
1818

1919
// Represents the recorded data for a single event.
2020
export class RecordedEvent {
2121
constructor(
22-
category: string,
23-
name: string,
24-
timestamp: number,
25-
extra?: ExtraMap,
26-
) {
27-
this.category = category;
28-
this.name = name;
29-
this.timestamp = timestamp;
30-
this.extra = extra;
31-
}
22+
// The event's category.
23+
//
24+
// This is defined by users in the metrics file.
25+
readonly category: string,
26+
// The event's name.
27+
//
28+
// This is defined by users in the metrics file.
29+
readonly name: string,
30+
// The timestamp of when the event was recorded.
31+
//
32+
// This allows to order events.
33+
readonly timestamp: number,
34+
// A map of all extra data values.
35+
//
36+
// The set of allowed extra keys is defined by users in the metrics file.
37+
readonly extra?: ExtraMap,
38+
) {}
3239

3340
static toJSONObject(e: RecordedEvent): JSONObject {
3441
return {
@@ -47,23 +54,6 @@ export class RecordedEvent {
4754
e["extra"] as ExtraMap | undefined
4855
);
4956
}
50-
51-
// The event's category.
52-
//
53-
// This is defined by users in the metrics file.
54-
readonly category: string;
55-
// The event's name.
56-
//
57-
// This is defined by users in the metrics file.
58-
readonly name: string;
59-
// The timestamp of when the event was recorded.
60-
//
61-
// This allows to order events.
62-
readonly timestamp: number;
63-
// A map of all extra data values.
64-
//
65-
// The set of allowed extra keys is defined by users in the metrics file.
66-
readonly extra?: ExtraMap;
6757
}
6858

6959
/**
@@ -91,7 +81,7 @@ class EventsDatabase {
9181
private eventsStore: Store;
9282

9383
constructor() {
94-
this.eventsStore = new WeakStore("unused");
84+
this.eventsStore = new PersistentStore("unused");
9585
}
9686

9787
/**
@@ -100,7 +90,7 @@ class EventsDatabase {
10090
* @param metric The metric to record to.
10191
* @param value The value we want to record to the given metric.
10292
*/
103-
async record(metric: MetricType, value: RecordedEvent): Promise<void> {
93+
async record(metric: EventMetricType, value: RecordedEvent): Promise<void> {
10494
if (metric.disabled) {
10595
return;
10696
}
@@ -127,26 +117,19 @@ class EventsDatabase {
127117
* @returns an array of `RecordedEvent` containing the found events or `undefined`
128118
* if no recorded event was found.
129119
*/
130-
async testGetValue(
120+
async getEvents(
131121
ping: string,
132-
metric: MetricType
122+
metric: EventMetricType
133123
): Promise<RecordedEvent[] | undefined> {
134-
const value = await this.eventsStore.get([ping]);
135-
if (!value) {
124+
const events = await this.getAndValidatePingData(ping);
125+
if (events.length === 0) {
136126
return undefined;
137127
}
138128

139-
const rawEvents = value as JSONArray;
140-
return rawEvents
129+
return events
141130
// Only report events for the requested metric.
142131
.filter((e) => {
143-
const rawEventObj = e as JSONObject;
144-
return (rawEventObj["category"] === metric.category)
145-
&& (rawEventObj["name"] === metric.name);
146-
})
147-
// Convert them to `RecordedEvent`s.
148-
.map((e) => {
149-
return RecordedEvent.fromJSONObject(e as JSONObject);
132+
return (e.category === metric.category) && (e.name === metric.name);
150133
});
151134
}
152135

@@ -164,7 +147,7 @@ class EventsDatabase {
164147
* @returns The ping payload found for the given parameters or an empty object
165148
* in case no data was found or the data that was found, was invalid.
166149
*/
167-
private async getAndValidatePingData(ping: string): Promise<JSONArray> {
150+
private async getAndValidatePingData(ping: string): Promise<RecordedEvent[]> {
168151
const data = await this.eventsStore.get([ping]);
169152
if (isUndefined(data)) {
170153
return [];
@@ -177,7 +160,7 @@ class EventsDatabase {
177160
return [];
178161
}
179162

180-
return data;
163+
return data.map((e) => RecordedEvent.fromJSONObject(e as JSONObject));
181164
}
182165

183166
/**
@@ -189,7 +172,7 @@ class EventsDatabase {
189172
* @returns An object containing all the metrics recorded to the given ping,
190173
* `undefined` in case the ping doesn't contain any recorded metrics.
191174
*/
192-
async getPingMetrics(ping: string, clearPingLifetimeData: boolean): Promise<JSONArray | undefined> {
175+
async getPingEvents(ping: string, clearPingLifetimeData: boolean): Promise<JSONArray | undefined> {
193176
const pingData = await this.getAndValidatePingData(ping);
194177

195178
if (clearPingLifetimeData) {
@@ -202,20 +185,16 @@ class EventsDatabase {
202185

203186
// Sort the events by their timestamp.
204187
const sortedData = pingData.sort((a, b) => {
205-
const objA = a as unknown as RecordedEvent;
206-
const objB = b as unknown as RecordedEvent;
207-
return objA["timestamp"] - objB["timestamp"];
188+
return a.timestamp - b.timestamp;
208189
});
209190

210191
// Make all the events relative to the first one.
211-
const firstTimestamp =
212-
(sortedData[0] as unknown as RecordedEvent)["timestamp"];
192+
const firstTimestamp = sortedData[0].timestamp;
213193

214194
return sortedData.map((e) => {
215-
const objE = e as JSONObject;
216-
const timestamp = (objE["timestamp"] as number) ?? 0;
217-
objE["timestamp"] = timestamp - firstTimestamp;
218-
return objE;
195+
const adjusted = RecordedEvent.toJSONObject(e);
196+
adjusted["timestamp"] = e.timestamp - firstTimestamp;
197+
return adjusted;
219198
});
220199
}
221200

src/metrics/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export abstract class MetricType implements CommonMetricData {
131131
* @returns The generated identifier.
132132
*/
133133
get identifier(): string {
134-
if (this.category && this.category.length > 0) {
134+
if (this.category.length > 0) {
135135
return `${this.category}.${this.name}`;
136136
} else {
137137
return this.name;

src/metrics/types/event.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@
55
import { MetricType, CommonMetricData } from "metrics";
66
import Glean from "glean";
77
import { ExtraMap, RecordedEvent } from "metrics/events_database";
8+
import { isUndefined } from "utils";
89

910
const MAX_LENGTH_EXTRA_KEY_VALUE = 100;
1011

1112
/**
12-
* A datetime metric.
13-
*
14-
* Used to record an absolute date and time,
15-
* such as the time the user first ran the application.
13+
* An event metric.
1614
*/
1715
class EventMetricType extends MetricType {
1816
private allowedExtraKeys?: string[];
@@ -30,17 +28,21 @@ class EventMetricType extends MetricType {
3028
* @returns the number of milliseconds since the time origin.
3129
*/
3230
protected getMonotonicNow(): number {
33-
return Math.round(performance.now() / 1000);
31+
// Sadly, `performance.now` is not available outside of browsers, which
32+
// means we should get creative to find a proper clock. Fall back to `Date.now`
33+
// for now, until bug 1690528 is fixed.
34+
const now = isUndefined(performance) ? Date.now() : performance.now();
35+
return Math.round(now / 1000);
3436
}
3537

3638
/**
3739
* Record an event by using the information
3840
* provided by the instance of this class.
3941
*
4042
* @param extra optional. This is a map, both keys and values need to be
41-
* strings, keys are identifiers. This is used for events where
42-
* additional richer context is needed.
43-
* The maximum length for values is 100 bytes.
43+
* strings, keys are identifiers. This is used for events where
44+
* additional richer context is needed.
45+
* The maximum length for values is 100 bytes.
4446
*/
4547
async record(extra?: ExtraMap): Promise<void> {
4648
if (!this.shouldRecord()) {
@@ -88,7 +90,7 @@ class EventMetricType extends MetricType {
8890
*/
8991
async testGetValue(ping?: string): Promise<RecordedEvent[] | undefined> {
9092
const pingToQuery = ping ?? this.sendInPings[0];
91-
const events = await Glean.eventsDatabase.testGetValue(pingToQuery, this);
93+
const events = await Glean.eventsDatabase.getEvents(pingToQuery, this);
9294
if (!events) {
9395
return undefined;
9496
}

src/metrics/utils.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { JSONValue } from "utils";
88
import { BooleanMetric } from "metrics/types/boolean";
99
import { CounterMetric } from "metrics/types/counter";
1010
import { DatetimeMetric } from "./types/datetime";
11-
import { EventMetric } from "./types/event";
1211
import { StringMetric } from "metrics/types/string";
1312
import { UUIDMetric } from "metrics/types/uuid";
1413

@@ -21,7 +20,6 @@ const METRIC_MAP: {
2120
"boolean": BooleanMetric,
2221
"counter": CounterMetric,
2322
"datetime": DatetimeMetric,
24-
"event": EventMetric,
2523
"string": StringMetric,
2624
"uuid": UUIDMetric,
2725
});

src/pings/maker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export async function getPingHeaders(): Promise<Record<string, string> | undefin
169169
*/
170170
export async function collectPing(ping: PingType, reason?: string): Promise<PingPayload | undefined> {
171171
const metricsData = await Glean.metricsDatabase.getPingMetrics(ping.name, true);
172-
const eventsData = await Glean.eventsDatabase.getPingMetrics(ping.name, true);
172+
const eventsData = await Glean.eventsDatabase.getPingEvents(ping.name, true);
173173
if (!metricsData && !eventsData && !ping.sendIfEmtpy) {
174174
console.info(`Storage for ${ping.name} empty. Bailing out.`);
175175
return;

tests/metrics/database.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ describe("MetricsDatabase", function() {
5757
const db = new Database();
5858
const metric = new StringMetricType({
5959
name: "aMetric",
60+
category: "",
6061
sendInPings: ["aPing", "otherPing", "oneMorePing"],
6162
lifetime: Lifetime.Application,
6263
disabled: false
@@ -75,6 +76,7 @@ describe("MetricsDatabase", function() {
7576
const db = new Database();
7677
const metric = new StringMetricType({
7778
name: "aMetric",
79+
category: "",
7880
sendInPings: ["aPing"],
7981
lifetime: Lifetime.Application,
8082
disabled: false
@@ -90,6 +92,7 @@ describe("MetricsDatabase", function() {
9092
const db = new Database();
9193
const metric = new StringMetricType({
9294
name: "aMetric",
95+
category: "",
9396
sendInPings: ["aPing"],
9497
lifetime: Lifetime.Application,
9598
disabled: true
@@ -154,6 +157,7 @@ describe("MetricsDatabase", function() {
154157
const db = new Database();
155158
const metric = new StringMetricType({
156159
name: "aMetric",
160+
category: "",
157161
sendInPings: ["aPing", "otherPing", "oneMorePing"],
158162
lifetime: Lifetime.Application,
159163
disabled: false
@@ -174,6 +178,7 @@ describe("MetricsDatabase", function() {
174178
const db = new Database();
175179
const metric = new StringMetricType({
176180
name: "aMetric",
181+
category: "",
177182
sendInPings: ["aPing"],
178183
lifetime: Lifetime.Application,
179184
disabled: true
@@ -192,6 +197,7 @@ describe("MetricsDatabase", function() {
192197
const db = new Database();
193198
const metric = new StringMetricType({
194199
name: "aMetric",
200+
category: "",
195201
sendInPings: ["aPing"],
196202
lifetime: Lifetime.Application,
197203
disabled: false
@@ -205,6 +211,7 @@ describe("MetricsDatabase", function() {
205211
const db = new Database();
206212
const metric = new StringMetricType({
207213
name: "aMetric",
214+
category: "",
208215
sendInPings: ["aPing"],
209216
lifetime: Lifetime.Application,
210217
disabled: false
@@ -217,6 +224,7 @@ describe("MetricsDatabase", function() {
217224
const db = new Database();
218225
const metric = new StringMetricType({
219226
name: "aMetric",
227+
category: "",
220228
sendInPings: ["aPing"],
221229
lifetime: Lifetime.Application,
222230
disabled: false

tests/metrics/events_database.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe("EventsDatabase", function() {
6767

6868
it("getPingMetrics returns undefined if nothing is recorded", async function () {
6969
const db = new EventsDatabase();
70-
const data = await db.getPingMetrics("test-unknown-ping", true);
70+
const data = await db.getPingEvents("test-unknown-ping", true);
7171

7272
assert.strictEqual(data, undefined);
7373
});
@@ -85,7 +85,7 @@ describe("EventsDatabase", function() {
8585

8686
// We didn't record anything yet, so we don't expect anything to be
8787
// stored.
88-
let snapshot = await db.getPingMetrics("store1", false);
88+
let snapshot = await db.getPingEvents("store1", false);
8989
assert.strictEqual(snapshot, undefined);
9090

9191
await db.record(metric, new RecordedEvent(
@@ -95,14 +95,14 @@ describe("EventsDatabase", function() {
9595
));
9696

9797
// Take a first snapshot and clear the recorded content.
98-
snapshot = await db.getPingMetrics("store1", true);
98+
snapshot = await db.getPingEvents("store1", true);
9999
assert.ok(snapshot != undefined);
100100

101101
// If we snapshot a second time, the store must be empty.
102-
const empty_snapshot = await db.getPingMetrics("store1", false);
102+
const empty_snapshot = await db.getPingEvents("store1", false);
103103
assert.strictEqual(empty_snapshot, undefined);
104104

105-
const store2 = await db.getPingMetrics("store2", false);
105+
const store2 = await db.getPingEvents("store2", false);
106106
for (const events of [snapshot, store2]) {
107107
assert.ok(events != undefined);
108108
assert.strictEqual(1, events.length);
@@ -142,7 +142,7 @@ describe("EventsDatabase", function() {
142142
10000,
143143
));
144144

145-
const snapshot = await db.getPingMetrics("store1", true);
145+
const snapshot = await db.getPingEvents("store1", true);
146146
assert.ok(snapshot);
147147
assert.strictEqual(3, snapshot.length);
148148
assert.strictEqual(0, (snapshot[0] as JSONObject)["timestamp"]);

0 commit comments

Comments
 (0)