[tor-commits] r24651: {arm} Replacing the file discriptor popup (which was both unused a (in arm/trunk: . src/interface src/util)

Damian Johnson atagar1 at gmail.com
Mon Apr 18 02:34:38 UTC 2011


Author: atagar
Date: 2011-04-18 02:34:38 +0000 (Mon, 18 Apr 2011)
New Revision: 24651

Removed:
   arm/trunk/src/interface/fileDescriptorPopup.py
Modified:
   arm/trunk/ChangeLog
   arm/trunk/README
   arm/trunk/armrc.sample
   arm/trunk/src/interface/__init__.py
   arm/trunk/src/interface/controller.py
   arm/trunk/src/interface/headerPanel.py
   arm/trunk/src/util/procTools.py
   arm/trunk/src/util/sysTools.py
   arm/trunk/src/util/torTools.py
Log:
Replacing the file discriptor popup (which was both unused and inaccurate) with:
- logged notice/warn when descriptors are running out
- an entry in the header panel when they're running low on descriptors



Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/ChangeLog	2011-04-18 02:34:38 UTC (rev 24651)
@@ -48,6 +48,8 @@
     * fix: recognizing the proper private ip ranges of the 172.* block
     * fix: missing 'is default' option from config sort ordering
     * fix (4/4/11, r24562): hidden service parsing issue when there's multiple spaces in the HiddenServicePort opition (caught by Nicolas Pouillard)
+    * fix (4/6/11, r24570): missing new connection components from installations (caught by Anthony Basile)
+    * fix (4/13/11, r24613): failed requests for our flags cause a syntax error (caught by qbi)
 
 1/7/11 - version 1.4.1 (r24054)
 Platform specific enhancements including BSD compatibility and vastly improved performance on Linux.

Modified: arm/trunk/README
===================================================================
--- arm/trunk/README	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/README	2011-04-18 02:34:38 UTC (rev 24651)
@@ -157,13 +157,10 @@
       __init__.py
       controller.py          - main display loop, handling input and layout
       headerPanel.py         - top of all pages, providing general information
+      descriptorPopup.py     - (popup) displays connection descriptor data
       
       logPanel.py            - (page 1) displays tor, arm, and torctl events
-      fileDescriptorPopup.py - (popup) displays file descriptors used by tor
-      
       connPanel.py           - (page 2) deprecated counterpart for connections/*
-      descriptorPopup.py     - (popup) displays connection descriptor data
-      
       configPanel.py         - (page 3) editor panel for the tor configuration
       torrcPanel.py          - (page 4) displays torrc and validation
     

Modified: arm/trunk/armrc.sample
===================================================================
--- arm/trunk/armrc.sample	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/armrc.sample	2011-04-18 02:34:38 UTC (rev 24651)
@@ -31,6 +31,10 @@
 # events.
 features.logFile 
 
+# If true, the header panel always shows the file descriptor usage. Otherwise
+# this is only displayed when we're running out.
+features.showFdUsage false
+
 # Paremters for the log panel
 # ---------------------------
 # showDateDividers
@@ -276,4 +280,6 @@
 log.stats.procResolutionFailover INFO
 log.stats.failedPsResolution INFO
 log.savingDebugLog NOTICE
+log.fdUsageSixtyPercent NOTICE
+log.fdUsageNinetyPercent WARN
 

Modified: arm/trunk/src/interface/__init__.py
===================================================================
--- arm/trunk/src/interface/__init__.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/interface/__init__.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -2,5 +2,5 @@
 Panels, popups, and handlers comprising the arm user interface.
 """
 
-__all__ = ["configPanel", "connPanel", "controller", "descriptorPopup", "fileDescriptorPopup", "headerPanel", "logPanel", "torrcPanel"]
+__all__ = ["configPanel", "connPanel", "controller", "descriptorPopup", "headerPanel", "logPanel", "torrcPanel"]
 

Modified: arm/trunk/src/interface/controller.py
===================================================================
--- arm/trunk/src/interface/controller.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/interface/controller.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -22,7 +22,6 @@
 import configPanel
 import torrcPanel
 import descriptorPopup
-import fileDescriptorPopup
 
 import interface.connections.connPanel
 import interface.connections.connEntry
@@ -551,7 +550,7 @@
   connections.RESOLVER_FINAL_FAILURE_MSG += " (connection related portions of the monitor won't function)"
   
   panels = {
-    "header": headerPanel.HeaderPanel(stdscr, startTime),
+    "header": headerPanel.HeaderPanel(stdscr, startTime, config),
     "popup": Popup(stdscr, 9),
     "graph": graphing.graphPanel.GraphPanel(stdscr),
     "log": logPanel.LogPanel(stdscr, loggedEvents, config)}
@@ -962,7 +961,7 @@
           popup.addfstr(3, 2, "<b>s</b>: graphed stats (<b>%s</b>)" % graphedStats)
           popup.addfstr(3, 41, "<b>i</b>: graph update interval (<b>%s</b>)" % graphing.graphPanel.UPDATE_INTERVALS[panels["graph"].updateInterval][0])
           popup.addfstr(4, 2, "<b>b</b>: graph bounds (<b>%s</b>)" % panels["graph"].bounds.lower())
-          popup.addfstr(4, 41, "<b>d</b>: file descriptors")
+          popup.addfstr(4, 41, "<b>a</b>: save snapshot of the log")
           popup.addfstr(5, 2, "<b>e</b>: change logged events")
           
           regexLabel = "enabled" if panels["log"].regexFilter else "disabled"
@@ -971,7 +970,6 @@
           hiddenEntryLabel = "visible" if panels["log"].showDuplicates else "hidden"
           popup.addfstr(6, 2, "<b>u</b>: duplicate log entries (<b>%s</b>)" % hiddenEntryLabel)
           popup.addfstr(6, 41, "<b>c</b>: clear event log")
-          popup.addfstr(7, 41, "<b>a</b>: save snapshot of the log")
           
           pageOverrideKeys = (ord('m'), ord('n'), ord('s'), ord('i'), ord('d'), ord('e'), ord('r'), ord('f'), ord('x'))
         if page == 1:
@@ -1121,21 +1119,6 @@
       panels["graph"].bounds = graphing.graphPanel.Bounds.next(panels["graph"].bounds)
       
       selectiveRefresh(panels, page)
-    elif page == 0 and key in (ord('d'), ord('D')):
-      # provides popup with file descriptors
-      panel.CURSES_LOCK.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:
-        panel.CURSES_LOCK.release()
-      
-      panels["graph"].redraw(True)
     elif page == 0 and (key == ord('a') or key == ord('A')):
       # allow user to enter a path to take a snapshot - abandons if left blank
       panel.CURSES_LOCK.acquire()

Deleted: arm/trunk/src/interface/fileDescriptorPopup.py
===================================================================
--- arm/trunk/src/interface/fileDescriptorPopup.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/interface/fileDescriptorPopup.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -1,189 +0,0 @@
-#!/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
-
-from util import panel, sysTools, uiTools
-
-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)
-      # TODO: better rewrite to take advantage of sysTools
-      
-      if not sysTools.isAvailable("lsof"): raise Exception("error: lsof is unavailable")
-      results = sysTools.call("lsof -np %s -F Ln" % torPid)
-      
-      # if we didn't get any results then tor's probably closed (keep defaults)
-      if len(results) == 0: return
-      
-      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')
-      
-      # checks if read failed due to permission issues
-      isPermissionDenied = True
-      for desc in descriptors:
-        if "Permission denied" not in desc:
-          isPermissionDenied = False
-          break
-      
-      if isPermissionDenied:
-        raise Exception("lsof error: Permission denied")
-      
-      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.strip() == "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("error: ulimit is unavailable")
-        self.fdLimit = int(results[0])
-        
-        # can't use sysTools for this call because ulimit isn't in the path...
-        # so how the **** am I to detect if it's available!
-        #if not sysTools.isAvailable("ulimit"): raise Exception("error: ulimit is unavailable")
-        #results = sysTools.call("ulimit -Hn")
-        #if len(results) == 0: raise Exception("error: ulimit call failed")
-        #self.fdLimit = int(results[0])
-    except Exception, exc:
-      # problem arose in calling or parsing lsof or ulimit calls
-      self.errorMsg = str(exc)
-    finally:
-      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 panel.CURSES_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) + 4
-    
-    popup.setHeight(popupHeight)
-    popup.recreate(stdscr, 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, 80)
-  finally:
-    panel.CURSES_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 | uiTools.getColor("red"))
-  else:
-    # text with file descriptor count and limit
-    fdCount = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
-    fdCountPer = 100 * fdCount / max(properties.fdLimit, 1)
-    
-    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 | uiTools.getColor(statsColor))
-    
-    # provides a progress bar reflecting the stats
-    barWidth = popup.maxX - len(countMsg) - 6 # space between "[ ]" in progress bar
-    barProgress = barWidth * fdCountPer / 100 # filled cells
-    if fdCount > 0: barProgress = max(1, barProgress) # ensures one cell is filled unless really zero
-    popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD)
-    popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | uiTools.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 | uiTools.getColor(color))
-      lineNum += 1
-      entryNum += 1
-  
-  popup.refresh()
-

Modified: arm/trunk/src/interface/headerPanel.py
===================================================================
--- arm/trunk/src/interface/headerPanel.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/interface/headerPanel.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -16,9 +16,10 @@
 
 import os
 import time
+import curses
 import threading
 
-from util import panel, sysTools, torTools, uiTools
+from util import log, panel, sysTools, torTools, uiTools
 
 # minimum width for which panel attempts to double up contents (two columns to
 # better use screen real estate)
@@ -32,24 +33,32 @@
 VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "obsolete": "red", "recommended": "green",  
                          "old": "red",  "unrecommended": "red",  "unknown": "cyan"}
 
+DEFAULT_CONFIG = {"features.showFdUsage": False,
+                  "log.fdUsageSixtyPercent": log.NOTICE,
+                  "log.fdUsageNinetyPercent": log.WARN}
+
 class HeaderPanel(panel.Panel, threading.Thread):
   """
   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),
-          orListenAddr, *address, *fingerprint, *flags, pid, startTime
+          orListenAddr, *address, *fingerprint, *flags, pid, startTime,
+          *fdUsed, fdLimit, isFdLimitEstimate
     sys/  hostname, os, version
     stat/ *%torCpu, *%armCpu, *rss, *%mem
   
   * volatile parameter that'll be reset on each update
   """
   
-  def __init__(self, stdscr, startTime):
+  def __init__(self, stdscr, startTime, config = None):
     panel.Panel.__init__(self, stdscr, "header", 0)
     threading.Thread.__init__(self)
     self.setDaemon(True)
     
+    self._config = dict(DEFAULT_CONFIG)
+    if config: config.update(self._config)
+    
     self._isTorConnected = True
     self._lastUpdate = -1       # time the content was last revised
     self._isPaused = False      # prevents updates if true
@@ -78,6 +87,10 @@
     # changes.
     self._lastResourceFetch = -1
     
+    # flag to indicate if we've already given file descriptor warnings
+    self._isFdSixtyPercentWarned = False
+    self._isFdNinetyPercentWarned = False
+    
     self.vals = {}
     self.valsLock = threading.RLock()
     self._update(True)
@@ -177,11 +190,36 @@
       else: break
     
     if self.vals["tor/orPort"]:
-      # Line 4 / Line 2 Right (fingerprint)
+      # Line 4 / Line 2 Right (fingerprint, and possibly file descriptor usage)
       y, x = (1, leftWidth) if isWide else (3, 0)
+      
       fingerprintLabel = uiTools.cropStr("fingerprint: %s" % self.vals["tor/fingerprint"], width)
       self.addstr(y, x, fingerprintLabel)
       
+      # if there's room and we're able to retrieve both the file descriptor
+      # usage and limit then it might be presented
+      if width - x - 59 >= 20 and self.vals["tor/fdUsed"] and self.vals["tor/fdLimit"]:
+        # display file descriptor usage if we're either configured to do so or
+        # running out
+        
+        fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals["tor/fdLimit"]
+        
+        if fdPercent >= 60 or self._config["features.showFdUsage"]:
+          fdPercentLabel, fdPercentFormat = "%i%%" % fdPercent, curses.A_NORMAL
+          if fdPercent >= 95:
+            fdPercentFormat = curses.A_BOLD | uiTools.getColor("red")
+          elif fdPercent >= 90:
+            fdPercentFormat = uiTools.getColor("red")
+          elif fdPercent >= 60:
+            fdPercentFormat = uiTools.getColor("yellow")
+          
+          estimateChar = "?" if self.vals["tor/isFdLimitEstimate"] else ""
+          baseLabel = "file desc: %i / %i%s (" % (self.vals["tor/fdUsed"], self.vals["tor/fdLimit"], estimateChar)
+          
+          self.addstr(y, x + 59, baseLabel)
+          self.addstr(y, x + 59 + len(baseLabel), fdPercentLabel, fdPercentFormat)
+          self.addstr(y, x + 59 + len(baseLabel) + len(fdPercentLabel), ")")
+      
       # Line 5 / Line 3 Left (flags)
       if self._isTorConnected:
         flagLine = "flags: "
@@ -349,6 +387,12 @@
         policyEntries += [policy.strip() for policy in exitPolicy.split(",")]
       self.vals["tor/exitPolicy"] = ", ".join(policyEntries)
       
+      # file descriptor limit for the process, if this can't be determined
+      # then the limit is None
+      fdLimit, fdIsEstimate = conn.getMyFileDescriptorLimit()
+      self.vals["tor/fdLimit"] = fdLimit
+      self.vals["tor/isFdLimitEstimate"] = fdIsEstimate
+      
       # system information
       unameVals = os.uname()
       self.vals["sys/hostname"] = unameVals[1]
@@ -364,6 +408,7 @@
       # reverts volatile parameters to defaults
       self.vals["tor/fingerprint"] = "Unknown"
       self.vals["tor/flags"] = []
+      self.vals["tor/fdUsed"] = 0
       self.vals["stat/%torCpu"] = "0"
       self.vals["stat/%armCpu"] = "0"
       self.vals["stat/rss"] = "0"
@@ -377,6 +422,27 @@
     self.vals["tor/fingerprint"] = conn.getInfo("fingerprint", self.vals["tor/fingerprint"])
     self.vals["tor/flags"] = conn.getMyFlags(self.vals["tor/flags"])
     
+    # Updates file descriptor usage and logs if the usage is high. If we don't
+    # have a known limit or it's obviously faulty (being lower than our
+    # current usage) then omit file descriptor functionality.
+    if self.vals["tor/fdLimit"]:
+      fdUsed = conn.getMyFileDescriptorUsage()
+      if fdUsed and fdUsed <= self.vals["tor/fdLimit"]: self.vals["tor/fdUsed"] = fdUsed
+      else: self.vals["tor/fdUsed"] = 0
+    
+    if self.vals["tor/fdUsed"] and self.vals["tor/fdLimit"]:
+      fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals["tor/fdLimit"]
+      estimatedLabel = " estimated" if self.vals["tor/isFdLimitEstimate"] else ""
+      msg = "Tor's%s file descriptor usage is at %i%%." % (estimatedLabel, fdPercent)
+      
+      if fdPercent >= 90 and not self._isFdNinetyPercentWarned:
+        self._isFdSixtyPercentWarned, self._isFdNinetyPercentWarned = True, True
+        msg += " If you run out Tor will be unable to continue functioning."
+        log.log(self._config["log.fdUsageNinetyPercent"], msg)
+      elif fdPercent >= 60 and not self._isFdSixtyPercentWarned:
+        self._isFdSixtyPercentWarned = True
+        log.log(self._config["log.fdUsageSixtyPercent"], msg)
+    
     # ps or proc derived resource usage stats
     if self.vals["tor/pid"]:
       resourceTracker = sysTools.getResourceTracker(self.vals["tor/pid"])

Modified: arm/trunk/src/util/procTools.py
===================================================================
--- arm/trunk/src/util/procTools.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/util/procTools.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -96,6 +96,31 @@
   _logProcRuntime("cwd", "/proc/%s/cwd" % pid, startTime)
   return cwd
 
+def getUid(pid):
+  """
+  Provides the user ID the given process is running under. This is None if it
+  can't be determined.
+  
+  Arguments:
+    pid - queried process
+  """
+  
+  startTime = time.time()
+  statusFile = open("/proc/%s/status" % pid)
+  statusFileLines = statusFile.readlines()
+  statusFile.close()
+  
+  result = None
+  for line in statusFileLines:
+    if line.startswith("Uid:"):
+      lineComp = line.split()
+      
+      if len(lineComp) >= 2 and lineComp[1].isdigit():
+        result = lineComp[1]
+  
+  _logProcRuntime("uid", "/proc/%s/status[Uid]" % pid, startTime)
+  return result
+
 def getMemoryUsage(pid):
   """
   Provides the memory usage in bytes for the given process of the form:

Modified: arm/trunk/src/util/sysTools.py
===================================================================
--- arm/trunk/src/util/sysTools.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/util/sysTools.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -8,8 +8,10 @@
 
 from util import log, procTools, uiTools
 
-# mapping of commands to if they're available or not
-CMD_AVAILABLE_CACHE = {}
+# Mapping of commands to if they're available or not. This isn't always
+# reliable, failing for some special commands. For these the cache is
+# prepopulated to skip lookups.
+CMD_AVAILABLE_CACHE = {"ulimit": True}
 
 # cached system call results, mapping the command issued to the (time, results) tuple
 CALL_CACHE = {}

Modified: arm/trunk/src/util/torTools.py
===================================================================
--- arm/trunk/src/util/torTools.py	2011-04-17 17:25:12 UTC (rev 24650)
+++ arm/trunk/src/util/torTools.py	2011-04-18 02:34:38 UTC (rev 24651)
@@ -10,6 +10,7 @@
 """
 
 import os
+import pwd
 import time
 import socket
 import thread
@@ -55,7 +56,8 @@
               "config/names", "info/names", "features/names", "events/names",
               "nsEntry", "descEntry", "address", "bwRate", "bwBurst",
               "bwObserved", "bwMeasured", "flags", "parsedVersion", "pid",
-              "pathPrefix", "startTime", "authorities", "circuits", "hsPorts")
+              "user", "fdLimit", "pathPrefix", "startTime", "authorities",
+              "circuits", "hsPorts")
 CACHE_GETINFO_PREFIX_ARGS = ("ip-to-country/", )
 
 # Tor has a couple messages (in or/router.c) for when our ip address changes:
@@ -838,6 +840,52 @@
     
     return self._getRelayAttr("pid", None)
   
+  def getMyUser(self):
+    """
+    Provides the user this process is running under. If unavailable this
+    provides None.
+    """
+    
+    return self._getRelayAttr("user", None)
+  
+  def getMyFileDescriptorUsage(self):
+    """
+    Provides the number of file descriptors currently being used by this
+    process. This returns None if this can't be determined.
+    """
+    
+    # The file descriptor usage is the size of the '/proc/<pid>/fd' contents
+    # http://linuxshellaccount.blogspot.com/2008/06/finding-number-of-open-file-descriptors.html
+    # I'm not sure about other platforms (like BSD) so erroring out there.
+    
+    self.connLock.acquire()
+    
+    result = None
+    if self.isAlive() and procTools.isProcAvailable():
+      myPid = self.getMyPid()
+      
+      if myPid:
+        try: result = len(os.listdir("/proc/%s/fd" % myPid))
+        except: pass
+    
+    self.connLock.release()
+    
+    return result
+  
+  def getMyFileDescriptorLimit(self):
+    """
+    Provides the maximum number of file descriptors this process can have.
+    Only the Tor process itself reliably knows this value, and the option for
+    getting this was added in Tor 0.2.3.x-final. If that's unavailable then
+    we estimate the file descriptor limit based on other factors.
+    
+    The return result is a tuple of the form:
+    (fileDescLimit, isEstimate)
+    and if all methods fail then both values are None.
+    """
+    
+    return self._getRelayAttr("fdLimit", (None, True))
+  
   def getMyDirAuthorities(self):
     """
     Provides a listing of IP/port tuples for the directory authorities we've
@@ -1645,6 +1693,55 @@
         result = parseVersion(self.getInfo("version", ""))
       elif key == "pid":
         result = getPid(int(self.getOption("ControlPort", 9051)), self.getOption("PidFile"))
+      elif key == "user":
+        # This was added in Tor 0.2.3.x-final so it's quite likely unavailable.
+        # Even if it is, it might fail and return an empty string.
+        queriedUser = self.getInfo("process/user")
+        
+        if queriedUser != None and queriedUser != "":
+          result = queriedUser
+        else:
+          myPid = self.getMyPid()
+          
+          if myPid:
+            # if proc contents are available then fetch the pid from there and
+            # convert it to the username
+            if procTools.isProcAvailable():
+              try:
+                myUid = procTools.getUid(myPid)
+                if myUid and myUid.isdigit():
+                  result = pwd.getpwuid(int(myUid)).pw_name
+              except: pass
+            
+            # fall back to querying via ps
+            if not result:
+              psResults = sysTools.call("ps -o user %s" % myPid)
+              if psResults and len(psResults) >= 2: result = psResults[1].strip()
+      elif key == "fdLimit":
+        # This was added in Tor 0.2.3.x-final so it's quite likely unavailable.
+        # Even if it is, it might fail and return -1.
+        
+        queriedLimit = self.getInfo("process/descriptor-limit")
+        
+        if queriedLimit != None and queriedLimit != "-1":
+          result = (int(queriedLimit), False)
+        else:
+          torUser = self.getMyUser()
+          
+          # 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.
+          
+          if torUser == "debian-tor":
+            # probably loaded via /etc/init.d/tor which changes descriptor limit
+            result = (8192, True)
+          else:
+            # uses ulimit to estimate (-H is for hard limit, which is what tor uses)
+            ulimitResults = sysTools.call("ulimit -Hn")
+            
+            if ulimitResults:
+              ulimit = ulimitResults[0].strip()
+              if ulimit.isdigit(): result = (int(ulimit), True)
       elif key == "pathPrefix":
         # make sure the path prefix is valid and exists (providing a notice if not)
         prefixPath = CONFIG["features.pathPrefix"].strip()



More information about the tor-commits mailing list