tor-browser

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

usecounters.py (26121B)


      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 import re
      6 
      7 
      8 def read_conf(conf_filename):
      9    # Can't read/write from a single StringIO, so make a new one for reading.
     10    stream = open(conf_filename)
     11 
     12    def parse_counters(stream):
     13        for line_num, full_line in enumerate(stream):
     14            line = full_line.rstrip("\n")
     15            if not line or line.startswith("//"):
     16                # empty line or comment
     17                continue
     18            m = re.match(r"method ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$", line)
     19            if m:
     20                interface_name, method_name = m.groups()
     21                yield {
     22                    "type": "method",
     23                    "interface_name": interface_name,
     24                    "method_name": method_name,
     25                }
     26                continue
     27            m = re.match(r"attribute ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$", line)
     28            if m:
     29                interface_name, attribute_name = m.groups()
     30                yield {
     31                    "type": "attribute",
     32                    "interface_name": interface_name,
     33                    "attribute_name": attribute_name,
     34                }
     35                continue
     36            m = re.match(r"custom ([A-Za-z0-9_]+) (.*)$", line)
     37            if m:
     38                name, desc = m.groups()
     39                yield {"type": "custom", "name": name, "desc": desc}
     40                continue
     41            raise ValueError(
     42                "error parsing %s at line %d" % (conf_filename, line_num + 1)
     43            )
     44 
     45    return parse_counters(stream)
     46 
     47 
     48 YAML_HEADER = """\
     49 # This file is AUTOGENERATED by usecounters.py. DO NOT EDIT.
     50 # (instead, re-run ./mach gen-use-counter-metrics)
     51 
     52 # This Source Code Form is subject to the terms of the Mozilla Public
     53 # License, v. 2.0. If a copy of the MPL was not distributed with this
     54 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     55 
     56 ---
     57 $schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
     58 $tags:
     59  - 'Core :: DOM: Core & HTML'
     60 
     61 """
     62 
     63 BASE_METRICS = """\
     64 use.counter:
     65  content_documents_destroyed:
     66    type: counter
     67    description: >
     68      A count of how many content documents were destroyed.
     69      Used to turn document use counters' counts into rates.
     70      Excludes documents for which we do not count use counters
     71      (See `Document::ShouldIncludeInTelemetry`).
     72    bugs:
     73      - https://bugzilla.mozilla.org/show_bug.cgi?id=1204994
     74      - https://bugzilla.mozilla.org/show_bug.cgi?id=1569672
     75      - https://bugzilla.mozilla.org/show_bug.cgi?id=1845779
     76      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
     77    data_reviews:
     78      - https://bugzilla.mozilla.org/show_bug.cgi?id=1569672
     79    notification_emails:
     80      - dom-core@mozilla.com
     81      - emilio@mozilla.com
     82    expires: never
     83    send_in_pings:
     84      - use-counters
     85 
     86  top_level_content_documents_destroyed:
     87    type: counter
     88    description: >
     89      A count of how many "pages" were destroyed.
     90      Used to turn page use counters' counts into rates.
     91      Excludes pages that contain only documents for which we do not count use
     92      counters (See `Document::ShouldIncludeInTelemetry`).
     93    bugs:
     94      - https://bugzilla.mozilla.org/show_bug.cgi?id=1204994
     95      - https://bugzilla.mozilla.org/show_bug.cgi?id=1569672
     96      - https://bugzilla.mozilla.org/show_bug.cgi?id=1845779
     97      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
     98    data_reviews:
     99      - https://bugzilla.mozilla.org/show_bug.cgi?id=1569672
    100    notification_emails:
    101      - dom-core@mozilla.com
    102      - emilio@mozilla.com
    103    expires: never
    104    send_in_pings:
    105      - use-counters
    106      - metrics
    107 
    108  dedicated_workers_destroyed:
    109    type: counter
    110    description: >
    111      A count of how many `Dedicated`-kind workers were destroyed.
    112      Used to turn dedicated worker use counters' counts into rates.
    113      Excludes chrome workers.
    114    bugs:
    115      - https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
    116      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
    117    data_reviews:
    118      - https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
    119    notification_emails:
    120      - dom-core@mozilla.com
    121      - emilio@mozilla.com
    122    expires: never
    123    send_in_pings:
    124      - use-counters
    125 
    126  shared_workers_destroyed:
    127    type: counter
    128    description: >
    129      A count of how many `Shared`-kind workers were destroyed.
    130      Used to turn shared worker use counters' counts into rates.
    131      Excludes chrome workers.
    132    bugs:
    133      - https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
    134      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
    135    data_reviews:
    136      - https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
    137    notification_emails:
    138      - dom-core@mozilla.com
    139      - emilio@mozilla.com
    140    expires: never
    141    send_in_pings:
    142      - use-counters
    143 
    144  service_workers_destroyed:
    145    type: counter
    146    description: >
    147      A count of how many `Service`-kind workers were destroyed.
    148      Used to turn service worker use counters' counts into rates.
    149      Excludes chrome workers.
    150    bugs:
    151      - https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
    152      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
    153    data_reviews:
    154      - https://bugzilla.mozilla.org/show_bug.cgi?id=1202706
    155    notification_emails:
    156      - dom-core@mozilla.com
    157      - emilio@mozilla.com
    158    expires: never
    159    send_in_pings:
    160      - use-counters
    161 
    162 """
    163 
    164 USE_COUNTER_TEMPLATE = """\
    165  {name}:
    166    type: counter
    167    description: >
    168      {desc}
    169      Compare against `{denominator}`
    170      to calculate the rate.
    171    bugs:
    172      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
    173    data_reviews:
    174      - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098
    175    notification_emails:
    176      - dom-core@mozilla.com
    177      - emilio@mozilla.com
    178    expires: never
    179    send_in_pings:
    180      - use-counters
    181 
    182 """
    183 
    184 
    185 def gen_use_counter_metrics():
    186    """
    187    Finds use counters in:
    188      * dom/base/UseCounters.conf
    189      * dom/base/UseCountersWorker.conf
    190      * dom/base/nsDeprecatedOperationsList.h
    191      * !/layout/style/ServoCSSPropList.py
    192      * servo/components/style/properties/counted_unknown_properties.py
    193    and overwrites the Glean metrics definition file
    194    `dom/base/use_counter_metrics.yaml` with definitions for each use counter found.
    195 
    196    IF YOU CHANGE THIS FUNCTION:
    197      * You should probably add your bug's number to USE_COUNTER_TEMPLATE, above.
    198 
    199    Returns 0 on success.
    200    """
    201    (
    202        page,
    203        doc,
    204        dedicated,
    205        shared,
    206        service,
    207        ops_page,
    208        ops_doc,
    209        css_page,
    210        css_doc,
    211    ) = parse_use_counters()
    212    import os
    213 
    214    import buildconfig
    215    from mozbuild.util import FileAvoidWrite
    216 
    217    yaml_path = os.path.join(
    218        buildconfig.topsrcdir, "dom", "base", "use_counter_metrics.yaml"
    219    )
    220    with FileAvoidWrite(yaml_path) as f:
    221        f.write(YAML_HEADER)
    222        f.write(BASE_METRICS)
    223 
    224        total = (
    225            len(page)
    226            + len(doc)
    227            + len(dedicated)
    228            + len(shared)
    229            + len(service)
    230            + len(ops_page)
    231            + len(ops_doc)
    232            + len(css_page)
    233            + len(css_doc)
    234        )
    235        f.write(f"# Total of {total} use counter metrics (excludes denominators).\n")
    236        f.write(f"# Total of {len(page)} 'page' use counters.\n")
    237        f.write("use.counter.page:\n")
    238        for [_, name, desc] in page:
    239            f.write(
    240                USE_COUNTER_TEMPLATE.format(
    241                    name=name,
    242                    desc=desc,
    243                    denominator="use.counter.top_level_content_documents_destroyed",
    244                )
    245            )
    246 
    247        f.write(f"# Total of {len(doc)} 'document' use counters.\n")
    248        f.write("use.counter.doc:\n")
    249        for [_, name, desc] in doc:
    250            f.write(
    251                USE_COUNTER_TEMPLATE.format(
    252                    name=name,
    253                    desc=desc,
    254                    denominator="use.counter.content_documents_destroyed",
    255                )
    256            )
    257 
    258        f.write(f"# Total of {len(dedicated)} 'dedicated worker' use counters.\n")
    259        f.write("use.counter.worker.dedicated:\n")
    260        for [_, name, desc] in dedicated:
    261            f.write(
    262                USE_COUNTER_TEMPLATE.format(
    263                    name=name,
    264                    desc=desc,
    265                    denominator="use.counter.dedicated_workers_destroyed",
    266                )
    267            )
    268 
    269        f.write(f"# Total of {len(shared)} 'shared worker' use counters.\n")
    270        f.write("use.counter.worker.shared:\n")
    271        for [_, name, desc] in shared:
    272            f.write(
    273                USE_COUNTER_TEMPLATE.format(
    274                    name=name,
    275                    desc=desc,
    276                    denominator="use.counter.shared_workers_destroyed",
    277                )
    278            )
    279 
    280        f.write(f"# Total of {len(service)} 'service worker' use counters.\n")
    281        f.write("use.counter.worker.service:\n")
    282        for [_, name, desc] in service:
    283            f.write(
    284                USE_COUNTER_TEMPLATE.format(
    285                    name=name,
    286                    desc=desc,
    287                    denominator="use.counter.service_workers_destroyed",
    288                )
    289            )
    290 
    291        f.write(
    292            f"# Total of {len(ops_page)} 'deprecated operations (page)' use counters.\n"
    293        )
    294        f.write("use.counter.deprecated_ops.page:\n")
    295        for [_, name, desc] in ops_page:
    296            f.write(
    297                USE_COUNTER_TEMPLATE.format(
    298                    name=name,
    299                    desc=desc,
    300                    denominator="use.counter.top_level_content_documents_destroyed",
    301                )
    302            )
    303 
    304        f.write(
    305            f"# Total of {len(ops_doc)} 'deprecated operations (document)' use counters.\n"
    306        )
    307        f.write("use.counter.deprecated_ops.doc:\n")
    308        for [_, name, desc] in ops_doc:
    309            f.write(
    310                USE_COUNTER_TEMPLATE.format(
    311                    name=name,
    312                    desc=desc,
    313                    denominator="use.counter.content_documents_destroyed",
    314                )
    315            )
    316 
    317        f.write(f"# Total of {len(css_page)} 'CSS (page)' use counters.\n")
    318        f.write("use.counter.css.page:\n")
    319        for [_, name, desc] in css_page:
    320            f.write(
    321                USE_COUNTER_TEMPLATE.format(
    322                    name=name,
    323                    desc=desc,
    324                    denominator="use.counter.top_level_content_documents_destroyed",
    325                )
    326            )
    327 
    328        f.write(f"# Total of {len(css_doc)} 'CSS (document)' use counters.\n")
    329        f.write("use.counter.css.doc:\n")
    330        for [_, name, desc] in css_doc:
    331            f.write(
    332                USE_COUNTER_TEMPLATE.format(
    333                    name=name,
    334                    desc=desc,
    335                    denominator="use.counter.content_documents_destroyed",
    336                )
    337            )
    338 
    339    return 0
    340 
    341 
    342 def parse_use_counters():
    343    """
    344    Finds use counters in:
    345      * dom/base/UseCounters.conf
    346      * dom/base/UseCountersWorker.conf
    347      * dom/base/nsDeprecatedOperationsList.h
    348      * !/layout/style/ServoCSSPropList.py
    349      * servo/components/style/properties/counted_unknown_properties.py
    350    and returns them as a tuple of lists of tuples of the form:
    351    (page, doc, dedicated, shared, service, ops_page, ops_doc, css_page, css_doc)
    352    where each of the items is a List<Tuple<enum_name, glean_name, description>>
    353    where `enum_name` is the name of the enum variant from UseCounter.h
    354    (like `eUseCounter_custom_CustomizedBuiltin`), and
    355    where `glean_name` is the name conjugated to Glean metric name safety.
    356    """
    357 
    358    # Note, this function contains a duplication of enum naming logic from UseCounter.h.
    359    # If you change the enum name format, you'll need to do it twice.
    360 
    361    # There are 3 kinds of Use Counters in conf files: method, attribute, custom.
    362    # `method` and `attribute` are presumed label-safe and are taken as-is.
    363    # `custom` can be any case, so are coerced to snake_case.
    364    import os
    365 
    366    import buildconfig
    367 
    368    uc_path = os.path.join(buildconfig.topsrcdir, "dom", "base", "UseCounters.conf")
    369    page = []
    370    doc = []
    371    for counter in read_conf(uc_path):
    372        if counter["type"] == "method":
    373            enum_name = (
    374                f"eUseCounter_{counter['interface_name']}_{counter['method_name']}"
    375            )
    376            glean_name = f"{counter['interface_name']}_{counter['method_name']}".lower()
    377            method = f"called {counter['interface_name']}.{counter['method_name']}"
    378            page.append((enum_name, glean_name, f"Whether a page called {method}."))
    379            doc.append((enum_name, glean_name, f"Whether a document called {method}."))
    380        elif counter["type"] == "attribute":
    381            enum_root = (
    382                f"eUseCounter_{counter['interface_name']}_{counter['attribute_name']}"
    383            )
    384            name = f"{counter['interface_name']}_{counter['attribute_name']}".lower()
    385            attr = f"{counter['interface_name']}.{counter['attribute_name']}"
    386            page.append((
    387                f"{enum_root}_getter",
    388                f"{name}_getter",
    389                f"Whether a page got {attr}.",
    390            ))
    391            page.append((
    392                f"{enum_root}_setter",
    393                f"{name}_setter",
    394                f"Whether a page set {attr}.",
    395            ))
    396            doc.append((
    397                f"{enum_root}_getter",
    398                f"{name}_getter",
    399                f"Whether a document got {attr}.",
    400            ))
    401            doc.append((
    402                f"{enum_root}_setter",
    403                f"{name}_setter",
    404                f"Whether a document set {attr}.",
    405            ))
    406        elif counter["type"] == "custom":
    407            enum_name = f"eUseCounter_custom_{counter['name']}"
    408            page.append((
    409                enum_name,
    410                to_snake_case(counter["name"]),
    411                f"Whether a page {counter['desc']}.",
    412            ))
    413            doc.append((
    414                enum_name,
    415                to_snake_case(counter["name"]),
    416                f"Whether a document {counter['desc']}.",
    417            ))
    418        else:
    419            print(f"Found unexpected use counter type {counter['type']}. Returning 1.")
    420            return 1
    421 
    422    worker_uc_path = os.path.join(
    423        buildconfig.topsrcdir, "dom", "base", "UseCountersWorker.conf"
    424    )
    425    dedicated = []
    426    shared = []
    427    service = []
    428    for counter in read_conf(worker_uc_path):
    429        if counter["type"] == "method":
    430            enum_name = f"{counter['interface_name']}_{counter['method_name']}"
    431            name = f"{counter['interface_name']}_{counter['method_name']}".lower()
    432            method = f"called {counter['interface_name']}.{counter['method_name']}"
    433            dedicated.append((
    434                enum_name,
    435                name,
    436                f"Whether a dedicated worker called {method}.",
    437            ))
    438            shared.append((
    439                enum_name,
    440                name,
    441                f"Whether a shared worker called {method}.",
    442            ))
    443            service.append((
    444                enum_name,
    445                name,
    446                f"Whether a service worker called {method}.",
    447            ))
    448        elif counter["type"] == "attribute":
    449            enum_root = f"{counter['interface_name']}_{counter['attribute_name']}"
    450            name = f"{counter['interface_name']}_{counter['attribute_name']}".lower()
    451            attr = f"{counter['interface_name']}.{counter['attribute_name']}"
    452            dedicated.append((
    453                f"{enum_root}_getter",
    454                f"{name}_getter",
    455                f"Whether a dedicated worker got {attr}.",
    456            ))
    457            dedicated.append((
    458                f"{enum_root}_setter",
    459                f"{name}_setter",
    460                f"Whether a dedicated worker set {attr}.",
    461            ))
    462            shared.append((
    463                f"{enum_root}_getter",
    464                f"{name}_getter",
    465                f"Whether a shared worker got {attr}.",
    466            ))
    467            shared.append((
    468                f"{enum_root}_setter",
    469                f"{name}_setter",
    470                f"Whether a shared worker set {attr}.",
    471            ))
    472            service.append((
    473                f"{enum_root}_getter",
    474                f"{name}_getter",
    475                f"Whether a service worker got {attr}.",
    476            ))
    477            service.append((
    478                f"{enum_root}_setter",
    479                f"{name}_setter",
    480                f"Whether a service worker set {attr}.",
    481            ))
    482        elif counter["type"] == "custom":
    483            enum_name = f"Custom_{counter['name']}"
    484            dedicated.append((
    485                enum_name,
    486                to_snake_case(counter["name"]),
    487                f"Whether a dedicated worker {counter['desc']}.",
    488            ))
    489            shared.append((
    490                enum_name,
    491                to_snake_case(counter["name"]),
    492                f"Whether a shared worker {counter['desc']}.",
    493            ))
    494            service.append((
    495                enum_name,
    496                to_snake_case(counter["name"]),
    497                f"Whether a service worker {counter['desc']}.",
    498            ))
    499        else:
    500            print(
    501                f"Found unexpected worker use counter type {counter['type']}. Returning 1."
    502            )
    503            return 1
    504 
    505    # nsDeprecatedOperationsList.h parsing is adapted from parse_histograms.py.
    506    operation_list_path = os.path.join(
    507        buildconfig.topsrcdir, "dom", "base", "nsDeprecatedOperationList.h"
    508    )
    509    operation_regex = re.compile("^DEPRECATED_OPERATION\\(([^)]+)\\)")
    510    ops_page = []
    511    ops_doc = []
    512    with open(operation_list_path) as f:
    513        for line in f:
    514            match = operation_regex.search(line)
    515            if not match:
    516                # No macro, probably whitespace or comment.
    517                continue
    518 
    519            op = match.group(1)
    520            op_name = to_snake_case(op)
    521            enum_name = f"eUseCounter_{op}"
    522            ops_page.append((enum_name, op_name, f"Whether a page used {op}."))
    523            ops_doc.append((enum_name, op_name, f"Whether a document used {op}."))
    524 
    525    # Theoretically, we could do this without a completed build
    526    # (ie, without the generated ServoCSSPropList.py) by sourcing direct from
    527    # servo/components/style/properties/data.py:PropertiesData(engine=gecko).
    528    #
    529    # ...but parse_histograms.py doesn't do this the hard way. Should we?
    530 
    531    import runpy
    532 
    533    proplist_path = os.path.join(
    534        buildconfig.topobjdir, "layout", "style", "ServoCSSPropList.py"
    535    )
    536    css_properties = runpy.run_path(proplist_path)["data"]
    537    css_page = []
    538    css_doc = []
    539    for prop in css_properties.values():
    540        # We prefix `prop_name` with `css_` to avoid colliding with C++ keywords
    541        # like `float`.
    542        prop_name = "css_" + to_snake_case(prop.name)
    543 
    544        # Dependency keywords: CSS_PROP_PUBLIC_OR_PRIVATE, GenerateServoCSSPropList.py.
    545        method = "Float" if prop.method == "CssFloat" else prop.method
    546        # Dependency keywords: CSS_PROP_DOMPROP_PREFIXED, GenerateServoCSSPropList.py.
    547        if method.startswith("Moz") and prop.type() != "alias":
    548            method = method[3:]  # remove the moz prefix
    549 
    550        enum_name = f"eUseCounter_property_{method}"
    551        css_page.append((
    552            enum_name,
    553            prop_name,
    554            f"Whether a page used the CSS property {prop.name}.",
    555        ))
    556        css_doc.append((
    557            enum_name,
    558            prop_name,
    559            f"Whether a document used the CSS property {prop.name}.",
    560        ))
    561 
    562    # Counted unknown properties: AKA - stuff that doesn't exist, but we want
    563    # to count uses of anyway.
    564    # We _might_ decide to implement these in the future, though, so we just add
    565    # them to the css_page, css_doc lists directly for continuity.
    566    # (We do give them a different description, though)
    567 
    568    import sys
    569 
    570    sys.path.append(os.path.join(buildconfig.topsrcdir, "layout", "style"))
    571    from GenerateCountedUnknownProperties import to_camel_case
    572 
    573    unknown_proplist_path = os.path.join(
    574        buildconfig.topsrcdir,
    575        "servo",
    576        "components",
    577        "style",
    578        "properties",
    579        "counted_unknown_properties.py",
    580    )
    581    unknown_properties = runpy.run_path(unknown_proplist_path)[
    582        "COUNTED_UNKNOWN_PROPERTIES"
    583    ]
    584    for prop in unknown_properties:
    585        enum_name = f"eUseCounter_unknown_property_{to_camel_case(prop)}"
    586        prop_name = to_snake_case(prop)
    587        css_page.append((
    588            enum_name,
    589            prop_name,
    590            f"Whether a page used the (unknown, counted) CSS property {prop}.",
    591        ))
    592        css_doc.append((
    593            enum_name,
    594            prop_name,
    595            f"Whether a document used the (unknown, counted) CSS property {prop}.",
    596        ))
    597 
    598    return (page, doc, dedicated, shared, service, ops_page, ops_doc, css_page, css_doc)
    599 
    600 
    601 def to_snake_case(kebab_or_pascal):
    602    """
    603    Takes `kebab_or_pascal` which is in PascalCase or kebab-case
    604    and conjugates it to "snake_case" (all lowercase, "_"-delimited).
    605    """
    606    return (
    607        re.sub("([A-Z]+)", r"_\1", kebab_or_pascal).replace("-", "_").lower().strip("_")
    608    )
    609 
    610 
    611 def metric_map(f, *inputs):
    612    """
    613    Parses all use counters and outputs UseCounter.cpp which contains implementations
    614    for two functions defined in UseCounter.h:
    615      * const char* IncrementUseCounter(UseCounter aUseCounter, bool aIsPage)
    616      * const char* IncrementWorkerUseCounter(UseCounterWorker aUseCounter, dom::WorkerKind aKind)
    617 
    618    (Basically big switch statements mapping from enums to glean metrics, calling Add())
    619    """
    620 
    621    (
    622        page,
    623        doc,
    624        dedicated,
    625        shared,
    626        service,
    627        ops_page,
    628        ops_doc,
    629        css_page,
    630        css_doc,
    631    ) = parse_use_counters()
    632 
    633    f.write(
    634        """\
    635 /* AUTOGENERATED by usecounters.py. DO NOT EDIT */
    636 /* This Source Code Form is subject to the terms of the Mozilla Public
    637 * License, v. 2.0. If a copy of the MPL was not distributed with this
    638 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
    639 
    640 #include "mozilla/dom/UseCounterMetrics.h"
    641 
    642 #include "mozilla/dom/WorkerPrivate.h"
    643 #include "mozilla/glean/DomUseCounterMetrics.h"
    644 
    645 namespace mozilla::dom {
    646 
    647 const char* IncrementUseCounter(UseCounter aUseCounter, bool aIsPage) {
    648  static constexpr struct {
    649    const char* name;
    650    glean::impl::CounterMetric<> doc_metric;
    651    glean::impl::CounterMetric<> page_metric;
    652  } kEntries[] = {
    653 """
    654    )
    655 
    656    # This order must match the order UseCounter is defined,
    657    # (and we guarantee it via the MOZ_ASSERT below at runtime).
    658    assert len(page) == len(doc)
    659    assert len(ops_page) == len(ops_doc)
    660    assert len(css_page) == len(css_doc)
    661 
    662    index = 0
    663    static_asserts = []
    664    for pc, dc in zip(page, doc):
    665        assert pc[0] == dc[0]
    666        assert pc[1] == dc[1]
    667        static_asserts.append(f"static_assert({index} == size_t(UseCounter::{pc[0]}));")
    668        f.write(
    669            f"""\
    670        {{
    671          "{pc[1]}",
    672          glean::use_counter_doc::{pc[1]},
    673          glean::use_counter_page::{pc[1]},
    674        }},
    675 """
    676        )
    677        index += 1
    678 
    679    for pc, dc in zip(ops_page, ops_doc):
    680        assert pc[0] == dc[0]
    681        assert pc[1] == dc[1]
    682        static_asserts.append(f"static_assert({index} == size_t(UseCounter::{pc[0]}));")
    683        f.write(
    684            f"""\
    685        {{
    686          "deprecated_ops.{pc[1]}",
    687          glean::use_counter_deprecated_ops_doc::{pc[1]},
    688          glean::use_counter_deprecated_ops_page::{pc[1]},
    689        }},
    690 """
    691        )
    692        index += 1
    693 
    694    for pc, dc in zip(css_page, css_doc):
    695        assert pc[0] == dc[0]
    696        assert pc[1] == dc[1]
    697        static_asserts.append(f"static_assert({index} == size_t(UseCounter::{pc[0]}));")
    698        f.write(
    699            f"""\
    700        {{
    701          "css.{pc[1]}",
    702          glean::use_counter_css_doc::{pc[1]},
    703          glean::use_counter_css_page::{pc[1]},
    704        }},
    705 """
    706        )
    707        index += 1
    708 
    709    f.write("};\n")
    710    f.write("\n".join(static_asserts))
    711    f.write(
    712        """\
    713  MOZ_ASSERT(size_t(aUseCounter) < std::size(kEntries));
    714  const auto& entry = kEntries[size_t(aUseCounter)];
    715  (aIsPage ? entry.page_metric : entry.doc_metric).Add();
    716  return entry.name;
    717 }
    718 
    719 const char* IncrementWorkerUseCounter(UseCounterWorker aUseCounter, WorkerKind aKind) {
    720  static constexpr struct {
    721    const char* name;
    722    glean::impl::CounterMetric<> dedicated_metric;
    723    glean::impl::CounterMetric<> shared_metric;
    724    glean::impl::CounterMetric<> service_metric;
    725  } kEntries[] = {
    726 """
    727    )
    728    assert len(dedicated) == len(shared)
    729    assert len(dedicated) == len(service)
    730    index = 0
    731    static_asserts = []
    732    for dc, sc, servicec in zip(dedicated, shared, service):
    733        assert dc[0] == sc[0]
    734        assert dc[1] == sc[1]
    735        assert dc[0] == servicec[0]
    736        assert dc[1] == servicec[1]
    737        static_asserts.append(
    738            f"static_assert({index} == size_t(UseCounterWorker::{dc[0]}));"
    739        )
    740        f.write(
    741            f"""\
    742        {{
    743          "{dc[1]}",
    744          glean::use_counter_worker_dedicated::{dc[1]},
    745          glean::use_counter_worker_shared::{dc[1]},
    746          glean::use_counter_worker_service::{dc[1]},
    747        }},
    748 """
    749        )
    750        index += 1
    751    f.write("};\n")
    752    f.write("\n".join(static_asserts))
    753    f.write(
    754        """\
    755  MOZ_ASSERT(size_t(aUseCounter) < std::size(kEntries));
    756  const auto& entry = kEntries[size_t(aUseCounter)];
    757  switch (aKind) {
    758    case WorkerKind::WorkerKindDedicated:
    759      entry.dedicated_metric.Add();
    760      break;
    761    case WorkerKind::WorkerKindShared:
    762      entry.shared_metric.Add();
    763      break;
    764    case WorkerKind::WorkerKindService:
    765      entry.service_metric.Add();
    766      break;
    767  }
    768  return entry.name;
    769 }
    770 
    771 }  // namespace mozilla
    772 """
    773    )