Fix import error in mac_platform_backend.py
[chromium-blink-merge.git] / tools / deep_memory_profiler / lib / range_dict.py
blob565789d561af4a2b171cdb870a368c574f9e89ab
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
3 # found in the LICENSE file.
5 import os
6 import sys
8 _BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
9 _BINTREES_PATH = os.path.join(
10 _BASE_PATH, os.pardir, os.pardir, 'third_party', 'bintrees')
11 sys.path.insert(0, _BINTREES_PATH)
13 from bintrees import FastRBTree # pylint: disable=F0401
16 class ExclusiveRangeDict(object):
17 """A class like dict whose key is a range [begin, end) of integers.
19 It has an attribute for each range of integers, for example:
20 [10, 20) => Attribute(0),
21 [20, 40) => Attribute(1),
22 [40, 50) => Attribute(2),
23 ...
25 An instance of this class is accessed only via iter_range(begin, end).
26 The instance is accessed as follows:
28 1) If the given range [begin, end) is not covered by the instance,
29 the range is newly created and iterated.
31 2) If the given range [begin, end) exactly covers ranges in the instance,
32 the ranges are iterated.
33 (See test_set() in tests/range_dict_tests.py.)
35 3) If the given range [begin, end) starts at and/or ends at a mid-point of
36 an existing range, the existing range is split by the given range, and
37 ranges in the given range are iterated. For example, consider a case that
38 [25, 45) is given to an instance of [20, 30), [30, 40), [40, 50). In this
39 case, [20, 30) is split into [20, 25) and [25, 30), and [40, 50) into
40 [40, 45) and [45, 50). Then, [25, 30), [30, 40), [40, 45) are iterated.
41 (See test_split() in tests/range_dict_tests.py.)
43 4) If the given range [begin, end) includes non-existing ranges in an
44 instance, the gaps are filled with new ranges, and all ranges are iterated.
45 For example, consider a case that [25, 50) is given to an instance of
46 [30, 35) and [40, 45). In this case, [25, 30), [35, 40) and [45, 50) are
47 created in the instance, and then [25, 30), [30, 35), [35, 40), [40, 45)
48 and [45, 50) are iterated.
49 (See test_fill() in tests/range_dict_tests.py.)
50 """
51 class RangeAttribute(object):
52 def __init__(self):
53 pass
55 def __str__(self):
56 return '<RangeAttribute>'
58 def __repr__(self):
59 return '<RangeAttribute>'
61 def copy(self): # pylint: disable=R0201
62 return ExclusiveRangeDict.RangeAttribute()
64 def __init__(self, attr=RangeAttribute):
65 self._tree = FastRBTree()
66 self._attr = attr
68 def iter_range(self, begin=None, end=None):
69 if not begin:
70 begin = self._tree.min_key()
71 if not end:
72 end = self._tree.max_item()[1][0]
74 # Assume that self._tree has at least one element.
75 if self._tree.is_empty():
76 self._tree[begin] = (end, self._attr())
78 # Create a beginning range (border)
79 try:
80 bound_begin, bound_value = self._tree.floor_item(begin)
81 bound_end = bound_value[0]
82 if begin >= bound_end:
83 # Create a blank range.
84 try:
85 new_end, _ = self._tree.succ_item(bound_begin)
86 except KeyError:
87 new_end = end
88 self._tree[begin] = (min(end, new_end), self._attr())
89 elif bound_begin < begin and begin < bound_end:
90 # Split the existing range.
91 new_end = bound_value[0]
92 new_value = bound_value[1]
93 self._tree[bound_begin] = (begin, new_value.copy())
94 self._tree[begin] = (new_end, new_value.copy())
95 else: # bound_begin == begin
96 # Do nothing (just saying it clearly since this part is confusing)
97 pass
98 except KeyError: # begin is less than the smallest element.
99 # Create a blank range.
100 # Note that we can assume self._tree has at least one element.
101 self._tree[begin] = (min(end, self._tree.min_key()), self._attr())
103 # Create an ending range (border)
104 try:
105 bound_begin, bound_value = self._tree.floor_item(end)
106 bound_end = bound_value[0]
107 if end > bound_end:
108 # Create a blank range.
109 new_begin = bound_end
110 self._tree[new_begin] = (end, self._attr())
111 elif bound_begin < end and end < bound_end:
112 # Split the existing range.
113 new_end = bound_value[0]
114 new_value = bound_value[1]
115 self._tree[bound_begin] = (end, new_value.copy())
116 self._tree[end] = (new_end, new_value.copy())
117 else: # bound_begin == begin
118 # Do nothing (just saying it clearly since this part is confusing)
119 pass
120 except KeyError: # end is less than the smallest element.
121 # It must not happen. A blank range [begin,end) has already been created
122 # even if [begin,end) is less than the smallest range.
123 # Do nothing (just saying it clearly since this part is confusing)
124 raise
126 missing_ranges = []
128 prev_end = None
129 for range_begin, range_value in self._tree.itemslice(begin, end):
130 range_end = range_value[0]
131 # Note that we can assume that we have a range beginning with |begin|
132 # and a range ending with |end| (they may be the same range).
133 if prev_end and prev_end != range_begin:
134 missing_ranges.append((prev_end, range_begin))
135 prev_end = range_end
137 for missing_begin, missing_end in missing_ranges:
138 self._tree[missing_begin] = (missing_end, self._attr())
140 for range_begin, range_value in self._tree.itemslice(begin, end):
141 yield range_begin, range_value[0], range_value[1]
143 def __str__(self):
144 return str(self._tree)