File: test_mirrors.py

package info (click to toggle)
firefox 142.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,591,884 kB
  • sloc: cpp: 7,451,570; javascript: 6,392,463; ansic: 3,712,584; python: 1,388,569; xml: 629,223; asm: 426,919; java: 184,857; sh: 63,439; makefile: 19,150; objc: 13,059; perl: 12,983; yacc: 4,583; cs: 3,846; pascal: 3,352; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (316 lines) | stat: -rw-r--r-- 13,729 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# This Source Code Form is subject to the terms of Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import sys
import unittest
from os import path
from pathlib import Path

import mozunit
from glean_parser import metrics, parser, util

TELEMETRY_ROOT_PATH = path.abspath(
    path.join(path.dirname(__file__), path.pardir, path.pardir)
)
sys.path.append(TELEMETRY_ROOT_PATH)
sys.path.append(path.join(TELEMETRY_ROOT_PATH, "build_scripts"))
from mozparsers import parse_events, parse_histograms, parse_scalars

FOG_ROOT_PATH = path.abspath(path.join(TELEMETRY_ROOT_PATH, path.pardir, "glean"))
sys.path.append(FOG_ROOT_PATH)
import metrics_index

sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
from run_glean_parser import GIFFT_TYPES

MIRROR_TYPES = {
    metric_type: [
        probe_type
        for probe_type in GIFFT_TYPES.keys()
        if metric_type in GIFFT_TYPES[probe_type]
    ]
    for (probe_type, metric_types) in GIFFT_TYPES.items()
    for metric_type in metric_types
}

# Event probes for which we permit the weaker event compatiblity checks:
# only ensuring that all the metric's extra keys are present in the probe,
# not ensuring that all the probe's extra keys are defined in the metric.
WEAKER_EVENT_COMPATIBILITY_PROBES = [
    "security.ui.protectionspopup#click",
    "intl.ui.browserLanguage#action",
    "privacy.ui.fpp#click",
    "slow_script_warning#shown",
    "address#address_form",
    "pwmgr#mgmt_interaction",
    "relay_integration#popup_option",
    "relay_integration#mask_panel",
    "security.ui.certerror#click",
    "security.ui.certerror#load",
]

# Event probes for which we permit there to be no mirror.
# Only included here are those with combinations of method+object that are unused.
UNMIRRORED_EVENT_ALLOWLIST = [
    "intl.ui.browserLanguage#action",
    "pwmgr#mgmt_interaction",
    "pwmgr#open_management",
]

# This import can error, but in that case we want the test to fail anyway.
from mozbuild.base import MozbuildObject

build = MozbuildObject.from_environment()


# Generator to yield metrics.
def mirroring_metrics(objs):
    for category, metric_objs in objs.value.items():
        for metric in metric_objs.values():
            if (
                hasattr(metric, "telemetry_mirror")
                and metric.telemetry_mirror is not None
            ):
                assert (
                    metric.type in MIRROR_TYPES.keys()
                ), f"{metric.type} is not a GIFFT-supported type."
                yield metric


# Events are compatible if their extra keys are compatible.
def ensure_compatible_event(metric, probe):
    # There is a pattern where Telemetry event definitions will have extra
    # keys that are only used by _some_ of the method+object pairs.
    # We only permit that pattern for old definitions that rely on it.
    if probe.identifier in WEAKER_EVENT_COMPATIBILITY_PROBES:
        for key in metric.allowed_extra_keys:
            # `event` metrics may have a `value` extra for mapping to a
            # mirror's value parameter.
            if key == "value":
                continue
            assert (
                key in probe.extra_keys
            ), f"Key {key} not in mirrored event probe {probe.identifier}. Be sure to add it."
    else:
        assert (
            metric.allowed_extra_keys == probe.extra_keys
            or metric.allowed_extra_keys == sorted(probe.extra_keys + ["value"])
        ), f"Metric {metric.identifier()}'s extra keys {metric.allowed_extra_keys} are not the same as probe {probe.identifier}'s extras {probe.extra_keys}."


# Histograms are compatible with metrics if they are
#  * keyed if the metric is labeled_*
#  * of a suitable `kind` (e.g. "linear", "exponential", or "enumerated")
def ensure_compatible_histogram(metric, probe):
    if metric.type == "counter":
        assert (
            probe.kind() == "count"
        ), f"Metric {metric.identifier()} is a `counter` mapping to a histogram, but {probe.name()} isn't a 'count' Histogram (is '{probe.kind()}')."
        return
    elif metric.type == "labeled_counter":
        if probe.kind() == "boolean":
            assert metric.ordered_labels == [
                "false",
                "true",
            ], f"Metric {metric.identifier()} is a `labeled_counter` mapping to a boolean histogram, but it doesn't have labels ['false', 'true'] (has {metric.ordered_labels} instead)."
        elif probe.kind() == "count":
            assert (
                probe.keyed()
            ), f"Metric {metric.identifier()} is a `labeled_counter` mapping to un-keyed 'count' histogram {probe.name()}."
        elif probe.kind() == "categorical":
            assert (
                metric.ordered_labels == probe.labels()
            ), f"Metric {metric.identifier()} is a `labeled_counter` mapping to categorical histogram {probe.name()}, but the labels don't match."
        else:
            assert (
                False
            ), f"Metric {metric.identifier()} is a `labeled_counter` mapping to a histogram, but {probe.name()} isn't a 'boolean, keyed 'count', or 'categorical' Histogram (is '{probe.kind()}')."
        return
    elif metric.type == "dual_labeled_counter":
        assert (
            probe.keyed()
        ), f"Metric {metric.identifier()} must mirror to a keyed histogram."
        if probe.kind() == "boolean":
            assert metric.ordered_categories == [
                "false",
                "true",
            ], f"Metric {metric.identifier()} is a `dual_labeled_counter` mapping to a keyed boolean histogram, but it doesn't have labels ['false', 'true'] (has {metric.ordered_labels} instead)."
        elif probe.kind() == "categorical":
            assert (
                metric.ordered_categories == probe.labels()
            ), f"Metric {metric.identifier()} is a `dual_labeled_counter` mapping to keyed categorical histogram {probe.name()}, but the labels don't match."
        return

    assert probe.kind() in [
        "linear",
        "exponential",
        "enumerated",
    ], f"Histogram {probe.name()}'s kind is not mirror-compatible."

    # We cannot assert that all enumerated hgrams are custom distributions
    # (some are e.g. timing_distributions), nor that all custom distributions
    # mirror to enumerated hgrams (some map to linear/exponential).
    # But in the case of a custom mapping to an enumerated, we check buckets.
    if probe.kind() == "enumerated" and metric.type in (
        "custom_distribution",
        "labeled_custom_distribution",
    ):
        n_values_plus_one = probe._n_buckets
        assert (
            metric.range_min == 0
            and metric.histogram_type == metrics.HistogramType.linear
            and metric.bucket_count == n_values_plus_one
        ), f"Metric {metric.identifier()} mapping to enumerated histogram {probe.name()} must have a range that starts at 0 (is {metric.range_min}), must have `linear` bucket allocation (is {metric.histogram_type}), and must have one more bucket than the probe's n_values (is {metric.bucket_count}, should be {n_values_plus_one})."
    assert (
        hasattr(metric, "labeled") and metric.labeled
    ) == probe.keyed(), f"Metric {metric.identifier()}'s labeledness must match mirrored histogram probe {probe.name()}'s keyedness."


# Scalars are compatible with metrics if they are
#  * keyed when necessary (e.g. when the metric is labeled_* or complex)
#  * of a compatible `kind` (e.g. `uint` for `counter` or `quantity`)
def ensure_compatible_scalar(metric, probe):
    mirror_should_be_keyed = (
        hasattr(metric, "labeled") and metric.labeled
    ) or metric.type in ["string_list", "rate"]
    assert (
        mirror_should_be_keyed == probe.keyed
    ), f"Metric {metric.identifier()}'s type ({metric.type}) must have appropriate keyedness in the mirrored scalar probe {probe.label}."

    TYPE_MAP = {
        "boolean": "boolean",
        "labeled_boolean": "boolean",
        "counter": "uint",
        "labeled_counter": "uint",
        "string": "string",
        "string_list": "boolean",
        "timespan": "uint",
        "uuid": "string",
        "url": "string",
        "datetime": "string",
        "quantity": "uint",
        "labeled_quantity": "uint",
        "rate": "uint",
    }
    assert (
        TYPE_MAP[metric.type] == probe.kind
    ), f"Metric {metric.identifier()}'s type ({metric.type}) requires a mirror probe scalar of kind '{TYPE_MAP[metric.type]}' which doesn't match mirrored scalar probe {probe.label}'s kind ({probe.kind})"


class TestTelemetryMirrors(unittest.TestCase):
    def test_compatible_mirrors(self):
        """Glean metrics can be mirrored via the `telemetry_mirror` property to
        Telemetry probes. Ensure the mirror is compatible with the metric."""

        # Step 1, parse all Glean metrics and Telemetry probes:
        metrics_yamls = [Path(build.topsrcdir, x) for x in metrics_index.metrics_yamls]
        # Accept any value of expires.
        parser_options = {
            "allow_reserved": True,
            "custom_is_expired": lambda expires: False,
            "custom_validate_expires": lambda expires: True,
        }
        objs = parser.parse_objects(metrics_yamls, parser_options)
        assert not util.report_validation_errors(objs)

        hgrams = list(
            parse_histograms.from_files(
                [path.join(TELEMETRY_ROOT_PATH, "Histograms.json")]
            )
        )

        scalars = list(
            parse_scalars.load_scalars(path.join(TELEMETRY_ROOT_PATH, "Scalars.yaml"))
        )

        events = list(
            parse_events.load_events(
                path.join(TELEMETRY_ROOT_PATH, "Events.yaml"), True
            )
        )

        # Step 2: For every mirroring Glean metric, assert its mirror Telemetry
        # probe is compatible.
        for metric in mirroring_metrics(objs):
            mirror = metric.telemetry_mirror.split("#")[-1]
            found = False
            for probe_type in MIRROR_TYPES[metric.type]:
                if probe_type == "Event":
                    for event in events:
                        for enum in event.enum_labels:
                            event_id = event.category_cpp + "_" + enum
                            if event_id == mirror:
                                found = True
                                ensure_compatible_event(metric, event)
                                break
                        if found:
                            break
                elif probe_type == "Histogram":
                    # To mirror to a Histogram if you also mirror to another type,
                    # you must prefix your mirror with "h#"
                    if len(
                        MIRROR_TYPES[metric.type]
                    ) > 1 and not metric.telemetry_mirror.startswith("h#"):
                        continue
                    for hgram in hgrams:
                        if hgram.name() == mirror:
                            found = True
                            ensure_compatible_histogram(metric, hgram)
                            break
                elif probe_type == "Scalar":
                    for scalar in scalars:
                        if scalar.enum_label == mirror:
                            found = True
                            ensure_compatible_scalar(metric, scalar)
                            break
                else:
                    assert (
                        False
                    ), f"mirror probe type {MIRROR_TYPES[metric.type]} isn't recognized."
            assert (
                found
            ), f"Mirror {metric.telemetry_mirror} not found for metric {metric.identifier()}"

        # Step 3: Forbid unmirrored-to probes
        for event in events:
            for enum in event.enum_labels:
                event_id = event.category_cpp + "_" + enum
                if event.identifier in UNMIRRORED_EVENT_ALLOWLIST:
                    # Some combinations of object+method are never used,
                    # but are nevertheless possible.
                    continue
                if event.category in ("telemetry.test", "telemetry.test.second"):
                    continue
                assert any(
                    metric.telemetry_mirror == event_id
                    for metric in mirroring_metrics(objs)
                ), f"No mirror metric found for event probe {event.identifier}."

        for hgram in hgrams:
            if hgram.keyed() and hgram.kind() in ("categorical", "boolean"):
                continue  # bug 1960567
            if hgram.name().startswith("TELEMETRY_TEST_"):
                continue
            assert any(
                metric.telemetry_mirror == hgram.name()
                or metric.telemetry_mirror == "h#" + hgram.name()
                for metric in mirroring_metrics(objs)
            ), f"No mirror metric found for histogram probe {hgram.name()}."

        for scalar in scalars:
            if scalar.label == "mathml.doc_count":
                continue  # bug 1962732
            if scalar.category in ("telemetry", "telemetry.discarded"):
                # Internal Scalars for use inside the Telemetry component.
                continue
            if scalar.category == "telemetry.test":
                continue
            assert any(
                metric.telemetry_mirror == scalar.enum_label
                for metric in mirroring_metrics(objs)
            ), f"No mirror metric found for scalar probe {scalar.label}."


if __name__ == "__main__":
    mozunit.main()