Skip to content

Commit 3879143

Browse files
authored
Enable the use of multiple categories for Gecko metrics (#79)
* Enable the use of multiple categories for Gecko metrics This introduces a new template used for generating a single mapping object. The object then calls in the other generated metrics, if needed. * Fix flake8 issues
1 parent fe5185c commit 3879143

File tree

5 files changed

+150
-40
lines changed

5 files changed

+150
-40
lines changed

glean_parser/kotlin.py

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from . import metrics
1515
from . import util
16+
from collections import defaultdict
1617

1718

1819
def kotlin_datatypes_filter(value):
@@ -91,6 +92,77 @@ def class_name(obj_type):
9192
return f'{util.Camelize(obj_type)}MetricType'
9293

9394

95+
def output_gecko_lookup(objs, output_dir, options={}):
96+
"""
97+
Given a tree of objects, generate a Kotlin map between Gecko histograms and
98+
Glean SDK metric types.
99+
100+
:param objects: A tree of objects (metrics and pings) as returned from
101+
`parser.parse_objects`.
102+
:param output_dir: Path to an output directory to write to.
103+
:param options: options dictionary, with the following optional keys:
104+
105+
- `namespace`: The package namespace to declare at the top of the
106+
generated files. Defaults to `GleanMetrics`.
107+
- `glean_namespace`: The package namespace of the glean library itself.
108+
This is where glean objects will be imported from in the generated
109+
code.
110+
"""
111+
template = util.get_jinja2_template(
112+
'kotlin.geckoview.jinja2',
113+
filters=(
114+
('kotlin', kotlin_datatypes_filter),
115+
('type_name', type_name),
116+
('class_name', class_name)
117+
)
118+
)
119+
120+
namespace = options.get('namespace', 'GleanMetrics')
121+
glean_namespace = options.get(
122+
'glean_namespace',
123+
'mozilla.components.service.glean'
124+
)
125+
126+
# Build a dictionary that contains data for metrics that are
127+
# histogram-like and contain a gecko_datapoint, with this format:
128+
#
129+
# {
130+
# "category": [
131+
# {"gecko_datapoint": "the-datapoint", "name": "the-metric-name"},
132+
# ...
133+
# ],
134+
# ...
135+
# }
136+
gecko_metrics = defaultdict(list)
137+
138+
for category_key, category_val in objs.items():
139+
# Support exfiltration of Gecko metrics from products using both the
140+
# Glean SDK and GeckoView. See bug 1566356 for more context.
141+
for metric in category_val.values():
142+
if getattr(metric, 'gecko_datapoint', False):
143+
gecko_metrics[category_key].append({
144+
'gecko_datapoint': metric.gecko_datapoint,
145+
'name': metric.name
146+
})
147+
148+
if not gecko_metrics:
149+
# Bail out and don't create a file if no gecko metrics
150+
# are found.
151+
return
152+
153+
filepath = output_dir / "GleanGeckoHistogramMapping.kt"
154+
with open(filepath, 'w', encoding='utf-8') as fd:
155+
fd.write(
156+
template.render(
157+
gecko_metrics=gecko_metrics,
158+
namespace=namespace,
159+
glean_namespace=glean_namespace,
160+
)
161+
)
162+
# Jinja2 squashes the final newline, so we explicitly add it
163+
fd.write('\n')
164+
165+
94166
def output_kotlin(objs, output_dir, options={}):
95167
"""
96168
Given a tree of objects, output Kotlin code to `output_dir`.
@@ -147,13 +219,6 @@ def output_kotlin(objs, output_dir, options={}):
147219
for metric in category_val.values()
148220
)
149221

150-
# Support exfiltration of Gecko metrics from products using both the
151-
# Glean SDK and GeckoView. See bug 1566356 for more context.
152-
has_gecko_datapoints = any(
153-
getattr(metric, 'gecko_datapoint', False)
154-
for metric in category_val.values()
155-
)
156-
157222
with open(filepath, 'w', encoding='utf-8') as fd:
158223
fd.write(
159224
template.render(
@@ -163,9 +228,11 @@ def output_kotlin(objs, output_dir, options={}):
163228
extra_args=extra_args,
164229
namespace=namespace,
165230
has_labeled_metrics=has_labeled_metrics,
166-
has_gecko_datapoints=has_gecko_datapoints,
167231
glean_namespace=glean_namespace,
168232
)
169233
)
170234
# Jinja2 squashes the final newline, so we explicitly add it
171235
fd.write('\n')
236+
237+
# TODO: Maybe this should just be a separate outputter?
238+
output_gecko_lookup(objs, output_dir, options)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// -*- mode: kotlin -*-
2+
3+
/*
4+
* AUTOGENERATED BY glean_parser. DO NOT EDIT.
5+
*/
6+
7+
/* This Source Code Form is subject to the terms of the Mozilla Public
8+
* License, v. 2.0. If a copy of the MPL was not distributed with this
9+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10+
11+
@file:Suppress("PackageNaming")
12+
package {{ namespace }}
13+
14+
import {{ glean_namespace }}.private.HistogramBase // ktlint-disable import-ordering no-unused-imports
15+
16+
/*
17+
* This class performs the mapping between Gecko Histograms and Glean SDK
18+
* metric types.
19+
*/
20+
internal object GleanGeckoHistogramMapping {
21+
// Support exfiltration of Gecko metrics from products using both the
22+
// Glean SDK and GeckoView. See bug 1566356 for more context.
23+
operator fun get(geckoMetricName: String): HistogramBase? {
24+
return when (geckoMetricName) {
25+
{% for category in gecko_metrics.keys()|sort %}
26+
// From {{ category|Camelize }}.kt
27+
{% for metric in gecko_metrics[category] %}
28+
"{{ metric.gecko_datapoint }}" -> {{ category|Camelize }}.{{ metric.name|camelize }}
29+
{% endfor %}
30+
{%- endfor %}
31+
else -> null
32+
}
33+
}
34+
}

glean_parser/templates/kotlin.jinja2

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020
@file:Suppress("PackageNaming")
2121
package {{ namespace }}
2222

23-
{% if has_gecko_datapoints %}
24-
import {{ glean_namespace }}.private.HistogramBase
25-
{% endif %}
2623
import {{ glean_namespace }}.private.Lifetime // ktlint-disable no-unused-imports
2724
import {{ glean_namespace }}.private.NoExtraKeys // ktlint-disable no-unused-imports
2825
import {{ glean_namespace }}.private.TimeUnit // ktlint-disable no-unused-imports
@@ -67,18 +64,4 @@ internal object {{ category_name|Camelize }} {
6764
{{ obj_declaration(obj, lazy=obj.type != 'ping') }}
6865
{% endif %}
6966
{%- endfor %}
70-
{% if has_gecko_datapoints %}
71-
// Support exfiltration of Gecko metrics from products using both the
72-
// Glean SDK and GeckoView. See bug 1566356 for more context.
73-
operator fun get(geckoMetricName: String): HistogramBase? {
74-
return when (geckoMetricName) {
75-
{% for obj in objs.values() %}
76-
{% if obj.gecko_datapoint %}
77-
"{{ obj.gecko_datapoint }}" -> {{ obj.name|camelize }}
78-
{% endif %}
79-
{%- endfor %}
80-
else -> null
81-
}
82-
}
83-
{% endif %}
8467
}

tests/data/gecko.yaml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
22

3-
gecko.imports:
4-
page_load_time:
3+
page.perf:
4+
load_time:
55
type: timing_distribution
6-
gecko_datapoint: FX_PAGE_LOAD_MS_2
6+
gecko_datapoint: GV_PAGE_LOAD_MS
77
time_unit: millisecond
88
lifetime: application
99
description: >
@@ -16,9 +16,9 @@ gecko.imports:
1616
- CHANGE-ME@example.com
1717
expires: 2100-01-01
1818

19-
startup_runtime:
19+
reload_time:
2020
type: timing_distribution
21-
gecko_datapoint: GV_STARTUP_RUNTIME_MS
21+
gecko_datapoint: GV_PAGE_RELOAD_MS
2222
time_unit: millisecond
2323
lifetime: application
2424
description: >
@@ -31,6 +31,22 @@ gecko.imports:
3131
- CHANGE-ME@example.com
3232
expires: 2100-01-01
3333

34+
gfx.content.checkerboard:
35+
duration:
36+
type: timing_distribution
37+
gecko_datapoint: CHECKERBOARD_DURATION
38+
time_unit: millisecond
39+
lifetime: application
40+
description: >
41+
A sample timing distribution metric exported from Gecko.
42+
bugs:
43+
- 1566356
44+
data_reviews:
45+
- http://example.com/reviews
46+
notification_emails:
47+
- CHANGE-ME@example.com
48+
expires: 2100-01-01
49+
3450
non.gecko.metrics:
3551
app_load_time:
3652
type: timing_distribution

tests/test_kotlin.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,16 +188,22 @@ def test_gecko_datapoints(tmpdir):
188188
{'allow_reserved': True}
189189
)
190190

191+
metrics_files = [
192+
'GfxContentCheckerboard.kt',
193+
'PagePerf.kt',
194+
'NonGeckoMetrics.kt'
195+
]
191196
assert (
192197
set(x.name for x in tmpdir.iterdir()) ==
193-
set([
194-
'GeckoImports.kt',
195-
'NonGeckoMetrics.kt'
196-
])
198+
set(['GleanGeckoHistogramMapping.kt'] + metrics_files)
197199
)
198200

199201
# Make sure descriptions made it in
200-
with open(tmpdir / 'GeckoImports.kt', 'r', encoding='utf-8') as fd:
202+
with open(
203+
tmpdir / 'GleanGeckoHistogramMapping.kt',
204+
'r',
205+
encoding='utf-8'
206+
) as fd:
201207
content = fd.read()
202208
# Make sure we're adding the relevant Glean SDK import, once.
203209
assert content.count(
@@ -210,17 +216,21 @@ def test_gecko_datapoints(tmpdir):
210216
# changes, otherwise validation will fail.
211217
expected_operator = """ operator fun get(geckoMetricName: String): HistogramBase? {
212218
return when (geckoMetricName) {
213-
"FX_PAGE_LOAD_MS_2" -> pageLoadTime
214-
"GV_STARTUP_RUNTIME_MS" -> startupRuntime
219+
// From GfxContentCheckerboard.kt
220+
"CHECKERBOARD_DURATION" -> GfxContentCheckerboard.duration
221+
// From PagePerf.kt
222+
"GV_PAGE_LOAD_MS" -> PagePerf.loadTime
223+
"GV_PAGE_RELOAD_MS" -> PagePerf.reloadTime
215224
else -> null
216225
}
217226
}"""
218227

219228
assert expected_operator in content
220229

221-
with open(tmpdir / 'NonGeckoMetrics.kt', 'r', encoding='utf-8') as fd:
222-
content = fd.read()
223-
assert 'HistogramBase' not in content
230+
for file_name in metrics_files:
231+
with open(tmpdir / file_name, 'r', encoding='utf-8') as fd:
232+
content = fd.read()
233+
assert 'HistogramBase' not in content
224234

225235
# Only run this test if ktlint is on the path
226236
if shutil.which('ktlint'):

0 commit comments

Comments
 (0)