commit 47acd940498dfe605324bd2e4f08826c7f8c5019
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun May 13 16:50:28 2012 -0700
Parsing 'exit-*' extrainfo lines
Handling the last extrainfo attributes...
* exit-stats-end
* exit-kibibytes-written
* exit-kibibytes-read
* exit-streams-opened
---
stem/descriptor/extrainfo_descriptor.py | 45 +++++++++++++++++++++++++-
test/unit/descriptor/extrainfo_descriptor.py | 35 +++++++++++++++++++-
2 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 2b6997c..9f71c3c 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -243,6 +243,13 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
entry_stats_interval (int) - length in seconds of the interval
entry_ips (dict) - mapping of locales to rounded count of unique user ips
+ Exit Attributes:
+ exit_stats_end (datetime) - end of the period when stats were gathered
+ exit_stats_interval (int) - length in seconds of the interval
+ exit_kibibytes_written (dict) - traffic per port (keys are ints or 'other')
+ exit_kibibytes_read (dict) - traffic per port (keys are ints or 'other')
+ exit_streams_opened (dict) - streams per port (keys are ints or 'other')
+
Bridge Attributes:
bridge_stats_end (datetime) - end of the period when stats were gathered
bridge_stats_interval (int) - length in seconds of the interval
@@ -336,6 +343,12 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
self.entry_stats_interval = None
self.entry_ips = None
+ self.exit_stats_end = None
+ self.exit_stats_interval = None
+ self.exit_kibibytes_written = None
+ self.exit_kibibytes_read = None
+ self.exit_streams_opened = None
+
self.bridge_stats_end = None
self.bridge_stats_interval = None
self.bridge_ips = None
@@ -522,7 +535,7 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
except ValueError:
if validate:
raise ValueError("Timestamp on %s line wasn't parseable: %s" % (keyword, line))
- elif keyword in ("cell-stats-end", "entry-stats-end", "bridge-stats-end", "dirreq-stats-end"):
+ elif keyword in ("cell-stats-end", "entry-stats-end", "exit-stats-end", "bridge-stats-end", "dirreq-stats-end"):
# "<keyword>" YYYY-MM-DD HH:MM:SS (NSEC s)
try:
@@ -534,6 +547,9 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
elif keyword == "entry-stats-end":
self.entry_stats_end = timestamp
self.entry_stats_interval = interval
+ elif keyword == "exit-stats-end":
+ self.exit_stats_end = timestamp
+ self.exit_stats_interval = interval
elif keyword == "bridge-stats-end":
self.bridge_stats_end = timestamp
self.bridge_stats_interval = interval
@@ -592,6 +608,32 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
self.dir_write_history_values = history_values
except ValueError, exc:
if validate: raise exc
+ elif keyword in ("exit-kibibytes-written", "exit-kibibytes-read", "exit-streams-opened"):
+ # "<keyword>" port=N,port=N,...
+
+ port_mappings = {}
+ error_msg = "Entries in %s line should only be PORT=N entries: %s" % (keyword, line)
+
+ if value:
+ for entry in value.split(","):
+ if not "=" in entry:
+ if validate: raise ValueError(error_msg)
+ else: continue
+
+ port, stat = entry.split("=", 1)
+
+ if (port == 'other' or stem.util.connection.is_valid_port(port)) and stat.isdigit():
+ if port != 'other': port = int(port)
+ port_mappings[port] = int(stat)
+ elif validate:
+ raise ValueError(error_msg)
+
+ if keyword == "exit-kibibytes-written":
+ self.exit_kibibytes_written = port_mappings
+ elif keyword == "exit-kibibytes-read":
+ self.exit_kibibytes_read = port_mappings
+ elif keyword == "exit-streams-opened":
+ self.exit_streams_opened = port_mappings
elif keyword in ("dirreq-v2-ips", "dirreq-v3-ips", "dirreq-v2-reqs", "dirreq-v3-reqs", "geoip-client-origins", "entry-ips", "bridge-ips"):
# "<keyword>" CC=N,CC=N,...
#
@@ -616,6 +658,7 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
locale_usage[locale] = int(count)
elif validate:
raise ValueError(error_msg)
+
if keyword == "dirreq-v2-ips":
self.dir_v2_ips = locale_usage
elif keyword == "dirreq-v3-ips":
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py
index 76a4344..5d4c910 100644
--- a/test/unit/descriptor/extrainfo_descriptor.py
+++ b/test/unit/descriptor/extrainfo_descriptor.py
@@ -363,7 +363,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
"<keyword>" YYYY-MM-DD HH:MM:SS (NSEC s)
"""
- for keyword in ('cell-stats-end', 'entry-stats-end', 'bridge-stats-end', 'dirreq-stats-end'):
+ for keyword in ('cell-stats-end', 'entry-stats-end', 'exit-stats-end', 'bridge-stats-end', 'dirreq-stats-end'):
end_attr = keyword.replace('-', '_').replace('dirreq', 'dir')
interval_attr = end_attr[:-4] + "_interval"
@@ -431,6 +431,39 @@ class TestExtraInfoDescriptor(unittest.TestCase):
self.assertEquals(None, getattr(desc, interval_attr))
self.assertEquals(None, getattr(desc, values_attr))
+ def test_port_mapping_lines(self):
+ """
+ Uses valid and invalid data to tests lines of the form...
+ "<keyword>" port=N,port=N,...
+ """
+
+ for keyword in ('exit-kibibytes-written', 'exit-kibibytes-read', 'exit-streams-opened'):
+ attr = keyword.replace('-', '_')
+
+ test_entries = (
+ ("", {}),
+ ("443=100,other=111", {443: 100, 'other': 111}),
+ ("80=115533759,443=1777,995=690", {80: 115533759, 443: 1777, 995: 690}),
+ )
+
+ for test_value, expected_value in test_entries:
+ desc_text = _make_descriptor({keyword: test_value})
+ desc = ExtraInfoDescriptor(desc_text)
+ self.assertEquals(expected_value, getattr(desc, attr))
+
+ test_entries = (
+ "8000000=115533759",
+ "-80=115533759",
+ "80=-115533759",
+ "=115533759",
+ "80=",
+ "80,115533759",
+ )
+
+ for entry in test_entries:
+ desc_text = _make_descriptor({keyword: entry})
+ self._expect_invalid_attr(desc_text, attr, {})
+
def test_locale_mapping_lines(self):
"""
Uses valid and invalid data to tests lines of the form...