| Index: tools/memory_inspector/memory_inspector/classification/rules.py
|
| diff --git a/tools/memory_inspector/memory_inspector/classification/rules.py b/tools/memory_inspector/memory_inspector/classification/rules.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5337c0c9d491e199fe15518902501f76105101dc
|
| --- /dev/null
|
| +++ b/tools/memory_inspector/memory_inspector/classification/rules.py
|
| @@ -0,0 +1,119 @@
|
| +# Copyright 2014 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.
|
| +
|
| +"""This module defines the core structure of the classification rules.
|
| +
|
| +This module does NOT specify how the rules filter the data: this responsibility
|
| +is of to the concrete classifiers, which have to override the Rule class herein
|
| +defined and know how to do the math.
|
| +
|
| +This module, instead, defines the format of the rules and the way they are
|
| +encoded and loaded (in a python-style dictionary file).
|
| +Rules are organized in a tree, where the root is always represented by a 'Total'
|
| +node, and the leaves are arbitrarily defined by the user, according to the
|
| +following principles:
|
| +- Order of siblings rules matter: what is caught by a rule will not be caught
|
| + by the next ones, but it is propagated to its children rules if any.
|
| +- Every non-leaf node X gets an implicit extra-children named X-other. This
|
| + catch-all child catches everything (within the parent rule scope) that is
|
| + not caught by the other siblings. This is to guarantee that, when doing the
|
| + math (the aggregation), at any level, the sum of the values in the leaves
|
| + match the value of their parent.
|
| +
|
| +The format of a rule dictionary is the following:
|
| +[
|
| +{
|
| + 'name': 'Name of the rule',
|
| + 'filter-X': 'The embedder will know how to interpret this value and will use
|
| + it to filter the data'
|
| + 'filter-Y': 'Idem'
|
| + children: [
|
| + {
|
| + 'name': 'Name of the sub-rule 1'
|
| + ... and so on recursively ,
|
| + },
|
| + ]
|
| +},
|
| +]
|
| +
|
| +And a typical resulting rule tree looks like this:
|
| + +----------------------+
|
| + | Total |
|
| + |----------------------|
|
| + +------------------+ Match all. +--------------------+
|
| + | +----------+-----------+ |
|
| + | | |
|
| + +-----v-----+ +-----v-----+ +------v----+
|
| + | Foo | | Bar | |Total-other|
|
| + |-----------| |-----------| |-----------|
|
| + |File: foo* | +---+File: bar* +-----+ | Match all |
|
| + +-----------+ | +-----------+ | +-----------+
|
| + | |
|
| + +------v------+ +------v----+
|
| + | Bar::Coffee | | Bar-other |
|
| + |-------------| |-----------|
|
| + |File: bar*cof| | Match all |
|
| + +-------------+ +-----------+
|
| +"""
|
| +
|
| +import ast
|
| +
|
| +
|
| +def Load(content, rule_builder):
|
| + """Construct a rule tree from a python-style dict representation.
|
| +
|
| + Args:
|
| + content: a string containing the dict (i.e. content of the rule file).
|
| + rule_builder: a method which takes two arguments (rule_name, filters_dict)
|
| + and returns a subclass of |Rule|. |filters_dict| is a dict of the keys
|
| + (filter-foo, filter-bar in the example above) for the rule node.
|
| + """
|
| + rules_dict = ast.literal_eval(content)
|
| + root = Rule('Total')
|
| + _MakeRuleNodeFromDictNode(root, rules_dict, rule_builder)
|
| + return root
|
| +
|
| +
|
| +class Rule(object):
|
| + """ An abstract class representing a rule node in the rules tree.
|
| +
|
| + Embedders must override the Match method when deriving this class.
|
| + """
|
| +
|
| + def __init__(self, name):
|
| + self.name = name
|
| + self.children = []
|
| +
|
| + def Match(self, _): # pylint: disable=R0201
|
| + """ The rationale of this default implementation is modeling the root
|
| + ('Total') and the catch-all (*-other) rules that every |RuleTree| must have,
|
| + regardless of the embedder-specific children rules. This is to guarantee
|
| + that the totals match at any level of the tree.
|
| + """
|
| + return True
|
| +
|
| + def AppendChild(self, child_rule):
|
| + assert(isinstance(child_rule, Rule))
|
| + duplicates = filter(lambda x: x.name == child_rule.name, self.children)
|
| + assert(not duplicates), 'Duplicate rule ' + child_rule.name
|
| + self.children.append(child_rule)
|
| +
|
| +
|
| +def _MakeRuleNodeFromDictNode(rule_node, dict_nodes, rule_builder):
|
| + """Recursive rule tree builder for traversing the rule dict."""
|
| + for dict_node in dict_nodes:
|
| + assert('name' in dict_node)
|
| + # Extract the filter keys (e.g., mmap-file, mmap-prot) that will be passed
|
| + # to the |rule_builder|
|
| + filter_keys = set(dict_node.keys()) - set(('name', 'children'))
|
| + filters = dict((k, dict_node[k]) for k in filter_keys)
|
| + child_rule = rule_builder(dict_node['name'], filters)
|
| + rule_node.AppendChild(child_rule)
|
| + dict_children = dict_node.get('children', {})
|
| + _MakeRuleNodeFromDictNode(child_rule, dict_children, rule_builder)
|
| +
|
| + # If the rule_node isn't a leaf, add the 'name-other' catch-all sibling to
|
| + # catch all the entries that matched this node but none of its children.
|
| + if len(rule_node.children):
|
| + rule_node.AppendChild(Rule(rule_node.name + '-other'))
|
|
|