[or-cvs] r22447: {arm} Full rewrite of the header panel, providing: - lightweight r (in arm/trunk: init interface util)
Damian Johnson
atagar1 at gmail.com
Mon May 31 22:41:08 UTC 2010
Author: atagar
Date: 2010-05-31 22:41:07 +0000 (Mon, 31 May 2010)
New Revision: 22447
Modified:
arm/trunk/init/starter.py
arm/trunk/interface/controller.py
arm/trunk/interface/cpuMemMonitor.py
arm/trunk/interface/headerPanel.py
arm/trunk/util/__init__.py
arm/trunk/util/connections.py
arm/trunk/util/hostnames.py
arm/trunk/util/panel.py
arm/trunk/util/torTools.py
arm/trunk/util/uiTools.py
Log:
Full rewrite of the header panel, providing:
- lightweight redrawing (smarter caching and moved updating into a daemon thread)
- more graceful handling of tiny displays
- configurable update rate
fix: revised sleep pattern used for threads, greatly reducing the time it takes to quit
Modified: arm/trunk/init/starter.py
===================================================================
--- arm/trunk/init/starter.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/init/starter.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -19,7 +19,7 @@
import util.torTools
import TorCtl.TorUtil
-VERSION = "1.3.5"
+VERSION = "1.3.5_dev"
LAST_MODIFIED = "Apr 8, 2010"
DEFAULT_CONTROL_ADDR = "127.0.0.1"
@@ -139,7 +139,7 @@
# sending problems to stdout if they arise
util.torTools.INCORRECT_PASSWORD_MSG = "Controller password found in '%s' was incorrect" % configPath
authPassword = config.get(AUTH_CFG, None)
- conn = util.torTools.makeConn(controlAddr, controlPort, authPassword)
+ conn = util.torTools.connect(controlAddr, controlPort, authPassword)
if conn == None: sys.exit(1)
controller = util.torTools.getConn()
Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/interface/controller.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -332,7 +332,7 @@
connections.RESOLVER_FINAL_FAILURE_MSG += " (connection related portions of the monitor won't function)"
panels = {
- "header": headerPanel.HeaderPanel(stdscr, conn, torPid),
+ "header": headerPanel.HeaderPanel(stdscr),
"popup": Popup(stdscr, 9),
"graph": graphPanel.GraphPanel(stdscr),
"log": logPanel.LogMonitor(stdscr, conn, loggedEvents)}
@@ -353,7 +353,7 @@
# statistical monitors for graph
panels["graph"].addStats("bandwidth", bandwidthMonitor.BandwidthMonitor(conn))
- panels["graph"].addStats("system resources", cpuMemMonitor.CpuMemMonitor(panels["header"]))
+ panels["graph"].addStats("system resources", cpuMemMonitor.CpuMemMonitor())
if not isBlindMode: panels["graph"].addStats("connections", connCountMonitor.ConnCountMonitor(conn))
panels["graph"].setStats("bandwidth")
@@ -374,6 +374,9 @@
TorUtil.loglevel = "DEBUG"
TorUtil.logfile = panels["log"]
+ # tells revised panels to run as daemons
+ panels["header"].start()
+
# warns if tor isn't updating descriptors
try:
if conn.get_option("FetchUselessDescriptors")[0][1] == "0" and conn.get_option("DirPort")[0][1] == "0":
@@ -400,7 +403,7 @@
try:
# if sighup received then reload related information
if sighupTracker.isReset:
- panels["header"]._updateParams(True)
+ #panels["header"]._updateParams(True)
# other panels that use torrc data
panels["conn"].resetOptions()
@@ -409,7 +412,7 @@
# if bandwidth graph is being shown then height might have changed
if panels["graph"].currentDisplay == "bandwidth":
- panels["graph"].height = panels["graph"].stats["bandwidth"].height
+ panels["graph"].setHeight(panels["graph"].stats["bandwidth"].height)
panels["torrc"].reset()
sighupTracker.isReset = False
@@ -420,7 +423,7 @@
# resilient in case of funky changes (such as resizing during popups)
# hack to make sure header picks layout before using the dimensions below
- panels["header"].getPreferredSize()
+ #panels["header"].getPreferredSize()
startY = 0
for panelKey in PAGE_S[:2]:
@@ -428,7 +431,7 @@
panels[panelKey].setParent(stdscr)
panels[panelKey].setWidth(-1)
panels[panelKey].setTop(startY)
- startY += panels[panelKey].height
+ startY += panels[panelKey].getHeight()
panels["popup"].recreate(stdscr, 80, startY)
@@ -440,7 +443,7 @@
panels[panelKey].setParent(stdscr)
panels[panelKey].setWidth(-1)
panels[panelKey].setTop(tmpStartY)
- tmpStartY += panels[panelKey].height
+ tmpStartY += panels[panelKey].getHeight()
# if it's been at least ten seconds since the last BW event Tor's probably done
if not isUnresponsive and not panels["log"].controlPortClosed and panels["log"].getHeartbeat() >= 10:
@@ -508,6 +511,10 @@
hostnames.stop() # halts and joins on hostname worker thread pool
if resolver: resolver.join() # joins on halted resolver
+ # stops panel daemons
+ panels["header"].stop()
+ panels["header"].join()
+
conn.close() # joins on TorCtl event thread
break
elif key == curses.KEY_LEFT or key == curses.KEY_RIGHT:
Modified: arm/trunk/interface/cpuMemMonitor.py
===================================================================
--- arm/trunk/interface/cpuMemMonitor.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/interface/cpuMemMonitor.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -5,7 +5,7 @@
import time
from TorCtl import TorCtl
-from util import sysTools, uiTools
+from util import sysTools, torTools, uiTools
import graphPanel
class CpuMemMonitor(graphPanel.GraphStats, TorCtl.PostEventListener):
@@ -14,11 +14,10 @@
headerPanel if recent enough (otherwise retrieved independently).
"""
- def __init__(self, headerPanel):
+ def __init__(self):
graphPanel.GraphStats.__init__(self)
TorCtl.PostEventListener.__init__(self)
graphPanel.GraphStats.initialize(self, "green", "cyan", 10)
- self.headerPanel = headerPanel # header panel, used to limit ps calls
def bandwidth_event(self, event):
# doesn't use events but this keeps it in sync with the bandwidth panel
@@ -27,25 +26,28 @@
# compared to how frequently it changes - now caching for five seconds
# (note this during the rewrite that its fidelity isn't at the second
# level)
- if self.headerPanel.lastUpdate + 5 >= time.time():
- # reuses ps results if recent enough
- self._processEvent(float(self.headerPanel.vals["%cpu"]), float(self.headerPanel.vals["rss"]) / 1024.0)
+ # TODO: when rewritten raise fidelity to second level if being actively
+ # looked at (or has been recently)
+ # TODO: dropped header requirement so any documentation will, of course,
+ # need to be revised
+ torPid = torTools.getConn().getPid()
+
+ # cached results stale - requery ps
+ # TODO: issue the same request as header panel to take advantage of cached results
+ sampling = []
+ psCall = None
+ if torPid:
+ psCall = sysTools.call("ps -p %s -o %s" % (torPid, "%cpu,rss"), 5, True)
+ if psCall and len(psCall) >= 2: sampling = psCall[1].strip().split()
+
+ if len(sampling) < 2:
+ # either ps failed or returned no tor instance, register error
+ # ps call failed (returned no tor instance or registered an error) -
+ # we need to register something (otherwise timescale would be thrown
+ # off) so keep old results
+ self._processEvent(self.lastPrimary, self.lastSecondary)
else:
- # cached results stale - requery ps
- sampling = []
- psCall = None
- if self.headerPanel.vals["pid"]:
- psCall = sysTools.call("ps -p %s -o %s" % (self.headerPanel.vals["pid"], "%cpu,rss"), 5, True)
- if psCall and len(psCall) >= 2: sampling = psCall[1].strip().split()
-
- if len(sampling) < 2:
- # either ps failed or returned no tor instance, register error
- # ps call failed (returned no tor instance or registered an error) -
- # we need to register something (otherwise timescale would be thrown
- # off) so keep old results
- self._processEvent(self.lastPrimary, self.lastSecondary)
- else:
- self._processEvent(float(sampling[0]), float(sampling[1]) / 1024.0)
+ self._processEvent(float(sampling[0]), float(sampling[1]) / 1024.0)
def getTitle(self, width):
return "System Resources:"
Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/interface/headerPanel.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -1,272 +1,340 @@
-#!/usr/bin/env python
-# summaryPanel.py -- Static system and Tor related information.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+"""
+Top panel for every page, containing basic system and tor related information.
+If there's room available then this expands to present its information in two
+columns, otherwise it's laid out as follows:
+ arm - <hostname> (<os> <sys/version>) Tor <tor/version> (<new, old, recommended, etc>)
+ <nickname> - <address>:<orPort>, [Dir Port: <dirPort>, ]Control Port (<open, password, cookie>): <controlPort>
+ cpu: <cpu%> mem: <mem> (<mem%>) uid: <uid> uptime: <upmin>:<upsec>
+ fingerprint: <fingerprint>
+Example:
+ arm - odin (Linux 2.6.24-24-generic) Tor 0.2.1.19 (recommended)
+ odin - 76.104.132.98:9001, Dir Port: 9030, Control Port (cookie): 9051
+ cpu: 14.6% mem: 42 MB (4.2%) pid: 20060 uptime: 48:27
+ fingerprint: BDAD31F6F318E0413833E8EBDA956F76E4D66788
+"""
+
import os
import time
-import socket
-from TorCtl import TorCtl
+import threading
-from util import panel, sysTools, uiTools
+from util import conf, log, panel, sysTools, torTools, uiTools
+# seconds between querying information
+DEFAULT_UPDATE_RATE = 5
+UPDATE_RATE_CFG = "updateRate.header"
+
# minimum width for which panel attempts to double up contents (two columns to
# better use screen real estate)
-MIN_DUAL_ROW_WIDTH = 140
+MIN_DUAL_COL_WIDTH = 141
FLAG_COLORS = {"Authority": "white", "BadExit": "red", "BadDirectory": "red", "Exit": "cyan",
"Fast": "yellow", "Guard": "green", "HSDir": "magenta", "Named": "blue",
"Stable": "blue", "Running": "yellow", "Unnamed": "magenta", "Valid": "green",
"V2Dir": "cyan", "V3Dir": "white"}
-VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "recommended": "green", "old": "red",
- "obsolete": "red", "unrecommended": "red", "unknown": "cyan"}
+VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "obsolete": "red", "recommended": "green",
+ "old": "red", "unrecommended": "red", "unknown": "cyan"}
-class HeaderPanel(panel.Panel):
+class HeaderPanel(panel.Panel, threading.Thread):
"""
- Draws top area containing static information.
+ Top area contenting tor settings and system information. Stats are stored in
+ the vals mapping, keys including:
+ tor/ version, versionStatus, nickname, orPort, dirPort, controlPort,
+ exitPolicy, isAuthPassword (bool), isAuthCookie (bool)
+ *address, *fingerprint, *flags
+ sys/ hostname, os, version
+ ps/ *%cpu, *rss, *%mem, pid, *etime
- arm - <System Name> (<OS> <Version>) Tor <Tor Version>
- <Relay Nickname> - <IP Addr>:<ORPort>, [Dir Port: <DirPort>, ]Control Port (<open, password, cookie>): <ControlPort>
- cpu: <cpu%> mem: <mem> (<mem%>) uid: <uid> uptime: <upmin>:<upsec>
- fingerprint: <Fingerprint>
-
- Example:
- arm - odin (Linux 2.6.24-24-generic) Tor 0.2.1.15-rc
- odin - 76.104.132.98:9001, Dir Port: 9030, Control Port (cookie): 9051
- cpu: 14.6% mem: 42 MB (4.2%) pid: 20060 uptime: 48:27
- fingerprint: BDAD31F6F318E0413833E8EBDA956F76E4D66788
+ * volatile parameter that'll be reset on each update
"""
- def __init__(self, stdscr, conn, torPid):
- panel.Panel.__init__(self, stdscr, 0, 6)
- self.vals = {"pid": torPid} # mapping of information to be presented
- self.conn = conn # Tor control port connection
- self.isPaused = False
- self.isWide = False # doubles up parameters to shorten section if room's available
- self.rightParamX = 0 # offset used for doubled up parameters
- self.lastUpdate = -1 # time last stats was retrived
- self._updateParams()
- self.getPreferredSize() # hack to force properly initialize size (when using wide version)
+ def __init__(self, stdscr):
+ panel.Panel.__init__(self, stdscr, 0)
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+
+ # seconds between querying updates
+ try:
+ self._updateRate = int(conf.getConfig("arm").get(UPDATE_RATE_CFG, DEFAULT_UPDATE_RATE))
+ except ValueError:
+ # value wasn't an integer
+ log.log(log.WARN, "Config: %s is expected to be an integer (defaulting to %i)" % (UPDATE_RATE_CFG, DEFAULT_UPDATE_RATE))
+ self._updateRate = DEFAULT_UPDATE_RATE
+
+ self._lastUpdate = -1 # time the content was last revised
+ self._isLastDrawWide = False
+ self._isChanged = False # new stats to be drawn if true
+ self._isPaused = False # prevents updates if true
+ self._halt = False # terminates thread if true
+ self._cond = threading.Condition() # used for pausing the thread
+
+ self.vals = {}
+ self.valsLock = threading.RLock()
+ self._update(True)
+
+ # listens for tor reload (sighup) events
+ torTools.getConn().addStatusListener(self.resetListener)
- def getPreferredSize(self):
- # width partially determines height (panel has two layouts)
- panelHeight, panelWidth = panel.Panel.getPreferredSize(self)
- self.isWide = panelWidth >= MIN_DUAL_ROW_WIDTH
- self.rightParamX = max(panelWidth / 2, 75) if self.isWide else 0
- self.setHeight(4 if self.isWide else 6)
- return panel.Panel.getPreferredSize(self)
+ def getHeight(self):
+ """
+ Provides the height of the content, which is dynamically determined by the
+ panel's maximum width.
+ """
+
+ isWide = self.getParent().getmaxyx()[1] >= MIN_DUAL_COL_WIDTH
+ return 4 if isWide else 6
def draw(self, subwindow, width, height):
- if not self.isPaused: self._updateParams()
+ self.valsLock.acquire()
+ isWide = width + 1 >= MIN_DUAL_COL_WIDTH
- # TODO: remove after a few revisions if this issue can't be reproduced
- # (seemed to be a freak ui problem...)
+ # space available for content
+ if isWide:
+ leftWidth = max(width / 2, 77)
+ rightWidth = width - leftWidth
+ else: leftWidth = rightWidth = width
- # extra erase/refresh is needed to avoid internal caching screwing up and
- # refusing to redisplay content in the case of graphical glitches - probably
- # an obscure curses bug...
- #self.win.erase()
- #self.win.refresh()
+ # Line 1 / Line 1 Left (system and tor version information)
+ sysNameLabel = "arm - %s" % self.vals["sys/hostname"]
+ contentSpace = min(leftWidth, 40)
- #self.clear()
-
- # Line 1 (system and tor version information)
- systemNameLabel = "arm - %s " % self.vals["sys-name"]
- systemVersionLabel = "%s %s" % (self.vals["sys-os"], self.vals["sys-version"])
-
- # wraps systemVersionLabel in parentheses and truncates if too long
- versionLabelMaxWidth = 40 - len(systemNameLabel)
- if len(systemNameLabel) > 40:
- # we only have room for the system name label
- systemNameLabel = systemNameLabel[:39] + "..."
- systemVersionLabel = ""
- elif len(systemVersionLabel) > versionLabelMaxWidth:
- # not enough room to show full version
- systemVersionLabel = "(%s...)" % systemVersionLabel[:versionLabelMaxWidth - 3].strip()
+ if len(sysNameLabel) + 10 <= contentSpace:
+ sysTypeLabel = "%s %s" % (self.vals["sys/os"], self.vals["sys/version"])
+ sysTypeLabel = uiTools.cropStr(sysTypeLabel, contentSpace - len(sysNameLabel) - 3, 4)
+ self.addstr(0, 0, "%s (%s)" % (sysNameLabel, sysTypeLabel))
else:
- # enough room for everything
- systemVersionLabel = "(%s)" % systemVersionLabel
+ self.addstr(0, 0, uiTools.cropStr(sysNameLabel, contentSpace))
- self.addstr(0, 0, "%s%s" % (systemNameLabel, systemVersionLabel))
+ contentSpace = leftWidth - 43
+ if 7 + len(self.vals["tor/version"]) + len(self.vals["tor/versionStatus"]) <= contentSpace:
+ versionColor = VERSION_STATUS_COLORS[self.vals["tor/versionStatus"]] if \
+ self.vals["tor/versionStatus"] in VERSION_STATUS_COLORS else "white"
+ versionStatusMsg = "<%s>%s</%s>" % (versionColor, self.vals["tor/versionStatus"], versionColor)
+ self.addfstr(0, 43, "Tor %s (%s)" % (self.vals["tor/version"], versionStatusMsg))
+ elif 11 <= contentSpace:
+ self.addstr(0, 43, uiTools.cropStr("Tor %s" % self.vals["tor/version"], contentSpace, 4))
- versionStatus = self.vals["status/version/current"]
- versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
+ # Line 2 / Line 2 Left (tor ip/port information)
+ entry = ""
+ dirPortLabel = ", Dir Port: %s" % self.vals["tor/dirPort"] if self.vals["tor/dirPort"] != "0" else ""
+ for label in (self.vals["tor/nickname"], " - " + self.vals["tor/address"], ":" + self.vals["tor/orPort"], dirPortLabel):
+ if len(entry) + len(label) <= leftWidth: entry += label
+ else: break
- # truncates torVersionLabel if too long
- torVersionLabel = self.vals["version"]
- versionLabelMaxWidth = (self.rightParamX if self.isWide else width) - 51 - len(versionStatus)
- if len(torVersionLabel) > versionLabelMaxWidth:
- torVersionLabel = torVersionLabel[:versionLabelMaxWidth - 1].strip() + "-"
+ if self.vals["tor/isAuthPassword"]: authType = "password"
+ elif self.vals["tor/isAuthCookie"]: authType = "cookie"
+ else: authType = "open"
- self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (torVersionLabel, versionColor, versionStatus, versionColor))
+ if len(entry) + 19 + len(self.vals["tor/controlPort"]) + len(authType) <= leftWidth:
+ authColor = "red" if authType == "open" else "green"
+ authLabel = "<%s>%s</%s>" % (authColor, authType, authColor)
+ self.addfstr(1, 0, "%s, Control Port (%s): %s" % (entry, authLabel, self.vals["tor/controlPort"]))
+ elif len(entry) + 16 + len(self.vals["tor/controlPort"]) <= leftWidth:
+ self.addstr(1, 0, "%s, Control Port: %s" % (entry, self.vals["tor/controlPort"]))
+ else: self.addstr(1, 0, entry)
- # Line 2 (authentication label red if open, green if credentials required)
- dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
+ # Line 3 / Line 1 Right (system usage info)
+ y, x = (0, leftWidth) if isWide else (2, 0)
+ if self.vals["ps/rss"] != "0": memoryLabel = uiTools.getSizeLabel(int(self.vals["ps/rss"]) * 1024)
+ else: memoryLabel = "0"
- if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
- elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
- else: controlPortAuthLabel = "open"
- controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
+ sysFields = ((0, "cpu: %s%%" % self.vals["ps/%cpu"]),
+ (13, "mem: %s (%s%%)" % (memoryLabel, self.vals["ps/%mem"])),
+ (34, "pid: %s" % (self.vals["ps/pid"] if self.vals["ps/etime"] else "")),
+ (47, "uptime: %s" % self.vals["ps/etime"]))
- labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
- self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
+ for (start, label) in sysFields:
+ if start + len(label) <= rightWidth: self.addstr(y, x + start, label)
+ else: break
- # Line 3 (system usage info) - line 1 right if wide
- y, x = (0, self.rightParamX) if self.isWide else (2, 0)
- self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
- self.addstr(y, x + 13, "mem: %s (%s%%)" % (uiTools.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
- self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
- self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
+ # Line 4 / Line 2 Right (fingerprint)
+ y, x = (1, leftWidth) if isWide else (3, 0)
+ self.addstr(y, x, "fingerprint: %s" % self.vals["tor/fingerprint"])
- # Line 4 (fingerprint) - line 2 right if wide
- y, x = (1, self.rightParamX) if self.isWide else (3, 0)
- self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
-
- # Line 5 (flags) - line 3 left if wide
+ # Line 5 / Line 3 Left (flags)
flagLine = "flags: "
- for flag in self.vals["flags"]:
+ for flag in self.vals["tor/flags"]:
flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
- if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
- self.addfstr(2 if self.isWide else 4, 0, flagLine)
+ if len(self.vals["tor/flags"]) > 0: flagLine = flagLine[:-2]
+ self.addfstr(2 if isWide else 4, 0, flagLine)
- # Line 3 right (exit policy) - only present if wide
- if self.isWide:
- exitPolicy = self.vals["ExitPolicy"]
+ # Undisplayed / Line 3 Right (exit policy)
+ if isWide:
+ exitPolicy = self.vals["tor/exitPolicy"]
# adds note when default exit policy is appended
- # TODO: the following catch-all policies arne't quite exhaustive
if exitPolicy == None: exitPolicy = "<default>"
- elif not (exitPolicy.endswith("accept *:*") or exitPolicy.endswith("accept *")) and not (exitPolicy.endswith("reject *:*") or exitPolicy.endswith("reject *")):
- exitPolicy += ", <default>"
+ elif not exitPolicy.endswith((" *:*", " *")): exitPolicy += ", <default>"
+ # color codes accepts to be green, rejects to be red, and default marker to be cyan
+ isSimple = len(exitPolicy) > rightWidth - 13
policies = exitPolicy.split(", ")
-
- # color codes accepts to be green, rejects to be red, and default marker to be cyan
- # TODO: instead base this on if there's space available for the full verbose version
- isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
for i in range(len(policies)):
policy = policies[i].strip()
- displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
+ displayedPolicy = policy.replace("accept", "").replace("reject", "").strip() if isSimple else policy
if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
policies[i] = policy
- exitPolicy = ", ".join(policies)
- self.addfstr(2, self.rightParamX, "exit policy: %s" % exitPolicy)
+ self.addfstr(2, leftWidth, "exit policy: %s" % ", ".join(policies))
+
+ self._isLastDrawWide = isWide
+ self._isChanged = False
+ self.valsLock.release()
+ def redraw(self, forceRedraw=False, block=False):
+ # determines if the content needs to be redrawn or not
+ isWide = self.getParent().getmaxyx()[1] >= MIN_DUAL_COL_WIDTH
+ panel.Panel.redraw(self, forceRedraw or self._isChanged or isWide != self._isLastDrawWide, block)
+
def setPaused(self, isPause):
"""
If true, prevents updates from being presented.
"""
- self.isPaused = isPause
+ self._isPaused = isPause
- def _updateParams(self, forceReload = False):
+ def run(self):
"""
- Updates mapping of static Tor settings and system information to their
- corresponding string values. Keys include:
- info - version, *address, *fingerprint, *flags, status/version/current
- sys - sys-name, sys-os, sys-version
- ps - *%cpu, *rss, *%mem, *pid, *etime
- config - Nickname, ORPort, DirPort, ControlPort, ExitPolicy
- config booleans - IsPasswordAuthSet, IsCookieAuthSet, IsAccountingEnabled
+ Keeps stats updated, querying new information at a set rate.
+ """
- * volatile parameter that'll be reset (otherwise won't be checked if
- already set)
+ while not self._halt:
+ timeSinceReset = time.time() - self._lastUpdate
+
+ if self._isPaused or timeSinceReset < self._updateRate:
+ sleepTime = max(0.5, self._updateRate - timeSinceReset)
+ self._cond.acquire()
+ if not self._halt: self._cond.wait(sleepTime)
+ self._cond.release()
+ else:
+ self._update()
+ self.redraw()
+
+ def stop(self):
"""
+ Halts further resolutions and terminates the thread.
+ """
- infoFields = ["address", "fingerprint"] # keys for which get_info will be called
- if len(self.vals) <= 1 or forceReload:
- lookupFailed = False
+ self._cond.acquire()
+ self._halt = True
+ self._cond.notifyAll()
+ self._cond.release()
+
+ def resetListener(self, conn, eventType):
+ """
+ Updates static parameters on tor reload (sighup) events.
+
+ Arguments:
+ conn - tor controller
+ eventType - type of event detected
+ """
+
+ if eventType == torTools.TOR_RESET:
+ self._update(True)
+
+ def _update(self, setStatic=False):
+ """
+ Updates stats in the vals mapping. By default this just revises volatile
+ attributes.
+
+ Arguments:
+ setStatic - resets all parameters, including relatively static values
+ """
+
+ self.valsLock.acquire()
+ conn = torTools.getConn()
+
+ if setStatic:
+ # version is truncated to first part, for instance:
+ # 0.2.2.13-alpha (git-feb8c1b5f67f2c6f) -> 0.2.2.13-alpha
+ self.vals["tor/version"] = conn.getInfo("version", "Unknown").split()[0]
+ self.vals["tor/versionStatus"] = conn.getInfo("status/version/current", "Unknown")
+ self.vals["tor/nickname"] = conn.getOption("Nickname", "")
+ self.vals["tor/orPort"] = conn.getOption("ORPort", "")
+ self.vals["tor/dirPort"] = conn.getOption("DirPort", "0")
+ self.vals["tor/controlPort"] = conn.getOption("ControlPort", "")
+ self.vals["tor/isAuthPassword"] = conn.getOption("HashedControlPassword") != None
+ self.vals["tor/isAuthCookie"] = conn.getOption("CookieAuthentication") == "1"
- # first call (only contasns 'pid' mapping) - retrieve static params
- infoFields += ["version", "status/version/current"]
+ # fetch exit policy (might span over multiple lines)
+ policyEntries = []
+ for exitPolicy in conn.getOption("ExitPolicy", [], True):
+ policyEntries += [policy.strip() for policy in exitPolicy[1].split(",")]
+ self.vals["tor/exitPolicy"] = ", ".join(policyEntries)
- # populates with some basic system information
+ # system information
unameVals = os.uname()
- self.vals["sys-name"] = unameVals[1]
- self.vals["sys-os"] = unameVals[0]
- self.vals["sys-version"] = unameVals[2]
+ self.vals["sys/hostname"] = unameVals[1]
+ self.vals["sys/os"] = unameVals[0]
+ self.vals["sys/version"] = unameVals[2]
- try:
- # parameters from the user's torrc
- configFields = ["Nickname", "ORPort", "DirPort", "ControlPort"]
- self.vals.update(dict([(key, self.conn.get_option(key)[0][1]) for key in configFields]))
-
- # fetch exit policy (might span over multiple lines)
- exitPolicyEntries = []
- for (key, value) in self.conn.get_option("ExitPolicy"):
- if value: exitPolicyEntries.append(value)
-
- self.vals["ExitPolicy"] = ", ".join(exitPolicyEntries)
-
- # simply keeps booleans for if authentication info is set
- self.vals["IsPasswordAuthSet"] = not self.conn.get_option("HashedControlPassword")[0][1] == None
- self.vals["IsCookieAuthSet"] = self.conn.get_option("CookieAuthentication")[0][1] == "1"
- self.vals["IsAccountingEnabled"] = self.conn.get_info('accounting/enabled')['accounting/enabled'] == "1"
- except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): lookupFailed = True
+ pid = conn.getPid()
+ self.vals["ps/pid"] = pid if pid else ""
- if lookupFailed:
- # tor connection closed or gave error - keep old values if available, otherwise set to empty string / false
- for field in configFields:
- if field not in self.vals: self.vals[field] = ""
-
- for field in ["IsPasswordAuthSet", "IsCookieAuthSet", "IsAccountingEnabled"]:
- if field not in self.vals: self.vals[field] = False
-
- # gets parameters that throw errors if unavailable
- for param in infoFields:
- try: self.vals.update(self.conn.get_info(param))
- except TorCtl.ErrorReply: self.vals[param] = "Unknown"
- except (TorCtl.TorCtlClosed, socket.error):
- # Tor shut down or crashed - keep last known values
- if not param in self.vals.keys() or not self.vals[param]: self.vals[param] = "Unknown"
+ # reverts volatile parameters to defaults
+ self.vals["tor/address"] = "Unknown"
+ self.vals["tor/fingerprint"] = "Unknown"
+ self.vals["tor/flags"] = []
+ self.vals["ps/%cpu"] = "0"
+ self.vals["ps/rss"] = "0"
+ self.vals["ps/%mem"] = "0"
+ self.vals["ps/etime"] = ""
- # if ORListenAddress is set overwrites 'address' (and possibly ORPort)
- try:
- listenAddr = self.conn.get_option("ORListenAddress")[0][1]
- if listenAddr:
- if ":" in listenAddr:
- # both ip and port overwritten
- self.vals["address"] = listenAddr[:listenAddr.find(":")]
- self.vals["ORPort"] = listenAddr[listenAddr.find(":") + 1:]
- else:
- self.vals["address"] = listenAddr
- except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass
+ # sets volatile parameters
+ volatile = {}
+ volatile["tor/address"] = conn.getInfo("address", self.vals["tor/address"])
+ volatile["tor/fingerprint"] = conn.getInfo("fingerprint", self.vals["tor/fingerprint"])
- # flags held by relay
- self.vals["flags"] = []
- if self.vals["fingerprint"] != "Unknown":
- try:
- nsCall = self.conn.get_network_status("id/%s" % self.vals["fingerprint"])
- if nsCall: self.vals["flags"] = nsCall[0].flags
- else: raise TorCtl.ErrorReply # network consensus couldn't be fetched
- except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass
+ # overwrite address if ORListenAddress is set (and possibly orPort too)
+ listenAddr = conn.getOption("ORListenAddress")
+ if listenAddr:
+ if ":" in listenAddr:
+ # both ip and port overwritten
+ volatile["address"] = listenAddr[:listenAddr.find(":")]
+ volatile["orPort"] = listenAddr[listenAddr.find(":") + 1:]
+ else:
+ volatile["address"] = listenAddr
+ # sets flags
+ if self.vals["tor/fingerprint"] != "Unknown":
+ # network status contains a couple of lines, looking like:
+ # r caerSidi p1aag7VwarGxqctS7/fS0y5FU+s 9On1TRGCEpljszPpJR1hKqlzaY8 2010-05-26 09:26:06 76.104.132.98 9001 0
+ # s Fast HSDir Named Running Stable Valid
+ nsResults = conn.getInfo("ns/id/%s" % self.vals["tor/fingerprint"], "").split("\n")
+ if len(nsResults) >= 2: volatile["tor/flags"] = nsResults[1][2:].split()
+
+ # ps derived stats
psParams = ["%cpu", "rss", "%mem", "etime"]
- sampling = []
- if self.vals["pid"]:
- # ps call provides header followed by params for tor
- # this caches the results for five seconds and suppress any exceptions
- # results are expected to look something like:
+ if self.vals["ps/pid"]:
+ # if call fails then everything except etime are zeroed out (most likely
+ # tor's no longer running)
+ volatile["ps/%cpu"] = "0"
+ volatile["ps/rss"] = "0"
+ volatile["ps/%mem"] = "0"
+
+ # the ps call formats results as:
# %CPU RSS %MEM ELAPSED
# 0.3 14096 1.3 29:51
- psCall = sysTools.call("ps -p %s -o %s" % (self.vals["pid"], ",".join(psParams)), 5, True)
- if psCall and len(psCall) >= 2: sampling = psCall[1].strip().split()
-
- if len(sampling) < len(psParams):
- # pid is unknown, ps call failed, or returned no tor instance - blank information except runtime
- if "etime" in self.vals: sampling = [""] * (len(psParams) - 1) + [self.vals["etime"]]
- else: sampling = [""] * len(psParams)
+ psCall = sysTools.call("ps -p %s -o %s" % (self.vals["ps/pid"], ",".join(psParams)), self._updateRate, True)
- # %cpu, rss, and %mem are better zeroed out
- for i in range(3): sampling[i] = "0"
+ if psCall and len(psCall) >= 2:
+ stats = psCall[1].strip().split()
+
+ if len(stats) == len(psParams):
+ for i in range(len(psParams)):
+ volatile["ps/" + psParams[i]] = stats[i]
- for i in range(len(psParams)):
- self.vals[psParams[i]] = sampling[i]
+ # checks if any changes have been made and merges volatile into vals
+ self._isChanged |= setStatic
+ for key, val in volatile.items():
+ self._isChanged |= self.vals[key] != val
+ self.vals[key] = val
- self.lastUpdate = time.time()
+ self._lastUpdate = time.time()
+ self.valsLock.release()
Modified: arm/trunk/util/__init__.py
===================================================================
--- arm/trunk/util/__init__.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/util/__init__.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -4,5 +4,5 @@
and safely working with curses (hiding some of the gory details).
"""
-__all__ = ["connections", "hostnames", "log", "panel", "sysTools", "torTools", "uiTools"]
+__all__ = ["conf", "connections", "hostnames", "log", "panel", "sysTools", "torTools", "uiTools"]
Modified: arm/trunk/util/connections.py
===================================================================
--- arm/trunk/util/connections.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/util/connections.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -7,7 +7,7 @@
- lsof lsof -nPi | grep "<process>\s*<pid>.*(ESTABLISHED)"
all queries dump its stderr (directing it to /dev/null). Unfortunately FreeBSD
-lacks support for the needed netstat flags, and has a completely different
+lacks support for the needed netstat flags and has a completely different
program for 'ss', so this is quite likely to fail there.
"""
@@ -139,7 +139,7 @@
if __name__ == '__main__':
# quick method for testing connection resolution
- userInput = raw_input("Enter query (RESOLVER PROCESS_NAME [PID]: ").split()
+ userInput = raw_input("Enter query (<ss, netstat, lsof> PROCESS_NAME [PID]): ").split()
# checks if there's enough arguments
if len(userInput) == 0: sys.exit(0)
@@ -243,15 +243,22 @@
self._connections = [] # connection cache (latest results)
self._isPaused = False
self._halt = False # terminates thread if true
+ self._cond = threading.Condition() # used for pausing the thread
self._subsiquentFailures = 0 # number of failed resolutions with the default in a row
self._resolverBlacklist = [] # resolvers that have failed to resolve
def run(self):
while not self._halt:
minWait = self.resolveRate if self.resolveRate else self.defaultRate
+ timeSinceReset = time.time() - self.lastLookup
- if self._isPaused or time.time() - self.lastLookup < minWait:
- time.sleep(RESOLVER_SLEEP_INTERVAL)
+ if self._isPaused or timeSinceReset < minWait:
+ sleepTime = max(0.2, minWait - timeSinceReset)
+
+ self._cond.acquire()
+ if not self._halt: self._cond.wait(sleepTime)
+ self._cond.release()
+
continue # done waiting, try again
isDefault = self.overwriteResolver == None
@@ -331,5 +338,8 @@
Halts further resolutions and terminates the thread.
"""
+ self._cond.acquire()
self._halt = True
+ self._cond.notifyAll()
+ self._cond.release()
Modified: arm/trunk/util/hostnames.py
===================================================================
--- arm/trunk/util/hostnames.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/util/hostnames.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -75,7 +75,7 @@
resolverRef, RESOLVER = RESOLVER, None
# joins on its worker thread pool
- resolverRef.halt = True
+ resolverRef.stop()
for t in resolverRef.threadPool: t.join()
RESOLVER_LOCK.release()
@@ -251,6 +251,7 @@
self.totalResolves = 0 # counter for the total number of addresses queried to be resolved
self.isPaused = False # prevents further resolutions if true
self.halt = False # if true, tells workers to stop
+ self.cond = threading.Condition() # used for pausing threads
# Determines if resolutions are made using os 'host' calls or python's
# 'socket.gethostbyaddr'. The following checks if the system has the
@@ -312,6 +313,16 @@
return None # timeout reached without resolution
+ def stop(self):
+ """
+ Halts further resolutions and terminates the thread.
+ """
+
+ self.cond.acquire()
+ self.halt = True
+ self.cond.notifyAll()
+ self.cond.release()
+
def _workerLoop(self):
"""
Simple producer-consumer loop followed by worker threads. This takes
@@ -322,13 +333,21 @@
while not self.halt:
# if resolver is paused then put a hold on further resolutions
- while self.isPaused and not self.halt: time.sleep(0.25)
- if self.halt: break
+ if self.isPaused:
+ self.cond.acquire()
+ if not self.halt: self.cond.wait(1)
+ self.cond.release()
+ continue
# snags next available ip, timeout is because queue can't be woken up
# when 'halt' is set
- try: ipAddr = self.unresolvedQueue.get(True, 0.25)
- except Queue.Empty: continue
+ try: ipAddr = self.unresolvedQueue.get_nowait()
+ except Queue.Empty:
+ # no elements ready, wait a little while and try again
+ self.cond.acquire()
+ if not self.halt: self.cond.wait(1)
+ self.cond.release()
+ continue
if self.halt: break
try:
Modified: arm/trunk/util/panel.py
===================================================================
--- arm/trunk/util/panel.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/util/panel.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -148,15 +148,18 @@
"""
newHeight, newWidth = self.parent.getmaxyx()
+ setHeight, setWidth = self.getHeight(), self.getWidth()
newHeight = max(0, newHeight - self.top)
- if self.height != -1: newHeight = min(self.height, newHeight)
- if self.width != -1: newWidth = min(self.width, newWidth)
+ if setHeight != -1: newHeight = min(newHeight, setHeight)
+ if setWidth != -1: newWidth = min(newWidth, setWidth)
return (newHeight, newWidth)
def draw(self, subwindow, width, height):
"""
Draws display's content. This is meant to be overwritten by
- implementations and not called directly (use redraw() instead).
+ implementations and not called directly (use redraw() instead). The
+ dimensions provided are the drawable dimensions, which in terms of width is
+ a column less than the actual space.
Arguments:
sudwindow - panel's current subwindow instance, providing raw access to
@@ -167,15 +170,16 @@
pass
- def redraw(self, refresh=False, block=False):
+ def redraw(self, forceRedraw=True, block=False):
"""
- Clears display and redraws its content.
+ Clears display and redraws its content. This can skip redrawing content if
+ able (ie, the subwindow's unchanged), instead just refreshing the display.
Arguments:
- refresh - skips redrawing content if able (ie, the subwindow's
- unchanged), instead just refreshing the display
- block - if drawing concurrently with other panels this determines if
- the request is willing to wait its turn or should be abandoned
+ forceRedraw - forces the content to be cleared and redrawn if true
+ block - if drawing concurrently with other panels this determines
+ if the request is willing to wait its turn or should be
+ abandoned
"""
# if the panel's completely outside its parent then this is a no-op
@@ -195,13 +199,14 @@
subwinMaxY, subwinMaxX = self.win.getmaxyx()
if isNewWindow or subwinMaxY != self.maxY or subwinMaxX != self.maxX:
- refresh = False
+ forceRedraw = True
self.maxY, self.maxX = subwinMaxY, subwinMaxX
if not CURSES_LOCK.acquire(block): return
try:
- self.win.erase() # clears any old contents
- if not refresh: self.draw(self.win, self.maxX, self.maxY)
+ if forceRedraw:
+ self.win.erase() # clears any old contents
+ self.draw(self.win, self.maxX - 1, self.maxY)
self.win.refresh()
finally:
CURSES_LOCK.release()
Modified: arm/trunk/util/torTools.py
===================================================================
--- arm/trunk/util/torTools.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/util/torTools.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -4,7 +4,7 @@
fetch a TorCtl instance to experiment with use the following:
>>> import util.torTools
->>> conn = util.torTools.makeConn()
+>>> conn = util.torTools.connect()
>>> conn.get_info("version")["version"]
'0.2.1.24'
"""
@@ -143,7 +143,7 @@
if issue: raise IOError("Failed to read authentication cookie (%s): %s" % (issue, authVal))
else: raise IOError("Failed to read authentication cookie: %s" % exc)
-def makeConn(controlAddr="127.0.0.1", controlPort=9051, passphrase=None):
+def connect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None):
"""
Convenience method for quickly getting a TorCtl connection. This is very
handy for debugging or CLI setup, handling setup and prompting for a password
@@ -271,9 +271,9 @@
"""
if conn.is_live() and conn != self.conn:
+ self.connLock.acquire()
+
if self.conn: self.close() # shut down current connection
-
- self.connLock.acquire()
self.conn = conn
self.conn.add_event_listener(self)
Modified: arm/trunk/util/uiTools.py
===================================================================
--- arm/trunk/util/uiTools.py 2010-05-31 15:17:34 UTC (rev 22446)
+++ arm/trunk/util/uiTools.py 2010-05-31 22:41:07 UTC (rev 22447)
@@ -5,6 +5,7 @@
- unit conversion for labels
"""
+import sys
import curses
# colors curses can handle
@@ -42,6 +43,59 @@
if not COLOR_ATTR_INITIALIZED: _initColors()
return COLOR_ATTR[color]
+def cropStr(msg, size, minWordLen = 4, addEllipse = True):
+ """
+ Provides the msg constrained to the given length, truncating on word breaks.
+ If the last words is long this truncates mid-word with an ellipse. If there
+ isn't room for even a truncated single word (or one word plus the ellipse if
+ inlcuding those) then this provides an empty string. Examples:
+
+ cropStr("This is a looooong message", 17)
+ "This is a looo..."
+
+ cropStr("This is a looooong message", 12)
+ "This is a..."
+
+ cropStr("This is a looooong message", 3)
+ ""
+
+ Arguments:
+ msg - source text
+ size - room available for text
+ minWordLen - minimum characters before which a word is dropped, requires
+ whole word if -1
+ addEllipse - includes an ellipse when truncating if true (dropped if size
+ size is
+ """
+
+ if minWordLen < 0: minWordLen = sys.maxint
+
+ if len(msg) <= size: return msg
+ else:
+ msgWords = msg.split(" ")
+ msgWords.reverse()
+
+ returnWords = []
+ sizeLeft = size - 3 if addEllipse else size
+
+ # checks that there's room for at least one word
+ if min(minWordLen, len(msgWords[-1])) > sizeLeft: return ""
+
+ while sizeLeft > 0:
+ nextWord = msgWords.pop()
+
+ if len(nextWord) <= sizeLeft:
+ returnWords.append(nextWord)
+ sizeLeft -= (len(nextWord) + 1)
+ elif minWordLen <= sizeLeft:
+ returnWords.append(nextWord[:sizeLeft])
+ sizeLeft = 0
+ else: sizeLeft = 0
+
+ returnMsg = " ".join(returnWords)
+ if addEllipse: returnMsg += "..."
+ return returnMsg
+
def getSizeLabel(bytes, decimal = 0, isLong = False):
"""
Converts byte count into label in its most significant units, for instance
More information about the tor-commits
mailing list