Skip to content

Commit 782ec70

Browse files
committed
bug 1645166 - Add support for rate metrics
Adds three new metric object model types: * "rate" for rates that own both their numerator and denominator * "numerator" for rates that have an external denominator * "denominator" for counters that are external denominators
1 parent c9e4507 commit 782ec70

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
- Add parser and object model support for `rate` metric type. ([bug 1645166](https://bugzilla.mozilla.org/show_bug.cgi?id=1645166))
6+
- Add parser and object model support for telemetry_mirror property. ([bug 1685406](https://bugzilla.mozilla.org/show_bug.cgi?id=1685406))
7+
58
## 2.4.0 (2021-02-18)
69

710
- **Experimental:** `glean_parser` has a new subcommand `coverage` to convert raw coverage reports

glean_parser/metrics.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,4 +364,12 @@ class LabeledCounter(Labeled, Counter):
364364
typename = "labeled_counter"
365365

366366

367+
class Rate(Metric):
368+
typename = "rate"
369+
370+
def __init__(self, *args, **kwargs):
371+
self.denominator_metric = kwargs.pop("denominator_metric", None)
372+
super().__init__(*args, **kwargs)
373+
374+
367375
ObjectTree = Dict[str, Dict[str, Union[Metric, pings.Ping]]]

glean_parser/schemas/metrics.2-0-0.schema.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ definitions:
130130
- labeled_boolean
131131
- labeled_string
132132
- labeled_counter
133+
- rate
133134

134135
description:
135136
title: Description
@@ -479,6 +480,18 @@ definitions:
479480
type: string
480481
minLength: 6
481482

483+
denominator_metric:
484+
title: The name of the denominator for this `rate` metric.
485+
description: |
486+
Denominators for `rate` metrics may be private and internal
487+
or shared and external.
488+
External denominators are `counter` metrics.
489+
This field names the `counter` metric that serves as this
490+
`rate` metric's external denominator.
491+
The named denominator must be defined in this component
492+
so glean_parser can find it.
493+
type: string
494+
482495
required:
483496
- type
484497
- bugs
@@ -605,3 +618,14 @@ additionalProperties:
605618
- decrypted_name
606619
description: |
607620
`jwe` is missing required parameter `decrypted_name`.
621+
- if:
622+
not:
623+
properties:
624+
type:
625+
const: rate
626+
then:
627+
properties:
628+
denominator_metric:
629+
description: |
630+
`denominator_metric` is only allowed for `rate`.
631+
maxLength: 0

glean_parser/translate.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pathlib import Path
1212
import os
1313
import shutil
14+
import sys
1415
import tempfile
1516
from typing import Any, Callable, Dict, Iterable, List, Optional
1617

@@ -59,6 +60,39 @@ def __init__(
5960
}
6061

6162

63+
def transform_metrics(objects):
64+
"""
65+
Transform the object model from one that represents the YAML definitions
66+
to one that reflects the type specifics needed by code generators.
67+
68+
e.g. This will transform a `rate` to be a `numerator` if its denominator is
69+
external.
70+
"""
71+
counters = {}
72+
numerators_by_denominator: Dict[str, Any] = {}
73+
for category_val in objects.values():
74+
for metric in category_val.values():
75+
fqmn = metric.identifier()
76+
if getattr(metric, "type", None) == "counter":
77+
counters[fqmn] = metric
78+
denominator_name = getattr(metric, "denominator_metric", None)
79+
if denominator_name:
80+
metric.type = "numerator"
81+
numerators_by_denominator.setdefault(denominator_name, [])
82+
numerators_by_denominator[denominator_name].append(fqmn)
83+
84+
for denominator_name, numerator_names in numerators_by_denominator.items():
85+
if denominator_name not in counters:
86+
print(
87+
f"No `counter` named {denominator_name} found to be used as"
88+
"denominator for {numerator_names}",
89+
file=sys.stderr,
90+
)
91+
return 1
92+
counters[denominator_name].type = "denominator"
93+
counters[denominator_name].numerators = numerator_names
94+
95+
6296
def translate_metrics(
6397
input_filepaths: Iterable[Path],
6498
output_dir: Path,

glean_parser/util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ def remove_output_params(d, output_params):
422422
"range_max",
423423
"range_min",
424424
"histogram_type",
425+
"numerators",
425426
]
426427

427428

tests/data/rate.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Any copyright is dedicated to the Public Domain.
2+
# https://creativecommons.org/publicdomain/zero/1.0/
3+
4+
---
5+
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
6+
7+
testing.rates:
8+
has_internal_denominator:
9+
type: rate
10+
lifetime: application
11+
description: >
12+
Test metric to ensure rates with internal denominators work.
13+
bugs:
14+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166
15+
data_reviews:
16+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166#c1
17+
notification_emails:
18+
- CHANGE-ME@example.com
19+
expires: never
20+
21+
has_external_denominator:
22+
type: rate
23+
lifetime: application
24+
description: >
25+
Test metric to ensure rates with external denominators work.
26+
bugs:
27+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166
28+
data_reviews:
29+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166#c1
30+
notification_emails:
31+
- CHANGE-ME@example.com
32+
expires: never
33+
denominator_metric: testing.rates.the_denominator
34+
35+
also_has_external_denominator:
36+
type: rate
37+
lifetime: application
38+
description: >
39+
Test metric to ensure rates with shared external denominators work.
40+
bugs:
41+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166
42+
data_reviews:
43+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166#c1
44+
notification_emails:
45+
- CHANGE-ME@example.com
46+
expires: never
47+
denominator_metric: testing.rates.the_denominator
48+
49+
the_denominator:
50+
type: counter
51+
lifetime: application
52+
description: >
53+
Test denominator for rate metrics.
54+
bugs:
55+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166
56+
data_reviews:
57+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1645166#c1
58+
notification_emails:
59+
- CHANGE-ME@example.com
60+
expires: never

tests/test_parser.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,3 +647,29 @@ def test_telemetry_mirror():
647647
all_metrics.value["telemetry.mirrored"]["parses_fine"].telemetry_mirror
648648
== "telemetry.test.string_kind"
649649
)
650+
651+
652+
def test_rates():
653+
"""
654+
Ensure that `rate` metrics parse properly.
655+
"""
656+
657+
all_metrics = parser.parse_objects(
658+
[ROOT / "data" / "rate.yaml"],
659+
config={"allow_reserved": False},
660+
)
661+
662+
errs = list(all_metrics)
663+
assert len(errs) == 0
664+
665+
category = all_metrics.value["testing.rates"]
666+
assert category["has_internal_denominator"].type == "rate"
667+
assert (
668+
category["has_external_denominator"].type == "rate"
669+
) # Hasn't been transformed to "numerator" yet
670+
assert (
671+
category["also_has_external_denominator"].type == "rate"
672+
) # Hasn't been transformed to "numerator" yet
673+
assert (
674+
category["the_denominator"].type == "counter"
675+
) # Hasn't been transformed to "denominator" yet

tests/test_translate.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,39 @@ def test_getting_line_number():
190190

191191
assert pings["custom-ping"].defined_in["line"] == 7
192192
assert metrics["core_ping"]["seq"].defined_in["line"] == 27
193+
194+
195+
def test_rates(tmpdir):
196+
def external_translator(all_objects, output_dir, options):
197+
assert (
198+
all_objects["testing.rates"]["has_external_denominator"].type == "rate"
199+
) # Hasn't yet been transformed
200+
201+
translate.transform_metrics(all_objects)
202+
203+
category = all_objects["testing.rates"]
204+
assert category["has_internal_denominator"].type == "rate"
205+
assert category["has_external_denominator"].type == "numerator"
206+
assert (
207+
category["has_external_denominator"].denominator_metric
208+
== "testing.rates.the_denominator"
209+
)
210+
assert category["also_has_external_denominator"].type == "numerator"
211+
assert (
212+
category["also_has_external_denominator"].denominator_metric
213+
== "testing.rates.the_denominator"
214+
)
215+
assert category["the_denominator"].type == "denominator"
216+
assert category["the_denominator"].numerators == [
217+
"testing.rates.has_external_denominator",
218+
"testing.rates.also_has_external_denominator",
219+
]
220+
221+
translate.translate_metrics(
222+
ROOT / "data" / "rate.yaml",
223+
Path(str(tmpdir)),
224+
external_translator,
225+
[],
226+
options={"foo": "bar"},
227+
parser_config={"allow_reserved": True},
228+
)

0 commit comments

Comments
 (0)