You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
11KB

  1. from __future__ import print_function
  2. from .lib import TestBase, FileCreator
  3. from smmap.mman import (
  4. WindowCursor,
  5. SlidingWindowMapManager,
  6. StaticWindowMapManager
  7. )
  8. from smmap.util import align_to_mmap
  9. from random import randint
  10. from time import time
  11. import os
  12. import sys
  13. from copy import copy
  14. class TestMMan(TestBase):
  15. def test_cursor(self):
  16. with FileCreator(self.k_window_test_size, "cursor_test") as fc:
  17. man = SlidingWindowMapManager()
  18. ci = WindowCursor(man) # invalid cursor
  19. assert not ci.is_valid()
  20. assert not ci.is_associated()
  21. assert ci.size() == 0 # this is cached, so we can query it in invalid state
  22. cv = man.make_cursor(fc.path)
  23. assert not cv.is_valid() # no region mapped yet
  24. assert cv.is_associated() # but it know where to map it from
  25. assert cv.file_size() == fc.size
  26. assert cv.path() == fc.path
  27. # copy module
  28. cio = copy(cv)
  29. assert not cio.is_valid() and cio.is_associated()
  30. # assign method
  31. assert not ci.is_associated()
  32. ci.assign(cv)
  33. assert not ci.is_valid() and ci.is_associated()
  34. # unuse non-existing region is fine
  35. cv.unuse_region()
  36. cv.unuse_region()
  37. # destruction is fine (even multiple times)
  38. cv._destroy()
  39. WindowCursor(man)._destroy()
  40. def test_memory_manager(self):
  41. slide_man = SlidingWindowMapManager()
  42. static_man = StaticWindowMapManager()
  43. for man in (static_man, slide_man):
  44. assert man.num_file_handles() == 0
  45. assert man.num_open_files() == 0
  46. winsize_cmp_val = 0
  47. if isinstance(man, StaticWindowMapManager):
  48. winsize_cmp_val = -1
  49. # END handle window size
  50. assert man.window_size() > winsize_cmp_val
  51. assert man.mapped_memory_size() == 0
  52. assert man.max_mapped_memory_size() > 0
  53. # collection doesn't raise in 'any' mode
  54. man._collect_lru_region(0)
  55. # doesn't raise if we are within the limit
  56. man._collect_lru_region(10)
  57. # doesn't fail if we over-allocate
  58. assert man._collect_lru_region(sys.maxsize) == 0
  59. # use a region, verify most basic functionality
  60. with FileCreator(self.k_window_test_size, "manager_test") as fc:
  61. fd = os.open(fc.path, os.O_RDONLY)
  62. try:
  63. for item in (fc.path, fd):
  64. c = man.make_cursor(item)
  65. assert c.path_or_fd() is item
  66. assert c.use_region(10, 10).is_valid()
  67. assert c.ofs_begin() == 10
  68. assert c.size() == 10
  69. with open(fc.path, 'rb') as fp:
  70. assert c.buffer()[:] == fp.read(20)[10:]
  71. if isinstance(item, int):
  72. self.assertRaises(ValueError, c.path)
  73. else:
  74. self.assertRaises(ValueError, c.fd)
  75. # END handle value error
  76. # END for each input
  77. finally:
  78. os.close(fd)
  79. # END for each manasger type
  80. def test_memman_operation(self):
  81. # test more access, force it to actually unmap regions
  82. with FileCreator(self.k_window_test_size, "manager_operation_test") as fc:
  83. with open(fc.path, 'rb') as fp:
  84. data = fp.read()
  85. fd = os.open(fc.path, os.O_RDONLY)
  86. try:
  87. max_num_handles = 15
  88. # small_size =
  89. for mtype, args in ((StaticWindowMapManager, (0, fc.size // 3, max_num_handles)),
  90. (SlidingWindowMapManager, (fc.size // 100, fc.size // 3, max_num_handles)),):
  91. for item in (fc.path, fd):
  92. assert len(data) == fc.size
  93. # small windows, a reasonable max memory. Not too many regions at once
  94. man = mtype(window_size=args[0], max_memory_size=args[1], max_open_handles=args[2])
  95. c = man.make_cursor(item)
  96. # still empty (more about that is tested in test_memory_manager()
  97. assert man.num_open_files() == 0
  98. assert man.mapped_memory_size() == 0
  99. base_offset = 5000
  100. # window size is 0 for static managers, hence size will be 0. We take that into consideration
  101. size = man.window_size() // 2
  102. assert c.use_region(base_offset, size).is_valid()
  103. rr = c.region()
  104. assert rr.client_count() == 2 # the manager and the cursor and us
  105. assert man.num_open_files() == 1
  106. assert man.num_file_handles() == 1
  107. assert man.mapped_memory_size() == rr.size()
  108. # assert c.size() == size # the cursor may overallocate in its static version
  109. assert c.ofs_begin() == base_offset
  110. assert rr.ofs_begin() == 0 # it was aligned and expanded
  111. if man.window_size():
  112. # but isn't larger than the max window (aligned)
  113. assert rr.size() == align_to_mmap(man.window_size(), True)
  114. else:
  115. assert rr.size() == fc.size
  116. # END ignore static managers which dont use windows and are aligned to file boundaries
  117. assert c.buffer()[:] == data[base_offset:base_offset + (size or c.size())]
  118. # obtain second window, which spans the first part of the file - it is a still the same window
  119. nsize = (size or fc.size) - 10
  120. assert c.use_region(0, nsize).is_valid()
  121. assert c.region() == rr
  122. assert man.num_file_handles() == 1
  123. assert c.size() == nsize
  124. assert c.ofs_begin() == 0
  125. assert c.buffer()[:] == data[:nsize]
  126. # map some part at the end, our requested size cannot be kept
  127. overshoot = 4000
  128. base_offset = fc.size - (size or c.size()) + overshoot
  129. assert c.use_region(base_offset, size).is_valid()
  130. if man.window_size():
  131. assert man.num_file_handles() == 2
  132. assert c.size() < size
  133. assert c.region() is not rr # old region is still available, but has not curser ref anymore
  134. assert rr.client_count() == 1 # only held by manager
  135. else:
  136. assert c.size() < fc.size
  137. # END ignore static managers which only have one handle per file
  138. rr = c.region()
  139. assert rr.client_count() == 2 # manager + cursor
  140. assert rr.ofs_begin() < c.ofs_begin() # it should have extended itself to the left
  141. assert rr.ofs_end() <= fc.size # it cannot be larger than the file
  142. assert c.buffer()[:] == data[base_offset:base_offset + (size or c.size())]
  143. # unising a region makes the cursor invalid
  144. c.unuse_region()
  145. assert not c.is_valid()
  146. if man.window_size():
  147. # but doesn't change anything regarding the handle count - we cache it and only
  148. # remove mapped regions if we have to
  149. assert man.num_file_handles() == 2
  150. # END ignore this for static managers
  151. # iterate through the windows, verify data contents
  152. # this will trigger map collection after a while
  153. max_random_accesses = 5000
  154. num_random_accesses = max_random_accesses
  155. memory_read = 0
  156. st = time()
  157. # cache everything to get some more performance
  158. includes_ofs = c.includes_ofs
  159. max_mapped_memory_size = man.max_mapped_memory_size()
  160. max_file_handles = man.max_file_handles()
  161. mapped_memory_size = man.mapped_memory_size
  162. num_file_handles = man.num_file_handles
  163. while num_random_accesses:
  164. num_random_accesses -= 1
  165. base_offset = randint(0, fc.size - 1)
  166. # precondition
  167. if man.window_size():
  168. assert max_mapped_memory_size >= mapped_memory_size()
  169. # END statics will overshoot, which is fine
  170. assert max_file_handles >= num_file_handles()
  171. assert c.use_region(base_offset, (size or c.size())).is_valid()
  172. csize = c.size()
  173. assert c.buffer()[:] == data[base_offset:base_offset + csize]
  174. memory_read += csize
  175. assert includes_ofs(base_offset)
  176. assert includes_ofs(base_offset + csize - 1)
  177. assert not includes_ofs(base_offset + csize)
  178. # END while we should do an access
  179. elapsed = max(time() - st, 0.001) # prevent zero divison errors on windows
  180. mb = float(1000 * 1000)
  181. print("%s: Read %i mb of memory with %i random on cursor initialized with %s accesses in %fs (%f mb/s)\n"
  182. % (mtype, memory_read / mb, max_random_accesses, type(item), elapsed, (memory_read / mb) / elapsed),
  183. file=sys.stderr)
  184. # an offset as large as the size doesn't work !
  185. assert not c.use_region(fc.size, size).is_valid()
  186. # collection - it should be able to collect all
  187. assert man.num_file_handles()
  188. assert man.collect()
  189. assert man.num_file_handles() == 0
  190. # END for each item
  191. # END for each manager type
  192. finally:
  193. os.close(fd)