Skip to content

Commit 4278377

Browse files
committed
Only delete actually invalid data when collecting pings
Previous behaviour was to delete the whole ping when that happened. This is a nice change either way, we should always delete as little data as possible. However this becomes even more necessary if we decide to go ahead with the changes in the previous commit.
1 parent e5502de commit 4278377

File tree

2 files changed

+74
-75
lines changed

2 files changed

+74
-75
lines changed

glean/src/core/metrics/database.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -42,38 +42,6 @@ export function generateReservedMetricIdentifiers(name: string): {
4242
};
4343
}
4444

45-
/**
46-
* Verifies if a given value is a valid Metrics object.
47-
*
48-
* @param v The value to verify
49-
* @returns Whether or not `v` is a valid Metrics object.
50-
*/
51-
export function isValidInternalMetricsRepresentation(v: unknown): v is Metrics {
52-
if (isObject(v)) {
53-
// The root keys should all be metric types.
54-
for (const metricType in v) {
55-
const metrics = v[metricType];
56-
if (isObject(metrics)) {
57-
for (const metricIdentifier in metrics) {
58-
if (!validateMetricInternalRepresentation(metricType, metrics[metricIdentifier])) {
59-
log(
60-
LOG_TAG,
61-
`Invalid metric representation found for metric "${metricIdentifier}"`,
62-
LoggingLevel.Debug
63-
);
64-
return false;
65-
}
66-
}
67-
} else {
68-
return false;
69-
}
70-
}
71-
return true;
72-
} else {
73-
return false;
74-
}
75-
}
76-
7745
/**
7846
* Creates the metrics payload from a metrics object with metrics in their internal representation.
7947
*
@@ -260,24 +228,60 @@ class MetricsDatabase {
260228
* @returns The ping payload found for the given parameters or an empty object
261229
* in case no data was found or the data that was found, was invalid.
262230
*/
263-
private async getAndValidatePingData(ping: string, lifetime: Lifetime): Promise<Metrics> {
231+
private async getCorrectedPingData(ping: string, lifetime: Lifetime): Promise<Metrics> {
264232
const store = this._chooseStore(lifetime);
265233
const data = await store.get([ping]);
266234
if (isUndefined(data)) {
267235
return {};
268236
}
269237

270-
if (!isValidInternalMetricsRepresentation(data)) {
238+
if (!isObject(data)) {
271239
log(
272240
LOG_TAG,
273-
`Unexpected value found for ping "${ping}" in "${lifetime}" store: ${JSON.stringify(data)}. Clearing.`,
241+
`Invalid value found in storage for ping "${ping}". Deleting.`,
274242
LoggingLevel.Debug
275243
);
276244
await store.delete([ping]);
277245
return {};
278246
}
279247

280-
return data;
248+
const correctedData: Metrics = {};
249+
// All top keys should be metric types.
250+
for (const metricType in data) {
251+
const metrics = data[metricType];
252+
if (!isObject(metrics)) {
253+
log(
254+
LOG_TAG,
255+
`Unexpected data found in storage for metrics of type "${metricType}" in ping "${ping}". Deleting.`,
256+
LoggingLevel.Debug
257+
);
258+
await store.delete([ping, metricType]);
259+
continue;
260+
}
261+
262+
for (const metricIdentifier in metrics) {
263+
if (!validateMetricInternalRepresentation(metricType, metrics[metricIdentifier])) {
264+
log(
265+
LOG_TAG,
266+
`Invalid value found in storage for metric "${metricIdentifier}". Deleting.`,
267+
LoggingLevel.Debug
268+
);
269+
270+
await store.delete([ping, metricType, metricIdentifier]);
271+
continue;
272+
}
273+
274+
if (!correctedData[metricType]) {
275+
correctedData[metricType] = {};
276+
}
277+
278+
// Coersion is fine here, `validateMetricInternalRepresentation`
279+
// validated that this is of the correct type.
280+
correctedData[metricType][metricIdentifier] = metrics[metricIdentifier] as JSONValue;
281+
}
282+
}
283+
284+
return correctedData;
281285
}
282286

283287
private processLabeledMetric(snapshot: Metrics, metricType: string, metricId: string, metricData: JSONValue) {
@@ -313,9 +317,9 @@ class MetricsDatabase {
313317
* `undefined` in case the ping doesn't contain any recorded metrics.
314318
*/
315319
async getPingMetrics(ping: string, clearPingLifetimeData: boolean): Promise<Metrics | undefined> {
316-
const userData = await this.getAndValidatePingData(ping, Lifetime.User);
317-
const pingData = await this.getAndValidatePingData(ping, Lifetime.Ping);
318-
const appData = await this.getAndValidatePingData(ping, Lifetime.Application);
320+
const userData = await this.getCorrectedPingData(ping, Lifetime.User);
321+
const pingData = await this.getCorrectedPingData(ping, Lifetime.Ping);
322+
const appData = await this.getCorrectedPingData(ping, Lifetime.Application);
319323

320324
if (clearPingLifetimeData && Object.keys(pingData).length > 0) {
321325
await this.clear(Lifetime.Ping, ping);

glean/tests/unit/core/metrics/database.spec.ts

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import assert from "assert";
66

7-
import Database, { generateReservedMetricIdentifiers, isValidInternalMetricsRepresentation } from "../../../../src/core/metrics/database";
7+
import Database, { generateReservedMetricIdentifiers } from "../../../../src/core/metrics/database";
88
import StringMetricType, { StringMetric } from "../../../../src/core/metrics/types/string";
99

1010
import type { JSONObject, JSONValue } from "../../../../src/core/utils";
@@ -259,48 +259,43 @@ describe("MetricsDatabase", function() {
259259
});
260260

261261
describe("getPingMetrics", function() {
262-
it("isValidInternalMetricsRepresentation validates correctly", function() {
263-
// Invalids
264-
assert.strictEqual(false, isValidInternalMetricsRepresentation("not even an object"));
265-
assert.strictEqual(false, isValidInternalMetricsRepresentation({ 1: "an array-like object in not a ping!" }));
266-
assert.strictEqual(false, isValidInternalMetricsRepresentation({
267-
"aPing": {
268-
"string": {
269-
"too.nested": "not quite"
270-
}
271-
}
262+
it("when incorrect data is found on the storage, it is deleted", async function() {
263+
const db = new Database();
264+
await db["appStore"].update(["aPing"], () => "not even an object");
265+
assert.strictEqual(await db.getPingMetrics("aPing", false), undefined);
266+
assert.strictEqual(await db["appStore"].get(["aPing"]), undefined);
267+
268+
await db["appStore"].update(["aPing"], () => ({
269+
"string": "this should be an object"
272270
}));
273-
assert.strictEqual(false, isValidInternalMetricsRepresentation({ "string": "almost!" }));
274-
// Valids
275-
assert.strictEqual(true, isValidInternalMetricsRepresentation({ "string": {} }));
276-
assert.strictEqual(true, isValidInternalMetricsRepresentation({ "string": { "there.we": "go" } }));
277-
assert.strictEqual(true, isValidInternalMetricsRepresentation({
271+
assert.strictEqual(await db.getPingMetrics("aPing", false), undefined);
272+
assert.deepStrictEqual(await db["appStore"].get(["aPing"]), {});
273+
274+
await db["appStore"].update(["aPing"], () => ({
278275
"string": {
279-
"string.one": "foo",
280-
"string.two": "bar",
281-
"string.three": "baz",
276+
"my.string": 1
282277
}
283278
}));
284-
assert.strictEqual(true, isValidInternalMetricsRepresentation({
279+
assert.strictEqual(await db.getPingMetrics("aPing", false), undefined);
280+
assert.deepStrictEqual(await db["appStore"].get(["aPing"]), { "string": {} });
281+
282+
await db["appStore"].update(["aPing"], () => ({
285283
"string": {
286-
"string.one": "foo",
287-
"string.two": "bar",
288-
"string.three": "baz",
284+
"my.string": "a string"
289285
},
290-
"boolean": {
291-
"this.is": true,
292-
"ping": false,
293-
"looks": true
294-
}
286+
"wrong": "very wrong"
295287
}));
296-
assert.strictEqual(true, isValidInternalMetricsRepresentation({}));
297-
});
298-
299-
it("when incorrect data is found on the storage, it is deleted", async function() {
300-
const db = new Database();
301-
await db["appStore"].update(["aPing"], () => "not even a string");
302-
assert.strictEqual(await db.getPingMetrics("aPing", false), undefined);
303-
assert.strictEqual(await db["appStore"].get(["aPing"]), undefined);
288+
assert.deepStrictEqual(await db.getPingMetrics("aPing", false), {
289+
"string": {
290+
"my.string": "a string"
291+
}
292+
});
293+
assert.deepStrictEqual(await db["appStore"].get(["aPing"]),
294+
{
295+
"string": {
296+
"my.string": "a string"
297+
}
298+
});
304299
});
305300

306301
it("getting a ping with metric from only one lifetime works correctly", async function() {

0 commit comments

Comments
 (0)