[tor-commits] [arm/master] Moving header panel sampling to its own class

atagar at torproject.org atagar at torproject.org
Mon Jun 23 15:00:33 UTC 2014


commit 08b684bdc3cb405e0993c62e8c06ba7600ca8220
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Jun 22 16:02:25 2014 -0700

    Moving header panel sampling to its own class
    
    Our header panel is a daemon thread that periodically pulls stats, rending the
    last result whenever redrawn. We've changed what we use for sampling a few
    times. First it was a dictionary, then a named tuple. Now I'm making the
    sampling its own class. This greatly helps split the model and view
    functionality.
---
 arm/controller.py   |    5 +-
 arm/header_panel.py |  306 +++++++++++++++++++++------------------------------
 2 files changed, 125 insertions(+), 186 deletions(-)

diff --git a/arm/controller.py b/arm/controller.py
index e1c3991..f18af9c 100644
--- a/arm/controller.py
+++ b/arm/controller.py
@@ -586,8 +586,7 @@ def start_arm(stdscr):
     stdscr    - curses window
   """
 
-  start_time = CONFIG['start_time']
-  init_controller(stdscr, start_time)
+  init_controller(stdscr, CONFIG['start_time'])
   control = get_controller()
 
   if not CONFIG["features.acsSupport"]:
@@ -619,7 +618,7 @@ def start_arm(stdscr):
 
   # logs the initialization time
 
-  log.info("arm started (initialization took %0.3f seconds)" % (time.time() - start_time))
+  log.info("arm started (initialization took %0.3f seconds)" % (time.time() - CONFIG['start_time']))
 
   # main draw loop
 
diff --git a/arm/header_panel.py b/arm/header_panel.py
index 41992e4..9bbd211 100644
--- a/arm/header_panel.py
+++ b/arm/header_panel.py
@@ -6,15 +6,16 @@ available.
 
 import os
 import time
-import collections
 import curses
 import threading
 
 import arm.util.tracker
 
-from stem import Signal
+import stem
+import stem.util.system
+
 from stem.control import Listener, State
-from stem.util import conf, log, proc, str_tools, system
+from stem.util import conf, log, proc, str_tools
 
 import arm.starter
 import arm.popups
@@ -30,38 +31,6 @@ CONFIG = conf.config_dict('arm', {
   'features.showFdUsage': False,
 })
 
-Sampling = collections.namedtuple('Sampling', [
-  'address',
-  'fingerprint',
-  'nickname',
-  'or_address',
-  'or_port',
-  'dir_port',
-
-  'control_port',
-  'socket_path',
-  'is_password_auth',
-  'is_cookie_auth',
-
-  'exit_policy',
-  'flags',
-  'version',
-  'version_status',
-
-  'pid',
-  'start_time',
-  'fd_limit',
-  'fd_used',
-
-  'tor_cpu',
-  'arm_cpu',
-  'rss',
-  'memory',
-  'hostname',
-  'os_name',
-  'os_version',
-])
-
 
 class HeaderPanel(panel.Panel, threading.Thread):
   """
@@ -74,7 +43,6 @@ class HeaderPanel(panel.Panel, threading.Thread):
     self.setDaemon(True)
 
     self._is_tor_connected = tor_controller().is_alive()
-    self._last_update = -1       # time the content was last revised
     self._halt = False           # terminates thread if true
     self._cond = threading.Condition()  # used for pausing the thread
 
@@ -83,25 +51,6 @@ class HeaderPanel(panel.Panel, threading.Thread):
 
     self._halt_time = None
 
-    # The last arm cpu usage sampling taken. This is a tuple of the form:
-    # (total arm cpu time, sampling timestamp)
-    #
-    # The initial cpu total should be zero. However, at startup the cpu time
-    # in practice is often greater than the real time causing the initially
-    # reported cpu usage to be over 100% (which shouldn't be possible on
-    # single core systems).
-    #
-    # Setting the initial cpu total to the value at this panel's init tends to
-    # give smoother results (staying in the same ballpark as the second
-    # sampling) so fudging the numbers this way for now.
-
-    self._arm_cpu_sampling = (sum(os.times()[:3]), start_time)
-
-    # Last sampling received from the ResourceTracker, used to detect when it
-    # changes.
-
-    self._last_resource_fetch = -1
-
     # flag to indicate if we've already given file descriptor warnings
 
     self._is_fd_sixty_percent_warned = False
@@ -111,7 +60,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     self.vals_lock = threading.RLock()
 
     with self.vals_lock:
-      self.vals = self._get_attributes()
+      self.vals = Sampling()
 
     # listens for tor reload (sighup) events
 
@@ -135,7 +84,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     Requests a new identity and provides a visual queue.
     """
 
-    tor_controller().signal(Signal.NEWNYM)
+    tor_controller().signal(stem.Signal.NEWNYM)
 
     # If we're wide then the newnym label in this panel will give an
     # indication that the signal was sent. Otherwise use a msg.
@@ -479,12 +428,25 @@ class HeaderPanel(panel.Panel, threading.Thread):
         is_changed = False
 
         if self.vals.pid:
-          resource_tracker = arm.util.tracker.get_resource_tracker()
-          is_changed = self._last_resource_fetch != resource_tracker.run_counter()
+          #resource_tracker = arm.util.tracker.get_resource_tracker()
+          #is_changed = self._last_resource_fetch != resource_tracker.run_counter()
+          is_changed = True  # TODO: we should decide to redraw or not based on if the sampling values have changed
 
-        if is_changed or current_time - self._last_update >= 20:
+        if is_changed or (self.vals and current_time - self.vals.retrieved >= 20):
           with self.vals_lock:
-            self.vals = self._get_attributes()
+            self.vals = Sampling(self.vals)
+
+            if self.vals.fd_used and self.vals.fd_limit:
+              fd_percent = 100 * self.vals.fd_used / self.vals.fd_limit
+              msg = "Tor's file descriptor usage is at %i%%." % fd_percent
+
+              if fd_percent >= 90 and not self._is_fd_ninety_percent_warned:
+                self._is_fd_sixty_percent_warned, self._is_fd_ninety_percent_warned = True, True
+                msg += " If you run out Tor will be unable to continue functioning."
+                log.warn(msg)
+              elif fd_percent >= 60 and not self._is_fd_sixty_percent_warned:
+                self._is_fd_sixty_percent_warned = True
+                log.notice(msg)
 
         self.redraw(True)
         last_draw += 1
@@ -510,7 +472,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       self._halt_time = None
 
       with self.vals_lock:
-        self.vals = self._get_attributes()
+        self.vals = Sampling(self.vals)
 
       if self.get_height() != initial_height:
         # We're toggling between being a relay and client, causing the height
@@ -526,140 +488,118 @@ class HeaderPanel(panel.Panel, threading.Thread):
       self._halt_time = time.time()
 
       with self.vals_lock:
-        self.vals = self._get_attributes()
+        self.vals = Sampling(self.vals)
 
       self.redraw(True)
 
-  def _get_attributes(self):
+
+class Sampling(object):
+  """
+  Statistical information rendered by the header panel.
+  """
+
+  def __init__(self, last_sampling = None):
     controller = tor_controller()
 
     or_listeners = controller.get_listeners(Listener.OR, [])
+    fd_limit = controller.get_info('process/descriptor-limit', '-1')
 
-    if not or_listeners:
-      or_address, or_port = '', ''
-    else:
-      # TODO: Relays can bind to multiple ports to listen for OR connections.
-      # Not sure how we'd like to surface that...
+    uname_vals = os.uname()
+    start_time = stem.util.system.get_start_time(controller.get_pid(None))
+    tor_resources = arm.util.tracker.get_resource_tracker().get_resource_usage()
+
+    self.retrieved = time.time()
+    self.arm_total_cpu_time = sum(os.times()[:3])
+
+    self.address = controller.get_info('address', '')
+    self.fingerprint = controller.get_info('fingerprint', 'Unknown')
+    self.nickname = controller.get_conf('Nickname', '')
+    self.or_address = or_listeners[0][0] if or_listeners else ''
+    self.or_port = or_listeners[0][1] if or_listeners else ''
+    self.dir_port = controller.get_conf('DirPort', '0')
+
+    self.control_port = controller.get_conf('ControlPort', '0')
+    self.socket_path = controller.get_conf('ControlSocket', '')
+    self.is_password_auth = controller.get_conf('HashedControlPassword', None) is not None
+    self.is_cookie_auth = controller.get_conf('CookieAuthentication', None) == '1'
+
+    self.exit_policy = str(controller.get_exit_policy(''))
+    self.flags = self._get_flags(controller)
+    self.version = str(controller.get_version('Unknown', '')).split()[0]
+    self.version_status = controller.get_info('status/version/current', 'Unknown')
+
+    self.pid = controller.get_pid('')
+    self.start_time = start_time if start_time else ''
+    self.fd_limit = int(fd_limit) if fd_limit.isdigit() else None
+    self.fd_used = self._get_fd_used(controller.get_pid(None)) if self.fd_limit else 0
+
+    self.tor_cpu = '%0.1f' % (100 * tor_resources.cpu_sample)
+    self.arm_cpu = '%0.1f' % (100 * self._get_cpu_percentage(last_sampling))
+    self.rss = str(tor_resources.memory_bytes)
+    self.memory = '%0.1f' % (100 * tor_resources.memory_percent)
+    self.hostname = uname_vals[1]
+    self.os_name = uname_vals[0]
+    self.os_version = uname_vals[2]
+
+  def _get_fd_used(self, pid):
+    """
+    Provides the number of file descriptors currently being used by this
+    process.
 
-      or_address, or_port = or_listeners[0]
+    :param int pid: process id to look up
 
-    # file descriptor limit for the process, if this can't be determined
-    # then the limit is None
+    :returns: **int** of the number of file descriptors used, **None** if this
+      can't be determined
+    """
 
-    fd_limit = controller.get_info('process/descriptor-limit', '-1')
+    # 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.
 
-    if fd_limit != '-1' and fd_limit.isdigit():
-      fd_limit = int(fd_limit)
-    else:
-      fd_limit = None
+    if pid and proc.is_available():
+      try:
+        return len(os.listdir('/proc/%s/fd' % pid))
+      except:
+        pass
 
-    uname_vals = os.uname()
+    return None
 
-    try:
-      start_time = system.get_start_time(controller.get_pid())
-    except:
-      start_time = None
-
-    flags = []
-    my_fingerprint = controller.get_info('fingerprint', None)
-
-    if my_fingerprint:
-      my_status_entry = controller.get_network_status(my_fingerprint)
-
-      if my_status_entry:
-        flags = my_status_entry.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.
-
-    fd_used = 0
-
-    if fd_limit:
-      fd_used = get_file_descriptor_usage(controller.get_pid(None))
-
-      if not fd_used or fd_used > fd_limit:
-        fd_used = 0
-
-    if fd_used and fd_limit:
-      fd_percent = 100 * fd_used / fd_limit
-      msg = "Tor's file descriptor usage is at %i%%." % fd_percent
-
-      if fd_percent >= 90 and not self._is_fd_ninety_percent_warned:
-        self._is_fd_sixty_percent_warned, self._is_fd_ninety_percent_warned = True, True
-        msg += " If you run out Tor will be unable to continue functioning."
-        log.warn(msg)
-      elif fd_percent >= 60 and not self._is_fd_sixty_percent_warned:
-        self._is_fd_sixty_percent_warned = True
-        log.notice(msg)
-
-    resource_tracker = arm.util.tracker.get_resource_tracker()
-
-    resources = resource_tracker.get_resource_usage()
-    self._last_resource_fetch = resource_tracker.run_counter()
-
-    tor_cpu = '%0.1f' % (100 * resources.cpu_sample)
-    tor_rss = str(resources.memory_bytes)
-    tor_memory = '%0.1f' % (100 * resources.memory_percent)
-
-    # determines the cpu time for the arm process (including user and system
-    # time of both the primary and child processes)
-
-    total_arm_cpu_time, current_time = sum(os.times()[:3]), time.time()
-    arm_cpu_telta = total_arm_cpu_time - self._arm_cpu_sampling[0]
-    arm_time_delta = current_time - self._arm_cpu_sampling[1]
-    python_cpu_time = arm_cpu_telta / arm_time_delta
-    sys_call_cpu_time = 0.0  # TODO: add a wrapper around call() to get this
-    arm_cpu = '%0.1f' % (100 * (python_cpu_time + sys_call_cpu_time))
-    self._arm_cpu_sampling = (total_arm_cpu_time, current_time)
-
-    self._last_update = current_time
-
-    return Sampling(
-      address = controller.get_info('address', ''),
-      fingerprint = controller.get_info('fingerprint', 'Unknown'),
-      nickname = controller.get_conf('Nickname', ''),
-      or_address = or_address,
-      or_port = or_port,
-      dir_port = controller.get_conf('DirPort', '0'),
-
-      control_port = controller.get_conf('ControlPort', '0'),
-      socket_path = controller.get_conf('ControlSocket', ''),
-      is_password_auth = controller.get_conf('HashedControlPassword', None) is not None,
-      is_cookie_auth = controller.get_conf('CookieAuthentication', None) == '1',
-
-      exit_policy = str(controller.get_exit_policy()),
-      flags = flags,
-      version = str(controller.get_version('Unknown')).split()[0],
-      version_status = controller.get_info('status/version/current', 'Unknown'),
-
-      pid = controller.get_pid(''),
-      start_time = start_time if start_time else '',
-      fd_limit = fd_limit,
-      fd_used = fd_used,
-
-      tor_cpu = tor_cpu,
-      arm_cpu = arm_cpu,
-      rss = tor_rss,
-      memory = tor_memory,
-      hostname = uname_vals[1],
-      os_name = uname_vals[0],
-      os_version = uname_vals[2],
-    )
-
-
-def get_file_descriptor_usage(pid):
-  """
-  Provides the number of file descriptors currently being used by this
-  process. This returns None if this can't be determined.
-  """
+  def _get_flags(self, controller):
+    """
+    Provides the flags held by our relay. This is an empty list if it can't be
+    determined, likely because we don't have our own router status entry yet.
 
-  # 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.
+    :param stem.control.Controller controller: tor control connection
+
+    :returns: **list** with the relays held by our relay
+    """
 
-  if pid and proc.is_available():
     try:
-      return len(os.listdir('/proc/%s/fd' % pid))
-    except:
-      return None
+      my_fingerprint = controller.get_info('fingerprint')
+      return controller.get_network_status(my_fingerprint).flags
+    except stem.ControllerError:
+      return []
+
+  def _get_cpu_percentage(self, last_sampling):
+    """
+    Determine the cpu usage of our own process since the last sampling.
+
+    :param arm.header_panel.Sampling last_sampling: sampling for which to
+      provide a CPU usage delta with
+
+    :returns: **float** representation for our cpu usage over the given period
+      of time
+    """
+
+    if last_sampling:
+      arm_cpu_delta = self.arm_total_cpu_time - last_sampling.arm_total_cpu_time
+      arm_time_delta = self.retrieved - last_sampling.retrieved
+
+      python_cpu_time = arm_cpu_delta / arm_time_delta
+      sys_call_cpu_time = 0.0  # TODO: add a wrapper around call() to get this
+
+      return python_cpu_time + sys_call_cpu_time
+    else:
+      return 0.0





More information about the tor-commits mailing list