[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] r23975: {arm} Querying connection information via the proc contents. This (in arm/trunk: . src src/interface src/util)



Author: atagar
Date: 2010-12-23 06:22:55 +0000 (Thu, 23 Dec 2010)
New Revision: 23975

Added:
   arm/trunk/src/util/procTools.py
Modified:
   arm/trunk/README
   arm/trunk/armrc.sample
   arm/trunk/src/interface/controller.py
   arm/trunk/src/settings.cfg
   arm/trunk/src/starter.py
   arm/trunk/src/test.py
   arm/trunk/src/util/__init__.py
   arm/trunk/src/util/connections.py
   arm/trunk/src/util/sysTools.py
Log:
Querying connection information via the proc contents.
This runs around 90% faster than netstat, lsof, or any other connection resolver. I've also added (and tested) utilities that can be used to replace netstat queries, though arm isn't configured yet to use them.



Modified: arm/trunk/README
===================================================================
--- arm/trunk/README	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/README	2010-12-23 06:22:55 UTC (rev 23975)
@@ -144,6 +144,7 @@
       hostnames.py   - service providing nonblocking reverse dns lookups
       log.py         - aggregator for application events
       panel.py       - wrapper for safely working with curses subwindows
+      procTools.py   - queries process & system information from /proc contents
       sysTools.py    - helper for system calls, providing client side caching
       torConfig.py   - functions for working with the torrc and config options
       torTools.py    - TorCtl wrapper, providing caching and derived information

Modified: arm/trunk/armrc.sample
===================================================================
--- arm/trunk/armrc.sample	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/armrc.sample	2010-12-23 06:22:55 UTC (rev 23975)
@@ -195,6 +195,7 @@
 log.torSetConf INFO
 log.torEventTypeUnrecognized NOTICE
 log.torPrefixPathInvalid NOTICE
+log.procCallMade DEBUG
 log.sysCallMade DEBUG
 log.sysCallCached NONE
 log.sysCallFailed INFO

Modified: arm/trunk/src/interface/controller.py
===================================================================
--- arm/trunk/src/interface/controller.py	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/interface/controller.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -1426,7 +1426,7 @@
         panels["conn"].sortConnections()
     elif page == 1 and (key == ord('u') or key == ord('U')):
       # provides menu to pick identification resolving utility
-      optionTypes = [None, connections.CMD_NETSTAT, connections.CMD_SOCKSTAT, connections.CMD_LSOF, connections.CMD_SS, connections.CMD_BSD_SOCKSTAT, connections.CMD_BSD_PROCSTAT]
+      optionTypes = [None, connections.CMD_PROC, connections.CMD_NETSTAT, connections.CMD_SOCKSTAT, connections.CMD_LSOF, connections.CMD_SS, connections.CMD_BSD_SOCKSTAT, connections.CMD_BSD_PROCSTAT]
       options = ["auto"] + [connections.CMD_STR[util] for util in optionTypes[1:]]
       
       initialSelection = connections.getResolver("tor").overwriteResolver # enums correspond to indices

Modified: arm/trunk/src/settings.cfg
===================================================================
--- arm/trunk/src/settings.cfg	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/settings.cfg	2010-12-23 06:22:55 UTC (rev 23975)
@@ -39,6 +39,7 @@
 #        (Network is unreachable; NOROUTE; count 47;    recommendation warn)
 # [WARN] 4 unknown, 1 missing key, 3 good, 0 bad, 1 no signature, 4 required
 # [ARM_DEBUG] refresh rate: 0.001 seconds
+# [ARM_DEBUG] proc call (process connections): /proc/net/[tcp|udp] (runtime: 0.0018)
 # [ARM_DEBUG] system call: ps -p 2354 -o %cpu,rss,%mem,etime (runtime: 0.02)
 # [ARM_DEBUG] system call: netstat -npt | grep 2354/tor (runtime: 0.02)
 # [ARM_DEBUG] recreating panel 'graph' with the dimensions of 14/124
@@ -66,6 +67,13 @@
 msg.WARN Problem bootstrapping. Stuck at
 msg.WARN *missing key,
 msg.ARM_DEBUG refresh rate:
+msg.ARM_DEBUG proc call (process cwd):
+msg.ARM_DEBUG proc call (process memory usage):
+msg.ARM_DEBUG proc call (process command
+msg.ARM_DEBUG proc call (process utime
+msg.ARM_DEBUG proc call (process stime
+msg.ARM_DEBUG proc call (process start time
+msg.ARM_DEBUG proc call (process connections):
 msg.ARM_DEBUG system call: ps
 msg.ARM_DEBUG system call: netstat
 msg.ARM_DEBUG recreating panel '

Modified: arm/trunk/src/starter.py
===================================================================
--- arm/trunk/src/starter.py	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/starter.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -19,6 +19,7 @@
 import util.hostnames
 import util.log
 import util.panel
+import util.procTools
 import util.sysTools
 import util.torConfig
 import util.torTools
@@ -218,7 +219,7 @@
   config.update(CONFIG)
   
   # loads user preferences for utilities
-  for utilModule in (util.conf, util.connections, util.hostnames, util.log, util.panel, util.sysTools, util.torConfig, util.torTools, util.uiTools):
+  for utilModule in (util.conf, util.connections, util.hostnames, util.log, util.panel, util.procTools, util.sysTools, util.torConfig, util.torTools, util.uiTools):
     utilModule.loadConfig(config)
   
   # overwrites undefined parameters with defaults

Modified: arm/trunk/src/test.py
===================================================================
--- arm/trunk/src/test.py	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/test.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -67,7 +67,7 @@
       # provide the selection options
       printDivider()
       print("Select a resolver:")
-      for i in range(1, 7):
+      for i in range(1, 8):
         print("  %i. %s" % (i, connections.CMD_STR[i]))
       print("  q. Go back to the main menu")
       
@@ -76,7 +76,7 @@
         printDivider()
         break
       
-      if userSelection.isdigit() and int(userSelection) in range(1, 7):
+      if userSelection.isdigit() and int(userSelection) in range(1, 8):
         try:
           resolver = int(userSelection)
           startTime = time.time()

Modified: arm/trunk/src/util/__init__.py
===================================================================
--- arm/trunk/src/util/__init__.py	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/util/__init__.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -4,5 +4,5 @@
 and safely working with curses (hiding some of the gory details).
 """
 
-__all__ = ["conf", "connections", "hostnames", "log", "panel", "sysTools", "torConfig", "torTools", "uiTools"]
+__all__ = ["conf", "connections", "hostnames", "log", "panel", "procTools", "sysTools", "torConfig", "torTools", "uiTools"]
 

Modified: arm/trunk/src/util/connections.py
===================================================================
--- arm/trunk/src/util/connections.py	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/util/connections.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -18,15 +18,15 @@
 """
 
 import os
-import sys
 import time
 import threading
 
-from util import log, sysTools
+from util import log, procTools, sysTools
 
 # enums for connection resolution utilities
-CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS, CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT = range(1, 7)
-CMD_STR = {CMD_NETSTAT: "netstat",
+CMD_PROC, CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS, CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT = range(1, 8)
+CMD_STR = {CMD_PROC: "proc",
+           CMD_NETSTAT: "netstat",
            CMD_SS: "ss",
            CMD_LSOF: "lsof",
            CMD_SOCKSTAT: "sockstat",
@@ -105,7 +105,8 @@
     # if the pid was undefined then match any in that field
     processPid = "[0-9]*"
   
-  if resolutionCmd == CMD_NETSTAT: return RUN_NETSTAT % (processPid, processName)
+  if resolutionCmd == CMD_PROC: return ""
+  elif resolutionCmd == CMD_NETSTAT: return RUN_NETSTAT % (processPid, processName)
   elif resolutionCmd == CMD_SS: return RUN_SS % (processName, processPid)
   elif resolutionCmd == CMD_LSOF: return RUN_LSOF % (processName, processPid)
   elif resolutionCmd == CMD_SOCKSTAT: return RUN_SOCKSTAT % (processName, processPid)
@@ -130,46 +131,56 @@
     processPid    - process ID (this helps improve accuracy)
   """
   
-  
-  # raises an IOError if the command fails or isn't available
-  cmd = getResolverCommand(resolutionCmd, processName, processPid)
-  results = sysTools.call(cmd)
-  
-  if not results: raise IOError("No results found using: %s" % cmd)
-  
-  # parses results for the resolution command
-  conn = []
-  for line in results:
-    if resolutionCmd == CMD_LSOF:
-      # Different versions of lsof have different numbers of columns, so
-      # stripping off the optional 'established' entry so we can just use
-      # the last one.
-      comp = line.replace("(ESTABLISHED)", "").strip().split()
-    else: comp = line.split()
+  if resolutionCmd == CMD_PROC:
+    # Attempts resolution via checking the proc contents.
+    if not processPid:
+      raise ValueError("proc resolution requires a pid")
     
-    if resolutionCmd == CMD_NETSTAT:
-      localIp, localPort = comp[3].split(":")
-      foreignIp, foreignPort = comp[4].split(":")
-    elif resolutionCmd == CMD_SS:
-      localIp, localPort = comp[4].split(":")
-      foreignIp, foreignPort = comp[5].split(":")
-    elif resolutionCmd == CMD_LSOF:
-      local, foreign = comp[-1].split("->")
-      localIp, localPort = local.split(":")
-      foreignIp, foreignPort = foreign.split(":")
-    elif resolutionCmd == CMD_SOCKSTAT:
-      localIp, localPort = comp[4].split(":")
-      foreignIp, foreignPort = comp[5].split(":")
-    elif resolutionCmd == CMD_BSD_SOCKSTAT:
-      localIp, localPort = comp[5].split(":")
-      foreignIp, foreignPort = comp[6].split(":")
-    elif resolutionCmd == CMD_BSD_PROCSTAT:
-      localIp, localPort = comp[9].split(":")
-      foreignIp, foreignPort = comp[10].split(":")
+    try:
+      return procTools.getConnections(processPid)
+    except Exception, exc:
+      raise IOError(str(exc))
+  else:
+    # Queries a resolution utility (netstat, lsof, etc). This raises an
+    # IOError if the command fails or isn't available.
+    cmd = getResolverCommand(resolutionCmd, processName, processPid)
+    results = sysTools.call(cmd)
     
-    conn.append((localIp, localPort, foreignIp, foreignPort))
-  
-  return conn
+    if not results: raise IOError("No results found using: %s" % cmd)
+    
+    # parses results for the resolution command
+    conn = []
+    for line in results:
+      if resolutionCmd == CMD_LSOF:
+        # Different versions of lsof have different numbers of columns, so
+        # stripping off the optional 'established' entry so we can just use
+        # the last one.
+        comp = line.replace("(ESTABLISHED)", "").strip().split()
+      else: comp = line.split()
+      
+      if resolutionCmd == CMD_NETSTAT:
+        localIp, localPort = comp[3].split(":")
+        foreignIp, foreignPort = comp[4].split(":")
+      elif resolutionCmd == CMD_SS:
+        localIp, localPort = comp[4].split(":")
+        foreignIp, foreignPort = comp[5].split(":")
+      elif resolutionCmd == CMD_LSOF:
+        local, foreign = comp[-1].split("->")
+        localIp, localPort = local.split(":")
+        foreignIp, foreignPort = foreign.split(":")
+      elif resolutionCmd == CMD_SOCKSTAT:
+        localIp, localPort = comp[4].split(":")
+        foreignIp, foreignPort = comp[5].split(":")
+      elif resolutionCmd == CMD_BSD_SOCKSTAT:
+        localIp, localPort = comp[5].split(":")
+        foreignIp, foreignPort = comp[6].split(":")
+      elif resolutionCmd == CMD_BSD_PROCSTAT:
+        localIp, localPort = comp[9].split(":")
+        foreignIp, foreignPort = comp[10].split(":")
+      
+      conn.append((localIp, localPort, foreignIp, foreignPort))
+    
+    return conn
 
 def isResolverAlive(processName, processPid = ""):
   """
@@ -226,8 +237,12 @@
   """
   
   if osType == None: osType = os.uname()[0]
-  if osType == "FreeBSD": return [CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT, CMD_LSOF]
-  else: return [CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS]
+  if osType == "FreeBSD":
+    return [CMD_BSD_SOCKSTAT, CMD_BSD_PROCSTAT, CMD_LSOF]
+  elif osType == "Linux":
+    return [CMD_PROC, CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS]
+  else:
+    return [CMD_NETSTAT, CMD_SOCKSTAT, CMD_LSOF, CMD_SS]
 
 class ConnectionResolver(threading.Thread):
   """
@@ -292,7 +307,7 @@
     self.defaultRate = CONFIG["queries.connections.minRate"]
     self.lastLookup = -1
     self.overwriteResolver = None
-    self.defaultResolver = CMD_NETSTAT
+    self.defaultResolver = CMD_PROC
     
     osType = os.uname()[0]
     self.resolverOptions = getSystemResolvers(osType)
@@ -303,7 +318,7 @@
     # sets the default resolver to be the first found in the system's PATH
     # (left as netstat if none are found)
     for resolver in self.resolverOptions:
-      if sysTools.isAvailable(CMD_STR[resolver]):
+      if resolver == CMD_PROC or sysTools.isAvailable(CMD_STR[resolver]):
         self.defaultResolver = resolver
         break
     

Added: arm/trunk/src/util/procTools.py
===================================================================
--- arm/trunk/src/util/procTools.py	                        (rev 0)
+++ arm/trunk/src/util/procTools.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -0,0 +1,269 @@
+"""
+Helper functions for querying process and system information from the /proc
+contents. Fetching information this way provides huge performance benefits
+over lookups via system utilities (ps, netstat, etc). For instance, resolving
+connections this way cuts the runtime by around 90% verses the alternatives.
+These functions may not work on all platforms (only Linux?).
+
+All functions raise IOErrors if unable to read their respective proc files.
+
+The method for reading these files (and some of the code) are borrowed from
+psutil:
+https://code.google.com/p/psutil/
+which was written by Jay Loden, Dave Daeschler, Giampaolo Rodola' and is under
+the BSD license.
+"""
+
+import os
+import sys
+import time
+import socket
+import base64
+
+from util import log
+
+# TODO: need to log the runtimes for proc queries
+# TODO: need to update the header panel time every second to give a more fluid appearance
+# TODO: finish integrating proc connection selection into arm's interface
+# TODO: integrate ps resultion into arm's header panel
+
+# cached system values
+SYS_START_TIME, SYS_PHYSICAL_MEMORY = None, None
+CLOCK_TICKS = os.sysconf(os.sysconf_names["SC_CLK_TCK"])
+STAT_COMMAND, STAT_CPU_UTIME, STAT_CPU_STIME, STAT_START_TIME = range(4)
+
+CONFIG = {"log.procCallMade": log.DEBUG}
+
+def loadConfig(config):
+  config.update(CONFIG)
+
+def getSystemStartTime():
+  """
+  Provides the unix time (seconds since epoch) when the system started.
+  """
+  
+  global SYS_START_TIME
+  if not SYS_START_TIME:
+    startTime = time.time()
+    statFile = open('/proc/stat')
+    statLines = statFile.readlines()
+    statFile.close()
+    
+    for line in statLines:
+      if line.startswith('btime'):
+        SYS_START_TIME = float(line.strip().split()[1])
+        break
+    
+    _logProcRuntime("system start time", "/proc/stat[btime]", startTime)
+  
+  return SYS_START_TIME
+
+def getPhysicalMemory():
+  """
+  Provides the total physical memory on the system in bytes.
+  """
+  
+  global SYS_PHYSICAL_MEMORY
+  if not SYS_PHYSICAL_MEMORY:
+    startTime = time.time()
+    memFile = open('/proc/meminfo')
+    memLines = memFile.readlines()
+    memFile.close()
+    
+    for line in memLines:
+      if line.startswith('MemTotal:'):
+        SYS_PHYSICAL_MEMORY = int(line.split()[1]) * 1024
+    
+    _logProcRuntime("system physical memory", "/proc/meminfo[MemTotal]", startTime)
+  
+  return SYS_PHYSICAL_MEMORY
+
+def getCwd(pid):
+  """
+  Provides the current working directory for the given process.
+  
+  Arguments:
+    pid - queried process
+  """
+  
+  startTime = time.time()
+  if pid == 0: cwd = ""
+  else: cwd = os.readlink("/proc/%s/cwd" % pid)
+  _logProcRuntime("cwd", "/proc/%s/cwd" % pid, startTime)
+  return cwd
+
+def getMemoryUsage(pid):
+  """
+  Provides the memory usage in bytes for the given process of the form:
+  (residentSize, virtualSize)
+  
+  Arguments:
+    pid - queried process
+  """
+  
+  # checks if this is the kernel process
+  if pid == 0: return (0, 0)
+  
+  startTime = time.time()
+  statusFile = open("/proc/%s/status" % pid)
+  statusFileLines = statusFile.readlines()
+  statusFile.close()
+  
+  residentSize, virtualSize = None, None
+  for line in statusFileLines:
+    if line.startswith("VmRSS"):
+      residentSize = int(line.split()[1]) * 1024
+      if virtualSize != None: break
+    elif line.startswith("VmSize:"):
+      virtualSize = int(line.split()[1]) * 1024
+      if residentSize != None: break
+  
+  _logProcRuntime("memory usage", "/proc/%s/status[VmRSS|VmSize]" % pid, startTime)
+  return (residentSize, virtualSize)
+
+def getStats(pid, *statTypes):
+  """
+  Provides process specific information. Options are:
+  STAT_COMMAND      command name under which the process is running
+  STAT_CPU_UTIME    total user time spent on the process
+  STAT_CPU_STIME    total system time spent on the process
+  STAT_START_TIME   when this process began, in unix time
+  
+  Arguments:
+    pid       - queried process
+    statTypes - information to be provided back
+  """
+  
+  startTime = time.time()
+  statFilePath = "/proc/%s/stat" % pid
+  statFile = open(statFilePath)
+  statContents = statFile.read().strip()
+  statFile.close()
+  
+  # contents are of the form:
+  # 8438 (tor) S 8407 8438 8407 34818 8438 4202496...
+  statComp = []
+  cmdStart, cmdEnd = statContents.find("("), statContents.find(")")
+  
+  if cmdStart != -1 and cmdEnd != -1:
+    statComp.append(statContents[:cmdStart])
+    statComp.append(statContents[cmdStart + 1:cmdEnd])
+    statComp += statContents[cmdEnd + 1:].split()
+  
+  if len(statComp) != 44:
+    raise IOError("stat file had an unexpected format: %s" % statFilePath)
+  
+  results, queriedStats = [], []
+  for statType in statTypes:
+    if statType == STAT_COMMAND:
+      queriedStats.append("command")
+      if pid == 0: results.append("sched")
+      else: results.append(statComp[1])
+    elif statType == STAT_CPU_UTIME:
+      queriedStats.append("utime")
+      if pid == 0: results.append("0")
+      else: results.append(str(float(statComp[13]) / CLOCK_TICKS))
+    elif statType == STAT_CPU_STIME:
+      queriedStats.append("stime")
+      if pid == 0: results.append("0")
+      else: results.append(str(float(statComp[14]) / CLOCK_TICKS))
+    elif statType == STAT_START_TIME:
+      queriedStats.append("start time")
+      if pid == 0: return getSystemStartTime()
+      else:
+        # According to documentation, starttime is in field 21 and the unit is
+        # jiffies (clock ticks). We divide it for clock ticks, then add the
+        # uptime to get the seconds since the epoch.
+        pStartTime = float(statComp[21]) / CLOCK_TICKS
+        results.append(str(pStartTime + getSystemStartTime()))
+  
+  _logProcRuntime("process %s" % ", ".join(queriedStats), "/proc/%s/stat" % pid, startTime)
+  return results
+
+def getConnections(pid):
+  """
+  Provides a listing of connection tuples of the form:
+  [(local_ipAddr1, local_port1, foreign_ipAddr1, foreign_port1), ...]
+  
+  If the information about a connection can't be queried (often due to
+  permission issues) then it's excluded from the listing.
+  
+  Arguments:
+    pid - ID of the process to be resolved
+  """
+  
+  if pid == "0": return []
+  
+  # fetches the inode numbers for socket file descriptors
+  startTime = time.time()
+  inodes = []
+  for fd in os.listdir("/proc/%s/fd" % pid):
+    try:
+      # File descriptor link, such as 'socket:[30899]'
+      fdName = os.readlink("/proc/%s/fd/%s" % (pid, fd))
+      
+      if fdName.startswith('socket:['):
+        inodes.append(fdName[8:-1])
+    except OSError:
+      pass # most likely couldn't be read due to permissions
+  
+  if not inodes:
+    # unable to fetch any connections for this process
+    return []
+  
+  # check for the connection information from the /proc/net contents
+  conn = []
+  for procFilePath in ("/proc/net/tcp", "/proc/net/udp"):
+    procFile = open(procFilePath)
+    procFile.readline() # skip the first line
+    
+    for line in procFile:
+      _, lAddr, fAddr, status, _, _, _, _, _, inode = line.split()[:10]
+      
+      if inode in inodes:
+        # if a tcp connection, skip if it isn't yet established
+        if procFilePath.endswith("/tcp") and status != "01":
+          continue
+        
+        localIp, localPort = _decodeProcAddressEncoding(lAddr)
+        foreignIp, foreignPort = _decodeProcAddressEncoding(fAddr)
+        conn.append((localIp, localPort, foreignIp, foreignPort))
+    
+    procFile.close()
+  
+  _logProcRuntime("process connections", "/proc/net/[tcp|udp]", startTime)
+  
+  return conn
+
+def _decodeProcAddressEncoding(addr):
+  """
+  Translates an address entry in the /proc/net/* contents to a human readable
+  form, for instance:
+  "0500000A:0016" -> ("10.0.0.5", "22")
+  
+  Reference:
+  http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
+  
+  Arguments:
+    addr - proc address entry to be decoded
+  """
+  
+  ip, port = addr.split(':')
+  
+  # the port is represented as a two-byte hexadecimal number
+  port = str(int(port, 16))
+  
+  if sys.version_info >= (3,):
+    ip = ip.encode('ascii')
+  
+  # The IPv4 address portion is a little-endian four-byte hexadecimal number.
+  # That is, the least significant byte is listed first, so we need to reverse
+  # the order of the bytes to convert it to an IP address.
+  ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip)[::-1])
+  
+  return (ip, port)
+
+def _logProcRuntime(parameter, procLocation, startTime):
+  msg = "proc call (%s): %s (runtime: %0.4f)" % (parameter, procLocation, time.time() - startTime)
+  log.log(CONFIG["log.procCallMade"], msg)
+

Modified: arm/trunk/src/util/sysTools.py
===================================================================
--- arm/trunk/src/util/sysTools.py	2010-12-22 07:11:24 UTC (rev 23974)
+++ arm/trunk/src/util/sysTools.py	2010-12-23 06:22:55 UTC (rev 23975)
@@ -3,11 +3,10 @@
 """
 
 import os
-import sys
 import time
 import threading
 
-from util import log
+from util import log, procTools
 
 # mapping of commands to if they're available or not
 CMD_AVAILABLE_CACHE = {}
@@ -94,26 +93,11 @@
     return PROCESS_NAME_CACHE[pid]
   
   processName, raisedExc = "", None
-  if sys.platform.lower().startswith("linux"):
-    if pid == 0:
-      # special case for the kernel process
-      processName = "sched"
-    else:
-      try:
-        statFilePath = "/proc/%s/stat" % pid
-        statFile = open(statFilePath)
-        statContents = statFile.read()
-        statFile.close()
-        
-        # contents are of the form:
-        # 8438 (tor) S 8407 8438 8407 34818 8438 4202496...
-        start, end = statContents.find("("), statContents.find(")")
-        if start != -1 and end != -1:
-          processName = statContents[start + 1:end]
-        else:
-          raise IOError("stat file had an unexpected format: %s" % statFilePath)
-      except IOError, exc:
-        raisedExc = exc
+  if os.uname()[0] == "Linux":
+    try:
+      processName = procTools.getStats(pid, procTools.STAT_COMMAND)[0]
+    except IOError, exc:
+      raisedExc = exc
   
   if raisedExc:
     if default == None: raise raisedExc