# 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/.
import re
def read_conf(conf_filename): # Can't read/write from a single StringIO, so make a new one for reading.
stream = open(conf_filename, "r")
def parse_counters(stream): for line_num, full_line in enumerate(stream):
line = full_line.rstrip("\n") ifnot line or line.startswith("//"): # empty line or comment continue
m = re.match(r"method ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$", line) if m:
interface_name, method_name = m.groups() yield { "type": "method", "interface_name": interface_name, "method_name": method_name,
} continue
m = re.match(r"attribute ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$", line) if m:
interface_name, attribute_name = m.groups() yield { "type": "attribute", "interface_name": interface_name, "attribute_name": attribute_name,
} continue
m = re.match(r"custom ([A-Za-z0-9_]+) (.*)$", line) if m:
name, desc = m.groups() yield {"type": "custom", "name": name, "desc": desc} continue raise ValueError( "error parsing %s at line %d" % (conf_filename, line_num + 1)
)
return parse_counters(stream)
YAML_HEADER = """\ # This file is AUTOGENERATED by usecounters.py. DO NOT EDIT. # (instead, re-run ./mach gen-use-counter-metrics)
# 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/.
def gen_use_counter_metrics(): """
Finds use counters in:
* dom/base/UseCounters.conf
* dom/base/UseCountersWorker.conf
* dom/base/nsDeprecatedOperationsList.h
* !/layout/style/ServoCSSPropList.py
* servo/components/style/properties/counted_unknown_properties.py and overwrites the Glean metrics definition file
`dom/base/use_counter_metrics.yaml` with definitions for each use counter found.
IF YOU CHANGE THIS FUNCTION:
* You should probably add your bug's number to USE_COUNTER_TEMPLATE, above.
Returns 0 on success. """
(
page,
doc,
dedicated,
shared,
service,
ops_page,
ops_doc,
css_page,
css_doc,
) = parse_use_counters() import os
import buildconfig from mozbuild.util import FileAvoidWrite
yaml_path = os.path.join(
buildconfig.topsrcdir, "dom", "base", "use_counter_metrics.yaml"
) with FileAvoidWrite(yaml_path) as f:
f.write(YAML_HEADER)
f.write(BASE_METRICS)
total = (
len(page)
+ len(doc)
+ len(dedicated)
+ len(shared)
+ len(service)
+ len(ops_page)
+ len(ops_doc)
+ len(css_page)
+ len(css_doc)
)
f.write(f"# Total of {total} use counter metrics (excludes denominators).\n")
f.write(f"# Total of {len(page)} 'page' use counters.\n")
f.write("use.counter.page:\n") for [_, name, desc] in page:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.top_level_content_documents_destroyed",
)
)
f.write(f"# Total of {len(doc)} 'document' use counters.\n")
f.write("use.counter.doc:\n") for [_, name, desc] in doc:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.content_documents_destroyed",
)
)
f.write(f"# Total of {len(dedicated)} 'dedicated worker' use counters.\n")
f.write("use.counter.worker.dedicated:\n") for [_, name, desc] in dedicated:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.dedicated_workers_destroyed",
)
)
f.write(f"# Total of {len(shared)} 'shared worker' use counters.\n")
f.write("use.counter.worker.shared:\n") for [_, name, desc] in shared:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.shared_workers_destroyed",
)
)
f.write(f"# Total of {len(service)} 'service worker' use counters.\n")
f.write("use.counter.worker.service:\n") for [_, name, desc] in service:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.service_workers_destroyed",
)
)
f.write(
f"# Total of {len(ops_page)} 'deprecated operations (page)' use counters.\n"
)
f.write("use.counter.deprecated_ops.page:\n") for [_, name, desc] in ops_page:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.top_level_content_documents_destroyed",
)
)
f.write(
f"# Total of {len(ops_doc)} 'deprecated operations (document)' use counters.\n"
)
f.write("use.counter.deprecated_ops.doc:\n") for [_, name, desc] in ops_doc:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.content_documents_destroyed",
)
)
f.write(f"# Total of {len(css_page)} 'CSS (page)' use counters.\n")
f.write("use.counter.css.page:\n") for [_, name, desc] in css_page:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.top_level_content_documents_destroyed",
)
)
f.write(f"# Total of {len(css_doc)} 'CSS (document)' use counters.\n")
f.write("use.counter.css.doc:\n") for [_, name, desc] in css_doc:
f.write(
USE_COUNTER_TEMPLATE.format(
name=name,
desc=desc,
denominator="use.counter.content_documents_destroyed",
)
)
return 0
def parse_use_counters(): """
Finds use counters in:
* dom/base/UseCounters.conf
* dom/base/UseCountersWorker.conf
* dom/base/nsDeprecatedOperationsList.h
* !/layout/style/ServoCSSPropList.py
* servo/components/style/properties/counted_unknown_properties.py and returns them as a tuple of lists of tuples of the form:
(page, doc, dedicated, shared, service, ops_page, ops_doc, css_page, css_doc)
where each of the items is a List<Tuple<enum_name, glean_name, description>>
where `enum_name` is the name of the enum variant from UseCounter.h
(like `eUseCounter_custom_CustomizedBuiltin`), and
where `glean_name` is the name conjugated to Glean metric name safety. """
# Note, this function contains a duplication of enum naming logic from UseCounter.h. # If you change the enum name format, you'll need to do it twice.
# There are 3 kinds of Use Counters in conf files: method, attribute, custom. # `method` and `attribute` are presumed label-safe and are taken as-is. # `custom` can be any case, so are coerced to snake_case. import os
import buildconfig
uc_path = os.path.join(buildconfig.topsrcdir, "dom", "base", "UseCounters.conf")
page = []
doc = [] for counter in read_conf(uc_path): if counter["type"] == "method":
enum_name = (
f"eUseCounter_{counter['interface_name']}_{counter['method_name']}"
)
glean_name = f"{counter['interface_name']}_{counter['method_name']}".lower()
method = f"called {counter['interface_name']}.{counter['method_name']}"
page.append((enum_name, glean_name, f"Whether a page called {method}."))
doc.append((enum_name, glean_name, f"Whether a document called {method}.")) elif counter["type"] == "attribute":
enum_root = (
f"eUseCounter_{counter['interface_name']}_{counter['attribute_name']}"
)
name = f"{counter['interface_name']}_{counter['attribute_name']}".lower()
attr = f"{counter['interface_name']}.{counter['attribute_name']}"
page.append(
(f"{enum_root}_getter", f"{name}_getter", f"Whether a page got {attr}.")
)
page.append(
(f"{enum_root}_setter", f"{name}_setter", f"Whether a page set {attr}.")
)
doc.append(
(
f"{enum_root}_getter",
f"{name}_getter",
f"Whether a document got {attr}.",
)
)
doc.append(
(
f"{enum_root}_setter",
f"{name}_setter",
f"Whether a document set {attr}.",
)
) elif counter["type"] == "custom":
enum_name = f"eUseCounter_custom_{counter['name']}"
page.append(
(
enum_name,
to_snake_case(counter["name"]),
f"Whether a page {counter['desc']}.",
)
)
doc.append(
(
enum_name,
to_snake_case(counter["name"]),
f"Whether a document {counter['desc']}.",
)
) else:
print(f"Found unexpected use counter type {counter['type']}. Returning 1.") return 1
worker_uc_path = os.path.join(
buildconfig.topsrcdir, "dom", "base", "UseCountersWorker.conf"
)
dedicated = []
shared = []
service = [] for counter in read_conf(worker_uc_path): if counter["type"] == "method":
enum_name = f"{counter['interface_name']}_{counter['method_name']}"
name = f"{counter['interface_name']}_{counter['method_name']}".lower()
method = f"called {counter['interface_name']}.{counter['method_name']}"
dedicated.append(
(enum_name, name, f"Whether a dedicated worker called {method}.")
)
shared.append(
(enum_name, name, f"Whether a shared worker called {method}.")
)
service.append(
(enum_name, name, f"Whether a service worker called {method}.")
) elif counter["type"] == "attribute":
enum_root = f"{counter['interface_name']}_{counter['attribute_name']}"
name = f"{counter['interface_name']}_{counter['attribute_name']}".lower()
attr = f"{counter['interface_name']}.{counter['attribute_name']}"
dedicated.append(
(
f"{enum_root}_getter",
f"{name}_getter",
f"Whether a dedicated worker got {attr}.",
)
)
dedicated.append(
(
f"{enum_root}_setter",
f"{name}_setter",
f"Whether a dedicated worker set {attr}.",
)
)
shared.append(
(
f"{enum_root}_getter",
f"{name}_getter",
f"Whether a shared worker got {attr}.",
)
)
shared.append(
(
f"{enum_root}_setter",
f"{name}_setter",
f"Whether a shared worker set {attr}.",
)
)
service.append(
(
f"{enum_root}_getter",
f"{name}_getter",
f"Whether a service worker got {attr}.",
)
)
service.append(
(
f"{enum_root}_setter",
f"{name}_setter",
f"Whether a service worker set {attr}.",
)
) elif counter["type"] == "custom":
enum_name = f"Custom_{counter['name']}"
dedicated.append(
(
enum_name,
to_snake_case(counter["name"]),
f"Whether a dedicated worker {counter['desc']}.",
)
)
shared.append(
(
enum_name,
to_snake_case(counter["name"]),
f"Whether a shared worker {counter['desc']}.",
)
)
service.append(
(
enum_name,
to_snake_case(counter["name"]),
f"Whether a service worker {counter['desc']}.",
)
) else:
print(
f"Found unexpected worker use counter type {counter['type']}. Returning 1."
) return 1
# nsDeprecatedOperationsList.h parsing is adapted from parse_histograms.py.
operation_list_path = os.path.join(
buildconfig.topsrcdir, "dom", "base", "nsDeprecatedOperationList.h"
)
operation_regex = re.compile("^DEPRECATED_OPERATION\\(([^)]+)\\)")
ops_page = []
ops_doc = [] with open(operation_list_path) as f: for line in f:
match = operation_regex.search(line) ifnot match: # No macro, probably whitespace or comment. continue
op = match.group(1)
op_name = to_snake_case(op)
enum_name = f"eUseCounter_{op}"
ops_page.append((enum_name, op_name, f"Whether a page used {op}."))
ops_doc.append((enum_name, op_name, f"Whether a document used {op}."))
# Theoretically, we could do this without a completed build # (ie, without the generated ServoCSSPropList.py) by sourcing direct from # servo/components/style/properties/data.py:PropertiesData(engine=gecko). # # ...but parse_histograms.py doesn't do this the hard way. Should we?
import runpy
proplist_path = os.path.join(
buildconfig.topobjdir, "layout", "style", "ServoCSSPropList.py"
)
css_properties = runpy.run_path(proplist_path)["data"]
css_page = []
css_doc = [] for prop in css_properties.values(): # We prefix `prop_name` with `css_` to avoid colliding with C++ keywords # like `float`.
prop_name = "css_" + to_snake_case(prop.name)
enum_name = f"eUseCounter_property_{method}"
css_page.append(
(enum_name, prop_name, f"Whether a page used the CSS property {prop.name}.")
)
css_doc.append(
(
enum_name,
prop_name,
f"Whether a document used the CSS property {prop.name}.",
)
)
# Counted unknown properties: AKA - stuff that doesn't exist, but we want # to count uses of anyway. # We _might_ decide to implement these in the future, though, so we just add # them to the css_page, css_doc lists directly for continuity. # (We do give them a different description, though)
import sys
sys.path.append(os.path.join(buildconfig.topsrcdir, "layout", "style")) from GenerateCountedUnknownProperties import to_camel_case
unknown_proplist_path = os.path.join(
buildconfig.topsrcdir, "servo", "components", "style", "properties", "counted_unknown_properties.py",
)
unknown_properties = runpy.run_path(unknown_proplist_path)[ "COUNTED_UNKNOWN_PROPERTIES"
] for prop in unknown_properties:
enum_name = f"eUseCounter_unknown_property_{to_camel_case(prop)}"
prop_name = to_snake_case(prop)
css_page.append(
(
enum_name,
prop_name,
f"Whether a page used the (unknown, counted) CSS property {prop}.",
)
)
css_doc.append(
(
enum_name,
prop_name,
f"Whether a document used the (unknown, counted) CSS property {prop}.",
)
)
def to_snake_case(kebab_or_pascal): """
Takes `kebab_or_pascal` which isin PascalCase or kebab-case and conjugates it to "snake_case" (all lowercase, "_"-delimited). """ return (
re.sub("([A-Z]+)", r"_\1", kebab_or_pascal).replace("-", "_").lower().strip("_")
)
def metric_map(f, *inputs): """
Parses all use counters and outputs UseCounter.cpp which contains implementations for two functions defined in UseCounter.h:
* const char* IncrementUseCounter(UseCounter aUseCounter, bool aIsPage)
* const char* IncrementWorkerUseCounter(UseCounterWorker aUseCounter, dom::WorkerKind aKind)
(Basically big switch statements mapping from enums to glean metrics, calling Add()) """
f.write( """\
/* AUTOGENERATED by usecounters.py. DO NOT EDIT */
/* 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 order must match the order UseCounter is defined, # (and we guarantee it via the MOZ_ASSERT below at runtime). assert len(page) == len(doc) assert len(ops_page) == len(ops_doc) assert len(css_page) == len(css_doc)
index = 0
static_asserts = [] for pc, dc in zip(page, doc): assert pc[0] == dc[0] assert pc[1] == dc[1]
static_asserts.append(f"static_assert({index} == size_t(UseCounter::{pc[0]}));")
f.write(
f"""\
{{ "{pc[1]}",
glean::use_counter_doc::{pc[1]},
glean::use_counter_page::{pc[1]},
}}, """
)
index += 1
for pc, dc in zip(ops_page, ops_doc): assert pc[0] == dc[0] assert pc[1] == dc[1]
static_asserts.append(f"static_assert({index} == size_t(UseCounter::{pc[0]}));")
f.write(
f"""\
{{ "deprecated_ops.{pc[1]}",
glean::use_counter_deprecated_ops_doc::{pc[1]},
glean::use_counter_deprecated_ops_page::{pc[1]},
}}, """
)
index += 1
for pc, dc in zip(css_page, css_doc): assert pc[0] == dc[0] assert pc[1] == dc[1]
static_asserts.append(f"static_assert({index} == size_t(UseCounter::{pc[0]}));")
f.write(
f"""\
{{ "css.{pc[1]}",
glean::use_counter_css_doc::{pc[1]},
glean::use_counter_css_page::{pc[1]},
}}, """
)
index += 1
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.