summaryrefslogtreecommitdiffstats
path: root/mozglue/baseprofiler/build/generate_profiling_categories.py
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/baseprofiler/build/generate_profiling_categories.py')
-rw-r--r--mozglue/baseprofiler/build/generate_profiling_categories.py350
1 files changed, 350 insertions, 0 deletions
diff --git a/mozglue/baseprofiler/build/generate_profiling_categories.py b/mozglue/baseprofiler/build/generate_profiling_categories.py
new file mode 100644
index 0000000000..64da056bbd
--- /dev/null
+++ b/mozglue/baseprofiler/build/generate_profiling_categories.py
@@ -0,0 +1,350 @@
+# 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/.
+
+# This script generates ProfilingCategoryList.h and profiling_categories.rs
+# files from profiling_categories.yaml.
+
+from collections import OrderedDict
+
+import yaml
+
+CPP_HEADER_TEMPLATE = """\
+/* 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/. */
+
+#ifndef {includeguard}
+#define {includeguard}
+
+/* This file is generated by generate_profiling_categories.py from
+ profiling_categories.yaml. DO NOT EDIT! */
+
+// Profiler sub-categories are applied to each sampled stack to describe the
+// type of workload that the CPU is busy with. Only one sub-category can be
+// assigned so be mindful that these are non-overlapping. The active category is
+// set by pushing a label to the profiling stack, or by the unwinder in cases
+// such as JITs. A profile sample in arbitrary C++/Rust will typically be
+// categorized based on the top of the label stack.
+//
+// The list of available color names for categories is:
+// transparent
+// blue
+// green
+// grey
+// lightblue
+// magenta
+// orange
+// purple
+// yellow
+
+// clang-format off
+
+{contents}
+
+// clang-format on
+
+#endif // {includeguard}
+"""
+
+CPP_MACRO_DEFINITION = """\
+#define MOZ_PROFILING_CATEGORY_LIST(BEGIN_CATEGORY, SUBCATEGORY, END_CATEGORY) \\
+"""
+
+RUST_TEMPLATE = """\
+/* 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/. */
+
+/* This file is generated by generate_profiling_categories.py from
+ profiling_categories.yaml. DO NOT EDIT! */
+
+{contents}\
+"""
+
+RUST_ENUM_TEMPLATE = """\
+#[repr(u32)]
+#[derive(Debug, Copy, Clone)]
+pub enum {name} {{
+{fields}
+}}
+"""
+
+RUST_CONVERSION_IMPL_TEMPLATE = """\
+impl {name} {{
+ pub fn to_cpp_enum_value(&self) -> u32 {{
+{content}
+ }}
+}}
+"""
+
+RUST_DEFAULT_IMPL_TEMPLATE = """\
+impl Default for {name} {{
+ fn default() -> Self {{
+{content}
+ }}
+}}
+"""
+
+RUST_MATCH_SELF = """\
+ match *self {{
+{fields}
+ }}
+"""
+
+
+def generate_header(c_out, includeguard, contents):
+ c_out.write(
+ CPP_HEADER_TEMPLATE.format(includeguard=includeguard, contents=contents)
+ )
+
+
+def generate_rust_file(c_out, contents):
+ c_out.write(RUST_TEMPLATE.format(contents=contents))
+
+
+def load_yaml(yaml_path):
+ # Load into an OrderedDict to ensure order is preserved. Note: Python 3.7+
+ # also preserves ordering for normal dictionaries.
+ # Code based on https://stackoverflow.com/a/21912744.
+ class OrderedLoader(yaml.Loader):
+ pass
+
+ def construct_mapping(loader, node):
+ loader.flatten_mapping(node)
+ return OrderedDict(loader.construct_pairs(node))
+
+ tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
+ OrderedLoader.add_constructor(tag, construct_mapping)
+
+ file_handler = open(yaml_path)
+ return yaml.load(file_handler, OrderedLoader)
+
+
+def generate_category_macro(name, label, color, subcategories):
+ contents = ' BEGIN_CATEGORY({name}, "{label}", "{color}") \\\n'.format(
+ name=name, label=label, color=color
+ )
+
+ subcategory_items = []
+
+ for subcategory in subcategories:
+ subcat_name = subcategory["name"]
+ assert isinstance(subcat_name, str)
+ subcat_label = subcategory["label"]
+ assert isinstance(subcat_label, str)
+
+ subcategory_items.append(
+ ' SUBCATEGORY({parent_cat}, {name}, "{label}") \\\n'.format(
+ parent_cat=name, name=subcat_name, label=subcat_label
+ )
+ )
+
+ contents += "".join(subcategory_items)
+ contents += " END_CATEGORY"
+
+ return contents
+
+
+def generate_macro_header(c_out, yaml_path):
+ """Generate ProfilingCategoryList.h from profiling_categories.yaml.
+ The generated file has a macro to generate the profiling category enums.
+ """
+
+ data = load_yaml(yaml_path)
+
+ # Stores the macro definition of each categories.
+ category_items = []
+
+ for category in data:
+ name = category["name"]
+ assert isinstance(name, str)
+ label = category["label"]
+ assert isinstance(label, str)
+ color = category["color"]
+ assert isinstance(color, str)
+ subcategories = category.get("subcategories", None)
+ assert (
+ isinstance(subcategories, list) and len(subcategories) > 0
+ ), "At least one subcategory expected as default in {}.".format(name)
+
+ category_items.append(
+ generate_category_macro(name, label, color, subcategories)
+ )
+
+ contents = CPP_MACRO_DEFINITION
+ contents += " \\\n".join(category_items)
+
+ generate_header(c_out, "baseprofiler_ProfilingCategoryList_h", contents)
+
+
+class RustEnum:
+ """Class that keeps the rust enum fields and impls.
+ This is used for generating the Rust ProfilingCategoryPair and ProfilingCategory
+ enums as well as ProfilingCategoryPair's sub category enums.
+ For example, this can either generate an enum with discrimant fields for sub
+ category enums and ProfilingCategory:
+ ```
+ #[repr(u32)]
+ #[derive(Debug, Copy, Clone)]
+ pub enum Graphics {
+ LayerBuilding = 0,
+ ...
+ }
+ ```
+ or can generate an enum with optional tuple values for ProfilingCategoryPair
+ to explicitly mention their sub categories:
+ ```
+ #[repr(u32)]
+ #[derive(Debug, Copy, Clone)]
+ pub enum ProfilingCategoryPair {
+ Network(Option<Network>),
+ ...
+ }
+ ```
+
+ And in addition to enums, it will generate impls for each enum. See one
+ example below:
+ ```
+ impl Default for Network {
+ fn default() -> Self {
+ Network::Other
+ }
+ }
+ ```
+ """
+
+ def __init__(self, name):
+ # Name of the Rust enum.
+ self.name = name
+ # Fields of the Rust enum. This list contains elements of
+ # (field_name, field_string) tuple for convenience.
+ self.fields = []
+ # Impls of the Rust enum. Each element is a string.
+ self.impls = []
+ # Default category of the Rust enum. Main enums won't have it, but all
+ # sub category enums must have one. This is being checked later.
+ self.default_category = None
+
+ def append_optional_tuple_field(self, field_name):
+ """Append the enum fields list with an optional tuple field."""
+ field = (field_name, " {name}(Option<{name}>),".format(name=field_name))
+ self.fields.append(field)
+
+ def append_discriminant_field(self, field_name, field_value):
+ """Append the enum fields list with a discriminant field."""
+ field = (
+ field_name,
+ " {name} = {value},".format(name=field_name, value=field_value),
+ )
+ self.fields.append(field)
+
+ def append_default_impl(self, default_category):
+ """Append the enum impls list with a default implementation."""
+ self.default_category = default_category
+
+ self.impls.append(
+ RUST_DEFAULT_IMPL_TEMPLATE.format(
+ name=self.name,
+ content=" {category}::{subcategory}".format(
+ category=self.name, subcategory=self.default_category
+ ),
+ )
+ )
+
+ def append_conversion_impl(self, content):
+ """Append the enum impls list with a conversion implementation for cpp values."""
+ self.impls.append(
+ RUST_CONVERSION_IMPL_TEMPLATE.format(name=self.name, content=content)
+ )
+
+ def to_rust_string(self):
+ """Serialize the enum with its impls as a string"""
+ joined_fields = "\n".join(map(lambda field: field[1], self.fields))
+ result = RUST_ENUM_TEMPLATE.format(name=self.name, fields=joined_fields)
+ result += "\n"
+ result += "\n".join(self.impls)
+ return result
+
+
+def generate_rust_enums(c_out, yaml_path):
+ """Generate profiling_categories.rs from profiling_categories.yaml.
+ The generated file has a profiling category enums and their impls.
+ """
+
+ data = load_yaml(yaml_path)
+
+ # Each category has its own enum for keeping its subcategories. We are
+ # keeping all of them here.
+ enums = []
+ # Parent enums for prifiling category and profiling category pair. They will
+ # be appended to the end of the `enums`.
+ profiling_category_pair_enum = RustEnum("ProfilingCategoryPair")
+ profiling_category_enum = RustEnum("ProfilingCategory")
+ profiling_category_pair_value = 0
+
+ for cat_index, category in enumerate(data):
+ cat_name = category["name"]
+ assert isinstance(cat_name, str)
+ cat_label = category["label"]
+ assert isinstance(cat_label, str)
+ # This will be used as our main enum field and sub category enum.
+ cat_label = "".join(filter(str.isalnum, cat_label))
+ cat_subcategories = category.get("subcategories", None)
+ assert (
+ isinstance(cat_subcategories, list) and len(cat_subcategories) > 0
+ ), "At least one subcategory expected as default in {}.".format(cat_name)
+
+ # Create a new enum for this sub category and append it to the enums list.
+ category_enum = RustEnum(cat_label)
+ enums.append(category_enum)
+
+ for subcategory in cat_subcategories:
+ subcat_name = subcategory["name"]
+ assert isinstance(subcat_name, str)
+ subcat_label = subcategory["label"]
+ assert isinstance(subcat_label, str)
+ friendly_subcat_name = None
+
+ if cat_name == subcat_name:
+ # This is the default sub-category. It should use the label as name.
+ friendly_subcat_name = subcat_label
+ category_enum.append_default_impl(subcat_label)
+ else:
+ # This is a non-default sub-category.
+ underscore_pos = subcat_name.find("_")
+ friendly_subcat_name = subcat_name[underscore_pos + 1 :]
+
+ friendly_subcat_name = "".join(filter(str.isalnum, friendly_subcat_name))
+ category_enum.append_discriminant_field(
+ friendly_subcat_name, profiling_category_pair_value
+ )
+ profiling_category_pair_value += 1
+
+ assert (
+ category_enum.default_category is not None
+ ), "There must be a default subcategory with the same name."
+
+ # Append the main enums.
+ profiling_category_pair_enum.append_optional_tuple_field(cat_label)
+ profiling_category_enum.append_discriminant_field(cat_label, cat_index)
+
+ # Add the main enums impls for conversion into cpp values.
+ profiling_category_pair_impl_fields = "\n".join(
+ " {enum_name}::{field_name}(val) => val.unwrap_or_default() as u32,".format(
+ enum_name="ProfilingCategoryPair", field_name=field
+ )
+ for field, _ in profiling_category_pair_enum.fields
+ )
+ profiling_category_pair_enum.append_conversion_impl(
+ RUST_MATCH_SELF.format(fields=profiling_category_pair_impl_fields)
+ )
+ profiling_category_enum.append_conversion_impl(" *self as u32")
+
+ # After adding all the sub category enums, we can add the main enums to the list.
+ enums.append(profiling_category_pair_enum)
+ enums.append(profiling_category_enum)
+
+ # Print all the enums and their impls.
+ contents = "\n".join(map(lambda enum: enum.to_rust_string(), enums))
+ generate_rust_file(c_out, contents)