[tor-commits] [stem/master] Adding support for CELL_STATS events

atagar at torproject.org atagar at torproject.org
Mon Nov 4 07:29:15 UTC 2013


commit 5898557b596c1e2dfbd89c44a621aefb5286955a
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Nov 3 19:35:38 2013 -0800

    Adding support for CELL_STATS events
    
    Yet another event type from...
    
    https://gitweb.torproject.org/torspec.git/commitdiff/6f2919a
---
 docs/change_log.rst          |    1 +
 stem/response/events.py      |  102 +++++++++++++++++++++++++++++++++++++++++-
 stem/version.py              |    2 +
 test/unit/response/events.py |   59 ++++++++++++++++++++++++
 4 files changed, 162 insertions(+), 2 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index f066b9b..5c6fed2 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -44,6 +44,7 @@ The following are only available within stem's `git repository
   * Added the id attribute to the :class:`~stem.response.events.ORConnEvent` (:spec:`6f2919a`)
   * Added `support for CONN_BW events <api/response.html#stem.response.events.ConnectionBandwidthEvent>`_ (:spec:`6f2919a`)
   * Added `support for CIRC_BW events <api/response.html#stem.response.events.CircuitBandwidthEvent>`_ (:spec:`6f2919a`)
+  * Added `support for CELL_STATS events <api/response.html#stem.response.events.CellStatsEvent>`_ (:spec:`6f2919a`)
 
 .. _version_1.1:
 
diff --git a/stem/response/events.py b/stem/response/events.py
index b892649..616bbfc 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -20,6 +20,7 @@ from stem.util import connection, log, str_tools, tor_tools
 
 KW_ARG = re.compile("^(.*) ([A-Za-z0-9_]+)=(\S*)$")
 QUOTED_KW_ARG = re.compile("^(.*) ([A-Za-z0-9_]+)=\"(.*)\"$")
+CELL_TYPE = re.compile("^[a-z0-9_]+$")
 
 
 class Event(stem.response.ControlMessage):
@@ -1008,15 +1009,114 @@ class CircuitBandwidthEvent(Event):
     self.read = long(self.read)
     self.written = long(self.written)
 
+
+class CellStatsEvent(Event):
+  """
+  Event emitted every second with a count of the number of cells types broken
+  down by the circuit. **These events are only emitted if TestingTorNetwork is
+  set.**
+
+  The CELL_STATS event was introduced in tor version 0.2.5.2-alpha.
+
+  .. versionadded:: 1.1.0-dev
+
+  :var str id: circuit identifier
+  :var str inbound_queue: inbound queue identifier
+  :var str inbound_connection: inbound connection identifier
+  :var dict inbound_added: mapping of added inbound cell types to their count
+  :var dict inbound_removed: mapping of removed inbound cell types to their count
+  :var dict inbound_time: mapping of inbound cell types to the time they took to write in milliseconds
+  :var str outbound_queue: outbound queue identifier
+  :var str outbound_connection: outbound connection identifier
+  :var dict outbound_added: mapping of added outbound cell types to their count
+  :var dict outbound_removed: mapping of removed outbound cell types to their count
+  :var dict outbound_time: mapping of outbound cell types to the time they took to write in milliseconds
+  """
+
+  _KEYWORD_ARGS = {
+    "ID": "id",
+    "InboundQueue": "inbound_queue",
+    "InboundConn": "inbound_connection",
+    "InboundAdded": "inbound_added",
+    "InboundRemoved": "inbound_removed",
+    "InboundTime": "inbound_time",
+    "OutboundQueue": "outbound_queue",
+    "OutboundConn": "outbound_connection",
+    "OutboundAdded": "outbound_added",
+    "OutboundRemoved": "outbound_removed",
+    "OutboundTime": "outbound_time",
+  }
+
+  _VERSION_ADDED = stem.version.Requirement.EVENT_CELL_STATS
+
+  def _parse(self):
+    if self.id and not tor_tools.is_valid_circuit_id(self.id):
+      raise stem.ProtocolError("Circuit IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.id, self))
+    elif self.inbound_queue and not tor_tools.is_valid_circuit_id(self.inbound_queue):
+      raise stem.ProtocolError("Queue IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.inbound_queue, self))
+    elif self.inbound_connection and not tor_tools.is_valid_connection_id(self.inbound_connection):
+      raise stem.ProtocolError("Connection IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.inbound_connection, self))
+    elif self.outbound_queue and not tor_tools.is_valid_circuit_id(self.outbound_queue):
+      raise stem.ProtocolError("Queue IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.outbound_queue, self))
+    elif self.outbound_connection and not tor_tools.is_valid_connection_id(self.outbound_connection):
+      raise stem.ProtocolError("Connection IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.outbound_connection, self))
+
+    self.inbound_added = _parse_cell_type_mapping(self.inbound_added)
+    self.inbound_removed = _parse_cell_type_mapping(self.inbound_removed)
+    self.inbound_time = _parse_cell_type_mapping(self.inbound_time)
+    self.outbound_added = _parse_cell_type_mapping(self.outbound_added)
+    self.outbound_removed = _parse_cell_type_mapping(self.outbound_removed)
+    self.outbound_time = _parse_cell_type_mapping(self.outbound_time)
+
+
+def _parse_cell_type_mapping(mapping):
+  """
+  Parses a mapping of the form...
+
+    key1:value1,key2:value2...
+
+  ... in which keys are strings and values are integers.
+
+  :param str mapping: value to be parsed
+
+  :returns: dict of **str => int** mappings
+
+  :rasies: **stem.ProtocolError** if unable to parse the mapping
+  """
+
+  if mapping is None:
+    return None
+
+  results = {}
+
+  for entry in mapping.split(','):
+    if not ':' in entry:
+      raise stem.ProtocolError("Mappings are expected to be of the form 'key:value', got '%s': %s" % (entry, mapping))
+
+    key, value = entry.split(':', 1)
+
+    if not CELL_TYPE.match(key):
+      raise stem.ProtocolError("Key had invalid characters, got '%s': %s" % (key, mapping))
+    elif not value.isdigit():
+      raise stem.ProtocolError("Values should just be integers, got '%s': %s" % (value, mapping))
+
+    results[key] = int(value)
+
+  return results
+
+
 EVENT_TYPE_TO_CLASS = {
   "ADDRMAP": AddrMapEvent,
   "AUTHDIR_NEWDESCS": AuthDirNewDescEvent,
   "BUILDTIMEOUT_SET": BuildTimeoutSetEvent,
   "BW": BandwidthEvent,
+  "CELL_STATS": CellStatsEvent,
   "CIRC": CircuitEvent,
+  "CIRC_BW": CircuitBandwidthEvent,
   "CIRC_MINOR": CircMinorEvent,
   "CLIENTS_SEEN": ClientsSeenEvent,
   "CONF_CHANGED": ConfChangedEvent,
+  "CONN_BW": ConnectionBandwidthEvent,
   "DEBUG": LogEvent,
   "DESCCHANGED": DescChangedEvent,
   "ERR": LogEvent,
@@ -1034,8 +1134,6 @@ EVENT_TYPE_TO_CLASS = {
   "STREAM": StreamEvent,
   "STREAM_BW": StreamBwEvent,
   "TRANSPORT_LAUNCHED": TransportLaunchedEvent,
-  "CONN_BW": ConnectionBandwidthEvent,
-  "CIRC_BW": CircuitBandwidthEvent,
   "WARN": LogEvent,
 
   # accounting for a bug in tor 0.2.0.22
diff --git a/stem/version.py b/stem/version.py
index b725ade..ef1f290 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -45,6 +45,7 @@ easily parsed and compared, for instance...
   **EVENT_TRANSPORT_LAUNCHED**          TRANSPORT_LAUNCHED events
   **EVENT_CONN_BW**                     CONN_BW events
   **EVENT_CIRC_BW**                     CIRC_BW events
+  **EVENT_CELL_STATS**                  CELL_STATS events
   **EXTENDCIRCUIT_PATH_OPTIONAL**       EXTENDCIRCUIT queries can omit the path if the circuit is zero
   **FEATURE_EXTENDED_EVENTS**           'EXTENDED_EVENTS' optional feature
   **FEATURE_VERBOSE_NAMES**             'VERBOSE_NAMES' optional feature
@@ -345,6 +346,7 @@ Requirement = stem.util.enum.Enum(
   ("EVENT_TRANSPORT_LAUNCHED", Version('0.2.5.0-alpha')),
   ("EVENT_CONN_BW", Version('0.2.5.2-alpha')),
   ("EVENT_CIRC_BW", Version('0.2.5.2-alpha')),
+  ("EVENT_CELL_STATS", Version('0.2.5.2-alpha')),
   ("EXTENDCIRCUIT_PATH_OPTIONAL", Version("0.2.2.9")),
   ("FEATURE_EXTENDED_EVENTS", Version("0.2.2.1-alpha")),
   ("FEATURE_VERBOSE_NAMES", Version("0.2.2.1-alpha")),
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 6b7b6f1..09fca4f 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -337,6 +337,26 @@ CIRC_BW = "650 CIRC_BW ID=11 READ=272 WRITTEN=817"
 CIRC_BW_BAD_WRITTEN_VALUE = "650 CIRC_BW ID=11 READ=272 WRITTEN=817.7"
 CIRC_BW_BAD_MISSING_ID = "650 CIRC_BW READ=272 WRITTEN=817"
 
+CELL_STATS_1 = "650 CELL_STATS ID=14 \
+OutboundQueue=19403 OutboundConn=15 \
+OutboundAdded=create_fast:1,relay_early:2 \
+OutboundRemoved=create_fast:1,relay_early:2 \
+OutboundTime=create_fast:0,relay_early:0"
+
+CELL_STATS_2 = "650 CELL_STATS \
+InboundQueue=19403 InboundConn=32 \
+InboundAdded=relay:1,created_fast:1 \
+InboundRemoved=relay:1,created_fast:1 \
+InboundTime=relay:0,created_fast:0 \
+OutboundQueue=6710 OutboundConn=18 \
+OutboundAdded=create:1,relay_early:1 \
+OutboundRemoved=create:1,relay_early:1 \
+OutboundTime=create:0,relay_early:0"
+
+CELL_STATS_BAD_1 = "650 CELL_STATS OutboundAdded=create_fast:-1,relay_early:2"
+CELL_STATS_BAD_2 = "650 CELL_STATS OutboundAdded=create_fast:arg,relay_early:-2"
+CELL_STATS_BAD_3 = "650 CELL_STATS OutboundAdded=create_fast!:1,relay_early:-2"
+
 
 def _get_event(content):
   controller_event = mocking.get_message(content)
@@ -1223,6 +1243,45 @@ class TestEvents(unittest.TestCase):
     self.assertRaises(ProtocolError, _get_event, CIRC_BW_BAD_WRITTEN_VALUE)
     self.assertRaises(ProtocolError, _get_event, CIRC_BW_BAD_MISSING_ID)
 
+  def test_cell_stats_event(self):
+    event = _get_event(CELL_STATS_1)
+
+    self.assertTrue(isinstance(event, stem.response.events.CellStatsEvent))
+    self.assertEqual(CELL_STATS_1.lstrip("650 "), str(event))
+    self.assertEqual("14", event.id)
+    self.assertEqual(None, event.inbound_queue)
+    self.assertEqual(None, event.inbound_connection)
+    self.assertEqual(None, event.inbound_added)
+    self.assertEqual(None, event.inbound_removed)
+    self.assertEqual(None, event.inbound_time)
+    self.assertEqual("19403", event.outbound_queue)
+    self.assertEqual("15", event.outbound_connection)
+    self.assertEqual({'create_fast': 1, 'relay_early' :2}, event.outbound_added)
+    self.assertEqual({'create_fast': 1, 'relay_early': 2}, event.outbound_removed)
+    self.assertEqual({'create_fast': 0, 'relay_early': 0}, event.outbound_time)
+
+    event = _get_event(CELL_STATS_2)
+
+    self.assertTrue(isinstance(event, stem.response.events.CellStatsEvent))
+    self.assertEqual(CELL_STATS_2.lstrip("650 "), str(event))
+    self.assertEqual(None, event.id)
+    self.assertEqual("19403", event.inbound_queue)
+    self.assertEqual("32", event.inbound_connection)
+    self.assertEqual({'relay': 1, 'created_fast': 1}, event.inbound_added)
+    self.assertEqual({'relay': 1, 'created_fast': 1}, event.inbound_removed)
+    self.assertEqual({'relay': 0, 'created_fast': 0}, event.inbound_time)
+    self.assertEqual("6710", event.outbound_queue)
+    self.assertEqual("18", event.outbound_connection)
+    self.assertEqual({'create': 1, 'relay_early': 1}, event.outbound_added)
+    self.assertEqual({'create': 1, 'relay_early': 1}, event.outbound_removed)
+    self.assertEqual({'create': 0, 'relay_early': 0}, event.outbound_time)
+
+    # check a few invalid mappings (bad key or value)
+
+    self.assertRaises(ProtocolError, _get_event, CELL_STATS_BAD_1)
+    self.assertRaises(ProtocolError, _get_event, CELL_STATS_BAD_2)
+    self.assertRaises(ProtocolError, _get_event, CELL_STATS_BAD_3)
+
   def test_unrecognized_enum_logging(self):
     """
     Checks that when event parsing gets a value that isn't recognized by stem's





More information about the tor-commits mailing list