OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Extract histogram names from the description XML file. | 5 """Extract histogram names from the description XML file. |
6 | 6 |
7 For more information on the format of the XML file, which is self-documenting, | 7 For more information on the format of the XML file, which is self-documenting, |
8 see histograms.xml; however, here is a simple example to get you started. The | 8 see histograms.xml; however, here is a simple example to get you started. The |
9 XML below will generate the following five histograms: | 9 XML below will generate the following five histograms: |
10 | 10 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
56 | 56 |
57 import copy | 57 import copy |
58 import logging | 58 import logging |
59 import xml.dom.minidom | 59 import xml.dom.minidom |
60 | 60 |
61 OWNER_FIELD_PLACEHOLDER = ( | 61 OWNER_FIELD_PLACEHOLDER = ( |
62 'Please list the metric\'s owners. Add more owner tags as needed.') | 62 'Please list the metric\'s owners. Add more owner tags as needed.') |
63 | 63 |
64 MAX_HISTOGRAM_SUFFIX_DEPENDENCY_DEPTH = 5 | 64 MAX_HISTOGRAM_SUFFIX_DEPENDENCY_DEPTH = 5 |
65 | 65 |
| 66 DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON = ( |
| 67 'Base histogram. Use suffixes of this histogram instead.') |
| 68 |
66 | 69 |
67 class Error(Exception): | 70 class Error(Exception): |
68 pass | 71 pass |
69 | 72 |
70 | 73 |
71 def _JoinChildNodes(tag): | 74 def _JoinChildNodes(tag): |
72 """Join child nodes into a single text. | 75 """Join child nodes into a single text. |
73 | 76 |
74 Applicable to leafs like 'summary' and 'detail'. | 77 Applicable to leafs like 'summary' and 'detail'. |
75 | 78 |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
223 def _ExtractOwners(xml_node): | 226 def _ExtractOwners(xml_node): |
224 """Extract all owners into a list from owner tag under |xml_node|.""" | 227 """Extract all owners into a list from owner tag under |xml_node|.""" |
225 owners = [] | 228 owners = [] |
226 for owner_node in xml_node.getElementsByTagName('owner'): | 229 for owner_node in xml_node.getElementsByTagName('owner'): |
227 owner_entry = _NormalizeString(_JoinChildNodes(owner_node)) | 230 owner_entry = _NormalizeString(_JoinChildNodes(owner_node)) |
228 if OWNER_FIELD_PLACEHOLDER not in owner_entry: | 231 if OWNER_FIELD_PLACEHOLDER not in owner_entry: |
229 owners.append(owner_entry) | 232 owners.append(owner_entry) |
230 return owners | 233 return owners |
231 | 234 |
232 | 235 |
| 236 def _ProcessBaseHistogramAttribute(node, histogram_entry): |
| 237 if node.hasAttribute('base'): |
| 238 is_base = node.getAttribute('base').lower() == 'true' |
| 239 histogram_entry['base'] = is_base |
| 240 if is_base and 'obsolete' not in histogram_entry: |
| 241 histogram_entry['obsolete'] = DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON |
| 242 |
| 243 |
233 def _ExtractHistogramsFromXmlTree(tree, enums): | 244 def _ExtractHistogramsFromXmlTree(tree, enums): |
234 """Extract all <histogram> nodes in the tree into a dictionary.""" | 245 """Extract all <histogram> nodes in the tree into a dictionary.""" |
235 | 246 |
236 # Process the histograms. The descriptions can include HTML tags. | 247 # Process the histograms. The descriptions can include HTML tags. |
237 histograms = {} | 248 histograms = {} |
238 have_errors = False | 249 have_errors = False |
239 last_name = None | 250 last_name = None |
240 for histogram in tree.getElementsByTagName('histogram'): | 251 for histogram in tree.getElementsByTagName('histogram'): |
241 name = histogram.getAttribute('name') | 252 name = histogram.getAttribute('name') |
242 if last_name is not None and name.lower() < last_name.lower(): | 253 if last_name is not None and name.lower() < last_name.lower(): |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
281 | 292 |
282 # Handle enum types. | 293 # Handle enum types. |
283 if histogram.hasAttribute('enum'): | 294 if histogram.hasAttribute('enum'): |
284 enum_name = histogram.getAttribute('enum') | 295 enum_name = histogram.getAttribute('enum') |
285 if enum_name not in enums: | 296 if enum_name not in enums: |
286 logging.error('Unknown enum %s in histogram %s', enum_name, name) | 297 logging.error('Unknown enum %s in histogram %s', enum_name, name) |
287 have_errors = True | 298 have_errors = True |
288 else: | 299 else: |
289 histogram_entry['enum'] = enums[enum_name] | 300 histogram_entry['enum'] = enums[enum_name] |
290 | 301 |
| 302 _ProcessBaseHistogramAttribute(histogram, histogram_entry) |
| 303 |
291 return histograms, have_errors | 304 return histograms, have_errors |
292 | 305 |
293 | 306 |
294 # Finds an <obsolete> node amongst |node|'s immediate children and returns its | 307 # Finds an <obsolete> node amongst |node|'s immediate children and returns its |
295 # content as a string. Returns None if no such node exists. | 308 # content as a string. Returns None if no such node exists. |
296 def _GetObsoleteReason(node): | 309 def _GetObsoleteReason(node): |
297 for child in node.childNodes: | 310 for child in node.childNodes: |
298 if child.localName == 'obsolete': | 311 if child.localName == 'obsolete': |
299 # There can be at most 1 obsolete element per node. | 312 # There can be at most 1 obsolete element per node. |
300 return _JoinChildNodes(child) | 313 return _JoinChildNodes(child) |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 if with_suffixes: | 404 if with_suffixes: |
392 suffixes_to_add = with_suffixes | 405 suffixes_to_add = with_suffixes |
393 else: | 406 else: |
394 suffixes_to_add = suffix_nodes | 407 suffixes_to_add = suffix_nodes |
395 for suffix in suffixes_to_add: | 408 for suffix in suffixes_to_add: |
396 suffix_name = suffix.getAttribute('name') | 409 suffix_name = suffix.getAttribute('name') |
397 try: | 410 try: |
398 new_histogram_name = _ExpandHistogramNameWithSuffixes( | 411 new_histogram_name = _ExpandHistogramNameWithSuffixes( |
399 suffix_name, histogram_name, histogram_suffixes) | 412 suffix_name, histogram_name, histogram_suffixes) |
400 if new_histogram_name != histogram_name: | 413 if new_histogram_name != histogram_name: |
401 histograms[new_histogram_name] = copy.deepcopy( | 414 new_histogram = copy.deepcopy(histograms[histogram_name]) |
402 histograms[histogram_name]) | 415 # Do not copy forward base histogram state to suffixed |
| 416 # histograms. Any suffixed histograms that wish to remain base |
| 417 # histograms must explicitly re-declare themselves as base |
| 418 # histograms. |
| 419 if new_histogram.get('base', False): |
| 420 del new_histogram['base'] |
| 421 if (new_histogram.get('obsolete', '') == |
| 422 DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON): |
| 423 del new_histogram['obsolete'] |
| 424 histograms[new_histogram_name] = new_histogram |
403 | 425 |
404 suffix_label = suffix_labels.get(suffix_name, '') | 426 suffix_label = suffix_labels.get(suffix_name, '') |
405 | 427 |
406 # TODO(yiyaoliu): Rename these to be consistent with the new naming. | 428 # TODO(yiyaoliu): Rename these to be consistent with the new naming. |
407 # It is kept unchanged for now to be it's used by dashboards. | 429 # It is kept unchanged for now to be it's used by dashboards. |
408 if 'fieldtrial_groups' not in histograms[new_histogram_name]: | 430 if 'fieldtrial_groups' not in histograms[new_histogram_name]: |
409 histograms[new_histogram_name]['fieldtrial_groups'] = [] | 431 histograms[new_histogram_name]['fieldtrial_groups'] = [] |
410 histograms[new_histogram_name]['fieldtrial_groups'].append( | 432 histograms[new_histogram_name]['fieldtrial_groups'].append( |
411 suffix_name) | 433 suffix_name) |
412 | 434 |
(...skipping 16 matching lines...) Expand all Loading... |
429 # group itself was obsolete as well. | 451 # group itself was obsolete as well. |
430 obsolete_reason = _GetObsoleteReason(suffix) | 452 obsolete_reason = _GetObsoleteReason(suffix) |
431 if not obsolete_reason: | 453 if not obsolete_reason: |
432 obsolete_reason = group_obsolete_reason | 454 obsolete_reason = group_obsolete_reason |
433 | 455 |
434 # If the suffix has an obsolete tag, all histograms it generates | 456 # If the suffix has an obsolete tag, all histograms it generates |
435 # inherit it. | 457 # inherit it. |
436 if obsolete_reason: | 458 if obsolete_reason: |
437 histograms[new_histogram_name]['obsolete'] = obsolete_reason | 459 histograms[new_histogram_name]['obsolete'] = obsolete_reason |
438 | 460 |
| 461 _ProcessBaseHistogramAttribute(suffix, histograms[new_histogram_name]) |
| 462 |
439 except Error: | 463 except Error: |
440 have_errors = True | 464 have_errors = True |
441 | 465 |
442 return have_errors | 466 return have_errors |
443 | 467 |
444 | 468 |
445 def ExtractHistogramsFromFile(file_handle): | 469 def ExtractHistogramsFromFile(file_handle): |
446 """Compute the histogram names and descriptions from the XML representation. | 470 """Compute the histogram names and descriptions from the XML representation. |
447 | 471 |
448 Args: | 472 Args: |
(...skipping 29 matching lines...) Expand all Loading... |
478 with open(filename, 'r') as f: | 502 with open(filename, 'r') as f: |
479 histograms, had_errors = ExtractHistogramsFromFile(f) | 503 histograms, had_errors = ExtractHistogramsFromFile(f) |
480 if had_errors: | 504 if had_errors: |
481 logging.error('Error parsing %s', filename) | 505 logging.error('Error parsing %s', filename) |
482 raise Error() | 506 raise Error() |
483 return histograms | 507 return histograms |
484 | 508 |
485 | 509 |
486 def ExtractNames(histograms): | 510 def ExtractNames(histograms): |
487 return sorted(histograms.keys()) | 511 return sorted(histograms.keys()) |
OLD | NEW |