[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