Index: tools/memory_inspector/memory_inspector/classification/results.py |
diff --git a/tools/memory_inspector/memory_inspector/classification/results.py b/tools/memory_inspector/memory_inspector/classification/results.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..24091485459110a13c9cc922687f79fce53a4de9 |
--- /dev/null |
+++ b/tools/memory_inspector/memory_inspector/classification/results.py |
@@ -0,0 +1,115 @@ |
+# 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 owns the logic for classifying and aggregating data in buckets. |
+ |
+This complements the structure defined in the rules module. Symmetrically, the |
+aggregated results are organized in a bucket tree, which structure is identical |
+to the one of the corresponding rule tree. |
+The logic for aggregation is the following: |
+- The client loads a "rule tree" defined by the end-user (e.g., in a file) which |
+ defines the final "shape" of the results. |
+- The rules define how to "match" a trace_record (e.g., a mmap line or a native |
+ allocation) given some of its properties (e.g. the mapped file or the prot. |
+ flags). |
+- The concrete classifier (which will use this module) knows how to count the |
+ values for each trace_record (e.g. [Dirty KB, Clean KB, RSS KB] for mmaps). |
+ Hence it decides the cardinality of the result nodes. |
+- The responsibility of this module is simply doing the math. |
+ |
+In the very essence this module adds up the counters of each node whereas the |
+trace_record being pushed in the tree (through the AddToMatchingNodes method) |
+matches a rule. |
+It just does this math in a hierarchical fashion following the shape the tree. |
+ |
+A typical result tree looks like this (each node has two values in the example): |
+ +----------------------+ |
+ | Total | |
+ |----------------------| |
+ +------------------+ (100, 1000) +--------------------+ |
+ | +----------+-----------+ | |
+ | | | |
+ +-----v-----+ +-----v-----+ +------v----+ |
+ | Foo | | Bar | |Total-other| |
+ |-----------| |-----------| |-----------| |
+ | (15, 100) | +---+ (80, 200) +-----+ | (5, 700) | |
+ +-----------+ | +-----------+ | +-----------+ |
+ | | |
+ +------v------+ +------v-----+ |
+ | Bar::Coffee | | Bar-other | |
+ |-------------| |------------| |
+ | (30, 120) | | (50, 80) | |
+ +-------------+ +------------+ |
+""" |
+ |
+from memory_inspector.classification import rules |
+ |
+ |
+class AggreatedResults(object): |
+ """A tree of results, where each node is a bucket (root: 'Total' bucket).""" |
+ |
+ def __init__(self, rule_tree, keys): |
+ """Initializes the bucket tree using the structure of the rules tree. |
+ |
+ Each node of the bucket tree is initialized with a list of len(keys) zeros. |
+ """ |
+ assert(isinstance(rule_tree, rules.Rule)) |
+ assert(isinstance(keys, list)) |
+ self.keys = keys |
+ self.total = AggreatedResults._MakeBucketNodeFromRule(rule_tree, len(keys)) |
+ |
+ def AddToMatchingNodes(self, trace_record, values): |
+ """Adds the provided |values| to the nodes that match the |trace_record|. |
+ |
+ Tree traversal logic: at any level, one and only one node will match the |
+ |trace_record| (in the worst case it will be the catchall *-other rule). |
+ When a node is matched, the traversal continues in its children and no |
+ further siblings in the upper levels are visited anymore. |
+ This is to guarantee that at any level the values of one node are equal to |
+ the sum of the values of all its children. |
+ |
+ Args: |
+ trace_record: any kind of object which can be matched by the Match method |
+ of the Rule object. |
+ values: a list of int(s) which represent the value associated to the |
+ matched trace_record. The cardinality of the list must be equal to the |
+ cardinality of the initial keys. |
+ """ |
+ assert(len(values) == len(self.keys)) |
+ AggreatedResults._AddToMatchingNodes( |
+ trace_record, values, self.total, len(self.keys)) |
+ |
+ @staticmethod |
+ def _AddToMatchingNodes(trace_record, values, bucket, num_keys): |
+ if not bucket.rule.Match(trace_record): |
+ return False |
+ for i in xrange(num_keys): |
+ bucket.values[i] += values[i] |
+ for child_bucket in bucket.children: |
+ if AggreatedResults._AddToMatchingNodes( |
+ trace_record, values, child_bucket, num_keys): |
+ break |
+ return True |
+ |
+ @staticmethod |
+ def _MakeBucketNodeFromRule(rule, num_keys): |
+ assert(isinstance(rule, rules.Rule)) |
+ bucket = Bucket(rule, num_keys) |
+ for child_rule in rule.children: |
+ bucket.children.append( |
+ AggreatedResults._MakeBucketNodeFromRule(child_rule, num_keys)) |
+ return bucket |
+ |
+ |
+class Bucket(object): |
+ """A bucket is a node in the results tree. """ |
+ def __init__(self, rule, num_keys): |
+ self.rule = rule |
+ self.values = [0] * num_keys |
+ self.children = [] |
+ |
+ |
+ @property |
+ def name(self): |
+ return self.rule.name |