Improve responsiveness of request and response viewing.

- Computing the view of a large body is expensive, so we introduce an LRU cache
to hold the latest 20 results.

- Use ListView more correctly, passing it individual urwid.Text snippets,
rather than a single large one. This hugely improves render time.
This commit is contained in:
Aldo Cortesi 2011-03-15 13:05:33 +13:00
parent 563d4161f1
commit fe1e2f16ff
4 changed files with 135 additions and 57 deletions

View File

@ -289,71 +289,31 @@ class ConnectionView(WWrap):
) )
return f return f
def _view_normal(self, conn, txt): def _conn_text(self, conn, viewmode):
for i in conn.content.splitlines():
txt.append(
("text", i),
)
txt.append(
("text", "\n"),
)
def _view_binary(self, conn, txt):
for offset, hex, s in utils.hexdump(conn.content):
txt.extend([
("offset", offset),
" ",
("text", hex),
" ",
("text", s),
"\n"
])
def _view_pretty(self, conn, txt):
for i in utils.pretty_xmlish(conn.content):
txt.append(
("text", i),
)
txt.append(
("text", "\n"),
)
def _conn_text(self, conn):
if conn: if conn:
txt = [] return self.master._cached_conn_text(conn.content, tuple(conn.headers.itemPairs()), viewmode)
txt.extend(
format_keyvals(
[(h+":", v) for (h, v) in sorted(conn.headers.itemPairs())],
key = "header",
val = "text"
)
)
txt.append("\n\n")
if conn.content:
if self.state.view_body_mode == VIEW_BODY_BINARY:
self._view_binary(conn, txt)
elif self.state.view_body_mode == VIEW_BODY_INDENT:
self.master.statusbar.update("Calculating pretty mode...")
self._view_pretty(conn, txt)
self.master.statusbar.update("")
else:
if utils.isBin(conn.content):
self._view_binary(conn, txt)
else:
self._view_normal(conn, txt)
return urwid.ListBox([urwid.Text(txt)])
else: else:
return urwid.ListBox([]) return urwid.ListBox([])
def view_request(self): def view_request(self):
self.state.view_flow_mode = VIEW_FLOW_REQUEST self.state.view_flow_mode = VIEW_FLOW_REQUEST
body = self._conn_text(self.flow.request) self.master.statusbar.update("Calculating view...")
body = self._conn_text(
self.flow.request,
self.state.view_body_mode
)
self.w = self.wrap_body(VIEW_FLOW_REQUEST, body) self.w = self.wrap_body(VIEW_FLOW_REQUEST, body)
self.master.statusbar.update("")
def view_response(self): def view_response(self):
self.state.view_flow_mode = VIEW_FLOW_RESPONSE self.state.view_flow_mode = VIEW_FLOW_RESPONSE
body = self._conn_text(self.flow.response) self.master.statusbar.update("Calculating view...")
body = self._conn_text(
self.flow.response,
self.state.view_body_mode
)
self.w = self.wrap_body(VIEW_FLOW_RESPONSE, body) self.w = self.wrap_body(VIEW_FLOW_RESPONSE, body)
self.master.statusbar.update("")
def refresh_connection(self, c=None): def refresh_connection(self, c=None):
if c == self.flow: if c == self.flow:
@ -729,11 +689,11 @@ class StatusBar(WWrap):
), ),
]), "statusbar") ]), "statusbar")
self.ib.set_w(status) self.ib.set_w(status)
self.master.drawscreen()
def update(self, text): def update(self, text):
self.helptext = text self.helptext = text
self.redraw() self.redraw()
self.master.drawscreen()
def selectable(self): def selectable(self):
return True return True
@ -896,6 +856,52 @@ class ConsoleMaster(flow.FlowMaster):
if options.server_replay: if options.server_replay:
self.server_playback_path(options.server_replay) self.server_playback_path(options.server_replay)
def _view_conn_normal(self, content, txt):
for i in content.splitlines():
txt.append(
urwid.Text(("text", i))
)
def _view_conn_binary(self, content, txt):
for offset, hex, s in utils.hexdump(content):
txt.append(urwid.Text([
("offset", offset),
" ",
("text", hex),
" ",
("text", s),
]))
def _view_conn_pretty(self, content, txt):
for i in utils.pretty_xmlish(content):
txt.append(
urwid.Text(("text", i)),
)
@utils.LRUCache(20)
def _cached_conn_text(self, content, hdrItems, viewmode):
hdr = []
hdr.extend(
format_keyvals(
[(h+":", v) for (h, v) in sorted(hdrItems)],
key = "header",
val = "text"
)
)
hdr.append("\n")
txt = [urwid.Text(hdr)]
if content:
if viewmode == VIEW_BODY_BINARY:
self._view_conn_binary(content, txt)
elif viewmode == VIEW_BODY_INDENT:
self._view_conn_pretty(content, txt)
else:
if utils.isBin(content):
self._view_conn_binary(content, txt)
else:
self._view_conn_normal(content, txt)
return urwid.ListBox(txt)
def _readflow(self, path): def _readflow(self, path):
path = os.path.expanduser(path) path = os.path.expanduser(path)
@ -1272,8 +1278,8 @@ class ConsoleMaster(flow.FlowMaster):
slave.start() slave.start()
try: try:
while not self._shutdown: while not self._shutdown:
size = self.drawscreen()
self.statusbar.redraw() self.statusbar.redraw()
size = self.drawscreen()
self.tick(q) self.tick(q)
self.ui.set_input_timeouts(max_wait=0.1) self.ui.set_input_timeouts(max_wait=0.1)
keys = self.ui.get_input() keys = self.ui.get_input()

View File

@ -303,6 +303,9 @@ class Response(controller.Msg):
del i["expires"] del i["expires"]
return c.output(header="").strip() return c.output(header="").strip()
def __hash__(self):
return id(self)
def refresh(self, now=None): def refresh(self, now=None):
""" """
This fairly complex and heuristic function refreshes a server This fairly complex and heuristic function refreshes a server

View File

@ -12,7 +12,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re, os, subprocess, datetime, textwrap, errno, sys, time import re, os, subprocess, datetime, textwrap, errno, sys, time, functools
def timestamp(): def timestamp():
@ -444,3 +444,41 @@ def dummy_cert(certdir, ca, commonname):
) )
if ret: return None if ret: return None
return certpath return certpath
class LRUCache:
"""
A decorator that implements a self-expiring LRU cache for class
methods (not functions!).
Cache data is tracked as attributes on the object itself. There is
therefore a separate cache for each object instance.
"""
def __init__(self, size=100):
self.size = size
def __call__(self, f):
cacheName = "_cached_%s"%f.__name__
cacheListName = "_cachelist_%s"%f.__name__
size = self.size
@functools.wraps(f)
def wrap(self, *args):
if not hasattr(self, cacheName):
setattr(self, cacheName, {})
setattr(self, cacheListName, [])
cache = getattr(self, cacheName)
cacheList = getattr(self, cacheListName)
if cache.has_key(args):
cacheList.remove(args)
cacheList.insert(0, args)
return cache[args]
else:
ret = f(self, *args)
cacheList.insert(0, args)
cache[args] = ret
if len(cacheList) > size:
d = cacheList.pop()
cache.pop(d)
return ret
return wrap

View File

@ -310,6 +310,36 @@ class udummy_cert(libpry.AutoTree):
assert os.path.exists(os.path.join(d, "foo.com.pem")) assert os.path.exists(os.path.join(d, "foo.com.pem"))
class uLRUCache(libpry.AutoTree):
def test_one(self):
class Foo:
ran = False
@utils.LRUCache(2)
def one(self, x):
self.ran = True
return x
f = Foo()
assert f.one(1) == 1
assert f.ran
f.ran = False
assert f.one(1) == 1
assert not f.ran
f.ran = False
assert f.one(1) == 1
assert not f.ran
assert f.one(2) == 2
assert f.one(3) == 3
assert f.ran
f.ran = False
assert f.one(1) == 1
assert f.ran
assert len(f._cached_one) == 2
assert len(f._cachelist_one) == 2
tests = [ tests = [
uformat_timestamp(), uformat_timestamp(),
@ -324,4 +354,5 @@ tests = [
upretty_xmlish(), upretty_xmlish(),
udummy_ca(), udummy_ca(),
udummy_cert(), udummy_cert(),
uLRUCache(),
] ]