commit e0adea1ffcccf1cde046dfad698da7ce6f19b51d Author: Damian Johnson atagar@torproject.org Date: Sun Apr 17 18:02:56 2016 -0700
Move panel threading to subclass
Three of our panels are also threads, performing actions at a set rate. All three have identical boilerplate so moving this to a DaemonPanel sublass of Panel.
This rejiggers how the log panel works. The work it did to sleep minimal amounts of time was both silly and pointless. --- nyx/panel/__init__.py | 46 ++++++++++++++++++++++++++++++++++++++++-- nyx/panel/connection.py | 47 ++----------------------------------------- nyx/panel/header.py | 37 ++-------------------------------- nyx/panel/log.py | 53 +++++++++---------------------------------------- nyxrc.sample | 4 ---- 5 files changed, 57 insertions(+), 130 deletions(-)
diff --git a/nyx/panel/__init__.py b/nyx/panel/__init__.py index 028ba73..86e64ac 100644 --- a/nyx/panel/__init__.py +++ b/nyx/panel/__init__.py @@ -6,11 +6,12 @@ Panels consisting the nyx interface. """
import collections -import inspect -import time import curses import curses.ascii import curses.textpad +import inspect +import threading +import time
import nyx.curses import stem.util.log @@ -710,3 +711,44 @@ class Panel(object): self.addch(top, left + width - 1, curses.ACS_URCORNER, *attributes) self.addch(top + height - 1, left, curses.ACS_LLCORNER, *attributes) self.addch(top + height - 1, left + width - 1, curses.ACS_LRCORNER, *attributes) + + +class DaemonPanel(Panel, threading.Thread): + def __init__(self, name, top = 0, left = 0, height = -1, width = -1, update_rate = 10): + Panel.__init__(self, name, top, left, height, width) + threading.Thread.__init__(self) + self.setDaemon(True) + + self._pause_condition = threading.Condition() + self._halt = False # terminates thread if true + self._update_rate = update_rate + + def _update(self): + pass + + def run(self): + """ + Performs our _update() action at the given rate. + """ + + last_ran = -1 + + while not self._halt: + if self.is_paused() or (time.time() - last_ran) < self._update_rate: + with self._pause_condition: + if not self._halt: + self._pause_condition.wait(0.2) + + continue # done waiting, try again + + self._update() + last_ran = time.time() + + def stop(self): + """ + Halts further resolutions and terminates the thread. + """ + + with self._pause_condition: + self._halt = True + self._pause_condition.notifyAll() diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py index e7e80f5..b75376d 100644 --- a/nyx/panel/connection.py +++ b/nyx/panel/connection.py @@ -10,7 +10,6 @@ import time import collections import curses import itertools -import threading
import nyx.curses import nyx.panel @@ -256,16 +255,14 @@ class CircuitEntry(Entry): return False
-class ConnectionPanel(nyx.panel.Panel, threading.Thread): +class ConnectionPanel(nyx.panel.DaemonPanel): """ Listing of connections tor is making, with information correlated against the current consensus and other data sources. """
def __init__(self): - nyx.panel.Panel.__init__(self, 'connections') - threading.Thread.__init__(self) - self.setDaemon(True) + nyx.panel.DaemonPanel.__init__(self, 'connections', update_rate = UPDATE_RATE)
self._scroller = nyx.curses.CursorScroller() self._entries = [] # last fetched display entries @@ -274,9 +271,6 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread):
self._last_resource_fetch = -1 # timestamp of the last ConnectionResolver results used
- self._pause_condition = threading.Condition() - self._halt = False # terminates thread if true - # Tracks exiting port and client country statistics
self._client_locale_usage = {} @@ -317,34 +311,6 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread): self._sort_order = results self._entries = sorted(self._entries, key = lambda entry: [entry.sort_value(attr) for attr in self._sort_order])
- def run(self): - """ - Keeps connections listing updated, checking for new entries at a set rate. - """ - - last_ran = -1 - - while not self._halt: - if self.is_paused() or not tor_controller().is_alive() or (time.time() - last_ran) < UPDATE_RATE: - with self._pause_condition: - if not self._halt: - self._pause_condition.wait(0.2) - - continue # done waiting, try again - - self._update() - self.redraw(True) - - # TODO: The following is needed to show results *but* causes curses to - # flicker. For our plans on this see... - # - # https://trac.torproject.org/projects/tor/ticket/18547#comment:1 - - # if last_ran == -1: - # nyx.tracker.get_consensus_tracker().update(tor_controller().get_network_statuses([])) - - last_ran = time.time() - def key_handlers(self): def _scroll(key): page_height = self.get_preferred_size()[0] - 1 @@ -630,15 +596,6 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread): x = self.addstr(y, x, line.entry.get_type().upper(), BOLD, *attr) x = self.addstr(y, x, ')', *attr)
- def stop(self): - """ - Halts further resolutions and terminates the thread. - """ - - with self._pause_condition: - self._halt = True - self._pause_condition.notifyAll() - def _update(self): """ Fetches the newest resolved connections. diff --git a/nyx/panel/header.py b/nyx/panel/header.py index 164929b..df139f0 100644 --- a/nyx/panel/header.py +++ b/nyx/panel/header.py @@ -9,7 +9,6 @@ available.
import os import time -import threading
import stem import stem.control @@ -39,21 +38,16 @@ CONFIG = conf.config_dict('nyx', { })
-class HeaderPanel(nyx.panel.Panel, threading.Thread): +class HeaderPanel(nyx.panel.DaemonPanel): """ Top area containing tor settings and system information. """
def __init__(self): - nyx.panel.Panel.__init__(self, 'header') - threading.Thread.__init__(self) - self.setDaemon(True) - + nyx.panel.DaemonPanel.__init__(self, 'header', update_rate = UPDATE_RATE) self._vals = Sampling.create()
self._last_width = nyx.curses.screen_size()[0] - self._pause_condition = threading.Condition() - self._halt = False # terminates thread if true self._reported_inactive = False
self._message = None @@ -200,33 +194,6 @@ class HeaderPanel(nyx.panel.Panel, threading.Thread):
_draw_status(subwindow, 0, subwindow.height - 1, self.is_paused(), self._message, *self._message_attr)
- def run(self): - """ - Keeps stats updated, checking for new information at a set rate. - """ - - last_ran = -1 - - while not self._halt: - if self.is_paused() or not self._vals.is_connected or (time.time() - last_ran) < UPDATE_RATE: - with self._pause_condition: - if not self._halt: - self._pause_condition.wait(0.2) - - continue # done waiting, try again - - self._update() - last_ran = time.time() - - def stop(self): - """ - Halts further resolutions and terminates the thread. - """ - - with self._pause_condition: - self._halt = True - self._pause_condition.notifyAll() - def reset_listener(self, controller, event_type, _): self._update()
diff --git a/nyx/panel/log.py b/nyx/panel/log.py index cc8183b..1e1e87e 100644 --- a/nyx/panel/log.py +++ b/nyx/panel/log.py @@ -9,7 +9,6 @@ regular expressions.
import os import time -import threading
import stem.response.events
@@ -28,8 +27,6 @@ from stem.util import conf, log def conf_handler(key, value): if key == 'features.log.prepopulateReadLimit': return max(0, value) - elif key == 'features.log.maxRefreshRate': - return max(10, value) elif key == 'cache.log_panel.size': return max(1000, value)
@@ -41,11 +38,12 @@ CONFIG = conf.config_dict('nyx', { 'features.log.showDuplicateEntries': False, 'features.log.prepopulate': True, 'features.log.prepopulateReadLimit': 5000, - 'features.log.maxRefreshRate': 300, 'features.log.regex': [], 'startup.events': 'N3', }, conf_handler)
+UPDATE_RATE = 0.3 + # The height of the drawn content is estimated based on the last time we redrew # the panel. It's chiefly used for scrolling and the bar indicating its # position. Letting the estimate be too inaccurate results in a display bug, so @@ -61,16 +59,14 @@ NYX_LOGGER = log.LogBuffer(log.Runlevel.DEBUG, yield_records = True) stem_logger.addHandler(NYX_LOGGER)
-class LogPanel(nyx.panel.Panel, threading.Thread): +class LogPanel(nyx.panel.DaemonPanel): """ Listens for and displays tor, nyx, and stem events. This prepopulates from tor's log file if it exists. """
def __init__(self): - nyx.panel.Panel.__init__(self, 'log') - threading.Thread.__init__(self) - self.setDaemon(True) + nyx.panel.DaemonPanel.__init__(self, 'log', update_rate = UPDATE_RATE)
logged_events = nyx.arguments.expand_events(CONFIG['startup.events']) self._event_log = nyx.log.LogGroup(CONFIG['cache.log_panel.size'], group_by_day = True) @@ -81,9 +77,8 @@ class LogPanel(nyx.panel.Panel, threading.Thread): self._show_duplicates = CONFIG['features.log.showDuplicateEntries']
self._scroller = nyx.curses.Scroller() - self._halt = False # terminates thread if true - self._pause_condition = threading.Condition() self._has_new_event = False + self._last_day = nyx.log.day_count(time.time())
# fetches past tor events from log file, if available
@@ -359,46 +354,19 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
return y + 1
- def run(self): + def _update(self): """ Redraws the display, coalescing updates if events are rapidly logged (for instance running at the DEBUG runlevel) while also being immediately responsive if additions are less frequent. """
- last_ran, last_day = -1, nyx.log.day_count(time.time()) - - while not self._halt: - current_day = nyx.log.day_count(time.time()) - time_since_reset = time.time() - last_ran - max_log_update_rate = CONFIG['features.log.maxRefreshRate'] / 1000.0 - - sleep_time = 0 - - if (not self._has_new_event and last_day == current_day) or self.is_paused(): - sleep_time = 5 - elif time_since_reset < max_log_update_rate: - sleep_time = max(0.05, max_log_update_rate - time_since_reset) - - if sleep_time: - with self._pause_condition: - if not self._halt: - self._pause_condition.wait(sleep_time) - - continue + current_day = nyx.log.day_count(time.time())
- last_ran, last_day = time.time(), current_day + if self._has_new_event or self._last_day != current_day: + self._last_day = current_day self.redraw(True)
- def stop(self): - """ - Halts further updates and terminates the thread. - """ - - with self._pause_condition: - self._halt = True - self._pause_condition.notifyAll() - def _register_tor_event(self, event): msg = ' '.join(str(event).split(' ')[1:])
@@ -426,6 +394,3 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
if self._filter.match(event.display_message): self._has_new_event = True - - with self._pause_condition: - self._pause_condition.notifyAll() diff --git a/nyxrc.sample b/nyxrc.sample index 612af6b..7bb300c 100644 --- a/nyxrc.sample +++ b/nyxrc.sample @@ -63,16 +63,12 @@ features.confirmQuit true # prepopulateReadLimit # maximum entries read from the log file, used to prevent huge log files from # causing a slow startup time. -# maxRefreshRate -# rate limiting (in milliseconds) for drawing the log if updates are made -# rapidly (for instance, when at the DEBUG runlevel) # regex # preconfigured regular expression pattern, up to five will be loaded
features.log.showDuplicateEntries false features.log.prepopulate true features.log.prepopulateReadLimit 5000 -features.log.maxRefreshRate 300 #features.log.regex My First Regex Pattern #features.log.regex ^My Second Regex Pattern$
tor-commits@lists.torproject.org