Make large results display quickly by loading them in pieces.
This commit is contained in:
parent
c339742203
commit
031fabf4e2
6 changed files with 151 additions and 14 deletions
|
|
@ -318,8 +318,7 @@ class Summary:
|
||||||
if jobs_added:
|
if jobs_added:
|
||||||
self._jobs_added_event.set()
|
self._jobs_added_event.set()
|
||||||
for result in deleted_results:
|
for result in deleted_results:
|
||||||
with contextlib.suppress(FileNotFoundError):
|
result.delete()
|
||||||
os.remove(result.pickle_path)
|
|
||||||
self.sort_entries()
|
self.sort_entries()
|
||||||
|
|
||||||
def _sweep_up(self, x, y):
|
def _sweep_up(self, x, y):
|
||||||
|
|
@ -470,6 +469,7 @@ class Summary:
|
||||||
def refresh_result(self, result):
|
def refresh_result(self, result):
|
||||||
if result.is_completed:
|
if result.is_completed:
|
||||||
result.reset()
|
result.reset()
|
||||||
|
result.delete()
|
||||||
self.closest_placeholder_generator = None
|
self.closest_placeholder_generator = None
|
||||||
self._jobs_added_event.set()
|
self._jobs_added_event.set()
|
||||||
self.completed_total -= 1
|
self.completed_total -= 1
|
||||||
|
|
|
||||||
|
|
@ -334,18 +334,19 @@ class View:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_appearance(text, pad_char=" "):
|
||||||
|
lines = text.splitlines()
|
||||||
|
if len(lines) == 0:
|
||||||
|
return []
|
||||||
|
max_width = max(len(line) for line in lines)
|
||||||
|
height = len(lines)
|
||||||
|
return appearance_resize(lines, (max_width, height), pad_char)
|
||||||
|
|
||||||
|
|
||||||
class Text:
|
class Text:
|
||||||
|
|
||||||
def __init__(self, text, pad_char=" "):
|
def __init__(self, text, pad_char=" "):
|
||||||
lines = text.splitlines()
|
self.text = str_to_appearance(text, pad_char)
|
||||||
if len(lines) == 0:
|
|
||||||
self.text = []
|
|
||||||
elif len(lines) == 1:
|
|
||||||
self.text = [lines[0]]
|
|
||||||
else:
|
|
||||||
max_width = max(len(line) for line in lines)
|
|
||||||
height = len(lines)
|
|
||||||
self.text = appearance_resize(lines, (max_width, height), pad_char)
|
|
||||||
|
|
||||||
def appearance_min(self):
|
def appearance_min(self):
|
||||||
return self.text
|
return self.text
|
||||||
|
|
@ -434,12 +435,16 @@ class Placeholder:
|
||||||
|
|
||||||
class Fixed:
|
class Fixed:
|
||||||
|
|
||||||
def __init__(self, appearance):
|
def __init__(self, appearance_min):
|
||||||
self.appearance_min_ = appearance
|
self.appearance_min_ = appearance_min
|
||||||
|
self.dimensions = appearance_dimensions(appearance_min)
|
||||||
|
|
||||||
def appearance_min(self):
|
def appearance_min(self):
|
||||||
return self.appearance_min_
|
return self.appearance_min_
|
||||||
|
|
||||||
|
def appearance_dimensions(self):
|
||||||
|
return self.dimensions
|
||||||
|
|
||||||
|
|
||||||
##########################
|
##########################
|
||||||
|
|
||||||
|
|
|
||||||
73
eris/paged_list.py
Normal file
73
eris/paged_list.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Copyright (C) 2019 Andrew Hamilton. All rights reserved.
|
||||||
|
# Licensed under the Artistic License 2.0.
|
||||||
|
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import gzip
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
|
||||||
|
class PagedList:
|
||||||
|
|
||||||
|
def __init__(self, list_, pages_dir, page_size, cache_size):
|
||||||
|
self.pages_dir = pages_dir # An empty or non-existant directory.
|
||||||
|
self.page_size = page_size
|
||||||
|
self.cache_size = cache_size
|
||||||
|
self._len = len(list_)
|
||||||
|
tmp_dir = pages_dir + ".tmp"
|
||||||
|
os.makedirs(tmp_dir)
|
||||||
|
if len(list_) == 0:
|
||||||
|
pages = [[]]
|
||||||
|
else:
|
||||||
|
pages = [list_[start:start+self.page_size]
|
||||||
|
for start in range(0, len(list_), self.page_size)]
|
||||||
|
for index, page in enumerate(pages):
|
||||||
|
pickle_path = os.path.join(tmp_dir, str(index))
|
||||||
|
with gzip.open(pickle_path, "wb") as file_:
|
||||||
|
pickle.dump(page, file_, protocol=pickle.HIGHEST_PROTOCOL)
|
||||||
|
self.page_count = len(pages)
|
||||||
|
os.rename(tmp_dir, self.pages_dir)
|
||||||
|
self._get_page = functools.lru_cache(maxsize=cache_size)(self._get_page)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self._len
|
||||||
|
|
||||||
|
def _get_page(self, index): # This is cached, see __init__.
|
||||||
|
pickle_path = os.path.join(self.pages_dir, str(index))
|
||||||
|
with gzip.open(pickle_path, "rb") as file_:
|
||||||
|
return pickle.load(file_)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
if type(index) == slice:
|
||||||
|
start, stop, step = index.indices(self._len)
|
||||||
|
start_page_index, start_page_offset = divmod(start, self.page_size)
|
||||||
|
stop_page_index, stop_page_offset = divmod(stop, self.page_size)
|
||||||
|
if stop_page_index == self.page_count:
|
||||||
|
stop_page_index -= 1
|
||||||
|
stop_page_offset = self.page_size
|
||||||
|
if start_page_index == stop_page_index:
|
||||||
|
return (self._get_page(start_page_index)
|
||||||
|
[start_page_offset:stop_page_offset])
|
||||||
|
else:
|
||||||
|
result = self._get_page(start_page_index)[start_page_offset:]
|
||||||
|
middle_pages = (self._get_page(page_index) for page_index in
|
||||||
|
range(start_page_index+1, stop_page_index))
|
||||||
|
for page in middle_pages:
|
||||||
|
result.extend(page)
|
||||||
|
result.extend(
|
||||||
|
self._get_page(stop_page_index)[:stop_page_offset])
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
page_index, page_offset = divmod(index, self.page_size)
|
||||||
|
return self._get_page(page_index)[page_offset]
|
||||||
|
|
||||||
|
def __getstate__(self): # Don't pickle the lru_cache.
|
||||||
|
state = self.__dict__.copy()
|
||||||
|
del state["_get_page"]
|
||||||
|
return state
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
self.__dict__ = state
|
||||||
|
self._get_page = \
|
||||||
|
functools.lru_cache(maxsize=self.cache_size)(self._get_page)
|
||||||
|
|
@ -14,6 +14,7 @@ import os
|
||||||
import os.path
|
import os.path
|
||||||
import pickle
|
import pickle
|
||||||
import pwd
|
import pwd
|
||||||
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
@ -588,6 +589,15 @@ class Result:
|
||||||
def appearance_min(self):
|
def appearance_min(self):
|
||||||
return [status_to_str(self.status)]
|
return [status_to_str(self.status)]
|
||||||
|
|
||||||
|
def get_pages_dir(self):
|
||||||
|
return self.pickle_path + ".pages"
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
with contextlib.suppress(FileNotFoundError):
|
||||||
|
os.remove(self.pickle_path)
|
||||||
|
with contextlib.suppress(FileNotFoundError):
|
||||||
|
shutil.rmtree(self.get_pages_dir())
|
||||||
|
|
||||||
def as_html(self):
|
def as_html(self):
|
||||||
html, styles = termstr.TermStr(status_to_str(self.status)).as_html()
|
html, styles = termstr.TermStr(status_to_str(self.status)).as_html()
|
||||||
return (f'<a title="{self.tool.__name__}" '
|
return (f'<a title="{self.tool.__name__}" '
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import signal
|
||||||
|
|
||||||
import eris.fill3 as fill3
|
import eris.fill3 as fill3
|
||||||
import eris.tools as tools
|
import eris.tools as tools
|
||||||
|
import eris.paged_list
|
||||||
|
|
||||||
|
|
||||||
class Worker:
|
class Worker:
|
||||||
|
|
@ -80,6 +81,15 @@ class Worker:
|
||||||
os.killpg(self.child_pgid, signal.SIGKILL)
|
os.killpg(self.child_pgid, signal.SIGKILL)
|
||||||
|
|
||||||
|
|
||||||
|
def make_result_widget(text, result):
|
||||||
|
appearance = fill3.str_to_appearance(text)
|
||||||
|
page_size = 500
|
||||||
|
if len(appearance) > page_size:
|
||||||
|
appearance = eris.paged_list.PagedList(
|
||||||
|
appearance, result.get_pages_dir(), page_size, cache_size=2)
|
||||||
|
return fill3.Fixed(appearance)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print(os.getpgid(os.getpid()), flush=True)
|
print(os.getpgid(os.getpid()), flush=True)
|
||||||
try:
|
try:
|
||||||
|
|
@ -88,7 +98,7 @@ def main():
|
||||||
tool = getattr(tools, tool_name)
|
tool = getattr(tools, tool_name)
|
||||||
result = tools.Result(path, tool)
|
result = tools.Result(path, tool)
|
||||||
status, text = tools.run_tool_no_error(path, tool)
|
status, text = tools.run_tool_no_error(path, tool)
|
||||||
result.result = fill3.Text(text)
|
result.result = make_result_widget(text, result)
|
||||||
print(status.value, flush=True)
|
print(status.value, flush=True)
|
||||||
except:
|
except:
|
||||||
tools.log_error()
|
tools.log_error()
|
||||||
|
|
|
||||||
39
tests/paged_list_test.py
Executable file
39
tests/paged_list_test.py
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python3.7
|
||||||
|
|
||||||
|
# Copyright (C) 2019 Andrew Hamilton. All rights reserved.
|
||||||
|
# Licensed under the Artistic License 2.0.
|
||||||
|
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import eris.paged_list as paged_list
|
||||||
|
|
||||||
|
|
||||||
|
class PagedListTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_getitem(self):
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
list_ = paged_list.PagedList([3, 4, 5, 6], temp_dir, 4, 2)
|
||||||
|
self.assertEqual(list_[1], 4)
|
||||||
|
self.assertEqual(list_[1:3], [4, 5])
|
||||||
|
self.assertEqual(list_[0:4], [3, 4, 5, 6])
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
list_ = paged_list.PagedList([3, 4, 5, 6], temp_dir, 2, 2)
|
||||||
|
self.assertEqual(list_[1:3], [4, 5])
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
list_ = paged_list.PagedList([3, 4, 5, 6, 7, 8], temp_dir, 2, 2)
|
||||||
|
self.assertEqual(list_[1:5], [4, 5, 6, 7])
|
||||||
|
self.assertEqual(list_[:2], [3, 4])
|
||||||
|
self.assertEqual(list_[2:], [5, 6, 7, 8])
|
||||||
|
|
||||||
|
def test_pickling(self):
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
list_ = paged_list.PagedList([3, 4, 5], temp_dir, 2, 2)
|
||||||
|
list_b = pickle.loads(pickle.dumps(list_))
|
||||||
|
self.assertEqual(list_b[1], 4)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue