[tor-commits] [tor/master] Add an "AccountingRule" feature to permit limiting bw usage by read+write

nickm at torproject.org nickm at torproject.org
Mon Sep 29 13:18:07 UTC 2014


commit 8527a2996675d9502551ccdd4224036a45aae47b
Author: Nick Mathewson <nickm at torproject.org>
Date:   Tue Sep 23 08:34:22 2014 -0400

    Add an "AccountingRule" feature to permit limiting bw usage by read+write
    
    Patch from "chobe".  Closes ticket 961.
---
 doc/tor.1.txt              |   38 ++++++++++++++--------
 src/or/config.c            |    5 +++
 src/or/hibernate.c         |   50 ++++++++++++++++++++---------
 src/or/hibernate.h         |    1 +
 src/or/or.h                |    4 +++
 src/or/router.c            |    7 +++-
 src/or/status.c            |    7 +++-
 src/test/include.am        |    1 +
 src/test/test.c            |    2 ++
 src/test/test_accounting.c |   76 ++++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 161 insertions(+), 30 deletions(-)

diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index d6b1432..c9172b8 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -1635,19 +1635,31 @@ is non-zero):
     to 0 will disable the heartbeat. (Default: 6 hours)
 
 [[AccountingMax]] **AccountingMax** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**|**TBytes**::
-    Never send more than the specified number of bytes in a given accounting
-    period, or receive more than that number in the period. For example, with
-    AccountingMax set to 1 GByte, a server could send 900 MBytes and
-    receive 800 MBytes and continue running. It will only hibernate once
-    one of the two reaches 1 GByte. When the number of bytes gets low,
-    Tor will stop accepting new connections and circuits.  When the
-    number of bytes is exhausted, Tor will hibernate until some
-    time in the next accounting period. To prevent all servers from waking at
-    the same time, Tor will also wait until a random point in each period
-    before waking up. If you have bandwidth cost issues, enabling hibernation
-    is preferable to setting a low bandwidth, since it provides users with a
-    collection of fast servers that are up some of the time, which is more
-    useful than a set of slow servers that are always "available".
+    Limits the max number of bytes sent and received within a set time period
+    using a given calculation rule (see: AccountingStart, AccountingRule).
+    Useful if you need to stay under a specific bandwidth. By default, the
+    number used for calculation is the max of either the bytes sent or 
+    received. For example, with AccountingMax set to 1 GByte, a server
+    could send 900 MBytes and receive 800 MBytes and continue running.
+    It will only hibernate once one of the two reaches 1 GByte. This can
+    be changed to use the sum of the both bytes received and sent by setting
+    the AccountingRule option to "sum" (total bandwidth in/out). When the
+    number of bytes remaining gets low, Tor will stop accepting new connections
+    and circuits. When the number of bytes is exhausted, Tor will hibernate
+    until some time in the next accounting period. To prevent all servers
+    from waking at the same time, Tor will also wait until a random point
+    in each period before waking up. If you have bandwidth cost issues,
+    enabling hibernation is preferable to setting a low bandwidth, since
+    it provides users with a collection of fast servers that are up some
+    of the time, which is more useful than a set of slow servers that are
+    always "available".
+
+[[AccountingRule]] **AccountingRule** **sum**|**max**::
+    How we determine when our AccountingMax has been reached (when we
+    should hibernate) during a time interval. Set to "max" to calculate
+    using the higher of either the sent or received bytes (this is the
+    default functionality). Set to "sum" to calculate using the sent
+    plus received bytes.
 
 [[AccountingStart]] **AccountingStart** **day**|**week**|**month** [__day__] __HH:MM__::
     Specify how long accounting periods last. If **month** is given, each
diff --git a/src/or/config.c b/src/or/config.c
index 16acec7..c7c6154 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -126,6 +126,7 @@ static config_abbrev_t option_abbrevs_[] = {
  */
 static config_var_t option_vars_[] = {
   V(AccountingMax,               MEMUNIT,  "0 bytes"),
+  V(AccountingRule,              STRING,  "max"),
   V(AccountingStart,             STRING,   NULL),
   V(Address,                     STRING,   NULL),
   V(AllowDotExit,                BOOL,     "0"),
@@ -3108,6 +3109,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
                "risky: they will all turn off at the same time, which may "
                "alert observers that they are being run by the same party.");
     }
+    if (options->AccountingRule &&
+      strcmp(options->AccountingRule, "sum") != 0 &&
+      strcmp(options->AccountingRule, "max") != 0)
+      REJECT("AccountingRule must be 'sum' or 'max'");
   }
 
   if (options->HTTPProxy) { /* parse it now */
diff --git a/src/or/hibernate.c b/src/or/hibernate.c
index c433ac1..6978f47 100644
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@ -410,6 +410,16 @@ configure_accounting(time_t now)
   accounting_set_wakeup_time();
 }
 
+/** Return the relevant number of bytes sent/received this interval
+ * based on the set AccountingRule */
+static uint64_t
+get_accounting_bytes(void)
+{
+  if (strcmp(get_options()->AccountingRule, "sum") == 0)
+    return n_bytes_read_in_interval+n_bytes_written_in_interval;
+  return MAX(n_bytes_read_in_interval, n_bytes_written_in_interval);
+}
+
 /** Set expected_bandwidth_usage based on how much we sent/received
  * per minute last interval (if we were up for at least 30 minutes),
  * or based on our declared bandwidth otherwise. */
@@ -421,6 +431,11 @@ update_expected_bandwidth(void)
   uint64_t max_configured = (options->RelayBandwidthRate > 0 ?
                              options->RelayBandwidthRate :
                              options->BandwidthRate) * 60;
+  /* max_configured is the larger of bytes read and bytes written
+   * If we are accounting based on sum, worst case is both are
+   * at max, doubling the expected sum of bandwidth */
+  if (strcmp(get_options()->AccountingRule, "sum") == 0)
+    max_configured *= 2;
 
 #define MIN_TIME_FOR_MEASUREMENT (1800)
 
@@ -439,8 +454,7 @@ update_expected_bandwidth(void)
      * doesn't know to store soft-limit info.  Just take rate at which
      * we were reading/writing in the last interval as our expected rate.
      */
-    uint64_t used = MAX(n_bytes_written_in_interval,
-                        n_bytes_read_in_interval);
+    uint64_t used = get_accounting_bytes();
     expected = used / (n_seconds_active_in_interval / 60);
   } else {
     /* If we haven't gotten enough data last interval, set 'expected'
@@ -715,8 +729,7 @@ hibernate_hard_limit_reached(void)
   uint64_t hard_limit = get_options()->AccountingMax;
   if (!hard_limit)
     return 0;
-  return n_bytes_read_in_interval >= hard_limit
-    || n_bytes_written_in_interval >= hard_limit;
+  return get_accounting_bytes() >= hard_limit;
 }
 
 /** Return true iff we have sent/received almost all the bytes we are willing
@@ -747,8 +760,7 @@ hibernate_soft_limit_reached(void)
 
   if (!soft_limit)
     return 0;
-  return n_bytes_read_in_interval >= soft_limit
-    || n_bytes_written_in_interval >= soft_limit;
+  return get_accounting_bytes() >= soft_limit;
 }
 
 /** Called when we get a SIGINT, or when bandwidth soft limit is
@@ -772,8 +784,7 @@ hibernate_begin(hibernate_state_t new_state, time_t now)
       hibernate_state == HIBERNATE_STATE_LIVE) {
     soft_limit_hit_at = now;
     n_seconds_to_hit_soft_limit = n_seconds_active_in_interval;
-    n_bytes_at_soft_limit = MAX(n_bytes_read_in_interval,
-                                n_bytes_written_in_interval);
+    n_bytes_at_soft_limit = get_accounting_bytes();
   }
 
   /* close listeners. leave control listener(s). */
@@ -1003,13 +1014,22 @@ getinfo_helper_accounting(control_connection_t *conn,
                  U64_PRINTF_ARG(n_bytes_written_in_interval));
   } else if (!strcmp(question, "accounting/bytes-left")) {
     uint64_t limit = get_options()->AccountingMax;
-    uint64_t read_left = 0, write_left = 0;
-    if (n_bytes_read_in_interval < limit)
-      read_left = limit - n_bytes_read_in_interval;
-    if (n_bytes_written_in_interval < limit)
-      write_left = limit - n_bytes_written_in_interval;
-    tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
-                 U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(write_left));
+    if (strcmp(get_options()->AccountingRule, "sum") == 0) {
+      uint64_t total_left = 0;
+      uint64_t total_bytes = get_accounting_bytes();
+      if (total_bytes < limit)
+        total_left = limit - total_bytes;
+      tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
+                   U64_PRINTF_ARG(total_left), U64_PRINTF_ARG(total_left));
+    } else {
+      uint64_t read_left = 0, write_left = 0;
+      if (n_bytes_read_in_interval < limit)
+        read_left = limit - n_bytes_read_in_interval;
+      if (n_bytes_written_in_interval < limit)
+        write_left = limit - n_bytes_written_in_interval;
+      tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
+                   U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(write_left));
+    }
   } else if (!strcmp(question, "accounting/interval-start")) {
     *answer = tor_malloc(ISO_TIME_LEN+1);
     format_iso_time(*answer, interval_start_time);
diff --git a/src/or/hibernate.h b/src/or/hibernate.h
index 38ecb75..799b582 100644
--- a/src/or/hibernate.h
+++ b/src/or/hibernate.h
@@ -28,6 +28,7 @@ void consider_hibernation(time_t now);
 int getinfo_helper_accounting(control_connection_t *conn,
                               const char *question, char **answer,
                               const char **errmsg);
+uint64_t get_accounting_max_total(void);
 
 #ifdef HIBERNATE_PRIVATE
 /** Possible values of hibernate_state */
diff --git a/src/or/or.h b/src/or/or.h
index b2b0d5f..20cfa5b 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -3775,6 +3775,10 @@ typedef struct {
   uint64_t AccountingMax; /**< How many bytes do we allow per accounting
                            * interval before hibernation?  0 for "never
                            * hibernate." */
+  char *AccountingRule; /**< How do we determine when our AccountingMax
+                         * has been reached?
+                         * "max" for when in or out reaches AccountingMax
+                         * "sum for when in plus out reaches AccountingMax */
 
   /** Base64-encoded hash of accepted passwords for the control system. */
   config_line_t *HashedControlPassword;
diff --git a/src/or/router.c b/src/or/router.c
index 4af8d26..96d16bb 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -1080,6 +1080,7 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
      * they're confused or to get statistics. */
     int interval_length = accounting_get_interval_length();
     uint32_t effective_bw = get_effective_bwrate(options);
+    uint64_t acc_bytes;
     if (!interval_length) {
       log_warn(LD_BUG, "An accounting interval is not allowed to be zero "
                        "seconds long. Raising to 1.");
@@ -1090,8 +1091,12 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
                          "accounting interval length %d", effective_bw,
                          U64_PRINTF_ARG(options->AccountingMax),
                          interval_length);
+
+    acc_bytes = options->AccountingMax;
+    if (strcmp(options->AccountingRule, "sum") == 0)
+      acc_bytes /= 2;
     if (effective_bw >=
-        options->AccountingMax / interval_length) {
+        acc_bytes / interval_length) {
       new_choice = 0;
       reason = "AccountingMax enabled";
     }
diff --git a/src/or/status.c b/src/or/status.c
index c4156d0..4158df5 100644
--- a/src/or/status.c
+++ b/src/or/status.c
@@ -145,10 +145,15 @@ log_accounting(const time_t now, const or_options_t *options)
   or_state_t *state = get_or_state();
   char *acc_rcvd = bytes_to_usage(state->AccountingBytesReadInInterval);
   char *acc_sent = bytes_to_usage(state->AccountingBytesWrittenInInterval);
-  char *acc_max = bytes_to_usage(options->AccountingMax);
+  const char *acc_rule = options->AccountingRule;
+  uint64_t acc_bytes = options->AccountingMax;
+  char *acc_max;
   time_t interval_end = accounting_get_end_time();
   char end_buf[ISO_TIME_LEN + 1];
   char *remaining = NULL;
+  if (strcmp(acc_rule, "sum") == 0)
+    acc_bytes *= 2;
+  acc_max = bytes_to_usage(acc_bytes);
   format_local_iso_time(end_buf, interval_end);
   remaining = secs_to_uptime(interval_end - now);
 
diff --git a/src/test/include.am b/src/test/include.am
index 77c92f1..03755a4 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -34,6 +34,7 @@ src_test_test_SOURCES = \
 	src/test/test_logging.c \
 	src/test/test_microdesc.c \
 	src/test/test_oom.c \
+	src/test/test_accounting.c \
 	src/test/test_options.c \
 	src/test/test_pt.c \
 	src/test/test_relaycell.c \
diff --git a/src/test/test.c b/src/test/test.c
index cfbe203..d2813ed 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -1305,6 +1305,7 @@ extern struct testcase_t hs_tests[];
 extern struct testcase_t nodelist_tests[];
 extern struct testcase_t routerkeys_tests[];
 extern struct testcase_t oom_tests[];
+extern struct testcase_t accounting_tests[];
 extern struct testcase_t policy_tests[];
 extern struct testcase_t status_tests[];
 extern struct testcase_t routerset_tests[];
@@ -1337,6 +1338,7 @@ static struct testgroup_t testgroups[] = {
   { "nodelist/", nodelist_tests },
   { "routerkeys/", routerkeys_tests },
   { "oom/", oom_tests },
+  { "accounting/", accounting_tests },
   { "policy/" , policy_tests },
   { "status/" , status_tests },
   { "routerset/" , routerset_tests },
diff --git a/src/test/test_accounting.c b/src/test/test_accounting.c
new file mode 100644
index 0000000..a1a2ec6
--- /dev/null
+++ b/src/test/test_accounting.c
@@ -0,0 +1,76 @@
+#include "or.h"
+#include "test.h"
+#define HIBERNATE_PRIVATE
+#include "hibernate.h"
+#include "config.h"
+#define STATEFILE_PRIVATE
+#include "statefile.h"
+
+#define NS_MODULE accounting
+
+#define NS_SUBMODULE limits
+
+/*
+ * Test to make sure accounting triggers hibernation
+ * correctly with both sum or max rules set
+ */
+
+static or_state_t *or_state;
+NS_DECL(or_state_t *, get_or_state, (void));
+static or_state_t *
+NS(get_or_state)(void)
+{
+  return or_state;
+}
+
+static void
+test_accounting_limits(void *arg)
+{
+  or_options_t *options = get_options_mutable();
+  time_t fake_time = time(NULL);
+  (void) arg;
+
+  NS_MOCK(get_or_state);
+  or_state = or_state_new();
+
+  options->AccountingMax = 100;
+  options->AccountingRule = tor_strdup("max");
+
+  tor_assert(accounting_is_enabled(options));
+  configure_accounting(fake_time);
+
+  accounting_add_bytes(10, 0, 1);
+  fake_time += 1;
+  consider_hibernation(fake_time);
+  tor_assert(we_are_hibernating() == 0);
+
+  accounting_add_bytes(90, 0, 1);
+  fake_time += 1;
+  consider_hibernation(fake_time);
+  tor_assert(we_are_hibernating() == 1);
+
+  options->AccountingMax = 200;
+  options->AccountingRule = tor_strdup("sum");
+
+  accounting_add_bytes(0, 10, 1);
+  fake_time += 1;
+  consider_hibernation(fake_time);
+  tor_assert(we_are_hibernating() == 0);
+
+  accounting_add_bytes(0, 90, 1);
+  fake_time += 1;
+  consider_hibernation(fake_time);
+  tor_assert(we_are_hibernating() == 1);
+  goto done;
+ done:
+  NS_UNMOCK(get_or_state);
+  or_state_free(or_state);
+}
+
+#undef NS_SUBMODULE
+
+struct testcase_t accounting_tests[] = {
+  { "bwlimits", test_accounting_limits, TT_FORK, NULL, NULL },
+  END_OF_TESTCASES
+};
+





More information about the tor-commits mailing list