167 lines
5.2 KiB
Python
167 lines
5.2 KiB
Python
# 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/.
|
|
|
|
"""
|
|
Outputter to generate C++ code for metrics.
|
|
"""
|
|
|
|
import functools
|
|
|
|
import jinja2
|
|
from glean_parser import metrics, util
|
|
from util import generate_metric_ids, generate_ping_ids, get_metrics
|
|
|
|
|
|
def type_name(obj):
|
|
"""
|
|
Returns the C++ type to use for a given metric object.
|
|
"""
|
|
|
|
if getattr(obj, "labeled", False):
|
|
class_name = util.Camelize(obj.type[8:]) # strips "labeled_" off the front.
|
|
label_enum = "DynamicLabel"
|
|
if obj.labels and len(obj.labels):
|
|
label_enum = f"{util.Camelize(obj.name)}Label"
|
|
if class_name == "Counter":
|
|
return f"Labeled<impl::{class_name}Metric<impl::CounterType::eBaseOrLabeled>, {label_enum}>"
|
|
return f"Labeled<impl::{class_name}Metric, {label_enum}>"
|
|
generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
|
|
if len(generate_enums):
|
|
for name, _ in generate_enums:
|
|
if not len(getattr(obj, name)) and isinstance(obj, metrics.Event):
|
|
return util.Camelize(obj.type) + "Metric<NoExtraKeys>"
|
|
else:
|
|
# we always use the `extra` suffix,
|
|
# because we only expose the new event API
|
|
suffix = "Extra"
|
|
return f"{util.Camelize(obj.type)}Metric<{util.Camelize(obj.name) + suffix}>"
|
|
generate_structure = getattr(obj, "_generate_structure", []) # Object metric?
|
|
if len(generate_structure):
|
|
generic = util.Camelize(obj.name) + "Object"
|
|
tag = generic + "Tag"
|
|
return f"ObjectMetric<{generic}, struct {tag}>"
|
|
suffix = "Metric"
|
|
if getattr(obj, "standalone", False):
|
|
suffix = "Standalone"
|
|
if obj.type == "counter":
|
|
return f"Counter{suffix}<impl::CounterType::eBaseOrLabeled>"
|
|
return util.Camelize(obj.type) + suffix
|
|
|
|
|
|
def extra_type_name(typ: str) -> str:
|
|
"""
|
|
Returns the corresponding C++ type for event's extra key types.
|
|
"""
|
|
|
|
if typ == "boolean":
|
|
return "bool"
|
|
elif typ == "string":
|
|
return "nsCString"
|
|
elif typ == "quantity":
|
|
return "uint32_t"
|
|
else:
|
|
return "UNSUPPORTED"
|
|
|
|
|
|
def structure_type_name(typ: str) -> str:
|
|
"""
|
|
Returns the corresponding C++ type for objects' structure types.
|
|
"""
|
|
|
|
if typ == "boolean":
|
|
return "bool"
|
|
elif typ == "string":
|
|
return "nsCString"
|
|
elif typ == "number":
|
|
return "int64_t"
|
|
else:
|
|
return "UNSUPPORTED"
|
|
|
|
|
|
def jsonwriter_prefix(typ: str) -> str:
|
|
"""
|
|
Returns the JSONWriter function prefix for a given structure type.
|
|
"""
|
|
|
|
if typ == "boolean":
|
|
return "Bool"
|
|
elif typ == "string":
|
|
return "String"
|
|
elif typ == "number":
|
|
return "Int"
|
|
else:
|
|
return "UNSUPPORTED"
|
|
|
|
|
|
def has_structure(all_objs) -> bool:
|
|
"""
|
|
Returns true if there's a metric in objs that needs a generated structure.
|
|
"""
|
|
for _, objs in all_objs.items():
|
|
for metric in objs.values():
|
|
if hasattr(metric, "_generate_structure"):
|
|
return True
|
|
return False
|
|
|
|
|
|
@functools.cache
|
|
def get_metrics_template(get_metric_id):
|
|
return util.get_jinja2_template(
|
|
"cpp.jinja2",
|
|
filters=(
|
|
("snake_case", lambda value: value.replace(".", "_").replace("-", "_")),
|
|
("type_name", type_name),
|
|
("extra_type_name", extra_type_name),
|
|
("structure_type_name", structure_type_name),
|
|
("jsonwriter_prefix", jsonwriter_prefix),
|
|
("has_structure", has_structure),
|
|
("metric_id", get_metric_id),
|
|
),
|
|
)
|
|
|
|
|
|
def output_cpp(objs, output_fd, options={}):
|
|
"""
|
|
Given a tree of objects, output C++ code to the file-like object `output_fd`.
|
|
|
|
:param objs: A tree of objects (metrics and pings) as returned from
|
|
`parser.parse_objects`.
|
|
:param output_fd: Writeable file to write the output to.
|
|
:param options: options dictionary.
|
|
"""
|
|
|
|
# Monkeypatch util.get_jinja2_template to find templates nearby
|
|
|
|
def get_local_template(template_name, filters=()):
|
|
env = jinja2.Environment(
|
|
loader=jinja2.PackageLoader("cpp", "templates"),
|
|
trim_blocks=True,
|
|
lstrip_blocks=True,
|
|
)
|
|
env.filters["camelize"] = util.camelize
|
|
env.filters["Camelize"] = util.Camelize
|
|
for filter_name, filter_func in filters:
|
|
env.filters[filter_name] = filter_func
|
|
return env.get_template(template_name)
|
|
|
|
util.get_jinja2_template = get_local_template
|
|
|
|
if "pings" in objs:
|
|
template = util.get_jinja2_template(
|
|
"cpp_pings.jinja2",
|
|
filters=(("ping_id", generate_ping_ids(objs)),),
|
|
)
|
|
if objs.get("tags"):
|
|
del objs["tags"]
|
|
else:
|
|
template = get_metrics_template(
|
|
options["get_metric_id"]
|
|
if "get_metric_id" in options
|
|
else generate_metric_ids(objs, options)
|
|
)
|
|
objs = get_metrics(objs)
|
|
|
|
output_fd.write(template.render(all_objs=objs, options=options))
|
|
output_fd.write("\n")
|