[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r20927: {arm} This will be the last update for a while since I'm about to (in arm/trunk: . interface)
Author: atagar
Date: 2009-11-08 22:51:58 -0500 (Sun, 08 Nov 2009)
New Revision: 20927
Added:
arm/trunk/interface/fileDescriptorPopup.py
Modified:
arm/trunk/ChangeLog
arm/trunk/README
arm/trunk/TODO
arm/trunk/arm.py
arm/trunk/interface/confPanel.py
arm/trunk/interface/connPanel.py
arm/trunk/interface/controller.py
arm/trunk/interface/hostnameResolver.py
Log:
This will be the last update for a while since I'm about to start a new job.
added: including family relays on connections listing
added: file descriptors dialog (stats and scrollable listing)
change: logs warning if torrc fails to load
fix: size and time labels used in torrc are expanded for validation
fix: duplicate torrc entries weren't being detected if not erroneous
fix: crashing issue when cleaning up hostname cache
fix: stretching connection lines to fill full screen
Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/ChangeLog 2009-11-09 03:51:58 UTC (rev 20927)
@@ -1,6 +1,17 @@
CHANGE LOG
-10/21/09 - version 1.2.1
+11/8/09 - version 1.2.2
+This will be the last update for a while since I'm about to start a new job.
+
+ * added: including family relays on connections listing
+ * added: file descriptors dialog (stats and scrollable listing)
+ * change: logs warning if torrc fails to load
+ * fix: size and time labels used in torrc are expanded for validation
+ * fix: duplicate torrc entries weren't being detected if not erroneous
+ * fix: crashing issue when cleaning up hostname cache
+ * fix: stretching connection lines to fill full screen
+
+10/21/09 - version 1.2.1 (r20814)
Substantial bundle of changes including torrc validation, improved arm event logging, and numerous bug fixes.
* added: verifies loaded torrc consistency against tor's actual state (gives warning and providing corrections)
Modified: arm/trunk/README
===================================================================
--- arm/trunk/README 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/README 2009-11-09 03:51:58 UTC (rev 20927)
@@ -21,10 +21,11 @@
Requirements:
Python 2.5
TorCtl (retrieved in svn checkout)
-Common *nix commands including: ps, pidof, tail, host, and netstat
Tor is running with an available control port. This means either...
... starting Tor with '--controlport <PORT>'
... or including 'ControlPort <PORT>' in your torrc
+For full functionality this requires common *nix commands including: ps, pidof,
+ tail, pwdx, host, netstat, lsof, and ulimit
This is started via 'arm' (use the '--help' argument for usage).
Modified: arm/trunk/TODO
===================================================================
--- arm/trunk/TODO 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/TODO 2009-11-09 03:51:58 UTC (rev 20927)
@@ -38,6 +38,7 @@
if set and there's extra room available show 'MaxAdvertisedBandwidth'
* when help popup is showing options let them be directly opened
requested by arma
+ * check family connections to see if they're alive (VERSION cell handshake?)
* update site's screenshots (pretty out of date...)
- Ideas (low priority)
Modified: arm/trunk/arm.py
===================================================================
--- arm/trunk/arm.py 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/arm.py 2009-11-09 03:51:58 UTC (rev 20927)
@@ -19,8 +19,8 @@
from interface import controller
from interface import logPanel
-VERSION = "1.2.1"
-LAST_MODIFIED = "Oct 21, 2009"
+VERSION = "1.2.2"
+LAST_MODIFIED = "Nov 8, 2009"
DEFAULT_CONTROL_ADDR = "127.0.0.1"
DEFAULT_CONTROL_PORT = 9051
Modified: arm/trunk/interface/confPanel.py
===================================================================
--- arm/trunk/interface/confPanel.py 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/interface/confPanel.py 2009-11-09 03:51:58 UTC (rev 20927)
@@ -12,6 +12,18 @@
# last updated for tor version 0.2.1.19
MULTI_LINE_PARAM = ["AlternateBridgeAuthority", "AlternateDirAuthority", "AlternateHSAuthority", "AuthDirBadDir", "AuthDirBadExit", "AuthDirInvalid", "AuthDirReject", "Bridge", "ControlListenAddress", "ControlSocket", "DirListenAddress", "DirPolicy", "DirServer", "DNSListenAddress", "ExitPolicy", "HashedControlPassword", "HiddenServiceDir", "HiddenServiceOptions", "HiddenServicePort", "HiddenServiceVersion", "HiddenServiceAuthorizeClient", "HidServAuth", "Log", "MapAddress", "NatdListenAddress", "NodeFamily", "ORListenAddress", "ReachableAddresses", "ReachableDirAddresses", "ReachableORAddresses", "RecommendedVersions", "RecommendedClientVersions", "RecommendedServerVersions", "SocksListenAddress", "SocksPolicy", "TransListenAddress", "__HashedControlSessionPassword"]
+# size modifiers allowed by config.c
+LABEL_KB = ["kb", "kbyte", "kbytes", "kilobyte", "kilobytes"]
+LABEL_MB = ["m", "mb", "mbyte", "mbytes", "megabyte", "megabytes"]
+LABEL_GB = ["gb", "gbyte", "gbytes", "gigabyte", "gigabytes"]
+LABEL_TB = ["tb", "terabyte", "terabytes"]
+
+# time modifiers allowed by config.c
+LABEL_MIN = ["minute", "minutes"]
+LABEL_HOUR = ["hour", "hours"]
+LABEL_DAY = ["day", "days"]
+LABEL_WEEK = ["week", "weeks"]
+
class ConfPanel(util.Panel):
"""
Presents torrc with syntax highlighting in a scroll-able area.
@@ -48,7 +60,7 @@
# checks if torrc differs from get_option data
self.irrelevantLines = []
self.corrections = {}
- correctedCmd = {} # mapping of corrected commands to line numbers
+ parsedCommands = {} # mapping of parsed commands to line numbers
for lineNumber in range(len(self.confContents)):
lineText = self.confContents[lineNumber].strip()
@@ -60,12 +72,29 @@
if argEnd == -1: argEnd = len(lineText)
command, argument = lineText[:ctlEnd], lineText[ctlEnd:argEnd].strip()
+ # expands value if it's a size or time
+ comp = argument.strip().lower().split(" ")
+ if len(comp) > 1:
+ size = 0
+ if comp[1] in LABEL_KB: size = int(comp[0]) * 1024
+ elif comp[1] in LABEL_MB: size = int(comp[0]) * 1048576
+ elif comp[1] in LABEL_GB: size = int(comp[0]) * 1073741824
+ elif comp[1] in LABEL_TB: size = int(comp[0]) * 1099511627776
+ elif comp[1] in LABEL_MIN: size = int(comp[0]) * 60
+ elif comp[1] in LABEL_HOUR: size = int(comp[0]) * 3600
+ elif comp[1] in LABEL_DAY: size = int(comp[0]) * 86400
+ elif comp[1] in LABEL_WEEK: size = int(comp[0]) * 604800
+ if size != 0: argument = str(size)
+
# most parameters are overwritten if defined multiple times, if so
# it's erased from corrections and noted as duplicate instead
- if not command in MULTI_LINE_PARAM and command in correctedCmd.keys():
- self.irrelevantLines.append(correctedCmd[command])
- del self.corrections[correctedCmd[command]]
+ if not command in MULTI_LINE_PARAM and command in parsedCommands.keys():
+ previousLineNum = parsedCommands[command]
+ self.irrelevantLines.append(previousLineNum)
+ if previousLineNum in self.corrections.keys(): del self.corrections[previousLineNum]
+ parsedCommands[command] = lineNumber + 1
+
# check validity against tor's actual state
try:
actualValues = []
@@ -73,8 +102,7 @@
actualValues.append(val)
if not argument in actualValues:
- self.corrections[lineNumber + 1] = ", ".join(actualValues)
- correctedCmd[command] = lineNumber + 1
+ self.corrections[lineNumber + 1] = argument + " - " + ", ".join(actualValues)
except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
pass # unable to load tor parameter to validate... weird
@@ -87,8 +115,9 @@
self.logger.monitor_event("NOTICE", "%s: %s (highlighted in blue)" % (baseMsg, ", ".join([str(val) for val in self.irrelevantLines])))
if self.corrections:
self.logger.monitor_event("WARN", "Tor's state differs from loaded torrc")
- except IOError:
+ except IOError, exc:
self.confContents = ["### Unable to load torrc ###"]
+ self.logger.monitor_event("WARN", "Unable to load torrc (%s)" % str(exc))
self.scroll = 0
def handleKey(self, key):
Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/interface/connPanel.py 2009-11-09 03:51:58 UTC (rev 20927)
@@ -25,8 +25,8 @@
LIST_LABEL = {LIST_IP: "IP Address", LIST_HOSTNAME: "Hostname", LIST_FINGERPRINT: "Fingerprint", LIST_NICKNAME: "Nickname"}
# attributes for connection types
-TYPE_COLORS = {"inbound": "green", "outbound": "blue", "client": "cyan", "directory": "magenta", "control": "red", "localhost": "yellow"}
-TYPE_WEIGHTS = {"inbound": 0, "outbound": 1, "client": 2, "directory": 3, "control": 4, "localhost": 5} # defines ordering
+TYPE_COLORS = {"inbound": "green", "outbound": "blue", "client": "cyan", "directory": "magenta", "control": "red", "family": "magenta", "localhost": "yellow"}
+TYPE_WEIGHTS = {"inbound": 0, "outbound": 1, "client": 2, "directory": 3, "control": 4, "family": 5, "localhost": 6} # defines ordering
# enums for indexes of ConnPanel 'connections' fields
CONN_TYPE, CONN_L_IP, CONN_L_PORT, CONN_F_IP, CONN_F_PORT, CONN_COUNTRY, CONN_TIME = range(7)
@@ -112,8 +112,8 @@
self.localhostEntry = None # special connection - tuple with (entry for this node, fingerprint)
self.sortOrdering = [ORD_TYPE, ORD_FOREIGN_LISTING, ORD_FOREIGN_PORT]
self.resolver = hostnameResolver.HostnameResolver()
- self.fingerprintLookupCache = {} # chache of (ip, port) -> fingerprint
- self.nicknameLookupCache = {} # chache of (ip, port) -> nickname
+ self.fingerprintLookupCache = {} # cache of (ip, port) -> fingerprint
+ self.nicknameLookupCache = {} # cache of (ip, port) -> nickname
self.fingerprintMappings = _getFingerprintMappings(self.conn) # mappings of ip -> [(port, fingerprint, nickname), ...]
self.providedGeoipWarning = False
self.orconnStatusCache = [] # cache for 'orconn-status' calls
@@ -132,11 +132,17 @@
self.pauseTime = 0 # time when paused
self.connectionsBuffer = [] # location where connections are stored while paused
self.connectionCountBuffer = []
+ self.familyResolutionsBuffer = {}
+ # mapping of ip/port to fingerprint of family entries, used in hack to short circuit (ip / port) -> fingerprint lookups
+ self.familyResolutions = {}
+
self.nickname = ""
self.orPort = "0"
self.dirPort = "0"
self.controlPort = "0"
+ self.family = [] # fingerpints of family entries
+
self.resetOptions()
# netstat results are tuples of the form:
@@ -150,6 +156,8 @@
self.reset()
def resetOptions(self):
+ self.familyResolutions = {}
+
try:
self.nickname = self.conn.get_option("Nickname")[0][1]
@@ -157,11 +165,17 @@
self.orPort = self.conn.get_option("ORPort")[0][1]
self.dirPort = self.conn.get_option("DirPort")[0][1]
self.controlPort = self.conn.get_option("ControlPort")[0][1]
+
+ # entry is None if not set, otherwise of the format "$<fingerprint>,$<fingerprint>"
+ familyEntry = self.conn.get_option("MyFamily")[0][1]
+ if familyEntry: self.family = [entry[1:] for entry in familyEntry.split(",")]
+ else: self.family = []
except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
self.nickname = ""
self.orPort = "0"
self.dirPort = "0"
self.controlPort = "0"
+ self.family = []
# change in client circuits
def circ_status_event(self, event):
@@ -227,6 +241,7 @@
# temporary variables for connections and count
connectionsTmp = []
connectionCountTmp = [0] * 5
+ familyResolutionsTmp = {}
try:
if self.clientConnectionCache == None:
@@ -309,15 +324,40 @@
else:
self.localhostEntry = None
+ # appends family connections
+ tmpCounter = 0 # used for unique port of unresolved family entries (funky hack)
+ for fingerprint in self.family:
+ try:
+ nsCommand = "ns/id/%s" % fingerprint
+ familyInfo = self.conn.get_info(nsCommand)[nsCommand].split()
+ familyAddress, familyPort = familyInfo[6], familyInfo[7]
+
+ countryCodeQuery = "ip-to-country/%s" % familyAddress
+ familyCountryCode = self.conn.get_info(countryCodeQuery)[countryCodeQuery]
+
+ if (familyAddress, familyPort) in connTimes: connTime = connTimes[(familyAddress, familyPort)]
+ else: connTime = time.time()
+
+ familyResolutionsTmp[(familyAddress, familyPort)] = fingerprint
+ connectionsTmp.append(("family", familyAddress, familyPort, familyAddress, familyPort, familyCountryCode, connTime))
+ except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
+ # use dummy entry for sorting - the redraw function notes that entries are unknown
+ portIdentifier = str(65536 + tmpCounter)
+ familyResolutionsTmp[("256.255.255.255", portIdentifier)] = fingerprint
+ connectionsTmp.append(("family", "256.255.255.255", portIdentifier, "256.255.255.255", portIdentifier, "??", time.time()))
+ tmpCounter += 1
+
self.lastUpdate = time.time()
# assigns results
if self.isPaused:
self.connectionsBuffer = connectionsTmp
self.connectionCountBuffer = connectionCountTmp
+ self.familyResolutionsBuffer = familyResolutionsTmp
else:
self.connections = connectionsTmp
self.connectionCount = connectionCountTmp
+ self.familyResolutions = familyResolutionsTmp
# hostnames are sorted at redraw - otherwise now's a good time
if self.listingType != LIST_HOSTNAME: self.sortConnections()
@@ -510,8 +550,27 @@
etc += "%-26s " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
dst = ("%%-%is" % foreignNicknameSpace) % dst
+
+ timeLabel = util.getTimeLabel(currentTime - entry[CONN_TIME], 1)
if type == "inbound": src, dst = dst, src
- lineEntry = "<%s>%s --> %s %s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, util.getTimeLabel(currentTime - entry[CONN_TIME], 1), type.upper(), " " * (9 - len(type)), color)
+ elif type == "family" and int(entry[CONN_L_PORT]) > 65535:
+ # this belongs to an unresolved family entry - replaces invalid data with "UNKNOWN"
+ timeLabel = "---"
+
+ if self.listingType == LIST_IP:
+ src = "%-21s" % "UNKNOWN"
+ dst = "%-26s" % "UNKNOWN"
+ elif self.listingType == LIST_HOSTNAME:
+ src = "%-15s" % "UNKNOWN"
+ dst = ("%%-%is" % len(dst)) % "UNKNOWN"
+ if len(etc) > 0: etc = etc.replace("256.255.255.255 (??)", "UNKNOWN" + " " * 13)
+ else:
+ ipStart = etc.find("256")
+ if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
+
+ padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) # padding needed to fill full line
+ lineEntry = "<%s>%s --> %s %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
+
if self.isCursorEnabled and entry == self.cursorSelection:
lineEntry = "<h>%s</h>" % lineEntry
@@ -540,6 +599,10 @@
if self.localhostEntry and ipAddr == self.localhostEntry[0][CONN_L_IP] and port == self.localhostEntry[0][CONN_L_PORT]:
return self.localhostEntry[1]
+ # checks if this belongs to a family entry
+ if (ipAddr, port) in self.familyResolutions.keys():
+ return self.familyResolutions[(ipAddr, port)]
+
port = int(port)
if (ipAddr, port) in self.fingerprintLookupCache:
return self.fingerprintLookupCache[(ipAddr, port)]
@@ -631,9 +694,11 @@
self.pauseTime = time.time()
self.connectionsBuffer = list(self.connections)
self.connectionCountBuffer = list(self.connectionCount)
+ self.familyResolutionsBuffer = dict(self.familyResolutions)
else:
self.connections = list(self.connectionsBuffer)
self.connectionCount = list(self.connectionCountBuffer)
+ self.familyResolutions = dict(self.familyResolutionsBuffer)
# pause buffer connections may be unsorted
if self.listingType != LIST_HOSTNAME: self.sortConnections()
Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/interface/controller.py 2009-11-09 03:51:58 UTC (rev 20927)
@@ -22,6 +22,7 @@
import connPanel
import confPanel
import descriptorPopup
+import fileDescriptorPopup
import util
import connResolver
@@ -503,15 +504,16 @@
popup.addfstr(1, 2, "s: graphed stats (<b>%s</b>)" % graphedStats)
popup.addfstr(1, 41, "i: graph update interval (<b>%s</b>)" % panels["graph"].updateInterval)
popup.addfstr(2, 2, "b: graph bounds (<b>%s</b>)" % graphPanel.BOUND_LABELS[panels["graph"].bounds])
- popup.addstr(2, 41, "e: change logged events")
+ popup.addstr(2, 41, "d: file descriptors")
+ popup.addstr(3, 2, "e: change logged events")
- regexLabel = "enabled" if panels["log"].regexFilter else "disabled"
- popup.addfstr(3, 2, "f: log regex filter (<b>%s</b>)" % regexLabel)
-
runlevelEventsLabel = "arm and tor"
if panels["log"].runlevelTypes == logPanel.RUNLEVEL_TOR_ONLY: runlevelEventsLabel = "tor only"
elif panels["log"].runlevelTypes == logPanel.RUNLEVEL_ARM_ONLY: runlevelEventsLabel = "arm only"
popup.addfstr(3, 41, "r: logged runlevels (<b>%s</b>)" % runlevelEventsLabel)
+
+ regexLabel = "enabled" if panels["log"].regexFilter else "disabled"
+ popup.addfstr(4, 2, "f: log regex filter (<b>%s</b>)" % regexLabel)
if page == 1:
popup.addstr(1, 2, "up arrow: scroll up a line")
popup.addstr(1, 41, "down arrow: scroll down a line")
@@ -608,6 +610,19 @@
elif page == 0 and (key == ord('b') or key == ord('B')):
# uses the next boundary type for graph
panels["graph"].bounds = (panels["graph"].bounds + 1) % 2
+ elif page == 0 and key in (ord('d'), ord('D')):
+ # provides popup with file descriptors
+ cursesLock.acquire()
+ try:
+ setPauseState(panels, isPaused, page, True)
+ curses.cbreak() # wait indefinitely for key presses (no timeout)
+
+ fileDescriptorPopup.showFileDescriptorPopup(panels["popup"], stdscr, torPid)
+
+ setPauseState(panels, isPaused, page)
+ curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
+ finally:
+ cursesLock.release()
elif page == 0 and (key == ord('e') or key == ord('E')):
# allow user to enter new types of events to log - unchanged if left blank
cursesLock.acquire()
@@ -791,8 +806,13 @@
selectedIp = selection[connPanel.CONN_F_IP]
selectedPort = selection[connPanel.CONN_F_PORT]
+
addrLabel = "address: %s:%s" % (selectedIp, selectedPort)
+ if selection[connPanel.CONN_TYPE] == "family" and int(selection[connPanel.CONN_L_PORT]) > 65535:
+ # unresolved family entry - unknown ip/port
+ addrLabel = "address: unknown"
+
hostname = resolver.resolve(selectedIp)
if hostname == None:
if resolver.isPaused: hostname = "DNS resolution disallowed"
Added: arm/trunk/interface/fileDescriptorPopup.py
===================================================================
--- arm/trunk/interface/fileDescriptorPopup.py (rev 0)
+++ arm/trunk/interface/fileDescriptorPopup.py 2009-11-09 03:51:58 UTC (rev 20927)
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+# fileDescriptorPopup.py -- provides open file descriptor stats and listing
+# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+
+import os
+import curses
+
+import util
+
+class PopupProperties:
+ """
+ State attributes of popup window for file descriptors. Any problem in system
+ calls will cause 'errorMsg' to be set (providing the notice rather than
+ displaying data). Under systems other than Solaris there's no way for a
+ process (other than tor itself) to know its file descriptor limit, so this
+ estimates.
+ """
+
+ def __init__(self, torPid):
+ self.fdFile, self.fdConn, self.fdMisc = [], [], []
+ self.fdLimit = 0
+ self.errorMsg = ""
+ self.scroll = 0
+
+ try:
+ ulimitCall = None
+
+ # retrieves list of open files, options are:
+ # n = no dns lookups, p = by pid, -F = show fields (L = login name, n = opened files)
+ lsofCall = os.popen("lsof -np %s -F Ln 2> /dev/null" % torPid)
+ results = lsofCall.readlines()
+ if len(results) == 0: raise Exception("lsof is unavailable")
+ torUser = results[1][1:]
+ results = results[2:] # skip first couple lines (pid listing and user)
+
+ # splits descriptors into buckets according to their type
+ descriptors = [entry[1:].strip() for entry in results] # strips off first character (always an 'n')
+
+ for desc in descriptors:
+ if os.path.exists(desc): self.fdFile.append(desc)
+ elif desc[0] != "/" and ":" in desc: self.fdConn.append(desc)
+ else: self.fdMisc.append(desc)
+
+ self.fdFile.sort()
+ self.fdConn.sort()
+ self.fdMisc.sort()
+
+ # This is guessing the open file limit. Unfortunately there's no way
+ # (other than "/usr/proc/bin/pfiles pid | grep rlimit" under Solaris) to
+ # get the file descriptor limit for an arbitrary process. What we need is
+ # for the tor process to provide the return value of the "getrlimit"
+ # function via a GET_INFO call.
+ if torUser == "debian-tor":
+ # probably loaded via /etc/init.d/tor which changes descriptor limit
+ self.fdLimit = 8192
+ else:
+ # uses ulimit to estimate (-H is for hard limit, which is what tor uses)
+ ulimitCall = os.popen("ulimit -Hn 2> /dev/null")
+ results = ulimitCall.readlines()
+ if len(results) == 0: raise Exception("ulimit is unavailable")
+ self.fdLimit = int(results[0])
+ except Exception, exc:
+ # problem arose in calling or parsing lsof or ulimit calls
+ self.errorMsg = "error: " + str(exc)
+ finally:
+ lsofCall.close()
+ if ulimitCall: ulimitCall.close()
+
+ def handleKey(self, key, height):
+ totalEntries = len(self.fdFile) + len(self.fdConn) + len(self.fdMisc)
+
+ if key == curses.KEY_UP: self.scroll = max(self.scroll - 1, 0)
+ elif key == curses.KEY_DOWN: self.scroll = max(0, min(self.scroll + 1, totalEntries - height))
+ elif key == curses.KEY_PPAGE: self.scroll = max(self.scroll - height, 0)
+ elif key == curses.KEY_NPAGE: self.scroll = max(0, min(self.scroll + height, totalEntries - height))
+
+def showFileDescriptorPopup(popup, stdscr, torPid):
+ """
+ Presents open file descriptors in popup window with the following controls:
+ Up, Down, Page Up, Page Down - scroll descriptors
+ Any other key - close popup
+ """
+
+ properties = PopupProperties(torPid)
+
+ if not popup.lock.acquire(False): return
+ try:
+ if properties.errorMsg:
+ popupWidth = len(properties.errorMsg) + 4
+ popupHeight = 3
+ else:
+ # uses longest entry to determine popup width
+ popupWidth = 40 # minimum width
+ for entry in properties.fdFile + properties.fdConn + properties.fdMisc:
+ popupWidth = max(popupWidth, len(entry) + 4)
+
+ popupHeight = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
+
+ popup._resetBounds()
+ popup.height = popupHeight
+ popup.recreate(stdscr, popup.startY, popupWidth)
+
+ while True:
+ draw(popup, properties)
+ key = stdscr.getch()
+
+ if key in (curses.KEY_UP, curses.KEY_DOWN, curses.KEY_PPAGE, curses.KEY_NPAGE):
+ # navigation - tweak properties and recreate popup
+ properties.handleKey(key, popup.maxY - 4)
+ else:
+ # closes popup
+ break
+
+ popup.height = 9
+ popup.recreate(stdscr, popup.startY, 80)
+ finally:
+ popup.lock.release()
+
+def draw(popup, properties):
+ popup.clear()
+ popup.win.box()
+
+ # top label
+ popup.addstr(0, 0, "Open File Descriptors:", curses.A_STANDOUT)
+
+ if properties.errorMsg:
+ popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | util.getColor("red"))
+ else:
+ # text with file descriptor count and limit
+ fdCount = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
+ fdCountPer = 100 * fdCount / properties.fdLimit
+
+ statsColor = "green"
+ if fdCountPer >= 90: statsColor = "red"
+ elif fdCountPer >= 50: statsColor = "yellow"
+
+ countMsg = "%i / %i (%i%%)" % (fdCount, properties.fdLimit, fdCountPer)
+ popup.addstr(1, 2, countMsg, curses.A_BOLD | util.getColor(statsColor))
+
+ # provides a progress bar reflecting the stats
+ barWidth = popup.maxX - len(countMsg) - 6 # space between "[ ]" in progress bar
+ barProgress = max(1, barWidth * fdCountPer / 100) # filled cells
+ popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD)
+ popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | util.getColor(statsColor))
+ popup.addstr(1, len(countMsg) + 4 + barWidth, "]", curses.A_BOLD)
+
+ popup.win.hline(2, 1, curses.ACS_HLINE, popup.maxX - 2)
+
+ # scrollable file descriptor listing
+ lineNum = 3
+ entryNum = properties.scroll
+ while lineNum <= popup.maxY - 2:
+ if entryNum < len(properties.fdFile):
+ line = properties.fdFile[entryNum]
+ color = "green"
+ elif entryNum < len(properties.fdFile) + len(properties.fdMisc):
+ line = properties.fdMisc[entryNum - len(properties.fdFile)]
+ color = "cyan"
+ else:
+ line = properties.fdConn[entryNum - len(properties.fdFile) - len(properties.fdMisc)]
+ color = "blue"
+
+ popup.addstr(lineNum, 2, line, curses.A_BOLD | util.getColor(color))
+ lineNum += 1
+ entryNum += 1
+
+ popup.refresh()
+
Modified: arm/trunk/interface/hostnameResolver.py
===================================================================
--- arm/trunk/interface/hostnameResolver.py 2009-11-09 02:52:44 UTC (rev 20926)
+++ arm/trunk/interface/hostnameResolver.py 2009-11-09 03:51:58 UTC (rev 20927)
@@ -69,8 +69,9 @@
threshold = currentCount - (RESOLVER_MAX_CACHE_SIZE - RESOLVER_CACHE_TRIM_SIZE) # max count of entries being removed
toDelete = []
- for (entryAddr, (entryHostname, entryAge)) in self.resolvedCache:
- if entryAge < threshold: toDelete.append(entryAddr)
+ # checks age of each entry, adding to toDelete if too old
+ for ipAddr in self.resolvedCache.keys():
+ if self.resolvedCache[ipAddr][1] < threshold: toDelete.append(ipAddr)
for entryAddr in toDelete: del self.resolvedCache[entryAddr]