[or-cvs] r19716: {arm} Decently big batch of feature additions and bug fixes. added (arm/trunk/interface)

atagar at seul.org atagar at seul.org
Mon Jun 15 06:42:20 UTC 2009


Author: atagar
Date: 2009-06-15 02:42:19 -0400 (Mon, 15 Jun 2009)
New Revision: 19716

Added:
   arm/trunk/interface/confPanel.py
   arm/trunk/interface/headerPanel.py
Removed:
   arm/trunk/interface/staticPanel.py
Modified:
   arm/trunk/interface/bandwidthPanel.py
   arm/trunk/interface/controller.py
   arm/trunk/interface/util.py
Log:
Decently big batch of feature additions and bug fixes.
added: second page that presents torrc with syntax highlighing, optional comment stripping, etc
added: ps sampling (cpu/memory usage, pid, and uptime)
added: help popup with page controls
bug fix: corrected issue that caused periodic refreshing to fail
bug fix: accounting reset time takes into account DST
bug fix: make accounting input and header pausable



Modified: arm/trunk/interface/bandwidthPanel.py
===================================================================
--- arm/trunk/interface/bandwidthPanel.py	2009-06-14 02:39:33 UTC (rev 19715)
+++ arm/trunk/interface/bandwidthPanel.py	2009-06-15 06:42:19 UTC (rev 19716)
@@ -34,6 +34,7 @@
     self.lastUploadRate = 0
     self.maxDownloadRate = 1      # max rates seen, used to determine graph bounds
     self.maxUploadRate = 1
+    self.accountingInfo = None    # accounting data (set by _updateAccountingInfo method)
     self.isPaused = False
     self.pauseBuffer = None       # mirror instance used to track updates when paused
     
@@ -112,35 +113,22 @@
             self.addstr(7 - row, col + 40, " ", curses.A_STANDOUT | ulColor)
         
         if self.isAccounting:
-          try:
-            accountingParams = self.conn.get_info(["accounting/hibernating", "accounting/bytes", "accounting/bytes-left", "accounting/interval-end"])
-            
-            hibernateStr = accountingParams["accounting/hibernating"]
+          if not self.isPaused: self._updateAccountingInfo()
+          
+          if self.accountingInfo:
+            status = self.accountingInfo["status"]
             hibernateColor = "green"
-            if hibernateStr == "soft": hibernateColor = "yellow"
-            elif hibernateStr == "hard": hibernateColor = "red"
+            if status == "soft": hibernateColor = "yellow"
+            elif status == "hard": hibernateColor = "red"
             
             self.addstr(9, 0, "Accounting (", curses.A_BOLD)
-            self.addstr(9, 12, hibernateStr, curses.A_BOLD | util.getColor(hibernateColor))
-            self.addstr(9, 12 + len(hibernateStr), "):", curses.A_BOLD)
+            self.addstr(9, 12, status, curses.A_BOLD | util.getColor(hibernateColor))
+            self.addstr(9, 12 + len(status), "):", curses.A_BOLD)
             
-            # timezone subtraction converts from gmt to local
-            sec = time.mktime(time.strptime(accountingParams["accounting/interval-end"], "%Y-%m-%d %H:%M:%S")) - time.time() - time.timezone
-            resetHours = sec / 3600
-            sec %= 3600
-            resetMin = sec / 60
-            sec %= 60
-            
-            self.addstr(9, 35, "Time to reset: %i:%02i:%02i" % (resetHours, resetMin, sec))
-            
-            read = util.getSizeLabel(int(accountingParams["accounting/bytes"].split(" ")[0]))
-            written = util.getSizeLabel(int(accountingParams["accounting/bytes"].split(" ")[1]))
-            limit = util.getSizeLabel(int(accountingParams["accounting/bytes"].split(" ")[0]) + int(accountingParams["accounting/bytes-left"].split(" ")[0]))
-            
-            self.addstr(10, 2, "%s / %s" % (read, limit), dlColor)
-            self.addstr(10, 37, "%s / %s" % (written, limit), ulColor)
-            
-          except TorCtl.TorCtlClosed:
+            self.addstr(9, 35, "Time to reset: %s" % self.accountingInfo["resetTime"])
+            self.addstr(10, 2, "%s / %s" % (self.accountingInfo["read"], self.accountingInfo["readLimit"]), dlColor)
+            self.addstr(10, 37, "%s / %s" % (self.accountingInfo["written"], self.accountingInfo["writtenLimit"]), ulColor)
+          else:
             self.addstr(9, 0, "Accounting:", curses.A_BOLD)
             self.addstr(9, 12, "Shutting Down...")
         
@@ -175,4 +163,38 @@
       self.downloadRates = self.pauseBuffer.downloadRates
       self.uploadRates = self.pauseBuffer.uploadRates
       self.redraw()
+  
+  def _updateAccountingInfo(self):
+    """
+    Updates mapping used for accounting info. This includes the following keys:
+    status, resetTime, read, written, readLimit, writtenLimit
+    
+    Sets mapping to None if the Tor connection is closed.
+    """
+    
+    try:
+      self.accountingInfo = {}
+      
+      accountingParams = self.conn.get_info(["accounting/hibernating", "accounting/bytes", "accounting/bytes-left", "accounting/interval-end"])
+      self.accountingInfo["status"] = accountingParams["accounting/hibernating"]
+      
+      # altzone subtraction converts from gmt to local with respect to DST
+      sec = time.mktime(time.strptime(accountingParams["accounting/interval-end"], "%Y-%m-%d %H:%M:%S")) - time.time() - time.altzone
+      resetHours = sec / 3600
+      sec %= 3600
+      resetMin = sec / 60
+      sec %= 60
+      self.accountingInfo["resetTime"] = "%i:%02i:%02i" % (resetHours, resetMin, sec)
+      
+      read = int(accountingParams["accounting/bytes"].split(" ")[0])
+      written = int(accountingParams["accounting/bytes"].split(" ")[1])
+      readLeft = int(accountingParams["accounting/bytes-left"].split(" ")[0])
+      writtenLeft = int(accountingParams["accounting/bytes-left"].split(" ")[1])
+      
+      self.accountingInfo["read"] = util.getSizeLabel(read)
+      self.accountingInfo["written"] = util.getSizeLabel(written)
+      self.accountingInfo["readLimit"] = util.getSizeLabel(read + readLeft)
+      self.accountingInfo["writtenLimit"] = util.getSizeLabel(written + writtenLeft)
+    except TorCtl.TorCtlClosed:
+      self.accountingInfo = None
 

Added: arm/trunk/interface/confPanel.py
===================================================================
--- arm/trunk/interface/confPanel.py	                        (rev 0)
+++ arm/trunk/interface/confPanel.py	2009-06-15 06:42:19 UTC (rev 19716)
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# confPanel.py -- Presents torrc with syntax highlighting.
+# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+
+import math
+import curses
+from TorCtl import TorCtl
+
+import util
+
+class ConfPanel(util.Panel):
+  """
+  Presents torrc with syntax highlighting in a scroll-able area.
+  """
+  
+  def __init__(self, lock, confLocation):
+    util.Panel.__init__(self, lock, -1)
+    self.confLocation = confLocation
+    self.showLineNum = True
+    self.stripComments = False
+    self.reset()
+  
+  def reset(self):
+    """
+    Reloads torrc contents and resets scroll height.
+    """
+    confFile = open(self.confLocation, "r")
+    self.confContents = confFile.readlines()
+    confFile.close()
+    self.scroll = 0
+  
+  def handleKey(self, key):
+    self._resetBounds()
+    pageHeight = self.maxY - 1
+    if key == curses.KEY_UP: self.scroll = max(self.scroll - 1, 0)
+    elif key == curses.KEY_DOWN: self.scroll = min(self.scroll + 1, len(self.confContents) - pageHeight)
+    elif key == curses.KEY_PPAGE: self.scroll = max(self.scroll - pageHeight, 0)
+    elif key == curses.KEY_NPAGE: self.scroll = min(self.scroll + pageHeight, len(self.confContents) - pageHeight)
+    elif key == ord('n') or key == ord('N'): self.showLineNum = not self.showLineNum
+    elif key == ord('r') or key == ord('R'): self.reset()
+    elif key == ord('s') or key == ord('S'):
+      self.stripComments = not self.stripComments
+      self.scroll = 0
+    self.redraw()
+  
+  def redraw(self):
+    if self.win:
+      if not self.lock.acquire(False): return
+      try:
+        self.clear()
+        self.addstr(0, 0, "Tor Config (%s):" % self.confLocation, util.LABEL_ATTR)
+        
+        if self.stripComments:
+          displayText = []
+          
+          for line in self.confContents:
+            commentStart = line.find("#")
+            if commentStart != -1: line = line[:commentStart]
+            
+            line = line.strip()
+            if line: displayText.append(line)
+        else: displayText = self.confContents
+        
+        pageHeight = self.maxY - 1
+        numFieldWidth = int(math.log10(len(displayText))) + 1
+        for i in range(self.scroll, min(len(displayText), self.scroll + pageHeight)):
+          lineText = displayText[i].strip()
+          endBreak = 0
+          
+          if self.showLineNum:
+            self.addstr(i - self.scroll + 1, 0, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | util.getColor("yellow"))
+            numOffset = numFieldWidth + 1
+          else: numOffset = 0
+          
+          if not lineText: continue
+          elif not lineText[0] == "#":
+            ctlBreak = lineText.find(" ")
+            endBreak = lineText.find("#")
+            if endBreak == -1: endBreak = len(lineText)
+            
+            self.addstr(i - self.scroll + 1, numOffset, lineText[:ctlBreak], curses.A_BOLD | util.getColor("green"))
+            self.addstr(i - self.scroll + 1, numOffset + ctlBreak, lineText[ctlBreak:endBreak], curses.A_BOLD | util.getColor("cyan"))
+          self.addstr(i - self.scroll + 1, numOffset + endBreak, lineText[endBreak:], util.getColor("white"))
+        
+        self.refresh()
+      finally:
+        self.lock.release()
+

Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2009-06-14 02:39:33 UTC (rev 19715)
+++ arm/trunk/interface/controller.py	2009-06-15 06:42:19 UTC (rev 19716)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# controller.py -- arm interface (curses monitor for relay status).
+# controller.py -- arm interface (curses monitor for relay status)
 # Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
 
 """
@@ -12,7 +12,8 @@
 from TorCtl import TorCtl
 
 import util
-import staticPanel
+import headerPanel
+import confPanel
 import bandwidthPanel
 import logPanel
 
@@ -21,10 +22,15 @@
                         # concurrency bugs produce especially sinister glitches)
 
 # enums for message in control label
-CTL_HELP, CTL_PAUSED, CTL_EVENT_INPUT, CTL_EVENT_ERR = range(4)
+CTL_HELP, CTL_PAUSED = range(2)
 
 # panel order per page
-PAGE_1 = ["summary", "control", "bandwidth", "log"]
+PAGE_S = ["header", "control", "popup"]    # sticky (ie, always available) page
+PAGES = [
+  ["bandwidth", "log"],
+  ["torrc"]]
+PAUSEABLE = ["header", "bandwidth", "log"]
+PAGE_COUNT = 2 # all page numbering is internally represented as 0-indexed
 # TODO: page 2: configuration information
 # TODO: page 3: current connections
 
@@ -33,24 +39,34 @@
   
   def __init__(self, lock):
     util.Panel.__init__(self, lock, 1)
-    self.msgType = CTL_HELP
-    self.arg = ""
+    self.msgText = CTL_HELP           # message text to be displyed
+    self.msgAttr = curses.A_NORMAL    # formatting attributes
+    self.page = 1                     # page number currently being displayed
   
-  def setMsg(self, msgType, arg=""):
-    self.msgType = msgType
-    self.arg = arg
+  def setMsg(self, msgText, msgAttr=curses.A_NORMAL):
+    """
+    Sets the message and display attributes. If msgType matches CTL_HELP or
+    CTL_PAUSED then uses the default message for those statuses.
+    """
+    
+    self.msgText = msgText
+    self.msgAttr = msgAttr
   
   def redraw(self):
     if self.win:
       self.clear()
       
-      if self.msgType == CTL_HELP: self.addstr(0, 0, "q: quit, e: change events, p: pause")
-      elif self.msgType == CTL_PAUSED: self.addstr(0, 0, "Paused", curses.A_STANDOUT)
-      elif self.msgType == CTL_EVENT_INPUT: self.addstr(0, 0, "Events to log: ")
-      elif self.msgType == CTL_EVENT_ERR: self.addstr(0, 0, self.arg, curses.A_STANDOUT)
-      else:
-        assert False, "Unrecognized event type for control label: " + str(self.msgType)
+      msgText = self.msgText
+      msgAttr = self.msgAttr
       
+      if msgText == CTL_HELP:
+        msgText = "page %i / %i - q: quit, p: pause, h: help" % (self.page, PAGE_COUNT)
+        msgAttr = curses.A_NORMAL
+      elif msgText == CTL_PAUSED:
+        msgText = "Paused"
+        msgAttr = curses.A_STANDOUT
+      
+      self.addstr(0, 0, msgText, msgAttr)
       self.refresh()
 
 def setEventListening(loggedEvents, conn, logListener):
@@ -108,12 +124,13 @@
   try: curses.curs_set(0)
   except curses.error: pass
   
-  staticInfo = staticPanel.getStaticInfo(conn)
   panels = {
-    "summary": staticPanel.SummaryPanel(cursesLock, staticInfo),
+    "header": headerPanel.HeaderPanel(cursesLock, conn),
     "control": ControlPanel(cursesLock),
+    "popup": util.Panel(cursesLock, 8),
     "bandwidth": bandwidthPanel.BandwidthMonitor(cursesLock, conn),
-    "log": logPanel.LogMonitor(cursesLock, loggedEvents)}
+    "log": logPanel.LogMonitor(cursesLock, loggedEvents),
+    "torrc": confPanel.ConfPanel(cursesLock, conn.get_info("config-file")["config-file"])}
   
   # listeners that update bandwidth and log panels with Tor status
   conn.add_event_listener(panels["log"])
@@ -126,6 +143,7 @@
   oldY, oldX = -1, -1
   isUnresponsive = False    # true if it's been over five seconds since the last BW event (probably due to Tor closing)
   isPaused = False          # if true updates are frozen
+  page = 0
   
   while True:
     # tried only refreshing when the screen was resized but it caused a
@@ -135,12 +153,21 @@
     cursesLock.acquire()
     try:
       y, x = stdscr.getmaxyx()
-      if y > oldY:
+      if x > oldX or y > oldY:
         # gives panels a chance to take advantage of the maximum bounds
         startY = 0
-        for panelKey in PAGE_1:
+        for panelKey in PAGE_S[:2]:
           panels[panelKey].recreate(stdscr, startY)
           startY += panels[panelKey].height
+        
+        isChanged = panels["popup"].recreate(stdscr, startY, 80)
+        
+        for panelSet in PAGES:
+          tmpStartY = startY
+          
+          for panelKey in panelSet:
+            panels[panelKey].recreate(stdscr, tmpStartY)
+            tmpStartY += panels[panelKey].height
       
       # if it's been at least five seconds since the last BW event Tor's probably done
       if not isUnresponsive and panels["log"].getHeartbeat() >= 5:
@@ -152,7 +179,7 @@
         panels["log"].monitor_event("WARN", "Relay resumed")
       
       # I haven't the foggiest why, but doesn't work if redrawn out of order...
-      for panelKey in PAGE_1: panels[panelKey].redraw()
+      for panelKey in (PAGE_S + PAGES[page]): panels[panelKey].redraw()
       oldY, oldX = y, x
       stdscr.refresh()
     finally:
@@ -160,20 +187,73 @@
     
     key = stdscr.getch()
     if key == 27 or key == ord('q') or key == ord('Q'): break # quits (also on esc)
-    elif key == ord('e') or key == ord('E'):
-      # allow user to enter new types of events to log - unchanged if left blank
+    elif key == curses.KEY_LEFT or key == curses.KEY_RIGHT:
+      # switch page
+      if key == curses.KEY_LEFT: page = (page - 1) % PAGE_COUNT
+      else: page = (page + 1) % PAGE_COUNT
       
+      # pauses panels that aren't visible to prevent events from accumilating
+      # (otherwise they'll wait on the curses lock which might get demanding)
+      for key in PAUSEABLE: panels[key].setPaused(isPaused or key not in PAGES[page])
+      
+      panels["control"].page = page + 1
+      panels["control"].refresh()
+    elif key == ord('p') or key == ord('P'):
+      # toggles update freezing
       cursesLock.acquire()
       try:
-        # pauses listeners so events can still be handed (otherwise they wait
-        # on curses lock which might get demanding if the user takes their time)
-        isBwPaused = panels["bandwidth"].isPaused
-        isLogPaused = panels["log"].isPaused
-        panels["bandwidth"].setPaused(True)
-        panels["log"].setPaused(True)
+        isPaused = not isPaused
+        for key in PAUSEABLE: panels[key].setPaused(isPaused or key not in PAGES[page])
+        msgType = CTL_PAUSED if isPaused else CTL_HELP
+        panels["control"].setMsg(msgType)
+      finally:
+        cursesLock.release()
+    elif key == ord('h') or key == ord('H'):
+      # displays popup for current page's controls
+      cursesLock.acquire()
+      try:
+        for key in PAUSEABLE: panels[key].setPaused(True)
         
+        panels["control"].setMsg("Press any key...")
+        panels["control"].redraw()
+        
+        # lists commands
+        popup = panels["popup"]
+        popup.clear()
+        popup.win.box()
+        popup.addstr(0, 0, "Page %i Commands:" % (page + 1), util.LABEL_ATTR)
+        
+        if page == 0:
+          popup.addstr(1, 2, "e: change logged events")
+        elif page == 1:
+          popup.addstr(1, 2, "up arrow: scroll up a line")
+          popup.addstr(1, 35, "down arrow: scroll down a line")
+          popup.addstr(2, 2, "page up: scroll up a page")
+          popup.addstr(2, 35, "page down: scroll down a page")
+          popup.addstr(3, 2, "s: toggle comment stripping")
+          popup.addstr(3, 35, "n: toggle line numbering")
+          popup.addstr(4, 2, "r: reload torrc")
+        
+        popup.refresh()
+        
+        curses.cbreak()
+        stdscr.getch()
+        curses.halfdelay(REFRESH_RATE * 10)
+        
+        msgType = CTL_PAUSED if isPaused else CTL_HELP
+        panels["control"].setMsg(msgType)
+        
+        for key in PAUSEABLE: panels[key].setPaused(isPaused or key not in PAGES[page])
+      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()
+      try:
+        for key in PAUSEABLE: panels[key].setPaused(True)
+        
         # provides prompt
-        panels["control"].setMsg(CTL_EVENT_INPUT)
+        panels["control"].setMsg("Events to log: ")
         panels["control"].redraw()
         
         # makes cursor and typing visible
@@ -181,16 +261,16 @@
         except curses.error: pass
         curses.echo()
         
-        # switches bandwidth area to list event types
-        bwPanel = panels["bandwidth"]
-        bwPanel.clear()
-        bwPanel.addstr(0, 0, "Event Types:", util.LABEL_ATTR)
+        # lists event types
+        popup = panels["popup"]
+        popup.clear()
+        popup.addstr(0, 0, "Event Types:", util.LABEL_ATTR)
         lineNum = 1
         for line in logPanel.EVENT_LISTING.split("\n"):
           line = line.strip()
-          bwPanel.addstr(lineNum, 0, line[:x - 1])
+          popup.addstr(lineNum, 0, line[:x - 1])
           lineNum += 1
-        bwPanel.refresh()
+        popup.refresh()
         
         # gets user input (this blocks monitor updates)
         eventsInput = panels["control"].win.getstr(0, 15)
@@ -200,6 +280,7 @@
         try: curses.curs_set(0)
         except curses.error: pass
         curses.noecho()
+        curses.halfdelay(REFRESH_RATE * 10) # evidenlty previous tweaks reset this...
         
         # TODO: it would be nice to quit on esc, but looks like this might not be possible...
         if eventsInput != "":
@@ -208,29 +289,18 @@
             loggedEvents = setEventListening(expandedEvents, conn, panels["log"])
             panels["log"].loggedEvents = loggedEvents
           except ValueError, exc:
-            panels["control"].setMsg(CTL_EVENT_ERR, "Invalid flags: %s" % str(exc))
+            panels["control"].setMsg("Invalid flags: %s" % str(exc), curses.A_STANDOUT)
             panels["control"].redraw()
             time.sleep(2)
         
         msgType = CTL_PAUSED if isPaused else CTL_HELP
         panels["control"].setMsg(msgType)
         
-        # returns listeners to previous pause status
-        panels["bandwidth"].setPaused(isBwPaused)
-        panels["log"].setPaused(isLogPaused)
+        for key in PAUSEABLE: panels[key].setPaused(isPaused or key not in PAGES[page])
       finally:
         cursesLock.release()
-    elif key == ord('p') or key == ord('P'):
-      # toggles update freezing
-      cursesLock.acquire()
-      try:
-        isPaused = not isPaused
-        panels["log"].setPaused(isPaused)
-        panels["bandwidth"].setPaused(isPaused)
-        msgType = CTL_PAUSED if isPaused else CTL_HELP
-        panels["control"].setMsg(msgType)
-      finally:
-        cursesLock.release()
+    elif page == 1:
+      panels["torrc"].handleKey(key)
 
 def startTorMonitor(conn, loggedEvents):
   curses.wrapper(drawTorMonitor, conn, loggedEvents)

Added: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py	                        (rev 0)
+++ arm/trunk/interface/headerPanel.py	2009-06-15 06:42:19 UTC (rev 19716)
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+# summaryPanel.py -- Static system and Tor related information.
+# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+
+import os
+import curses
+from TorCtl import TorCtl
+
+import util
+
+class HeaderPanel(util.Panel):
+  """
+  Draws top area containing static information.
+  
+  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
+  """
+  
+  def __init__(self, lock, conn):
+    util.Panel.__init__(self, lock, 5)
+    self.vals = []            # mapping of information to be presented
+    self.conn = conn          # Tor control port connection
+    self.isPaused = False
+    self._updateParams()
+  
+  def redraw(self):
+    if self.win:
+      if not self.isPaused: self._updateParams()
+      
+      # 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()
+      
+      self.clear()
+      
+      # Line 1
+      self.addstr(0, 0, "arm - %s (%s %s)" % (self.vals["sys-name"], self.vals["sys-os"], self.vals["sys-version"]))
+      self.addstr(0, 45, "Tor %s" % self.vals["version"])
+      
+      # Line 2 (authentication label red if open, green if credentials required)
+      dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if not self.vals["DirPort"] == None else ""
+      
+      # TODO: if both cookie and password are set then which takes priority?
+      if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
+      elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
+      else: controlPortAuthLabel = "open"
+      controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
+      
+      labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
+      self.addstr(1, 0, labelStart)
+      xLoc = len(labelStart)
+      self.addstr(1, xLoc, controlPortAuthLabel, util.getColor(controlPortAuthColor))
+      xLoc += len(controlPortAuthLabel)
+      self.addstr(1, xLoc, "): %s" % self.vals["ControlPort"])
+      
+      # Line 3 (system usage info)
+      self.addstr(2, 0, "cpu: %s%%" % self.vals["%cpu"])
+      self.addstr(2, 13, "mem: %s (%s%%)" % (util.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
+      self.addstr(2, 34, "pid: %s" % self.vals["pid"])
+      self.addstr(2, 47, "uptime: %s" % self.vals["etime"])
+      
+      # Line 4 (fingerprint)
+      self.addstr(3, 0, "fingerprint: %s" % self.vals["fingerprint"])
+      
+      
+      # Lines 3-5
+      #self.addstr(3, 0, "Config: %s" % self.vals["config-file"])
+      #exitPolicy = self.vals["ExitPolicy"]
+      
+      # adds note when default exit policy is appended
+      #if exitPolicy == None: exitPolicy = "<default>"
+      #elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
+      #  exitPolicy += ", <default>"
+      #self.addstr(4, 0, "Exit Policy: %s" % exitPolicy)
+      
+      self.refresh()
+  
+  def setPaused(self, isPause):
+    """
+    If true, prevents updates from being presented.
+    """
+    
+    self.isPaused = isPause
+  
+  def _updateParams(self):
+    """
+    Updates mapping of static Tor settings and system information to their
+    corresponding string values. Keys include:
+    info - version, config-file, *address, *fingerprint
+    sys - sys-name, sys-os, sys-version
+    ps - *%cpu, *rss, *%mem, *pid, *etime
+    config - Nickname, ORPort, DirPort, ControlPort, ExitPolicy
+    config booleans - IsPasswordAuthSet, IsCookieAuthSet, IsAccountingEnabled
+    
+    * volatile parameter that'll be reset (otherwise won't be checked if
+    already set)
+    """
+    
+    if not self.vals:
+      # retrieves static params
+      self.vals = self.conn.get_info(["version", "config-file"])
+      
+      # populates with some basic system information
+      unameVals = os.uname()
+      self.vals["sys-name"] = unameVals[1]
+      self.vals["sys-os"] = unameVals[0]
+      self.vals["sys-version"] = unameVals[2]
+      
+      # parameters from the user's torrc
+      configFields = ["Nickname", "ORPort", "DirPort", "ControlPort", "ExitPolicy"]
+      self.vals.update(dict([(key, self.conn.get_option(key)[0][1]) for key in configFields]))
+      
+      # 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"
+    
+    # gets parameters that throw errors if unavailable
+    for param in ["address", "fingerprint"]:
+      try: self.vals.update(self.conn.get_info(param))
+      except TorCtl.ErrorReply: self.vals[param] = "Unknown"
+      except TorCtl.TorCtlClosed:
+        # Tor shut down - keep last known values
+        if not self.vals[param]: self.vals[param] = "Unknown"
+    
+    # ps call provides header followed by params for tor
+    psParams = ["%cpu", "rss", "%mem", "pid", "etime"]
+    psCall = os.popen('ps -C %s -o %s' % ("tor", ",".join(psParams)))
+    
+    try: sampling = psCall.read().strip().split()[len(psParams):]
+    except IOError: sampling = [] # ps call failed
+    psCall.close()
+    
+    if len(sampling) < 5:
+      # either ps failed or returned no tor instance
+      sampling = [""] * len(psParams)
+      
+      # %cpu, rss, and %mem are better zeroed out
+      for i in range(3): sampling[i] = "0"
+    
+    for i in range(len(psParams)):
+      self.vals[psParams[i]] = sampling[i]
+

Deleted: arm/trunk/interface/staticPanel.py
===================================================================
--- arm/trunk/interface/staticPanel.py	2009-06-14 02:39:33 UTC (rev 19715)
+++ arm/trunk/interface/staticPanel.py	2009-06-15 06:42:19 UTC (rev 19716)
@@ -1,114 +0,0 @@
-#!/usr/bin/env python
-# summaryPanel.py -- Static system and Tor related information.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-import os
-import curses
-from TorCtl import TorCtl
-
-import util
-
-def getStaticInfo(conn):
-  """
-  Provides mapping of static Tor settings and system information to their
-  corresponding string values. Keys include:
-  info - version, config-file, address, fingerprint
-  sys - sys-name, sys-os, sys-version
-  config - Nickname, ORPort, DirPort, ControlPort, ExitPolicy
-  config booleans - IsPasswordAuthSet, IsCookieAuthSet, IsAccountingEnabled
-  """
-  
-  vals = conn.get_info(["version", "config-file"])
- 
-  # gets parameters that throw errors if unavailable
-  for param in ["address", "fingerprint"]:
-    try:
-      vals.update(conn.get_info(param))
-    except TorCtl.ErrorReply:
-      vals[param] = "Unknown"
-  
-  # populates with some basic system information
-  unameVals = os.uname()
-  vals["sys-name"] = unameVals[1]
-  vals["sys-os"] = unameVals[0]
-  vals["sys-version"] = unameVals[2]
-  
-  # parameters from the user's torrc
-  configFields = ["Nickname", "ORPort", "DirPort", "ControlPort", "ExitPolicy"]
-  vals.update(dict([(key, conn.get_option(key)[0][1]) for key in configFields]))
-  
-  # simply keeps booleans for if authentication info is set
-  vals["IsPasswordAuthSet"] = not conn.get_option("HashedControlPassword")[0][1] == None
-  vals["IsCookieAuthSet"] = conn.get_option("CookieAuthentication")[0][1] == "1"
-  
-  vals["IsAccountingEnabled"] = conn.get_info('accounting/enabled')['accounting/enabled'] == "1"
-  
-  return vals
-
-class SummaryPanel(util.Panel):
-  """
-  Draws top area containing static information.
-  
-  arm - <System Name> (<OS> <Version>)     Tor <Tor Version>
-  <Relay Nickname> - <IP Addr>:<ORPort>, [Dir Port: <DirPort>, ]Control Port (<open, password, cookie>): <ControlPort>
-  Fingerprint: <Fingerprint>
-  Config: <Config>
-  Exit Policy: <ExitPolicy>
-  
-  Example:
-  arm - odin (Linux 2.6.24-24-generic)     Tor 0.2.0.34 (r18423)
-  odin - 76.104.132.98:9001, Dir Port: 9030, Control Port (cookie): 9051
-  Fingerprint: BDAD31F6F318E0413833E8EBDA956F76E4D66788
-  Config: /home/atagar/.vidalia/torrc
-  Exit Policy: reject *:*
-  """
-  
-  def __init__(self, lock, vals):
-    util.Panel.__init__(self, lock, 6)
-    self.vals = vals          # mapping of information to be presented
-  
-  def redraw(self):
-    i = 1
-    
-    if self.win:
-      # 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()
-      
-      self.clear()
-      
-      # Line 1
-      self.addstr(0, 0, "arm - %s (%s %s)" % (self.vals["sys-name"], self.vals["sys-os"], self.vals["sys-version"]))
-      self.addstr(0, 45, "Tor %s" % self.vals["version"])
-      
-      # Line 2 (authentication label red if open, green if credentials required)
-      dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if not self.vals["DirPort"] == None else ""
-      
-      # TODO: if both cookie and password are set then which takes priority?
-      if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
-      elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
-      else: controlPortAuthLabel = "open"
-      controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
-      
-      labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
-      self.addstr(1, 0, labelStart)
-      xLoc = len(labelStart)
-      self.addstr(1, xLoc, controlPortAuthLabel, util.getColor(controlPortAuthColor))
-      xLoc += len(controlPortAuthLabel)
-      self.addstr(1, xLoc, "): %s" % self.vals["ControlPort"])
-      
-      # Lines 3-5
-      self.addstr(2, 0, "Fingerprint: %s" % self.vals["fingerprint"])
-      self.addstr(3, 0, "Config: %s" % self.vals["config-file"])
-      exitPolicy = self.vals["ExitPolicy"]
-      
-      # adds note when default exit policy is appended
-      if exitPolicy == None: exitPolicy = "<default>"
-      elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
-        exitPolicy += ", <default>"
-      self.addstr(4, 0, "Exit Policy: %s" % exitPolicy)
-      
-      self.refresh()
-

Modified: arm/trunk/interface/util.py
===================================================================
--- arm/trunk/interface/util.py	2009-06-14 02:39:33 UTC (rev 19715)
+++ arm/trunk/interface/util.py	2009-06-15 06:42:19 UTC (rev 19716)
@@ -79,7 +79,7 @@
   def redraw(self):
     pass # overwritten by implementations
   
-  def recreate(self, stdscr, startY):
+  def recreate(self, stdscr, startY, maxX=-1):
     """
     Creates a new subwindow for the panel if:
     - panel currently doesn't have a subwindow
@@ -105,11 +105,12 @@
     newHeight = y - startY
     if self.height != -1: newHeight = min(newHeight, self.height)
     
-    if self.startY != startY or newHeight > self.maxY or self.isDisplaced:
+    if self.startY != startY or newHeight > self.maxY or self.isDisplaced or (self.maxX > maxX and maxX != -1):
       # window growing or moving - recreate
       self.startY = startY
       startY = min(startY, y - 1) # better create a displaced window than leave it as None
-    
+      if maxX != -1: x = min(x, maxX)
+      
       self.win = stdscr.subwin(newHeight, x, startY, 0)
       return True
     else: return False



More information about the tor-commits mailing list