123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- # Copyright 2019 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- import os
- import re
- import sys
- sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'histograms'))
- import extract_histograms
- import histogram_paths
- import merge_xml
- LOCAL_METRIC_RE = re.compile(r'metrics\.([^,]+)')
- INVALID_LOCAL_METRIC_FIELD_ERROR = (
- 'Invalid index field specification in ukm metric %(event)s:%(metric)s, the '
- 'following metrics are used as index fields but are not configured to '
- 'support it: [%(invalid_metrics)s]\n\n'
- 'See https://chromium.googlesource.com/chromium/src.git/+/main/services/'
- 'metrics/ukm_api.md#aggregation-by-metrics-in-the-same-event for '
- 'instructions on how to configure them.')
- def _isMetricValidAsIndexField(metric_node):
- """Checks if a given metric node can be used as a field in an index tag.
- Has the following requirements:
- * 'history' is the only aggregation target (no others are considered)
- * there will be at most 1 'aggregation', 1 'history', and 1 'statistic'
- element in a metric element
- * enumerations are the only metric types that are valid
- Args:
- metric_node: A metric node to check.
- Returns: True or False, depending on whethere the given node is valid as an
- index field.
- """
- aggregation_nodes = metric_node.getElementsByTagName('aggregation')
- if aggregation_nodes.length != 1:
- return False
- history_nodes = aggregation_nodes[0].getElementsByTagName('history')
- if history_nodes.length != 1:
- return False
- statistic_nodes = history_nodes[0].getElementsByTagName('statistics')
- if statistic_nodes.length != 1:
- return False
- # Only enumeration type metrics are supported as index fields.
- enumeration_nodes = statistic_nodes[0].getElementsByTagName('enumeration')
- return bool(enumeration_nodes)
- def _getIndexFields(metric_node):
- """Get a list of fields from index node descendents of a metric_node."""
- aggregation_nodes = metric_node.getElementsByTagName('aggregation')
- if not aggregation_nodes:
- return []
- history_nodes = aggregation_nodes[0].getElementsByTagName('history')
- if not history_nodes:
- return []
- index_nodes = history_nodes[0].getElementsByTagName('index')
- if not index_nodes:
- return []
- return [index_node.getAttribute('fields') for index_node in index_nodes]
- def _getLocalMetricIndexFields(metric_node):
- """Gets a set of metric names being used as local-metric index fields."""
- index_fields = _getIndexFields(metric_node)
- local_metric_fields = set()
- for fields in index_fields:
- local_metric_fields.update(LOCAL_METRIC_RE.findall(fields))
- return local_metric_fields
- class UkmXmlValidation(object):
- """Validations for the content of ukm.xml."""
- def __init__(self, ukm_config):
- """Attributes:
- config: A XML minidom Element representing the root node of the UKM config
- tree.
- """
- self.config = ukm_config
- def checkEventsHaveOwners(self):
- """Check that every event in the config has at least one owner."""
- errors = []
- for event_node in self.config.getElementsByTagName('event'):
- event_name = event_node.getAttribute('name')
- owner_nodes = event_node.getElementsByTagName('owner')
- # Check <owner> tag is present for each event.
- if not owner_nodes:
- errors.append("<owner> tag is required for event '%s'." % event_name)
- continue
- for owner_node in owner_nodes:
- # Check <owner> tag actually has some content.
- if not owner_node.childNodes:
- errors.append(
- "<owner> tag for event '%s' should not be empty." % event_name)
- continue
- for email in owner_node.childNodes:
- # Check <owner> tag's content is an email address, not a username.
- if not ('@chromium.org' in email.data or '@google.com' in email.data):
- errors.append("<owner> tag for event '%s' expects a Chromium or "
- "Google email address." % event_name)
- isSuccess = not errors
- return (isSuccess, errors)
- def checkMetricTypeIsSpecified(self):
- """Check each metric is either specified with an enum or a unit."""
- errors = []
- warnings = []
- enum_tree = merge_xml.MergeFiles([histogram_paths.ENUMS_XML])
- enums, _ = extract_histograms.ExtractEnumsFromXmlTree(enum_tree)
- for event_node in self.config.getElementsByTagName('event'):
- for metric_node in event_node.getElementsByTagName('metric'):
- if metric_node.hasAttribute('enum'):
- enum_name = metric_node.getAttribute('enum');
- # Check if the enum is defined in enums.xml.
- if enum_name not in enums:
- errors.append("Unknown enum %s in ukm metric %s:%s." %
- (enum_name, event_node.getAttribute('name'),
- metric_node.getAttribute('name')))
- elif not metric_node.hasAttribute('unit'):
- warnings.append("Warning: Neither \'enum\' or \'unit\' is specified "
- "for ukm metric %s:%s."
- % (event_node.getAttribute('name'),
- metric_node.getAttribute('name')))
- isSuccess = not errors
- return (isSuccess, errors, warnings)
- def checkLocalMetricIsAggregated(self):
- """Checks that index fields don't list invalid metrics."""
- errors = []
- for event_node in self.config.getElementsByTagName('event'):
- metric_nodes = event_node.getElementsByTagName('metric')
- valid_index_field_metrics = {node.getAttribute('name')
- for node in metric_nodes
- if _isMetricValidAsIndexField(node)}
- for metric_node in metric_nodes:
- local_metric_index_fields = _getLocalMetricIndexFields(metric_node)
- invalid_metrics = local_metric_index_fields - valid_index_field_metrics
- if invalid_metrics:
- event_name = event_node.getAttribute('name')
- metric_name = metric_node.getAttribute('name')
- invalid_metrics_string = ', '.join(sorted(invalid_metrics))
- errors.append(INVALID_LOCAL_METRIC_FIELD_ERROR %(
- {'event': event_name, 'metric': metric_name,
- 'invalid_metrics': invalid_metrics_string}))
- is_success = not errors
- return (is_success, errors)
|