tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)