3 _logger = logging.getLogger('arvados.ranges')
5 # Log level below 'debug' !
9 __slots__ = ("locator", "range_start", "range_size", "segment_offset")
11 def __init__(self, locator, range_start, range_size, segment_offset=0):
12 self.locator = locator
13 self.range_start = range_start
14 self.range_size = range_size
15 self.segment_offset = segment_offset
18 return "Range(%r, %r, %r, %r)" % (self.locator, self.range_start, self.range_size, self.segment_offset)
20 def __eq__(self, other):
21 return (self.locator == other.locator and
22 self.range_start == other.range_start and
23 self.range_size == other.range_size and
24 self.segment_offset == other.segment_offset)
26 def first_block(data_locators, range_start):
29 # range_start/block_start is the inclusive lower bound
30 # range_end/block_end is the exclusive upper bound
32 hi = len(data_locators)
34 i = int((hi + lo) / 2)
35 block_size = data_locators[i].range_size
36 block_start = data_locators[i].range_start
37 block_end = block_start + block_size
39 # perform a binary search for the first block
40 # assumes that all of the blocks are contiguous, so range_start is guaranteed
41 # to either fall into the range of a block or be outside the block range entirely
42 while not (range_start >= block_start and range_start < block_end):
44 # must be out of range, fail
46 if range_start > block_start:
50 i = int((hi + lo) / 2)
51 block_size = data_locators[i].range_size
52 block_start = data_locators[i].range_start
53 block_end = block_start + block_size
57 class LocatorAndRange(object):
58 __slots__ = ("locator", "block_size", "segment_offset", "segment_size")
60 def __init__(self, locator, block_size, segment_offset, segment_size):
61 self.locator = locator
62 self.block_size = block_size
63 self.segment_offset = segment_offset
64 self.segment_size = segment_size
66 def __eq__(self, other):
67 return (self.locator == other.locator and
68 self.block_size == other.block_size and
69 self.segment_offset == other.segment_offset and
70 self.segment_size == other.segment_size)
73 return "LocatorAndRange(%r, %r, %r, %r)" % (self.locator, self.block_size, self.segment_offset, self.segment_size)
75 def locators_and_ranges(data_locators, range_start, range_size, limit=None):
76 """Get blocks that are covered by a range.
78 Returns a list of LocatorAndRange objects.
81 list of Range objects, assumes that blocks are in order and contiguous
90 Maximum segments to return, default None (unlimited). Will truncate the
91 result if there are more segments needed to cover the range than the
98 range_end = range_start + range_size
100 i = first_block(data_locators, range_start)
104 # We should always start at the first segment due to the binary
106 while i < len(data_locators) and len(resp) != limit:
107 dl = data_locators[i]
108 block_start = dl.range_start
109 block_size = dl.range_size
110 block_end = block_start + block_size
111 _logger.log(RANGES_SPAM,
112 "%s range_start %s block_start %s range_end %s block_end %s",
113 dl.locator, range_start, block_start, range_end, block_end)
114 if range_end <= block_start:
115 # range ends before this block starts, so don't look at any more locators
118 if range_start >= block_start and range_end <= block_end:
119 # range starts and ends in this block
120 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset + (range_start - block_start), range_size))
121 elif range_start >= block_start and range_end > block_end:
122 # range starts in this block
123 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset + (range_start - block_start), block_end - range_start))
124 elif range_start < block_start and range_end > block_end:
125 # range starts in a previous block and extends to further blocks
126 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset, block_size))
127 elif range_start < block_start and range_end <= block_end:
128 # range starts in a previous block and ends in this block
129 resp.append(LocatorAndRange(dl.locator, block_size, dl.segment_offset, range_end - block_start))
130 block_start = block_end
134 def replace_range(data_locators, new_range_start, new_range_size, new_locator, new_segment_offset):
136 Replace a file segment range with a new segment.
139 data_locators will be updated in place
142 list of Range objects, assumes that segments are in order and contiguous
145 start of range to replace in data_locators
148 size of range to replace in data_locators
151 locator for new segment to be inserted
154 segment offset within the locator
157 if new_range_size == 0:
160 new_range_end = new_range_start + new_range_size
162 if len(data_locators) == 0:
163 data_locators.append(Range(new_locator, new_range_start, new_range_size, new_segment_offset))
166 last = data_locators[-1]
167 if (last.range_start+last.range_size) == new_range_start:
168 if last.locator == new_locator:
169 # extend last segment
170 last.range_size += new_range_size
172 data_locators.append(Range(new_locator, new_range_start, new_range_size, new_segment_offset))
175 i = first_block(data_locators, new_range_start)
179 # We should always start at the first segment due to the binary
181 while i < len(data_locators):
182 dl = data_locators[i]
183 old_segment_start = dl.range_start
184 old_segment_end = old_segment_start + dl.range_size
185 _logger.log(RANGES_SPAM,
186 "%s range_start %s segment_start %s range_end %s segment_end %s",
187 dl, new_range_start, old_segment_start, new_range_end,
189 if new_range_end <= old_segment_start:
190 # range ends before this segment starts, so don't look at any more locators
193 if old_segment_start <= new_range_start and new_range_end <= old_segment_end:
194 # new range starts and ends in old segment
195 # split segment into up to 3 pieces
196 if (new_range_start-old_segment_start) > 0:
197 data_locators[i] = Range(dl.locator, old_segment_start, (new_range_start-old_segment_start), dl.segment_offset)
198 data_locators.insert(i+1, Range(new_locator, new_range_start, new_range_size, new_segment_offset))
200 data_locators[i] = Range(new_locator, new_range_start, new_range_size, new_segment_offset)
202 if (old_segment_end-new_range_end) > 0:
203 data_locators.insert(i+2, Range(dl.locator, new_range_end, (old_segment_end-new_range_end), dl.segment_offset + (new_range_start-old_segment_start) + new_range_size))
205 elif old_segment_start <= new_range_start and new_range_end > old_segment_end:
206 # range starts in this segment
207 # split segment into 2 pieces
208 data_locators[i] = Range(dl.locator, old_segment_start, (new_range_start-old_segment_start), dl.segment_offset)
209 data_locators.insert(i+1, Range(new_locator, new_range_start, new_range_size, new_segment_offset))
211 elif new_range_start < old_segment_start and new_range_end >= old_segment_end:
212 # range starts in a previous segment and extends to further segments
213 # delete this segment
216 elif new_range_start < old_segment_start and new_range_end < old_segment_end:
217 # range starts in a previous segment and ends in this segment
218 # move the starting point of this segment up, and shrink it.
219 data_locators[i] = Range(dl.locator, new_range_end, (old_segment_end-new_range_end), dl.segment_offset + (new_range_end-old_segment_start))