[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r23663: {arm} added: config panel can now show tor's state, the torrc, and (in arm/trunk: . src src/interface src/util)
Author: atagar
Date: 2010-10-24 00:10:16 +0000 (Sun, 24 Oct 2010)
New Revision: 23663
Modified:
arm/trunk/armrc.sample
arm/trunk/src/interface/confPanel.py
arm/trunk/src/interface/controller.py
arm/trunk/src/starter.py
arm/trunk/src/util/conf.py
Log:
added: config panel can now show tor's state, the torrc, and arm's state/rc
added: INFO level logging for arm startup time
fix: extra line when line cap was reached
Modified: arm/trunk/armrc.sample
===================================================================
--- arm/trunk/armrc.sample 2010-10-23 22:03:33 UTC (rev 23662)
+++ arm/trunk/armrc.sample 2010-10-24 00:10:16 UTC (rev 23663)
@@ -13,9 +13,6 @@
# Renders the interface with color if set and the terminal supports it
features.colorInterface true
-# Checks the torrc for issues, warning and hilighting problems if true.
-features.torrc.validate true
-
# Set this if you're running in a chroot jail or other environment where tor's
# resources (log, state, etc) should have a prefix in their paths.
features.pathPrefix
@@ -56,13 +53,19 @@
# Paremters for the config panel
# ---------------------------
+# type
+# 0 -> tor state, 1 -> torrc, 2 -> arm state, 3 -> armrc
+# validate
+# checks torrc and armrcs for issues, warning and hilighting problems if true
# showScrollbars
# displays scrollbars when the torrc content is longer than the display
# maxLinesPerEntry
# max number of lines to display for a single entry in the torrc
+features.config.type 0
+features.config.validate true
features.config.showScrollbars true
-features.config.maxLinesPerEntry 5
+features.config.maxLinesPerEntry 8
# General graph parameters
# ------------------------
@@ -142,6 +145,7 @@
cache.armLog.trimSize 200
# Runlevels at which arm logs its events
+log.startTime INFO
log.refreshRate DEBUG
log.configEntryNotFound NONE
log.configEntryUndefined NOTICE
Modified: arm/trunk/src/interface/confPanel.py
===================================================================
--- arm/trunk/src/interface/confPanel.py 2010-10-23 22:03:33 UTC (rev 23662)
+++ arm/trunk/src/interface/confPanel.py 2010-10-24 00:10:16 UTC (rev 23663)
@@ -6,15 +6,20 @@
import curses
import threading
-from util import log, panel, torrc, uiTools
+from util import conf, log, panel, torrc, torTools, uiTools
-DEFAULT_CONFIG = {"features.torrc.validate": True,
+DEFAULT_CONFIG = {"features.config.type": 0,
+ "features.config.validate": True,
"features.config.showScrollbars": True,
- "features.config.maxLinesPerEntry": 5,
+ "features.config.maxLinesPerEntry": 8,
"log.confPanel.torrcReadFailed": log.WARN,
"log.torrcValidation.duplicateEntries": log.NOTICE,
"log.torrcValidation.torStateDiffers": log.NOTICE}
+# configurations that can be displayed
+TOR_STATE, TORRC, ARM_STATE, ARMRC = range(4)
+CONFIG_LABELS = {TORRC: "torrc", TOR_STATE: "tor state", ARMRC: "armrc", ARM_STATE: "arm state"}
+
class ConfPanel(panel.Panel):
"""
Presents torrc, armrc, or loaded settings with syntax highlighting in a
@@ -26,99 +31,150 @@
self._config = dict(DEFAULT_CONFIG)
if config:
- config.update(self._config, {"features.config.maxLinesPerEntry": 1})
+ config.update(self._config, {
+ "features.config.type": (0, 3),
+ "features.config.maxLinesPerEntry": 1})
self.valsLock = threading.RLock()
self.scroll = 0
+ self.showLabel = True # shows top label if true, hides otherwise
self.showLineNum = True
self.stripComments = False
- self.confLocation = ""
- self.confContents = None # read torrc, None if it failed to load
- self.corrections = {}
+ # type of config currently being displayed
+ self.configType = self._config["features.config.type"]
+
+ # Mappings of config types to tuples of:
+ # (contents, corrections, confLocation)
+ # This maps to None if they haven't been loaded yet or failed to load.
+ self.configs = {TORRC: None, TOR_STATE: None, ARMRC: None, ARM_STATE: None}
+
# height of the content when last rendered (the cached value is invalid if
# _lastContentHeightArgs is None or differs from the current dimensions)
self._lastContentHeight = 1
self._lastContentHeightArgs = None
- self.reset()
+ self.loadConfig(TOR_STATE)
+ self.loadConfig(TORRC)
- def reset(self, logErrors = True):
+ def loadConfig(self, configType = None, logErrors = True):
"""
- Reloads torrc contents and resets scroll height. Returns True if
- successful, else false.
+ Reloads configuration or state contents and resets scroll height. Returns
+ True if successful, else false.
Arguments:
- logErrors - logs if unable to read the torrc or issues are found during
- validation
+ configType - configuration type to load (displayed config type if None)
+ logErrors - logs if unable to read the torrc or issues are found during
+ validation
"""
self.valsLock.acquire()
+ if configType == None: configType = self.configType
+ confContents, corrections, confLocation = [], {}, None
- try:
- self.confLocation = torrc.getConfigLocation()
- confFile = open(self.confLocation, "r")
- self.confContents = confFile.readlines()
- confFile.close()
- self.scroll = 0
+ if configType in (TORRC, ARMRC):
+ # load configuration file
+ try:
+ if configType == TORRC: confLocation = torrc.getConfigLocation()
+ else:
+ confLocation = conf.getConfig("arm").path
+ if not confLocation: raise IOError("no armrc has been loaded")
+
+ confFile = open(confLocation, "r")
+ confContents = confFile.readlines()
+ confFile.close()
+ self.scroll = 0
+ except IOError, exc:
+ self.configs[configType] = None
+ msg = "Unable to load torrc (%s)" % exc
+ if logErrors: log.log(self._config["log.confPanel.torrcReadFailed"], msg)
+ self.valsLock.release()
+ return False
- # sets the content height to be something somewhat reasonable
- self._lastContentHeight = len(self.confContents)
- self._lastContentHeightArgs = None
- except IOError, exc:
- self.confContents = None
- msg = "Unable to load torrc (%s)" % exc
- if logErrors: log.log(self._config["log.confPanel.torrcReadFailed"], msg)
- self.valsLock.release()
- return False
-
- if self._config["features.torrc.validate"]:
- self.corrections = torrc.validate(self.confContents)
-
- if self.corrections and logErrors:
- # logs issues found during validation
- irrelevantLines, mismatchLines = [], []
- for lineNum in self.corrections:
- problem = self.corrections[lineNum][0]
- if problem == torrc.VAL_DUPLICATE: irrelevantLines.append(lineNum)
- elif problem == torrc.VAL_MISMATCH: mismatchLines.append(lineNum)
+ if configType == TORRC and self._config["features.config.validate"]:
+ # TODO: add armrc validation
+ corrections = torrc.validate(confContents)
- if irrelevantLines:
- irrelevantLines.sort()
+ if corrections and logErrors:
+ # logs issues found during validation
+ irrelevantLines, mismatchLines = [], []
+ for lineNum in corrections:
+ problem = corrections[lineNum][0]
+ if problem == torrc.VAL_DUPLICATE: irrelevantLines.append(lineNum)
+ elif problem == torrc.VAL_MISMATCH: mismatchLines.append(lineNum)
- if len(irrelevantLines) > 1: first, second, third = "Entries", "are", ", including lines"
- else: first, second, third = "Entry", "is", " on line"
- msgStart = "%s in your torrc %s ignored due to duplication%s" % (first, second, third)
- msgLines = ", ".join([str(val + 1) for val in irrelevantLines])
- msg = "%s: %s (highlighted in blue)" % (msgStart, msgLines)
- log.log(self._config["log.torrcValidation.duplicateEntries"], msg)
+ if irrelevantLines:
+ irrelevantLines.sort()
+
+ if len(irrelevantLines) > 1: first, second, third = "Entries", "are", ", including lines"
+ else: first, second, third = "Entry", "is", " on line"
+ msgStart = "%s in your torrc %s ignored due to duplication%s" % (first, second, third)
+ msgLines = ", ".join([str(val + 1) for val in irrelevantLines])
+ msg = "%s: %s (highlighted in blue)" % (msgStart, msgLines)
+ log.log(self._config["log.torrcValidation.duplicateEntries"], msg)
+
+ if mismatchLines:
+ mismatchLines.sort()
+ msgStart = "Tor's state differs from loaded torrc on line%s" % ("s" if len(mismatchLines) > 1 else "")
+ msgLines = ", ".join([str(val + 1) for val in mismatchLines])
+ msg = "%s: %s" % (msgStart, msgLines)
+ log.log(self._config["log.torrcValidation.torStateDiffers"], msg)
+
+ if confContents:
+ # Restricts contents to be displayable characters:
+ # - Tabs print as three spaces. Keeping them as tabs is problematic for
+ # the layout since it's counted as a single character, but occupies
+ # several cells.
+ # - Strips control and unprintable characters.
+ for lineNum in range(len(confContents)):
+ lineText = confContents[lineNum]
+ lineText = lineText.replace("\t", " ")
+ lineText = "".join([char for char in lineText if curses.ascii.isprint(char)])
+ confContents[lineNum] = lineText
+ elif configType == TOR_STATE:
+ # for all recognized tor config options, provide their current value
+ conn = torTools.getConn()
+ configOptionQuery = conn.getInfo("config/names", "").strip().split("\n")
+
+ for lineNum in range(len(configOptionQuery)):
+ # lines are of the form "<option> <type>", like:
+ # UseEntryGuards Boolean
+ line = configOptionQuery[lineNum]
+ confOption, confType = line.strip().split(" ", 1)
+ confValue = ", ".join(conn.getOption(confOption, [], True))
- if mismatchLines:
- mismatchLines.sort()
- msgStart = "Tor's state differs from loaded torrc on line%s" % ("s" if len(mismatchLines) > 1 else "")
- msgLines = ", ".join([str(val + 1) for val in mismatchLines])
- msg = "%s: %s" % (msgStart, msgLines)
- log.log(self._config["log.torrcValidation.torStateDiffers"], msg)
+ # provides nicer values for recognized types
+ if not confValue: confValue = "<none>"
+ elif confType == "Boolean" and confValue in ("0", "1"):
+ confValue = "False" if confValue == "0" else "True"
+ elif confType == "DataSize" and confValue.isdigit():
+ confValue = uiTools.getSizeLabel(int(confValue))
+ elif confType == "TimeInterval" and confValue.isdigit():
+ confValue = uiTools.getTimeLabel(int(confValue), isLong = True)
+
+ confContents.append("%s %s\n" % (confOption, confValue))
+
+ # hijacks the correction field to display the value's type
+ corrections[lineNum] = (None, confType)
+ elif configType == ARM_STATE:
+ # loaded via the conf utility
+ armConf = conf.getConfig("arm")
+ for key in armConf.getKeys():
+ confContents.append("%s %s\n" % (key, ", ".join(armConf.getValue(key, [], True))))
+ confContents.sort()
- if self.confContents:
- # Restricts contents to be displayable characters:
- # - Tabs print as three spaces. Keeping them as tabs is problematic for
- # the layout since it's counted as a single character, but occupies
- # several cells.
- # - Strips control and unprintable characters.
- for lineNum in range(len(self.confContents)):
- lineText = self.confContents[lineNum]
- lineText = lineText.replace("\t", " ")
- lineText = "".join([char for char in lineText if curses.ascii.isprint(char)])
- self.confContents[lineNum] = lineText
+ self.configs[configType] = (confContents, corrections, confLocation)
- self.redraw(True)
+ # sets the content height to be something somewhat reasonable
+ self._lastContentHeight = len(confContents)
+ self._lastContentHeightArgs = None
+
self.valsLock.release()
return True
def handleKey(self, key):
self.valsLock.acquire()
- if uiTools.isScrollKey(key) and self.confContents != None:
+ if uiTools.isScrollKey(key) and self.configs[self.configType] != None:
pageHeight = self.getPreferredSize()[0] - 1
newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self._lastContentHeight)
@@ -131,12 +187,30 @@
self.redraw(True)
elif key == ord('s') or key == ord('S'):
self.stripComments = not self.stripComments
- self.scroll = 0
self._lastContentHeightArgs = None
self.redraw(True)
self.valsLock.release()
+ def setConfigType(self, configType):
+ """
+ Sets the type of configuration to be displayed. If the configuration isn't
+ already loaded then this fetches it.
+
+ Arguments
+ configType - enum representing the type of configuration to be loaded
+ """
+
+ if self.configType != configType or not self.configs[configType]:
+ self.valsLock.acquire()
+ self.configType = configType
+
+ if not self.configs[configType]: self.loadConfig()
+
+ self._lastContentHeightArgs = None
+ self.redraw(True)
+ self.valsLock.release()
+
def draw(self, subwindow, width, height):
self.valsLock.acquire()
@@ -147,18 +221,17 @@
# height if not).
trustLastContentHeight = self._lastContentHeightArgs == (width, height)
- # draws the top label
- locationLabel = " (%s)" % self.confLocation if self.confLocation else ""
- self.addstr(0, 0, "Tor Config%s:" % locationLabel, curses.A_STANDOUT)
-
# restricts scroll location to valid bounds
self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1))
- renderedContents = self.confContents
- if self.confContents == None:
- renderedContents = ["### Unable to load torrc ###"]
+ renderedContents, corrections, confLocation = None, {}, None
+ if self.configs[self.configType]:
+ renderedContents, corrections, confLocation = self.configs[self.configType]
+
+ if renderedContents == None:
+ renderedContents = ["### Unable to load the %s ###" % CONFIG_LABELS[self.configType]]
elif self.stripComments:
- renderedContents = torrc.stripComments(self.confContents)
+ renderedContents = torrc.stripComments(renderedContents)
# offset to make room for the line numbers
lineNumOffset = int(math.log10(len(renderedContents))) + 2 if self.showLineNum else 0
@@ -171,12 +244,22 @@
displayLine = -self.scroll + 1 # line we're drawing on
+ # draws the top label
+ if self.showLabel:
+ sourceLabel = "Tor" if self.configType in (TORRC, TOR_STATE) else "Arm"
+ typeLabel = "Config" if self.configType in (TORRC, ARMRC) else "State"
+ locationLabel = " (%s)" % confLocation if confLocation else ""
+ self.addstr(0, 0, "%s %s%s:" % (sourceLabel, typeLabel, locationLabel), curses.A_STANDOUT)
+
for lineNumber in range(0, len(renderedContents)):
lineText = renderedContents[lineNumber]
lineText = lineText.rstrip() # remove ending whitespace
- # blank lines are hidden when stripping comments
- hideLine = self.stripComments and not lineText
+ # blank lines are hidden when stripping comments, and undefined
+ # values are dropped if showing tor's state
+ if self.stripComments:
+ if not lineText: continue
+ elif self.configType == TOR_STATE and "<none>" in lineText: continue
# splits the line into its component (msg, format) tuples
lineComp = {"option": ["", curses.A_BOLD | uiTools.getColor("green")],
@@ -202,8 +285,8 @@
lineComp["argument"][0] = lineText[optionEnd:]
# gets the correction
- if lineNumber in self.corrections:
- lineIssue, lineIssueMsg = self.corrections[lineNumber]
+ if lineNumber in corrections:
+ lineIssue, lineIssueMsg = corrections[lineNumber]
if lineIssue == torrc.VAL_DUPLICATE:
lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
@@ -211,9 +294,14 @@
elif lineIssue == torrc.VAL_MISMATCH:
lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
lineComp["correction"][0] = " (%s)" % lineIssueMsg
+ else:
+ # For some types of configs the correction field is simply used to
+ # provide extra data (for instance, the type for tor state fields).
+ lineComp["correction"][0] = " (%s)" % lineIssueMsg
+ lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta")
# draws the line number
- if self.showLineNum and not hideLine and displayLine < height and displayLine >= 1:
+ if self.showLineNum and displayLine < height and displayLine >= 1:
lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow"))
@@ -224,15 +312,14 @@
while displayQueue:
msg, format = displayQueue.pop(0)
- if hideLine: break
maxMsgSize, includeBreak = width - cursorLoc, False
if len(msg) >= maxMsgSize:
# message is too long - break it up
- includeBreak = True
if lineOffset == maxLinesPerEntry - 1:
msg = uiTools.cropStr(msg, maxMsgSize)
else:
+ includeBreak = True
msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.END_WITH_HYPHEN, True)
displayQueue.insert(0, (remainder.strip(), format))
@@ -240,12 +327,16 @@
if msg and drawLine < height and drawLine >= 1:
self.addstr(drawLine, cursorLoc, msg, format)
+ # If we're done, and have added content to this line, then start
+ # further content on the next line.
cursorLoc += len(msg)
- if includeBreak or not displayQueue:
+ includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset
+
+ if includeBreak:
lineOffset += 1
cursorLoc = lineNumOffset + scrollOffset
- displayLine += lineOffset
+ displayLine += max(lineOffset, 1)
if trustLastContentHeight and displayLine >= height: break
Modified: arm/trunk/src/interface/controller.py
===================================================================
--- arm/trunk/src/interface/controller.py 2010-10-23 22:03:33 UTC (rev 23662)
+++ arm/trunk/src/interface/controller.py 2010-10-24 00:10:16 UTC (rev 23663)
@@ -46,6 +46,7 @@
"queries.refreshRate.rate": 5,
"log.torEventTypeUnrecognized": log.NOTICE,
"features.graph.bw.prepopulate": True,
+ "log.startTime": log.INFO,
"log.refreshRate": log.DEBUG,
"log.configEntryUndefined": log.NOTICE}
@@ -304,7 +305,7 @@
for panelKey in PAGES[page]:
panels[panelKey].redraw(True)
-def drawTorMonitor(stdscr, loggedEvents, isBlindMode):
+def drawTorMonitor(stdscr, startTime, loggedEvents, isBlindMode):
"""
Starts arm interface reflecting information on provided control port.
@@ -456,6 +457,9 @@
# TODO: popups need to force the panels it covers to redraw (or better, have
# a global refresh function for after changing pages, popups, etc)
+ initTime = time.time() - startTime
+ log.log(CONFIG["log.startTime"], "arm started (initialization took %0.3f seconds)" % initTime)
+
# TODO: come up with a nice, clean method for other threads to immediately
# terminate the draw loop and provide a stacktrace
while True:
@@ -705,6 +709,8 @@
popup.addfstr(4, 2, "<b>r</b>: reload torrc")
popup.addfstr(4, 41, "<b>x</b>: reset tor (issue sighup)")
+
+ popup.addfstr(5, 2, "<b>c</b>: displayed configuration (<b>%s</b>)" % confPanel.CONFIG_LABELS[panels["torrc"].configType])
popup.addstr(7, 2, "Press any key...")
popup.refresh()
@@ -1359,8 +1365,9 @@
panel.CURSES_LOCK.release()
elif page == 2 and key == ord('r') or key == ord('R'):
# reloads torrc, providing a notice if successful or not
- isSuccessful = panels["torrc"].reset(False)
- resetMsg = "torrc reloaded" if isSuccessful else "failed to reload torrc"
+ isSuccessful = panels["torrc"].loadConfig(logErrors = False)
+ confTypeLabel = confPanel.CONFIG_LABELS[panels["torrc"].configType]
+ resetMsg = "%s reloaded" % confTypeLabel if isSuccessful else "failed to reload %s" % confTypeLabel
if isSuccessful: panels["torrc"].redraw(True)
panels["control"].setMsg(resetMsg, curses.A_STANDOUT)
@@ -1397,6 +1404,26 @@
setPauseState(panels, isPaused, page)
finally:
panel.CURSES_LOCK.release()
+ elif page == 2 and (key == ord('c') or key == ord('C')):
+ # provides menu to pick config being displayed
+ options = [confPanel.CONFIG_LABELS[confType] for confType in range(4)]
+ initialSelection = panels["torrc"].configType
+
+ # hides top label of the graph panel and pauses panels
+ panels["torrc"].showLabel = False
+ panels["torrc"].redraw(True)
+ setPauseState(panels, isPaused, page, True)
+
+ selection = showMenu(stdscr, panels["popup"], "Configuration:", options, initialSelection)
+
+ # reverts changes made for popup
+ panels["torrc"].showLabel = True
+ setPauseState(panels, isPaused, page)
+
+ # applies new setting
+ if selection != -1: panels["torrc"].setConfigType(selection)
+
+ selectiveRefresh(panels, page)
elif page == 0:
panels["log"].handleKey(key)
elif page == 1:
@@ -1404,9 +1431,9 @@
elif page == 2:
panels["torrc"].handleKey(key)
-def startTorMonitor(loggedEvents, isBlindMode):
+def startTorMonitor(startTime, loggedEvents, isBlindMode):
try:
- curses.wrapper(drawTorMonitor, loggedEvents, isBlindMode)
+ curses.wrapper(drawTorMonitor, startTime, loggedEvents, isBlindMode)
except KeyboardInterrupt:
pass # skip printing stack trace in case of keyboard interrupt
Modified: arm/trunk/src/starter.py
===================================================================
--- arm/trunk/src/starter.py 2010-10-23 22:03:33 UTC (rev 23662)
+++ arm/trunk/src/starter.py 2010-10-24 00:10:16 UTC (rev 23663)
@@ -8,6 +8,7 @@
import os
import sys
+import time
import getopt
import version
@@ -75,6 +76,7 @@
return True
if __name__ == '__main__':
+ startTime = time.time()
param = dict([(key, None) for key in DEFAULTS.keys()])
configPath = DEFAULT_CONFIG # path used for customized configuration
@@ -175,9 +177,12 @@
conn = TorCtl.TorCtl.connect(controlAddr, controlPort, authPassword)
if conn == None: sys.exit(1)
+ # initializing the connection may require user input (for the password)
+ # scewing the startup time results so this isn't counted
+ initTime = time.time() - startTime
controller = util.torTools.getConn()
controller.init(conn)
- interface.controller.startTorMonitor(expandedEvents, param["startup.blindModeEnabled"])
+ interface.controller.startTorMonitor(time.time() - initTime, expandedEvents, param["startup.blindModeEnabled"])
conn.close()
Modified: arm/trunk/src/util/conf.py
===================================================================
--- arm/trunk/src/util/conf.py 2010-10-23 22:03:33 UTC (rev 23662)
+++ arm/trunk/src/util/conf.py 2010-10-24 00:10:16 UTC (rev 23663)
@@ -55,6 +55,7 @@
Creates a new configuration instance.
"""
+ self.path = None # location last loaded from
self.contents = {} # configuration key/value pairs
self.contentsLock = threading.RLock()
self.requestedKeys = set()
@@ -240,6 +241,7 @@
if key in self.contents: self.contents[key].append(value)
else: self.contents[key] = [value]
+ self.path = path
self.contentsLock.release()
def save(self, saveBackup=True):