1# -*- coding: utf-8 -*- 2 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7import cpp 8import js 9import os 10import re 11import rust 12import sys 13 14import jinja2 15 16from util import generate_metric_ids 17from glean_parser import lint, parser, util 18from mozbuild.util import FileAvoidWrite 19from pathlib import Path 20 21 22GIFFT_TYPES = { 23 "Event": ["event"], 24 "Histogram": ["timing_distribution", "memory_distribution", "custom_distribution"], 25 "Scalar": [ 26 "boolean", 27 "labeled_boolean", 28 "counter", 29 "labeled_counter", 30 "string", 31 "string_list", 32 "timespan", 33 "uuid", 34 "datetime", 35 "quantity", 36 ], 37} 38 39 40def get_parser_options(moz_app_version): 41 app_version_major = moz_app_version.split(".", 1)[0] 42 return { 43 "allow_reserved": False, 44 "custom_is_expired": lambda expires: expires == "expired" 45 or expires != "never" 46 and int(expires) <= int(app_version_major), 47 "custom_validate_expires": lambda expires: expires in ("expired", "never") 48 or re.fullmatch(r"\d\d+", expires, flags=re.ASCII), 49 } 50 51 52def parse(args): 53 """ 54 Parse and lint the input files, 55 then return the parsed objects for further processing. 56 """ 57 58 # Unfortunately, GeneratedFile appends `flags` directly after `inputs` 59 # instead of listifying either, so we need to pull stuff from a *args. 60 yaml_array = args[:-1] 61 moz_app_version = args[-1] 62 63 input_files = [Path(x) for x in yaml_array] 64 65 # Derived heavily from glean_parser.translate.translate. 66 # Adapted to how mozbuild sends us a fd, and to expire on versions not dates. 67 68 options = get_parser_options(moz_app_version) 69 70 # Lint the yaml first, then lint the metrics. 71 if lint.lint_yaml_files(input_files, parser_config=options): 72 # Warnings are Errors 73 sys.exit(1) 74 75 all_objs = parser.parse_objects(input_files, options) 76 if util.report_validation_errors(all_objs): 77 sys.exit(1) 78 79 nits = lint.lint_metrics(all_objs.value, options) 80 if nits is not None and any(nit.check_name != "EXPIRED" for nit in nits): 81 # Treat Warnings as Errors in FOG. 82 # But don't fail the whole build on expired metrics (it blocks testing). 83 sys.exit(1) 84 85 return all_objs.value, options 86 87 88# Must be kept in sync with the length of `deps` in moz.build. 89DEPS_LEN = 15 90 91 92def main(output_fd, *args): 93 args = args[DEPS_LEN:] 94 all_objs, options = parse(args) 95 rust.output_rust(all_objs, output_fd, options) 96 97 98def cpp_metrics(output_fd, *args): 99 args = args[DEPS_LEN:] 100 all_objs, options = parse(args) 101 cpp.output_cpp(all_objs, output_fd, options) 102 103 104def js_metrics(output_fd, *args): 105 args = args[DEPS_LEN:] 106 all_objs, options = parse(args) 107 js.output_js(all_objs, output_fd, options) 108 109 110def gifft_map(output_fd, *args): 111 probe_type = args[-1] 112 args = args[DEPS_LEN:-1] 113 all_objs, options = parse(args) 114 115 # Events also need to output maps from event extra enum to strings. 116 # Sadly we need to generate code for all possible events, not just mirrored. 117 # Otherwise we won't compile. 118 if probe_type == "Event": 119 output_path = Path(os.path.dirname(output_fd.name)) 120 with FileAvoidWrite(output_path / "EventExtraGIFFTMaps.cpp") as cpp_fd: 121 output_gifft_map(output_fd, probe_type, all_objs, cpp_fd) 122 else: 123 output_gifft_map(output_fd, probe_type, all_objs, None) 124 125 126def output_gifft_map(output_fd, probe_type, all_objs, cpp_fd): 127 get_metric_id = generate_metric_ids(all_objs) 128 ids_to_probes = {} 129 for category_name, objs in all_objs.items(): 130 for metric in objs.values(): 131 if ( 132 hasattr(metric, "telemetry_mirror") 133 and metric.telemetry_mirror is not None 134 ): 135 info = (metric.telemetry_mirror, f"{category_name}.{metric.name}") 136 if metric.type in GIFFT_TYPES[probe_type]: 137 if info in ids_to_probes.values(): 138 print( 139 f"Telemetry mirror {metric.telemetry_mirror} already registered", 140 file=sys.stderr, 141 ) 142 sys.exit(1) 143 ids_to_probes[get_metric_id(metric)] = info 144 # If we don't support a mirror for this metric type: build error. 145 elif not any( 146 [ 147 metric.type in types_for_probe 148 for types_for_probe in GIFFT_TYPES.values() 149 ] 150 ): 151 print( 152 f"Glean metric {category_name}.{metric.name} is of type {metric.type}" 153 " which can't be mirrored (we don't know how).", 154 file=sys.stderr, 155 ) 156 sys.exit(1) 157 158 env = jinja2.Environment( 159 loader=jinja2.PackageLoader("run_glean_parser", "templates"), 160 trim_blocks=True, 161 lstrip_blocks=True, 162 ) 163 env.filters["snake_case"] = lambda value: value.replace(".", "_").replace("-", "_") 164 env.filters["Camelize"] = util.Camelize 165 template = env.get_template("gifft.jinja2") 166 output_fd.write( 167 template.render( 168 ids_to_probes=ids_to_probes, 169 probe_type=probe_type, 170 ) 171 ) 172 output_fd.write("\n") 173 174 # Events also need to output maps from event extra enum to strings. 175 # Sadly we need to generate code for all possible events, not just mirrored. 176 # Otherwise we won't compile. 177 if probe_type == "Event": 178 template = env.get_template("gifft_events.jinja2") 179 cpp_fd.write(template.render(all_objs=all_objs)) 180 cpp_fd.write("\n") 181 182 183if __name__ == "__main__": 184 main(sys.stdout, *sys.argv[1:]) 185