commit 2bac3dc5831be7ad6219fd915de3656f1bc38437
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Sep 30 12:24:09 2017 -0700
Replace pause conditionals with sleep
Threading conditionals are the proper method of doing an interruptable sleep,
so nyx can shut down right away when quit. However, each of these conditionals
impose a surprisingly high tax in terms of baseline cpu usage. Fully half the
cpu when idle is just spent on checking conditionals. Guess they're implemented
with some sort of busy puller...
We don't need interruptability. It's nice, but having our interface take a
fraction of a second is well worth halving our cpu usage. When idle this change
drops cpu usage from 2.1% to 1.1% for me.
Pause Cpu
0.1s 1.4%
0.2s 1.3%
0.3s 1.2%
0.4s 1.1%
0.5s 1.1%
0.6s 1.1%
---
nyx/__init__.py | 6 ++++++
nyx/panel/__init__.py | 18 ++++++++----------
nyx/panel/connection.py | 8 +++-----
nyx/tracker.py | 18 ++++++------------
4 files changed, 23 insertions(+), 27 deletions(-)
diff --git a/nyx/__init__.py b/nyx/__init__.py
index 7475878..c326cd3 100644
--- a/nyx/__init__.py
+++ b/nyx/__init__.py
@@ -112,6 +112,12 @@ stem.control.CACHEABLE_GETINFO_PARAMS = list(stem.control.CACHEABLE_GETINFO_PARA
stem.control.LOG_CACHE_FETCHES = False
+# Duration for threads to pause when there's no work left to do. This is a
+# compromise - lower means fater shutdown when quit but higher means lower cpu
+# usage when running.
+
+PAUSE_TIME = 0.4
+
SCHEMA_VERSION = 2 # version of our scheme, bump this if you change the following
SCHEMA = (
'CREATE TABLE schema(version INTEGER)',
diff --git a/nyx/panel/__init__.py b/nyx/panel/__init__.py
index 2139634..3e1aa61 100644
--- a/nyx/panel/__init__.py
+++ b/nyx/panel/__init__.py
@@ -33,7 +33,7 @@ import time
import nyx.curses
-from nyx import nyx_interface
+from nyx import PAUSE_TIME, nyx_interface
__all__ = [
'config',
@@ -189,7 +189,6 @@ class DaemonPanel(Panel, threading.Thread):
threading.Thread.__init__(self)
self.setDaemon(True)
- self._pause_condition = threading.Condition()
self._halt = False # terminates thread if true
self._update_rate = update_rate
@@ -201,13 +200,14 @@ class DaemonPanel(Panel, threading.Thread):
Performs our _update() action at the given rate.
"""
- last_ran = -1
+ last_ran = None
while not self._halt:
- if nyx_interface().is_paused() or (time.time() - last_ran) < self._update_rate:
- with self._pause_condition:
- if not self._halt:
- self._pause_condition.wait(max(0.2, self._update_rate - 0.01))
+ if nyx_interface().is_paused() or (last_ran and time.time() - last_ran < self._update_rate):
+ sleep_until = last_ran + self._update_rate + 0.1
+
+ while not self._halt and time.time() < sleep_until:
+ time.sleep(PAUSE_TIME)
continue # done waiting, try again
@@ -219,6 +219,4 @@ class DaemonPanel(Panel, threading.Thread):
Halts further resolutions and terminates the thread.
"""
- with self._pause_condition:
- self._halt = True
- self._pause_condition.notifyAll()
+ self._halt = True
diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py
index 670486c..c172908 100644
--- a/nyx/panel/connection.py
+++ b/nyx/panel/connection.py
@@ -16,7 +16,7 @@ import nyx.panel
import nyx.popups
import nyx.tracker
-from nyx import nyx_interface, tor_controller
+from nyx import PAUSE_TIME, nyx_interface, tor_controller
from nyx.curses import WHITE, NORMAL, BOLD, HIGHLIGHT
from nyx.menu import MenuItem, Submenu, RadioMenuItem, RadioGroup
@@ -472,10 +472,6 @@ class ConnectionPanel(nyx.panel.DaemonPanel):
start_time = time.time()
while True:
- with self._pause_condition:
- if not self._halt:
- self._pause_condition.wait(0.5)
-
resolution_count = conn_resolver.run_counter()
if resolution_count != 0:
@@ -484,6 +480,8 @@ class ConnectionPanel(nyx.panel.DaemonPanel):
break
elif self._halt:
return
+ else:
+ time.sleep(PAUSE_TIME)
controller = tor_controller()
LAST_RETRIEVED_CIRCUITS = controller.get_circuits([])
diff --git a/nyx/tracker.py b/nyx/tracker.py
index 9f1d72d..b2190fe 100644
--- a/nyx/tracker.py
+++ b/nyx/tracker.py
@@ -60,7 +60,7 @@ import stem.control
import stem.descriptor.router_status_entry
import stem.util.log
-from nyx import tor_controller
+from nyx import PAUSE_TIME, tor_controller
from stem.util import conf, connection, enum, proc, str_tools, system
CONFIG = conf.config_dict('nyx', {
@@ -378,7 +378,6 @@ class Daemon(threading.Thread):
self._run_counter = 0 # counter for the number of successful runs
self._is_paused = False
- self._pause_condition = threading.Condition()
self._halt = False # terminates thread if true
controller = tor_controller()
@@ -387,14 +386,11 @@ class Daemon(threading.Thread):
def run(self):
while not self._halt:
- time_since_last_ran = time.time() - self._last_ran
+ if self._is_paused or time.time() - self._last_ran < self._rate:
+ sleep_until = self._last_ran + self._rate + 0.1
- if self._is_paused or time_since_last_ran < self._rate:
- sleep_duration = max(0.02, self._rate - time_since_last_ran)
-
- with self._pause_condition:
- if not self._halt:
- self._pause_condition.wait(sleep_duration)
+ while not self._halt and time.time() < sleep_until:
+ time.sleep(PAUSE_TIME)
continue # done waiting, try again
@@ -465,9 +461,7 @@ class Daemon(threading.Thread):
Halts further work and terminates the thread.
"""
- with self._pause_condition:
- self._halt = True
- self._pause_condition.notifyAll()
+ self._halt = True
def _tor_status_listener(self, controller, event_type, _):
with self._process_lock: