Skip to content

Commit 612c82d

Browse files
author
Beatriz Rizental
authored
Merge pull request #1006 from ChinYing-Li/bug_1716955
Bug 1716955: Initial implementation of the rate metric
2 parents f57b86e + 645da93 commit 612c82d

File tree

6 files changed

+417
-0
lines changed

6 files changed

+417
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
[Full changelog](https://github.com/mozilla/glean.js/compare/v0.28.0...main)
44

5+
* [#1006](https://github.com/mozilla/glean.js/pull/1006): Implement the rate metric.
6+
57
# v0.28.0 (2021-12-08)
68

79
[Full changelog](https://github.com/mozilla/glean.js/compare/v0.27.0...v0.28.0)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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 { Context } from "../../context.js";
8+
import { Metric } from "../metric.js";
9+
import { testOnly } from "../../utils.js";
10+
import { isNumber, isObject } from "../../utils.js";
11+
import type { JSONValue } from "../../utils.js";
12+
import { ErrorType } from "../../error/error_type.js";
13+
14+
const LOG_TAG = "core.metrics.RateMetricType";
15+
16+
export type Rate = {
17+
numerator: number,
18+
denominator: number
19+
};
20+
21+
export class RateMetric extends Metric<Rate, Rate> {
22+
constructor(v: unknown) {
23+
super(v);
24+
}
25+
26+
get numerator(): number {
27+
return this._inner.numerator;
28+
}
29+
30+
get denominator(): number {
31+
return this._inner.denominator;
32+
}
33+
34+
validate(v: unknown): v is Rate {
35+
if (!isObject(v) || Object.keys(v).length !== 2) {
36+
return false;
37+
}
38+
39+
const numeratorVerification = "numerator" in v && isNumber(v.numerator) && v.numerator >= 0;
40+
const denominatorVerification = "denominator" in v && isNumber(v.denominator) && v.denominator >= 0;
41+
return numeratorVerification && denominatorVerification;
42+
}
43+
44+
payload(): Rate {
45+
return {
46+
numerator: this._inner.numerator,
47+
denominator: this._inner.denominator
48+
};
49+
}
50+
}
51+
52+
/*
53+
* A rate metric.
54+
*
55+
* Used to determine the proportion of things via two counts:
56+
* * A numerator defining the amount of times something happened,
57+
* * A denominator counting the amount of times someting could have happened.
58+
*
59+
* Both numerator and denominator can only be incremented, not decremented.
60+
*/
61+
class RateMetricType extends MetricType {
62+
constructor(meta: CommonMetricData) {
63+
super("rate", meta);
64+
}
65+
66+
/**
67+
* Increases the numerator by amount.
68+
*
69+
* # Note
70+
*
71+
* Records an `InvalidValue` error if the `amount` is negative.
72+
*
73+
* @param amount The amount to increase by. Should be non-negative.
74+
*/
75+
addToNumerator(amount: number): void {
76+
Context.dispatcher.launch(async () => {
77+
if (!this.shouldRecord(Context.uploadEnabled)) {
78+
return;
79+
}
80+
81+
if (amount < 0) {
82+
await Context.errorManager.record(
83+
this,
84+
ErrorType.InvalidValue,
85+
`Added negative value ${amount} to numerator.`
86+
);
87+
return;
88+
}
89+
90+
const transformFn = ((amount) => {
91+
return (v?: JSONValue): RateMetric => {
92+
let metric: RateMetric;
93+
let result: number;
94+
try {
95+
metric = new RateMetric(v);
96+
result = metric.numerator + amount;
97+
} catch {
98+
metric = new RateMetric({
99+
numerator: amount,
100+
denominator: 0
101+
});
102+
result = amount;
103+
}
104+
105+
if (result > Number.MAX_SAFE_INTEGER) {
106+
result = Number.MAX_SAFE_INTEGER;
107+
}
108+
109+
metric.set({
110+
numerator: result,
111+
denominator: metric.denominator
112+
});
113+
return metric;
114+
};
115+
})(amount);
116+
117+
await Context.metricsDatabase.transform(this, transformFn);
118+
});
119+
}
120+
121+
/**
122+
* Increases the denominator by amount.
123+
*
124+
* # Note
125+
*
126+
* Records an `InvalidValue` error if the `amount` is negative.
127+
*
128+
* @param amount The amount to increase by. Should be non-negative.
129+
*/
130+
addToDenominator(amount: number): void {
131+
Context.dispatcher.launch(async () => {
132+
if (!this.shouldRecord(Context.uploadEnabled)) {
133+
return;
134+
}
135+
136+
if (amount < 0) {
137+
await Context.errorManager.record(
138+
this,
139+
ErrorType.InvalidValue,
140+
`Added negative value ${amount} to denominator.`
141+
);
142+
return;
143+
}
144+
145+
const transformFn = ((amount) => {
146+
return (v?: JSONValue): RateMetric => {
147+
let metric: RateMetric;
148+
let result: number;
149+
try {
150+
metric = new RateMetric(v);
151+
result = metric.denominator + amount;
152+
} catch {
153+
metric = new RateMetric({
154+
numerator: 0,
155+
denominator: amount
156+
});
157+
result = amount;
158+
}
159+
160+
if (result > Number.MAX_SAFE_INTEGER) {
161+
result = Number.MAX_SAFE_INTEGER;
162+
}
163+
164+
metric.set({
165+
numerator: metric.numerator,
166+
denominator: result
167+
});
168+
return metric;
169+
};
170+
})(amount);
171+
172+
await Context.metricsDatabase.transform(this, transformFn);
173+
});
174+
}
175+
176+
/**
177+
* Test-only API.**
178+
*
179+
* Gets the currently stored value as an object.
180+
*
181+
* # Note
182+
*
183+
* This function will return the Rate for convenience.
184+
*
185+
* This doesn't clear the stored value.
186+
*
187+
* @param ping the ping from which we want to retrieve this metrics value from.
188+
* Defaults to the first value in `sendInPings`.
189+
* @returns The value found in storage or `undefined` if nothing was found.
190+
*/
191+
@testOnly(LOG_TAG)
192+
async testGetValue(ping: string = this.sendInPings[0]): Promise<Rate | undefined> {
193+
let metric: Rate | undefined;
194+
await Context.dispatcher.testLaunch(async () => {
195+
metric = await Context.metricsDatabase.getMetric<Rate>(ping, this);
196+
});
197+
return metric;
198+
}
199+
}
200+
201+
export default RateMetricType;

glean/src/core/metrics/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { BooleanMetric } from "./types/boolean.js";
1111
import { CounterMetric } from "./types/counter.js";
1212
import { DatetimeMetric } from "./types/datetime.js";
1313
import { QuantityMetric } from "./types/quantity.js";
14+
import { RateMetric } from "./types/rate.js";
1415
import { StringMetric } from "./types/string.js";
1516
import { StringListMetric } from "./types/string_list.js";
1617
import { TextMetric } from "./types/text.js";
@@ -31,6 +32,7 @@ const METRIC_MAP: {
3132
"labeled_counter": LabeledMetric,
3233
"labeled_string": LabeledMetric,
3334
"quantity": QuantityMetric,
35+
"rate": RateMetric,
3436
"string": StringMetric,
3537
"string_list": StringListMetric,
3638
"text": TextMetric,

glean/src/index/qt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import DatetimeMetricType from "../core/metrics/types/datetime.js";
1515
import EventMetricType from "../core/metrics/types/event.js";
1616
import LabeledMetricType from "../core/metrics/types/labeled.js";
1717
import QuantityMetricType from "../core/metrics/types/quantity.js";
18+
import RateMetricType from "../core/metrics/types/rate.js";
1819
import StringMetricType from "../core/metrics/types/string.js";
1920
import StringListMetricType from "../core/metrics/types/string_list.js";
2021
import TextMetricType from "../core/metrics/types/text.js";
@@ -35,6 +36,7 @@ export default {
3536
EventMetricType,
3637
LabeledMetricType,
3738
QuantityMetricType,
39+
RateMetricType,
3840
StringMetricType,
3941
StringListMetricType,
4042
TimespanMetricType,

glean/tests/integration/schema/metrics.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,19 @@ for_testing:
175175
send_in_pings:
176176
- testing
177177
unit: sample
178+
rate:
179+
type: rate
180+
description: |
181+
Sample rate metric
182+
bugs:
183+
- https://bugzilla.mozilla.org/000000
184+
data_reviews:
185+
- https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3
186+
notification_emails:
187+
- me@mozilla.com
188+
expires: never
189+
send_in_pings:
190+
- testing
178191
url:
179192
type: url
180193
description: |

0 commit comments

Comments
 (0)