diff options
Diffstat (limited to 'toolkit/components/glean/build_scripts/translate_events.py')
-rw-r--r-- | toolkit/components/glean/build_scripts/translate_events.py | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/toolkit/components/glean/build_scripts/translate_events.py b/toolkit/components/glean/build_scripts/translate_events.py new file mode 100644 index 0000000000..5936a67132 --- /dev/null +++ b/toolkit/components/glean/build_scripts/translate_events.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- + +# This Source Code Form is subject to the terms of the 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/. + +""" +Create a Legacy Telemetry event definition for the provided, named Glean event metric. +""" + +import re +import sys +from os import path +from pathlib import Path +from typing import Sequence + +import yaml +from glean_parser import parser, util + +TC_ROOT_PATH = path.abspath(path.join(path.dirname(__file__), path.pardir, path.pardir)) +sys.path.append(TC_ROOT_PATH) +# The parsers live in a subdirectory of "build_scripts", account for that. +# NOTE: if the parsers are moved, this logic will need to be updated. +sys.path.append(path.join(TC_ROOT_PATH, "telemetry", "build_scripts")) + +from mozparsers.parse_events import convert_to_cpp_identifier # noqa: E402 + +bug_number_pattern = re.compile(r"\d+") + + +class IndentingDumper(yaml.Dumper): + def increase_indent(self, flow=False, indentless=False): + return super(IndentingDumper, self).increase_indent(flow, False) + + +def get_bug_number_from_url(url: str) -> int: + bug = bug_number_pattern.search(url) + # Python lacks a safe cast, so we will return 1 if we fail to bubble up. + if bug is not None: + try: + bug = int(bug[0]) + except TypeError: + print(f"Failed to parse {bug[0]} to an integer") + return 1 + return bug + print(f"Failed to find a valid bug in the url {url}") + return 1 + + +def create_legacy_mirror_def(category_name: str, metric_name: str, event_objects: list): + event_cpp_enum = convert_to_cpp_identifier(category_name, "_") + "_" + event_cpp_enum += convert_to_cpp_identifier(metric_name, ".") + "_" + event_cpp_enum += convert_to_cpp_identifier(event_objects[0], ".") + + print( + f"""The Glean event {category_name}.{metric_name} has generated the {event_cpp_enum} Legacy Telemetry event. To link Glean to Legacy, please include in {category_name}.{metric_name}'s definition the following property: +telemetry_mirror: {event_cpp_enum} +See https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/gifft.html#the-telemetry-mirror-property-in-metrics-yaml +for more information. +""" + ) + + +def translate_event( + event_category_and_name: str, + append: bool, + metrics_files: Sequence[Path], + legacy_yaml_file: Path, +) -> int: + """ + Commandline helper for translating Glean events to Legacy. + + :param event_name: the event to look for + :param metrics_files: List of Path objects to load metrics from. + :return: Non-zero if there were any errors. + """ + + # Event objects are a Legacy Telemetry event field that is rarely used. We + # always broadcast into a single pre-defined object. + event_objects = ["events"] + + if event_category_and_name is not None: + *event_category, event_name = event_category_and_name.rsplit(".", maxsplit=1) + else: + print("Please provide an event (in category.metricName format) to translate.") + return 1 + + if len(event_category) > 0: + event_category = util.snake_case(event_category[0]) + event_name = util.snake_case(event_name) + else: + print( + f"Your event '{event_category_and_name}' did not conform to the a.category.a_metric_name format." + ) + return 1 + + metrics_files = util.ensure_list(metrics_files) + + # Accept any value of expires. + parser_options = { + "allow_reserved": True, + "custom_is_expired": lambda expires: False, + "custom_validate_expires": lambda expires: True, + } + all_objects = parser.parse_objects(metrics_files, parser_options) + + if util.report_validation_errors(all_objects): + return 1 + + for category_name, metrics in all_objects.value.items(): + for metric in metrics.values(): + metric_name = util.snake_case(metric.name) + category_name = util.snake_case(category_name) + + if metric_name != event_name or category_name != event_category: + continue + + if metric.type != "event": + print( + f"Metric {event_category_and_name} was found, but was a {metric.type} metric, not an event metric." + ) + return 1 + + bugs_list = [get_bug_number_from_url(m) for m in metric.bugs] + # Bail out if there was a parse error (error printed in get_bug_number_from_url) + if 1 in bugs_list: + return 1 + + metric_dict = { + "objects": event_objects, + "description": str(metric.description).strip(), + "bug_numbers": bugs_list, + "notification_emails": metric.notification_emails, + "expiry_version": str(metric.expires), + "products": ["firefox"], + "record_in_processes": ["all"], + } + + if len(metric.extra_keys.items()) > 0: + # Convert extra keys into a nested dictionary that YAML can understand + extra_keys = {} + for extra_key_pair in metric.extra_keys.items(): + description = ( + extra_key_pair[1]["description"].replace("\n", " ").strip() + ) + extra_keys[extra_key_pair[0]] = description + + metric_dict["extra_keys"] = extra_keys + + metric_dict = {metric_name: metric_dict} + metric_dict = {category_name: metric_dict} + + metric_yaml = yaml.dump( + metric_dict, + default_flow_style=False, + width=78, + indent=2, + Dumper=IndentingDumper, + ) + + if append: + with open(legacy_yaml_file, "a") as file: + print("", file=file) + print(metric_yaml, file=file) + print( + f"Apended {event_category_and_name} to the Legacy Events.yaml file. Please confirm the details by opening" + ) + print(legacy_yaml_file) + print("and checking that all fields are correct.") + + create_legacy_mirror_def(event_name, category_name, event_objects) + return 0 + + print(metric_yaml) + create_legacy_mirror_def(event_name, category_name, event_objects) + + return 0 |