Skip to content

Commit 5768afd

Browse files
committed
Introduce QuantityMetric (#281)
1 parent dc353f1 commit 5768afd

File tree

4 files changed

+216
-0
lines changed

4 files changed

+216
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[Full changelog](https://github.com/mozilla/glean.js/compare/v0.11.0...main)
44

55
* [#279](https://github.com/mozilla/glean.js/pull/279): BUGFIX: Ensure only empty pings triggers logging of "empty ping" messages.
6+
* [#281](https://github.com/mozilla/glean.js/pull/281): Add QuantityMetric
67

78
# v0.11.0 (2021-05-03)
89

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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 type { CommonMetricData } from "../index.js";
6+
import { MetricType } from "../index.js";
7+
import { isNumber } from "../../utils.js";
8+
import { Context } from "../../context.js";
9+
import { Metric } from "../metric.js";
10+
11+
export class QuantityMetric extends Metric<number, number> {
12+
constructor(v: unknown) {
13+
super(v);
14+
}
15+
16+
validate(v: unknown): v is number {
17+
if (!isNumber(v)) {
18+
return false;
19+
}
20+
21+
if (v < 0) {
22+
return false;
23+
}
24+
25+
return true;
26+
}
27+
28+
payload(): number {
29+
return this._inner;
30+
}
31+
}
32+
33+
/**
34+
* A quantity metric.
35+
*
36+
* Used to store quantity.
37+
* The value can only be non-negative.
38+
*/
39+
class QuantityMetricType extends MetricType {
40+
constructor(meta: CommonMetricData) {
41+
super("quantity", meta);
42+
}
43+
44+
/**
45+
* An internal implemention of `set` that does not dispatch the recording task.
46+
*
47+
* # Important
48+
*
49+
* This is absolutely not meant to be used outside of Glean itself.
50+
* It may cause multiple issues because it cannot guarantee
51+
* that the recording of the metric will happen in order with other Glean API calls.
52+
*
53+
* @param instance The metric instance to record to.
54+
* @param value The string we want to set to.
55+
*/
56+
static async _private_setUndispatched(instance: QuantityMetricType, value: number): Promise<void> {
57+
if (!instance.shouldRecord(Context.uploadEnabled)) {
58+
return;
59+
}
60+
61+
if (value < 0) {
62+
console.warn(`Attempted to set an invalid value ${value}. Ignoring.`);
63+
return;
64+
}
65+
66+
if (value > Number.MAX_SAFE_INTEGER) {
67+
console.warn(`Attempted to set a big value ${value}. Capped at ${Number.MAX_SAFE_INTEGER}.`);
68+
value = Number.MAX_SAFE_INTEGER;
69+
}
70+
71+
const metric = new QuantityMetric(value);
72+
await Context.metricsDatabase.record(instance, metric);
73+
}
74+
75+
/**
76+
* Sets to the specified quantity value.
77+
*
78+
* @param value the value to set.
79+
*/
80+
set(value: number): void {
81+
Context.dispatcher.launch(() => QuantityMetricType._private_setUndispatched(this, value));
82+
}
83+
84+
/**
85+
* **Test-only API.**
86+
*
87+
* Gets the currently stored value as a number.
88+
*
89+
* This doesn't clear the stored value.
90+
*
91+
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
92+
*
93+
* @param ping the ping from which we want to retrieve this metrics value from.
94+
* Defaults to the first value in `sendInPings`.
95+
*
96+
* @returns The value found in storage or `undefined` if nothing was found.
97+
*/
98+
async testGetValue(ping: string = this.sendInPings[0]): Promise<number | undefined> {
99+
let metric: number | undefined;
100+
await Context.dispatcher.testLaunch(async () => {
101+
metric = await Context.metricsDatabase.getMetric<number>(ping, this);
102+
});
103+
return metric;
104+
}
105+
}
106+
107+
export default QuantityMetricType;

glean/src/core/metrics/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { LabeledMetric } from "./types/labeled.js";
1010
import { BooleanMetric } from "./types/boolean.js";
1111
import { CounterMetric } from "./types/counter.js";
1212
import { DatetimeMetric } from "./types/datetime.js";
13+
import { QuantityMetric } from "./types/quantity";
1314
import { StringMetric } from "./types/string.js";
1415
import { TimespanMetric } from "./types/timespan.js";
1516
import { UUIDMetric } from "./types/uuid.js";
@@ -26,6 +27,7 @@ const METRIC_MAP: {
2627
"labeled_boolean": LabeledMetric,
2728
"labeled_counter": LabeledMetric,
2829
"labeled_string": LabeledMetric,
30+
"quantity": QuantityMetric,
2931
"string": StringMetric,
3032
"timespan": TimespanMetric,
3133
"uuid": UUIDMetric,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 assert from "assert";
6+
import { Context } from "../../../src/core/context";
7+
8+
import Glean from "../../../src/core/glean";
9+
import { Lifetime } from "../../../src/core/metrics/lifetime";
10+
import QuantityMetricType from "../../../src/core/metrics/types/quantity";
11+
12+
describe("QuantityMetric", function() {
13+
const testAppId = `gleanjs.test.${this.title}`;
14+
15+
beforeEach(async function() {
16+
await Glean.testResetGlean(testAppId);
17+
});
18+
19+
it("attempting to get the value of a metric that hasn't been recorded doesn't error", async function() {
20+
const metric = new QuantityMetricType({
21+
category: "aCategory",
22+
name: "aQuantityMetric",
23+
sendInPings: ["aPing", "twoPing", "threePing"],
24+
lifetime: Lifetime.Ping,
25+
disabled: false
26+
});
27+
28+
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
29+
});
30+
31+
it("attempting to set when glean upload is disabled is a no-op", async function() {
32+
Glean.setUploadEnabled(false);
33+
34+
const metric = new QuantityMetricType({
35+
category: "aCategory",
36+
name: "aQuantityMetric",
37+
sendInPings: ["aPing", "twoPing", "threePing"],
38+
lifetime: Lifetime.Ping,
39+
disabled: false
40+
});
41+
42+
metric.set(10);
43+
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
44+
});
45+
46+
it("ping payload is correct", async function() {
47+
const metric = new QuantityMetricType({
48+
category: "aCategory",
49+
name: "aQuantityMetric",
50+
sendInPings: ["aPing"],
51+
lifetime: Lifetime.Ping,
52+
disabled: false
53+
});
54+
55+
metric.set(10);
56+
assert.strictEqual(await metric.testGetValue("aPing"), 10);
57+
58+
const snapshot = await Context.metricsDatabase.getPingMetrics("aPing", true);
59+
assert.deepStrictEqual(snapshot, {
60+
"quantity": {
61+
"aCategory.aQuantityMetric": 10
62+
}
63+
});
64+
});
65+
66+
it("set properly sets the value in all pings", async function() {
67+
const metric = new QuantityMetricType({
68+
category: "aCategory",
69+
name: "aQuantityMetric",
70+
sendInPings: ["aPing", "twoPing", "threePing"],
71+
lifetime: Lifetime.Ping,
72+
disabled: false
73+
});
74+
75+
metric.set(0);
76+
assert.strictEqual(await metric.testGetValue("aPing"), 0);
77+
assert.strictEqual(await metric.testGetValue("twoPing"), 0);
78+
assert.strictEqual(await metric.testGetValue("threePing"), 0);
79+
});
80+
81+
it("must not set when passed negative", async function() {
82+
const metric = new QuantityMetricType({
83+
category: "aCategory",
84+
name: "aQuantityMetric",
85+
sendInPings: ["aPing"],
86+
lifetime: Lifetime.Ping,
87+
disabled: false
88+
});
89+
90+
metric.set(-1);
91+
assert.strictEqual(await metric.testGetValue("aPing"), undefined);
92+
});
93+
94+
it("saturates at boundary", async function() {
95+
const metric = new QuantityMetricType({
96+
category: "aCategory",
97+
name: "aQuantityMetric",
98+
sendInPings: ["aPing"],
99+
lifetime: Lifetime.Ping,
100+
disabled: false
101+
});
102+
103+
metric.set(Number.MAX_SAFE_INTEGER+1);
104+
assert.strictEqual(await metric.testGetValue("aPing"), Number.MAX_SAFE_INTEGER);
105+
});
106+
});

0 commit comments

Comments
 (0)