generate_profiling_categories.py (10873B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 # This script generates ProfilingCategoryList.h and profiling_categories.rs 6 # files from profiling_categories.yaml. 7 8 import yaml 9 10 CPP_HEADER_TEMPLATE = """\ 11 /* This Source Code Form is subject to the terms of the Mozilla Public 12 * License, v. 2.0. If a copy of the MPL was not distributed with this 13 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 14 15 #ifndef {includeguard} 16 #define {includeguard} 17 18 /* This file is generated by generate_profiling_categories.py from 19 profiling_categories.yaml. DO NOT EDIT! */ 20 21 // Profiler sub-categories are applied to each sampled stack to describe the 22 // type of workload that the CPU is busy with. Only one sub-category can be 23 // assigned so be mindful that these are non-overlapping. The active category is 24 // set by pushing a label to the profiling stack, or by the unwinder in cases 25 // such as JITs. A profile sample in arbitrary C++/Rust will typically be 26 // categorized based on the top of the label stack. 27 // 28 // The list of available color names for categories is: 29 // transparent 30 // blue 31 // green 32 // grey 33 // lightblue 34 // magenta 35 // orange 36 // purple 37 // yellow 38 39 // clang-format off 40 41 {contents} 42 43 // clang-format on 44 45 #endif // {includeguard} 46 """ 47 48 CPP_MACRO_DEFINITION = """\ 49 #define MOZ_PROFILING_CATEGORY_LIST(BEGIN_CATEGORY, SUBCATEGORY, END_CATEGORY) \\ 50 """ 51 52 RUST_TEMPLATE = """\ 53 /* This Source Code Form is subject to the terms of the Mozilla Public 54 * License, v. 2.0. If a copy of the MPL was not distributed with this 55 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 56 57 /* This file is generated by generate_profiling_categories.py from 58 profiling_categories.yaml. DO NOT EDIT! */ 59 60 {contents}\ 61 """ 62 63 RUST_ENUM_TEMPLATE = """\ 64 #[repr(u32)] 65 #[derive(Debug, Copy, Clone)] 66 pub enum {name} {{ 67 {fields} 68 }} 69 """ 70 71 RUST_CONVERSION_IMPL_TEMPLATE = """\ 72 impl {name} {{ 73 pub fn to_cpp_enum_value(&self) -> u32 {{ 74 {content} 75 }} 76 }} 77 """ 78 79 RUST_DEFAULT_IMPL_TEMPLATE = """\ 80 impl Default for {name} {{ 81 fn default() -> Self {{ 82 {content} 83 }} 84 }} 85 """ 86 87 RUST_MATCH_SELF = """\ 88 match *self {{ 89 {fields} 90 }} 91 """ 92 93 94 def generate_header(c_out, includeguard, contents): 95 c_out.write( 96 CPP_HEADER_TEMPLATE.format(includeguard=includeguard, contents=contents) 97 ) 98 99 100 def generate_rust_file(c_out, contents): 101 c_out.write(RUST_TEMPLATE.format(contents=contents)) 102 103 104 def load_yaml(yaml_path): 105 file_handler = open(yaml_path) 106 return yaml.safe_load(file_handler) 107 108 109 def generate_category_macro(name, label, color, subcategories): 110 contents = f' BEGIN_CATEGORY({name}, "{label}", "{color}") \\\n' 111 112 subcategory_items = [] 113 114 for subcategory in subcategories: 115 subcat_name = subcategory["name"] 116 assert isinstance(subcat_name, str) 117 subcat_label = subcategory["label"] 118 assert isinstance(subcat_label, str) 119 120 subcategory_items.append( 121 f' SUBCATEGORY({name}, {subcat_name}, "{subcat_label}") \\\n' 122 ) 123 124 contents += "".join(subcategory_items) 125 contents += " END_CATEGORY" 126 127 return contents 128 129 130 def generate_macro_header(c_out, yaml_path): 131 """Generate ProfilingCategoryList.h from profiling_categories.yaml. 132 The generated file has a macro to generate the profiling category enums. 133 """ 134 135 data = load_yaml(yaml_path) 136 137 # Stores the macro definition of each categories. 138 category_items = [] 139 140 for category in data: 141 name = category["name"] 142 assert isinstance(name, str) 143 label = category["label"] 144 assert isinstance(label, str) 145 color = category["color"] 146 assert isinstance(color, str) 147 subcategories = category.get("subcategories", None) 148 assert isinstance(subcategories, list) and len(subcategories) > 0, ( 149 f"At least one subcategory expected as default in {name}." 150 ) 151 152 category_items.append( 153 generate_category_macro(name, label, color, subcategories) 154 ) 155 156 contents = CPP_MACRO_DEFINITION 157 contents += " \\\n".join(category_items) 158 159 generate_header(c_out, "baseprofiler_ProfilingCategoryList_h", contents) 160 161 162 class RustEnum: 163 """Class that keeps the rust enum fields and impls. 164 This is used for generating the Rust ProfilingCategoryPair and ProfilingCategory 165 enums as well as ProfilingCategoryPair's sub category enums. 166 For example, this can either generate an enum with discrimant fields for sub 167 category enums and ProfilingCategory: 168 ``` 169 #[repr(u32)] 170 #[derive(Debug, Copy, Clone)] 171 pub enum Graphics { 172 LayerBuilding = 0, 173 ... 174 } 175 ``` 176 or can generate an enum with optional tuple values for ProfilingCategoryPair 177 to explicitly mention their sub categories: 178 ``` 179 #[repr(u32)] 180 #[derive(Debug, Copy, Clone)] 181 pub enum ProfilingCategoryPair { 182 Network(Option<Network>), 183 ... 184 } 185 ``` 186 187 And in addition to enums, it will generate impls for each enum. See one 188 example below: 189 ``` 190 impl Default for Network { 191 fn default() -> Self { 192 Network::Other 193 } 194 } 195 ``` 196 """ 197 198 def __init__(self, name): 199 # Name of the Rust enum. 200 self.name = name 201 # Fields of the Rust enum. This list contains elements of 202 # (field_name, field_string) tuple for convenience. 203 self.fields = [] 204 # Impls of the Rust enum. Each element is a string. 205 self.impls = [] 206 # Default category of the Rust enum. Main enums won't have it, but all 207 # sub category enums must have one. This is being checked later. 208 self.default_category = None 209 210 def append_optional_tuple_field(self, field_name): 211 """Append the enum fields list with an optional tuple field.""" 212 field = (field_name, f" {field_name}(Option<{field_name}>),") 213 self.fields.append(field) 214 215 def append_discriminant_field(self, field_name, field_value): 216 """Append the enum fields list with a discriminant field.""" 217 field = ( 218 field_name, 219 f" {field_name} = {field_value},", 220 ) 221 self.fields.append(field) 222 223 def append_default_impl(self, default_category): 224 """Append the enum impls list with a default implementation.""" 225 self.default_category = default_category 226 227 self.impls.append( 228 RUST_DEFAULT_IMPL_TEMPLATE.format( 229 name=self.name, 230 content=f" {self.name}::{self.default_category}", 231 ) 232 ) 233 234 def append_conversion_impl(self, content): 235 """Append the enum impls list with a conversion implementation for cpp values.""" 236 self.impls.append( 237 RUST_CONVERSION_IMPL_TEMPLATE.format(name=self.name, content=content) 238 ) 239 240 def to_rust_string(self): 241 """Serialize the enum with its impls as a string""" 242 joined_fields = "\n".join(map(lambda field: field[1], self.fields)) 243 result = RUST_ENUM_TEMPLATE.format(name=self.name, fields=joined_fields) 244 result += "\n" 245 result += "\n".join(self.impls) 246 return result 247 248 249 def generate_rust_enums(c_out, yaml_path): 250 """Generate profiling_categories.rs from profiling_categories.yaml. 251 The generated file has a profiling category enums and their impls. 252 """ 253 254 data = load_yaml(yaml_path) 255 256 # Each category has its own enum for keeping its subcategories. We are 257 # keeping all of them here. 258 enums = [] 259 # Parent enums for prifiling category and profiling category pair. They will 260 # be appended to the end of the `enums`. 261 profiling_category_pair_enum = RustEnum("ProfilingCategoryPair") 262 profiling_category_enum = RustEnum("ProfilingCategory") 263 profiling_category_pair_value = 0 264 265 for cat_index, category in enumerate(data): 266 cat_name = category["name"] 267 assert isinstance(cat_name, str) 268 cat_label = category["label"] 269 assert isinstance(cat_label, str) 270 # This will be used as our main enum field and sub category enum. 271 cat_label = "".join(filter(str.isalnum, cat_label)) 272 cat_subcategories = category.get("subcategories", None) 273 assert isinstance(cat_subcategories, list) and len(cat_subcategories) > 0, ( 274 f"At least one subcategory expected as default in {cat_name}." 275 ) 276 277 # Create a new enum for this sub category and append it to the enums list. 278 category_enum = RustEnum(cat_label) 279 enums.append(category_enum) 280 281 for subcategory in cat_subcategories: 282 subcat_name = subcategory["name"] 283 assert isinstance(subcat_name, str) 284 subcat_label = subcategory["label"] 285 assert isinstance(subcat_label, str) 286 friendly_subcat_name = None 287 288 if cat_name == subcat_name: 289 # This is the default sub-category. It should use the label as name. 290 friendly_subcat_name = subcat_label 291 category_enum.append_default_impl(subcat_label) 292 else: 293 # This is a non-default sub-category. 294 underscore_pos = subcat_name.find("_") 295 friendly_subcat_name = subcat_name[underscore_pos + 1 :] 296 297 friendly_subcat_name = "".join(filter(str.isalnum, friendly_subcat_name)) 298 category_enum.append_discriminant_field( 299 friendly_subcat_name, profiling_category_pair_value 300 ) 301 profiling_category_pair_value += 1 302 303 assert category_enum.default_category is not None, ( 304 "There must be a default subcategory with the same name." 305 ) 306 307 # Append the main enums. 308 profiling_category_pair_enum.append_optional_tuple_field(cat_label) 309 profiling_category_enum.append_discriminant_field(cat_label, cat_index) 310 311 # Add the main enums impls for conversion into cpp values. 312 profiling_category_pair_impl_fields = "\n".join( 313 " {enum_name}::{field_name}(val) => val.unwrap_or_default() as u32,".format( 314 enum_name="ProfilingCategoryPair", field_name=field 315 ) 316 for field, _ in profiling_category_pair_enum.fields 317 ) 318 profiling_category_pair_enum.append_conversion_impl( 319 RUST_MATCH_SELF.format(fields=profiling_category_pair_impl_fields) 320 ) 321 profiling_category_enum.append_conversion_impl(" *self as u32") 322 323 # After adding all the sub category enums, we can add the main enums to the list. 324 enums.append(profiling_category_pair_enum) 325 enums.append(profiling_category_enum) 326 327 # Print all the enums and their impls. 328 contents = "\n".join(map(lambda enum: enum.to_rust_string(), enums)) 329 generate_rust_file(c_out, contents)