[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