[or-cvs] r22573: {arm} Client side caching for several frequently fetched parameter (in arm/trunk: interface interface/graphing util)

Damian Johnson atagar1 at gmail.com
Wed Jun 30 05:40:05 UTC 2010


Author: atagar
Date: 2010-06-30 05:40:05 +0000 (Wed, 30 Jun 2010)
New Revision: 22573

Modified:
   arm/trunk/interface/controller.py
   arm/trunk/interface/graphing/bandwidthStats.py
   arm/trunk/interface/graphing/psStats.py
   arm/trunk/interface/headerPanel.py
   arm/trunk/interface/logPanel.py
   arm/trunk/util/torTools.py
Log:
Client side caching for several frequently fetched parameters (fingerprint, flags, etc) as well as several bug fixes.



Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2010-06-30 02:56:50 UTC (rev 22572)
+++ arm/trunk/interface/controller.py	2010-06-30 05:40:05 UTC (rev 22573)
@@ -340,7 +340,7 @@
   except curses.error: pass
   
   # attempts to determine tor's current pid (left as None if unresolveable, logging an error later)
-  torPid = torTools.getConn().getPid()
+  torPid = torTools.getConn().getMyPid()
   
   try:
     confLocation = conn.get_info("config-file")["config-file"]
@@ -450,7 +450,7 @@
         # other panels that use torrc data
         panels["conn"].resetOptions()
         #if not isBlindMode: panels["graph"].stats["connections"].resetOptions(conn)
-        panels["graph"].stats["bandwidth"].resetOptions()
+        #panels["graph"].stats["bandwidth"].resetOptions()
         
         # if bandwidth graph is being shown then height might have changed
         if panels["graph"].currentDisplay == "bandwidth":

Modified: arm/trunk/interface/graphing/bandwidthStats.py
===================================================================
--- arm/trunk/interface/graphing/bandwidthStats.py	2010-06-30 02:56:50 UTC (rev 22572)
+++ arm/trunk/interface/graphing/bandwidthStats.py	2010-06-30 05:40:05 UTC (rev 22573)
@@ -20,76 +20,20 @@
 
 DEFAULT_CONFIG = {"features.graph.bw.accounting.show": True, "features.graph.bw.accounting.rate": 10, "features.graph.bw.accounting.isTimeLong": False}
 
-class ObservedBandwidthTracker(TorCtl.PostEventListener):
+class BandwidthStats(graphPanel.GraphStats, TorCtl.PostEventListener):
   """
-  This keeps track of the relay's current observed bandwidth (the throughput
-  noted by the directory authorities and used by clients for relay selection).
-  This is a parameter of the descriptors and hence is likely to get stale.
-  """
-  
-  def __init__(self):
-    TorCtl.PostEventListener.__init__(self)
-    self.observedBandwidth = None
-    
-    conn = torTools.getConn()
-    conn.addEventListener(self) # listenes for NEWDESC events
-    conn.addStatusListener(self.resetListener) # listens for new controllers
-    
-    self._parseDescriptors()
-  
-  def getObservedBandwidth(self):
-    """
-    Reports the observed bandwidth noted in the cached descriptors. This
-    provides None if descriptors are unavailable or otherwise unable to be
-    parsed.
-    """
-    
-    return self.observedBandwidth
-  
-  def new_desc_event(self, event):
-    myFingerprint = torTools.getConn().getFingerprint()
-    if not myFingerprint or myFingerprint in event.idlist:
-      self._parseDescriptors()
-  
-  def _parseDescriptors(self):
-    conn = torTools.getConn()
-    myFingerprint = conn.getInfo("fingerprint")
-    myDescLines = []
-    
-    if myFingerprint:
-      myDesc = conn.getInfo("desc/id/%s" % myFingerprint)
-      if myDesc: myDescLines = myDesc.split("\n")
-    
-    for line in myDescLines:
-      if line.startswith("bandwidth"):
-        # line should look something like:
-        # bandwidth 40960 102400 47284
-        comp = line.split()
-        
-        if len(comp) == 4 and comp[-1].isdigit():
-          self.observedBandwidth = uiTools.getSizeLabel(int(comp[-1]), 1)
-          return
-    
-    self.observedBandwidth = None # no bandwidth desc entry found
-  
-  def resetListener(self, conn, eventType):
-    if eventType == torTools.TOR_INIT: self._parseDescriptors()
-
-class BandwidthStats(graphPanel.GraphStats):
-  """
   Uses tor BW events to generate bandwidth usage graph.
   """
   
   def __init__(self, config=None):
     graphPanel.GraphStats.__init__(self)
+    TorCtl.PostEventListener.__init__(self)
     
     self._config = dict(DEFAULT_CONFIG)
     if config:
       config.update(self._config)
       self._config["features.graph.bw.accounting.rate"] = max(1, self._config["features.graph.bw.accounting.rate"])
     
-    self.observedBwTracker = ObservedBandwidthTracker()
-    
     # accounting data (set by _updateAccountingInfo method)
     self.accountingLastUpdated = 0
     self.accountingInfo = dict([(arg, "") for arg in ACCOUNTING_ARGS])
@@ -97,41 +41,16 @@
     # listens for tor reload (sighup) events which can reset the bandwidth
     # rate/burst and if tor's using accounting
     conn = torTools.getConn()
-    self.isAccounting, self.bwRate, self.bwBurst = False, None, None
+    self._titleStats, self.isAccounting = [], False
     self.resetListener(conn, torTools.TOR_INIT) # initializes values
     conn.addStatusListener(self.resetListener)
   
   def resetListener(self, conn, eventType):
-    # queries for rate, burst, and accounting status if it might have changed
-    if eventType == torTools.TOR_INIT:
-      if self._config["features.graph.bw.accounting.show"]:
-        self.isAccounting = conn.getInfo('accounting/enabled') == '1'
-      
-      # effective relayed bandwidth is the minimum of BandwidthRate,
-      # MaxAdvertisedBandwidth, and RelayBandwidthRate (if set)
-      effectiveRate = int(conn.getOption("BandwidthRate"))
-      
-      relayRate = conn.getOption("RelayBandwidthRate")
-      if relayRate and relayRate != "0":
-        effectiveRate = min(effectiveRate, int(relayRate))
-      
-      maxAdvertised = conn.getOption("MaxAdvertisedBandwidth")
-      if maxAdvertised: effectiveRate = min(effectiveRate, int(maxAdvertised))
-      
-      # effective burst (same for BandwidthBurst and RelayBandwidthBurst)
-      effectiveBurst = int(conn.getOption("BandwidthBurst"))
-      
-      relayBurst = conn.getOption("RelayBandwidthBurst")
-      if relayBurst and relayBurst != "0":
-        effectiveBurst = min(effectiveBurst, int(relayBurst))
-      
-      self.bwRate = uiTools.getSizeLabel(effectiveRate, 1)
-      self.bwBurst = uiTools.getSizeLabel(effectiveBurst, 1)
-      
-      # if both are using rounded values then strip off the ".0" decimal
-      if ".0" in self.bwRate and ".0" in self.bwBurst:
-        self.bwRate = self.bwRate.replace(".0", "")
-        self.bwBurst = self.bwBurst.replace(".0", "")
+    # updates title parameters and accounting status if they changed
+    self.new_desc_event(None) # updates title params
+    
+    if self._config["features.graph.bw.accounting.show"]:
+      self.isAccounting = conn.getInfo('accounting/enabled') == '1'
   
   def bandwidth_event(self, event):
     if self.isAccounting and self.isNextTickRedraw():
@@ -183,10 +102,7 @@
         panel.addfstr(10, 0, "<b>Accounting:</b> Connection Closed...")
   
   def getTitle(self, width):
-    stats, observedBw = [], self.observedBwTracker.getObservedBandwidth()
-    if self.bwRate: stats.append("limit: %s" % self.bwRate)
-    if self.bwBurst: stats.append("burst: %s" % self.bwBurst)
-    if observedBw: stats.append("observed: %s" % observedBw)
+    stats = list(self._titleStats)
     
     while True:
       if not stats: return "Bandwidth:"
@@ -227,6 +143,28 @@
   def getPreferredHeight(self):
     return 13 if self.isAccounting else 10
   
+  def new_desc_event(self, event):
+    # updates self._titleStats with updated values
+    conn = torTools.getConn()
+    myFingerprint = conn.getMyFingerprint()
+    
+    if not self._titleStats or not myFingerprint or (event and myFingerprint in event.idlist):
+      bwRate = uiTools.getSizeLabel(conn.getMyBandwidthRate(), 1)
+      bwBurst = uiTools.getSizeLabel(conn.getMyBandwidthBurst(), 1)
+      bwObserved = conn.getMyBandwidthObserved()
+      
+      # if both are using rounded values then strip off the ".0" decimal
+      if ".0" in bwRate and ".0" in bwBurst:
+        bwRate = bwRate.replace(".0", "")
+        bwBurst = bwBurst.replace(".0", "")
+      
+      stats = []
+      stats.append("limit: %s" % bwRate)
+      stats.append("burst: %s" % bwBurst)
+      if bwObserved: stats.append("observed: %s" % uiTools.getSizeLabel(bwObserved, 1))
+      
+      self._titleStats = stats
+  
   def _getAvgLabel(self, isPrimary):
     total = self.primaryTotal if isPrimary else self.secondaryTotal
     return "avg: %s/sec" % uiTools.getSizeLabel((total / max(1, self.tick)) * 1024, 1)

Modified: arm/trunk/interface/graphing/psStats.py
===================================================================
--- arm/trunk/interface/graphing/psStats.py	2010-06-30 02:56:50 UTC (rev 22572)
+++ arm/trunk/interface/graphing/psStats.py	2010-06-30 05:40:05 UTC (rev 22573)
@@ -26,7 +26,7 @@
     self._config = dict(DEFAULT_CONFIG)
     if config: config.update(self._config)
     
-    self.queryPid = torTools.getConn().getPid()
+    self.queryPid = torTools.getConn().getMyPid()
     self.queryParam = [self._config["features.graph.ps.primaryStat"], self._config["features.graph.ps.secondaryStat"]]
     
     # If we're getting the same stats as the header panel then issues identical

Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py	2010-06-30 02:56:50 UTC (rev 22572)
+++ arm/trunk/interface/headerPanel.py	2010-06-30 05:40:05 UTC (rev 22573)
@@ -294,7 +294,7 @@
       self.vals["sys/os"] = unameVals[0]
       self.vals["sys/version"] = unameVals[2]
       
-      pid = conn.getPid()
+      pid = conn.getMyPid()
       self.vals["ps/pid"] = pid if pid else ""
       
       # reverts volatile parameters to defaults
@@ -313,16 +313,9 @@
     if self.vals["tor/address"] == "Unknown":
       volatile["tor/address"] = conn.getInfo("address", self.vals["tor/address"])
     
-    volatile["tor/fingerprint"] = conn.getFingerprint(self.vals["tor/fingerprint"])
+    volatile["tor/fingerprint"] = conn.getMyFingerprint(self.vals["tor/fingerprint"])
+    volatile["tor/flags"] = conn.getMyFlags(self.vals["tor/flags"])
     
-    # sets flags
-    nsEntry = conn.getNetworkStatus()
-    if nsEntry:
-      # network status contains a couple of lines, looking like:
-      # r caerSidi p1aag7VwarGxqctS7/fS0y5FU+s 9On1TRGCEpljszPpJR1hKqlzaY8 2010-05-26 09:26:06 76.104.132.98 9001 0
-      # s Fast HSDir Named Running Stable Valid
-      if len(nsEntry) >= 2: volatile["tor/flags"] = nsEntry[1][2:].split()
-    
     # ps derived stats
     psParams = ["%cpu", "rss", "%mem", "etime"]
     if self.vals["ps/pid"]:

Modified: arm/trunk/interface/logPanel.py
===================================================================
--- arm/trunk/interface/logPanel.py	2010-06-30 02:56:50 UTC (rev 22572)
+++ arm/trunk/interface/logPanel.py	2010-06-30 05:40:05 UTC (rev 22573)
@@ -215,11 +215,12 @@
   
   def ns_event(self, event):
     # NetworkStatus params: nickname, idhash, orhash, ip, orport (int), dirport (int), flags, idhex, bandwidth, updated (datetime)
-    msg = ""
-    for ns in event.nslist:
-      msg += ", %s (%s:%i)" % (ns.nickname, ns.ip, ns.orport)
-    if len(msg) > 1: msg = msg[2:]
-    self.registerEvent("NS", "Listed (%i): %s" % (len(event.nslist), msg), "blue")
+    if "NS" in self.loggedEvents:
+      msg = ""
+      for ns in event.nslist:
+        msg += ", %s (%s:%i)" % (ns.nickname, ns.ip, ns.orport)
+      if len(msg) > 1: msg = msg[2:]
+      self.registerEvent("NS", "Listed (%i): %s" % (len(event.nslist), msg), "blue")
   
   def new_consensus_event(self, event):
     if "NEWCONSENSUS" in self.loggedEvents:

Modified: arm/trunk/util/torTools.py
===================================================================
--- arm/trunk/util/torTools.py	2010-06-30 02:56:50 UTC (rev 22572)
+++ arm/trunk/util/torTools.py	2010-06-30 05:40:05 UTC (rev 22573)
@@ -262,11 +262,15 @@
     self._isReset = False               # internal flag for tracking resets
     self._status = TOR_CLOSED           # current status of the attached control port
     self._statusTime = 0                # unix timestamp for the duration of the status
-    self._myNsEntry = None              # network status entry for this relay (none if unset or possibly changed)
     
-    # cached information static for a connection (None if unset, "UNKNOWN" if
-    # unable to be determined)
-    self.pid = None
+    # cached getInfo parameters (None if unset or possibly changed)
+    self._myNsEntry, self._myDescEntry = None, None
+    self._myBwRate, self._myBwBurst, self._myBwObserved = None, None, None
+    self._myFlags = None
+    self._myFingerprint = None
+    
+    # None if unset, "UNKNOWN" if unable to be determined
+    self._myPid = None
   
   def init(self, conn=None):
     """
@@ -289,6 +293,7 @@
       if self.conn: self.close() # shut down current connection
       self.conn = conn
       self.conn.add_event_listener(self)
+      for listener in self.eventListeners: self.conn.add_event_listener(listener)
       
       # sets the events listened for by the new controller (incompatable events
       # are dropped with a logged warning)
@@ -311,7 +316,6 @@
     if self.conn:
       self.conn.close()
       self.conn = None
-      self.pid = None
       self.connLock.release()
       
       self._status = TOR_CLOSED
@@ -419,53 +423,186 @@
     if not suppressExc and raisedExc: raise raisedExc
     else: return result
   
-  def getFingerprint(self, default = None):
+  def getMyNetworkStatus(self):
     """
+    Provides the network status entry for this relay if available (otherwise
+    provides None).
+    """
+    
+    if self._myNsEntry: return self._myNsEntry
+    
+    self.connLock.acquire()
+    
+    myFingerprint = self.getMyFingerprint()
+    if not self._myNsEntry and myFingerprint:
+      nsResults = self.getInfo("ns/id/%s" % myFingerprint)
+      if nsResults: self._myNsEntry = nsResults.split("\n")
+    
+    self.connLock.release()
+    
+    return self._myNsEntry
+  
+  def getMyDescriptor(self):
+    """
+    Provides the descrptor entry for this relay if available (otherwise
+    provides None).
+    """
+    
+    if self._myDescEntry: return self._myDescEntry
+      
+    self.connLock.acquire()
+    
+    myFingerprint = self.getMyFingerprint()
+    if not self._myDescEntry and myFingerprint:
+      descResults = self.getInfo("desc/id/%s" % myFingerprint)
+      if descResults: self._myDescEntry = descResults.split("\n")
+    
+    self.connLock.release()
+    
+    return self._myDescEntry
+  
+  def getMyBandwidthRate(self):
+    """
+    Provides the effective relaying bandwidth rate of this relay.
+    """
+    
+    if self._myBwRate: return self._myBwRate
+    
+    # effective relayed bandwidth is the minimum of BandwidthRate,
+    # MaxAdvertisedBandwidth, and RelayBandwidthRate (if set)
+    self.connLock.acquire()
+    
+    if not self._myBwRate:
+      effectiveRate = int(self.getOption("BandwidthRate"))
+      
+      relayRate = self.getOption("RelayBandwidthRate")
+      if relayRate and relayRate != "0":
+        effectiveRate = min(effectiveRate, int(relayRate))
+      
+      maxAdvertised = self.getOption("MaxAdvertisedBandwidth")
+      if maxAdvertised: effectiveRate = min(effectiveRate, int(maxAdvertised))
+      
+      self._myBwRate = effectiveRate
+    
+    self.connLock.release()
+    
+    return self._myBwRate
+  
+  def getMyBandwidthBurst(self):
+    """
+    Provides the effective bandwidth burst rate of this relay.
+    """
+    
+    if self._myBwBurst: return self._myBwBurst
+    
+    # effective burst (same for BandwidthBurst and RelayBandwidthBurst)
+    self.connLock.acquire()
+    
+    if not self._myBwBurst:
+      effectiveBurst = int(self.getOption("BandwidthBurst"))
+      
+      relayBurst = self.getOption("RelayBandwidthBurst")
+      if relayBurst and relayBurst != "0":
+        effectiveBurst = min(effectiveBurst, int(relayBurst))
+      
+      self._myBwBurst = effectiveBurst
+    
+    self.connLock.release()
+    
+    return self._myBwBurst
+  
+  def getMyBandwidthObserved(self):
+    """
+    Provides the relay's current observed bandwidth (the throughput as noted by
+    the directory authorities and used by clients for relay selection). This is
+    fetched from the descriptors and hence will get stale if descriptors aren't
+    periodically updated.
+    
+    This provides None if descriptors are unavailable or otherwise unable to be
+    parsed.
+    """
+    
+    if self._myBwObserved: return self._myBwObserved
+    
+    self.connLock.acquire()
+    
+    myDescriptor = self.getMyDescriptor()
+    if not self._myBwObserved and myDescriptor:
+      for line in myDescriptor:
+        if line.startswith("bandwidth"):
+          # line should look something like:
+          # bandwidth 40960 102400 47284
+          comp = line.split()
+          
+          if len(comp) == 4 and comp[-1].isdigit():
+            self._myBwObserved = int(comp[-1])
+            break
+    
+    self.connLock.release()
+    
+    return self._myBwObserved
+  
+  def getMyFingerprint(self, default = None):
+    """
     Provides the fingerprint for this relay.
     
     Arguments:
       default - result if the query fails
     """
     
-    # TODO: figure out what can cause the fingerprint to change so this can be cached
-    myFingerprint = self.getInfo("fingerprint")
+    # fingerprints are kept until sighup if set (most likely not even a setconf
+    # can change it since it's in the data directory)
+    if self._myFingerprint: return self._myFingerprint
     
-    if myFingerprint: return myFingerprint
+    self.connLock.acquire()
+    if not self._myFingerprint: self._myFingerprint = self.getInfo("fingerprint")
+    self.connLock.release()
+    
+    if self._myFingerprint: return self._myFingerprint
     else: return default
   
-  def getNetworkStatus(self):
+  def getMyFlags(self, default = None):
     """
-    Provides the network status entry for this relay if available (otherwise
-    provides None).
+    Provides the flags held by this relay.
+    
+    Arguments:
+      default - result if the query fails
     """
     
-    if self._myNsEntry: return self._myNsEntry
+    if self._myFlags: return self.myFlags
     
     self.connLock.acquire()
     
-    myFingerprint = self.getFingerprint()
-    if myFingerprint:
-      nsResults = self.getInfo("ns/id/%s" % myFingerprint)
-      if nsResults: self._myNsEntry = nsResults.split("\n")
+    myNetworkStatus = self.getMyNetworkStatus()
+    if not self._myFlags and myNetworkStatus:
+      # network status contains a couple of lines, looking like:
+      # r caerSidi p1aag7VwarGxqctS7/fS0y5FU+s 9On1TRGCEpljszPpJR1hKqlzaY8 2010-05-26 09:26:06 76.104.132.98 9001 0
+      # s Fast HSDir Named Running Stable Valid
+      if len(myNetworkStatus) >= 2: self._myFlags = myNetworkStatus[1][2:].split()
     
     self.connLock.release()
     
-    return self._myNsEntry
+    if self._myFlags: return self._myFlags
+    else: return default
   
-  def getPid(self):
+  def getMyPid(self):
     """
     Provides the pid of the attached tor process (None if no controller exists
     or this can't be determined).
     """
     
+    if self._myPid:
+      if self._myPid != "UNKNOWN" or not self.isAlive(): return None
+      else: return self._myPid
+    
     self.connLock.acquire()
     
-    if self.pid == "UNKNOWN" or not self.isAlive(): result = None
-    elif self.pid: result = self.pid
+    if self._myPid == "UNKNOWN" or not self.isAlive(): result = None
+    elif self._myPid: result = self._myPid
     else:
       result = getPid(int(self.getOption("ControlPort", 9051)))
-      if result: self.pid = result
-      else: self.pid = "UNKNOWN"
+      if result: self._myPid = result
+      else: self._myPid = "UNKNOWN"
     
     self.connLock.release()
     
@@ -658,7 +795,7 @@
             if self._isReset: break
           
           if not self._isReset:
-            errorLine, torPid = "", self.getPid()
+            errorLine, torPid = "", self.getMyPid()
             if torPid:
               for line in pkillOutput:
                 if line.startswith("pkill: %s - " % torPid):
@@ -689,19 +826,22 @@
       thread.start_new_thread(self._notifyStatusListeners, (TOR_INIT,))
   
   def ns_event(self, event):
-    # TODO: Not sure if idhash or orhash is the relay's fingerprint
-    #myFingerprint = self.getFingerprint()
-    #if myFingerprint:
-    #  for ns in event.nslist:
-    #    if ns.idhash == myFingerprint:
-    #      self._myNsEntry = None
-    #      return
-    
-    self._myNsEntry = None
+    myFingerprint = self.getMyFingerprint()
+    if myFingerprint:
+      for ns in event.nslist:
+        if ns.idhex == myFingerprint:
+          self._myNsEntry, self._myFlags = None, None
+          return
+    else: self._myNsEntry, self._myFlags = None, None
   
   def new_consensus_event(self, event):
-    self._myNsEntry = None
+    self._myNsEntry, self._myFlags = None, None
   
+  def new_desc_event(self, event):
+    myFingerprint = self.getMyFingerprint()
+    if not myFingerprint or myFingerprint in event.idlist:
+      self._myDescEntry, self._myBwObserved = None, None
+  
   def _notifyStatusListeners(self, eventType):
     """
     Sends a notice to all current listeners that a given change in tor's
@@ -711,6 +851,11 @@
       eventType - enum representing tor's new status
     """
     
+    # resets cached getInfo parameters
+    self._myNsEntry, self._myDescEntry = None, None
+    self._myBwRate, self._myBwBurst, self._myBwObserved = None, None, None
+    self._myFlags, self._myFingerprint, self._myPid = None, None, None
+    
     for callback in self.statusListeners:
       callback(self, eventType)
 



More information about the tor-commits mailing list