[tor-commits] [stem/master] Adding get_accounting_stats()
atagar at torproject.org
atagar at torproject.org
Sun Sep 14 22:11:54 UTC 2014
commit b82c89ebc55f6d9fb54dfa93afd03af6e34980dd
Author: Damian Johnson <atagar at torproject.org>
Date: Sun Sep 14 14:40:22 2014 -0700
Adding get_accounting_stats()
Controller method for fetching our accounting stats. Much nicer than calling
getinfo() three times and parsing the results ourselves.
---
docs/change_log.rst | 1 +
stem/control.py | 73 +++++++++++++++++++++++++++++++++++++++
test/unit/control/controller.py | 30 ++++++++++++++++
3 files changed, 104 insertions(+)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index ec7eaef..ff58d12 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -42,6 +42,7 @@ The following are only available within Stem's `git repository
* **Controller**
+ * Added :func:`~stem.control.Controller.get_accounting_stats` to the :class:`~stem.control.Controller`
* Added :func:`~stem.control.BaseController.connection_time` to the :class:`~stem.control.BaseController`
* Changed :func:`~stem.control.Controller.get_microdescriptor`, :func:`~stem.control.Controller.get_server_descriptor`, and :func:`~stem.control.Controller.get_network_status` to get our own descriptor if no fingerprint or nickname is provided.
* Added :class:`~stem.exit_policy.ExitPolicy` methods for more easily handling 'private' policies (the `default prefix <https://www.torproject.org/docs/tor-manual.html.en#ExitPolicyRejectPrivate>`_) and the defaultly appended suffix. This includes :func:`~stem.exit_policy.ExitPolicy.has_private`, :func:`~stem.exit_policy.ExitPolicy.strip_private`, :func:`~stem.exit_policy.ExitPolicy.has_default`, and :func:`~stem.exit_policy.ExitPolicy.strip_default` :class:`~stem.exit_policy.ExitPolicy` methods in addition to :func:`~stem.exit_policy.ExitPolicyRule.is_private` and :func:`~stem.exit_policy.ExitPolicyRule.is_default` for the :class:`~stem.exit_policy.ExitPolicyRule`. (:trac:`10107`)
diff --git a/stem/control.py b/stem/control.py
index 3c26666..57e4550 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -77,6 +77,7 @@ If you're fine with allowing your script to raise exceptions then this can be mo
|- get_exit_policy - provides our exit policy
|- get_ports - provides the local ports where tor is listening for connections
|- get_listeners - provides the addresses and ports where tor is listening for connections
+ |- get_accounting_stats - provides stats related to relaying limits
|- get_protocolinfo - information about the controller interface
|- get_user - provides the user tor is running as
|- get_pid - provides the pid of our tor process
@@ -212,6 +213,9 @@ If you're fine with allowing your script to raise exceptions then this can be mo
============= ===========
"""
+import calendar
+import collections
+import datetime
import io
import os
import Queue
@@ -327,6 +331,19 @@ SERVER_DESCRIPTORS_UNSUPPORTED = "Tor is presently not configured to retrieve \
server descriptors. As of Tor version 0.2.3.25 it downloads microdescriptors \
instead unless you set 'UseMicrodescriptors 0' in your torrc."
+AccountingStats = collections.namedtuple('AccountingStats', [
+ 'retrieved',
+ 'status',
+ 'interval_end',
+ 'time_until_reset',
+ 'read_bytes',
+ 'read_bytes_left',
+ 'read_limit',
+ 'written_bytes',
+ 'write_bytes_left',
+ 'write_limit',
+])
+
class BaseController(object):
"""
@@ -1183,6 +1200,62 @@ class Controller(BaseController):
else:
return default
+ def get_accounting_stats(self, default = UNDEFINED):
+ """
+ Provides stats related to our relaying limitations if AccountingMax was set
+ in our torrc. This provides a **namedtuple** with the following
+ attributes...
+
+ * retrieved (float) - unix timestamp for when this was fetched
+ * status (str) - hibernation status of 'awake', 'soft', or 'hard'
+ * interval_end (datetime)
+ * time_until_reset (int) - seconds until our limits reset
+ * read_bytes (int)
+ * read_bytes_left (int)
+ * read_limit (int)
+ * written_bytes (int)
+ * write_bytes_left (int)
+ * write_limit (int)
+
+ .. versionadded:: 1.3.0
+
+ :param object default: response if the query fails
+
+ :returns: **namedtuple** with our accounting stats
+
+ :raises: :class:`stem.ControllerError` if unable to determine the listeners
+ and no default was provided
+ """
+
+ try:
+ retrieved = time.time()
+ status = self.get_info('accounting/hibernating')
+ interval_end = self.get_info('accounting/interval-end')
+ used = self.get_info('accounting/bytes')
+ left = self.get_info('accounting/bytes-left')
+
+ interval_end = datetime.datetime.strptime(interval_end, '%Y-%m-%d %H:%M:%S')
+ used_read, used_written = [int(val) for val in used.split(' ', 1)]
+ left_read, left_written = [int(val) for val in left.split(' ', 1)]
+
+ return AccountingStats(
+ retrieved = retrieved,
+ status = status,
+ interval_end = interval_end,
+ time_until_reset = int(retrieved) - calendar.timegm(interval_end.timetuple()),
+ read_bytes = used_read,
+ read_bytes_left = left_read,
+ read_limit = used_read + left_read,
+ written_bytes = used_written,
+ write_bytes_left = left_written,
+ write_limit = used_written + left_written,
+ )
+ except Exception as exc:
+ if default == UNDEFINED:
+ raise exc
+ else:
+ return default
+
def get_socks_listeners(self, default = UNDEFINED):
"""
Provides the SOCKS **(address, port)** tuples that tor has open.
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py
index 17b64fd..4e04f9d 100644
--- a/test/unit/control/controller.py
+++ b/test/unit/control/controller.py
@@ -3,6 +3,7 @@ Unit tests for the stem.control module. The module's primarily exercised via
integ tests, but a few bits lend themselves to unit testing.
"""
+import datetime
import io
import unittest
@@ -279,6 +280,35 @@ class TestControl(unittest.TestCase):
get_info_mock.return_value = response
self.assertRaises(stem.ProtocolError, self.controller.get_socks_listeners)
+ @patch('stem.control.Controller.get_info')
+ @patch('time.time', Mock(return_value = 1410723698.276578))
+ def test_get_accounting_stats(self, get_info_mock):
+ """
+ Exercises the get_accounting_stats() method.
+ """
+
+ get_info_mock.side_effect = lambda param, **kwargs: {
+ 'accounting/hibernating': 'awake',
+ 'accounting/interval-end': '2014-09-14 19:41:00',
+ 'accounting/bytes': '4837 2050',
+ 'accounting/bytes-left': '102944 7440',
+ }[param]
+
+ expected = stem.control.AccountingStats(
+ 1410723698.276578,
+ 'awake',
+ datetime.datetime(2014, 9, 14, 19, 41),
+ 38,
+ 4837, 102944, 107781,
+ 2050, 7440, 9490,
+ )
+
+ self.assertEqual(expected, self.controller.get_accounting_stats())
+
+ get_info_mock.side_effect = ControllerError('nope, too bad')
+ self.assertRaises(ControllerError, self.controller.get_accounting_stats)
+ self.assertEqual('my default', self.controller.get_accounting_stats('my default'))
+
@patch('stem.connection.get_protocolinfo')
def test_get_protocolinfo(self, get_protocolinfo_mock):
"""
More information about the tor-commits
mailing list