[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] [tor/master] Write all statistics to disk exactly every 24 hours.



Author: Karsten Loesing <karsten.loesing@xxxxxxx>
Date: Wed, 19 Aug 2009 15:41:12 +0200
Subject: Write all statistics to disk exactly every 24 hours.
Commit: 4e29f3342754fa8802c364b58f7b2f98e983a291

---
 doc/tor.1.in             |   12 ++-
 src/or/circuitlist.c     |    2 +-
 src/or/config.c          |   40 +++-----
 src/or/connection.c      |    4 +-
 src/or/connection_edge.c |    2 +-
 src/or/geoip.c           |  106 ++++++++++++----------
 src/or/main.c            |   44 ++++++++-
 src/or/or.h              |   22 +++--
 src/or/rephist.c         |  225 +++++++++++++++++++++-------------------------
 9 files changed, 237 insertions(+), 220 deletions(-)

diff --git a/doc/tor.1.in b/doc/tor.1.in
index 54e35aa..7606ffb 100644
--- a/doc/tor.1.in
+++ b/doc/tor.1.in
@@ -1060,24 +1060,26 @@ A filename containing GeoIP data, for use with BridgeRecordUsageByCountry.
 .TP
 \fBCellStatistics \fR\fB0\fR|\fB1\fR\fP
 When this option is enabled, Tor writes statistics on the mean time that
-cells spend in circuit queues to disk every 24 hours. (Default: 0)
+cells spend in circuit queues to disk every 24 hours. Cannot be changed
+while Tor is running. (Default: 0)
 .LP
 .TP
 \fBDirReqStatistics \fR\fB0\fR|\fB1\fR\fP
 When this option is enabled, Tor writes statistics on the number and
-response time of network status requests to disk every 24 hours.
-(Default: 0)
+response time of network status requests to disk every 24 hours. Cannot be
+changed while Tor is running. (Default: 0)
 .LP
 .TP
 \fBEntryStatistics \fR\fB0\fR|\fB1\fR\fP
 When this option is enabled, Tor writes statistics on the number of
-directly connecting clients to disk every 24 hours. (Default: 0)
+directly connecting clients to disk every 24 hours. Cannot be changed
+while Tor is running. (Default: 0)
 .LP
 .TP
 \fBExitPortStatistics \fR\fB0\fR|\fB1\fR\fP
 When this option is enabled, Tor writes statistics on the number of
 relayed bytes and opened stream per exit port to disk every 24 hours.
-(Default: 0)
+Cannot be changed while Tor is running. (Default: 0)
 .LP
 .TP
 \fBExtraInfoStatistics \fR\fB0\fR|\fB1\fR\fP
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index 04b1d8e..0655596 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -449,7 +449,7 @@ circuit_free(circuit_t *circ)
     or_circuit_t *ocirc = TO_OR_CIRCUIT(circ);
     /* Remember cell statistics for this circuit before deallocating. */
     if (get_options()->CellStatistics)
-      add_circ_to_buffer_stats(circ, time(NULL));
+      rep_hist_buffer_stats_add_circ(circ, time(NULL));
     mem = ocirc;
     memlen = sizeof(or_circuit_t);
     tor_assert(circ->magic == OR_CIRCUIT_MAGIC);
diff --git a/src/or/config.c b/src/or/config.c
index 7b3f0ad..acc544b 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -1407,29 +1407,13 @@ options_act(or_options_t *old_options)
     tor_free(actual_fname);
   }
 
-  if (options->DirReqStatistics) {
+  if (options->DirReqStatistics && !geoip_is_loaded()) {
     /* Check if GeoIP database could be loaded. */
-    if (!geoip_is_loaded()) {
-      log_warn(LD_CONFIG, "Configured to measure directory request "
-               "statistics, but no GeoIP database found!");
-      return -1;
-    }
-    log_notice(LD_CONFIG, "Configured to count directory requests by "
-               "country and write aggregate statistics to disk. Check the "
-               "dirreq-stats file in your data directory that will first "
-               "be written in 24 hours from now.");
+    log_warn(LD_CONFIG, "Configured to measure directory request "
+             "statistics, but no GeoIP database found!");
+    return -1;
   }
 
-  if (options->ExitPortStatistics)
-    log_notice(LD_CONFIG, "Configured to measure exit port statistics. "
-               "Look for the exit-stats file that will first be written to "
-               "the data directory in 24 hours from now.");
-
-  if (options->CellStatistics)
-    log_notice(LD_CONFIG, "Configured to measure cell statistics. Look "
-               "for the buffer-stats file that will first be written to "
-               "the data directory in 24 hours from now.");
-
   if (options->EntryStatistics) {
     if (should_record_bridge_info(options)) {
       /* Don't allow measuring statistics on entry guards when configured
@@ -1442,11 +1426,7 @@ options_act(or_options_t *old_options)
       log_warn(LD_CONFIG, "Configured to measure entry node statistics, "
                "but no GeoIP database found!");
       return -1;
-    } else
-      log_notice(LD_CONFIG, "Configured to measure entry node "
-                 "statistics. Look for the entry-stats file that will "
-                 "first be written to the data directory in 24 hours "
-                 "from now.");
+    }
   }
 
   /* Check if we need to parse and add the EntryNodes config option. */
@@ -3784,6 +3764,16 @@ options_transition_allowed(or_options_t *old, or_options_t *new_val,
     return -1;
   }
 
+  if (old->CellStatistics != new_val->CellStatistics ||
+      old->DirReqStatistics != new_val->DirReqStatistics ||
+      old->EntryStatistics != new_val->EntryStatistics ||
+      old->ExitPortStatistics != new_val->ExitPortStatistics) {
+    *msg = tor_strdup("While Tor is running, changing either "
+                      "CellStatistics, DirReqStatistics, EntryStatistics, "
+                      "or ExitPortStatistics is not allowed.");
+    return -1;
+  }
+
   return 0;
 }
 
diff --git a/src/or/connection.c b/src/or/connection.c
index 48f8278..c961f16 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -1704,12 +1704,12 @@ connection_buckets_decrement(connection_t *conn, time_t now,
 
   if (num_read > 0) {
     if (conn->type == CONN_TYPE_EXIT)
-      rep_hist_note_exit_bytes_read(conn->port, num_read, now);
+      rep_hist_note_exit_bytes_read(conn->port, num_read);
     rep_hist_note_bytes_read(num_read, now);
   }
   if (num_written > 0) {
     if (conn->type == CONN_TYPE_EXIT)
-      rep_hist_note_exit_bytes_written(conn->port, num_written, now);
+      rep_hist_note_exit_bytes_written(conn->port, num_written);
     rep_hist_note_bytes_written(num_written, now);
   }
 
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 1df576d..f252027 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -333,7 +333,7 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn)
            escaped_safe_str(conn->address),conn->port,
            safe_str(fmt_addr(&conn->addr)));
 
-  rep_hist_note_exit_stream_opened(conn->port, approx_time());
+  rep_hist_note_exit_stream_opened(conn->port);
 
   conn->state = EXIT_CONN_STATE_OPEN;
   connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */
diff --git a/src/or/geoip.c b/src/or/geoip.c
index 824428b..9f37c02 100644
--- a/src/or/geoip.c
+++ b/src/or/geoip.c
@@ -12,8 +12,6 @@
 #include "ht.h"
 
 static void clear_geoip_db(void);
-static void dump_geoip_stats(void);
-static void dump_entry_stats(void);
 
 /** An entry from the GeoIP file: maps an IP range to a country. */
 typedef struct geoip_entry_t {
@@ -390,37 +388,6 @@ geoip_note_client_seen(geoip_client_action_t action,
       return;
   }
 
-  /* Rotate the current request period. */
-  while (current_request_period_starts + REQUEST_HIST_PERIOD < now) {
-    if (!geoip_countries)
-      geoip_countries = smartlist_create();
-    if (!current_request_period_starts) {
-      current_request_period_starts = now;
-      break;
-    }
-    /* Also discard all items in the client history that are too old.
-     * (This only works here because bridge and directory stats are
-     * independent. Otherwise, we'd only want to discard those items
-     * with action GEOIP_CLIENT_NETWORKSTATUS{_V2}.) */
-    geoip_remove_old_clients(current_request_period_starts);
-    /* Before rotating, write the current stats to disk. */
-    dump_geoip_stats();
-    if (get_options()->EntryStatistics)
-      dump_entry_stats();
-    /* Now rotate request period */
-    SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
-        memmove(&c->n_v2_ns_requests[0], &c->n_v2_ns_requests[1],
-                sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
-        memmove(&c->n_v3_ns_requests[0], &c->n_v3_ns_requests[1],
-                sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
-        c->n_v2_ns_requests[REQUEST_HIST_LEN-1] = 0;
-        c->n_v3_ns_requests[REQUEST_HIST_LEN-1] = 0;
-      });
-    current_request_period_starts += REQUEST_HIST_PERIOD;
-    if (n_old_request_periods < REQUEST_HIST_LEN-1)
-      ++n_old_request_periods;
-  }
-
   lookup.ipaddr = addr;
   lookup.action = (int)action;
   ent = HT_FIND(clientmap, &client_history, &lookup);
@@ -940,12 +907,20 @@ geoip_get_request_history(time_t now, geoip_client_action_t action)
   return result;
 }
 
+/** Start time of directory request stats. */
+static time_t start_of_dirreq_stats_interval;
+
+/** Initialize directory request stats. */
+void
+geoip_dirreq_stats_init(time_t now)
+{
+  start_of_dirreq_stats_interval = now;
+}
+
 /** Store all our geoip statistics into $DATADIR/dirreq-stats. */
-static void
-dump_geoip_stats(void)
+void
+geoip_dirreq_stats_write(time_t now)
 {
-  time_t now = time(NULL);
-  time_t request_start;
   char *filename = get_datadir_fname("dirreq-stats");
   char *data_v2 = NULL, *data_v3 = NULL;
   char written[ISO_TIME_LEN+1];
@@ -957,11 +932,14 @@ dump_geoip_stats(void)
   if (!get_options()->DirReqStatistics)
     goto done;
 
+  /* Discard all items in the client history that are too old. */
+  geoip_remove_old_clients(start_of_dirreq_stats_interval);
+
   data_v2 = geoip_get_client_history_dirreq(now,
                 GEOIP_CLIENT_NETWORKSTATUS_V2);
   data_v3 = geoip_get_client_history_dirreq(now,
                 GEOIP_CLIENT_NETWORKSTATUS);
-  format_iso_time(written, geoip_get_history_start() + REQUEST_HIST_PERIOD);
+  format_iso_time(written, now);
   out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
                                     0600, &open_file);
   if (!out)
@@ -973,8 +951,6 @@ dump_geoip_stats(void)
   tor_free(data_v2);
   tor_free(data_v3);
 
-  request_start = current_request_period_starts -
-    (n_old_request_periods * REQUEST_HIST_PERIOD);
   data_v2 = geoip_get_request_history(now, GEOIP_CLIENT_NETWORKSTATUS_V2);
   data_v3 = geoip_get_request_history(now, GEOIP_CLIENT_NETWORKSTATUS);
   if (fprintf(out, "dirreq-v3-reqs %s\ndirreq-v2-reqs %s\n",
@@ -1033,6 +1009,22 @@ dump_geoip_stats(void)
 
   finish_writing_to_file(open_file);
   open_file = NULL;
+
+  /* Rotate request period */
+  SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
+      memmove(&c->n_v2_ns_requests[0], &c->n_v2_ns_requests[1],
+              sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
+      memmove(&c->n_v3_ns_requests[0], &c->n_v3_ns_requests[1],
+              sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
+      c->n_v2_ns_requests[REQUEST_HIST_LEN-1] = 0;
+      c->n_v3_ns_requests[REQUEST_HIST_LEN-1] = 0;
+    });
+  current_request_period_starts += REQUEST_HIST_PERIOD;
+  if (n_old_request_periods < REQUEST_HIST_LEN-1)
+    ++n_old_request_periods;
+
+  start_of_dirreq_stats_interval = now;
+
  done:
   if (open_file)
     abort_writing_to_file(open_file);
@@ -1041,29 +1033,46 @@ dump_geoip_stats(void)
   tor_free(data_v3);
 }
 
+/** Start time of entry stats. */
+static time_t start_of_entry_stats_interval;
+
+/** Initialize entry stats. */
+void
+geoip_entry_stats_init(time_t now)
+{
+  start_of_entry_stats_interval = now;
+}
+
 /** Store all our geoip statistics as entry guards into
  * $DATADIR/entry-stats. */
-static void
-dump_entry_stats(void)
+void
+geoip_entry_stats_write(time_t now)
 {
-#ifdef ENABLE_ENTRY_STATS
-  time_t now = time(NULL);
   char *filename = get_datadir_fname("entry-stats");
   char *data = NULL;
   char written[ISO_TIME_LEN+1];
   open_file_t *open_file = NULL;
   FILE *out;
 
-  data = geoip_get_client_history(now, GEOIP_CLIENT_CONNECT);
-  format_iso_time(written, geoip_get_history_start() + REQUEST_HIST_PERIOD);
+  if (!get_options()->EntryStatistics)
+    goto done;
+
+  /* Discard all items in the client history that are too old. */
+  geoip_remove_old_clients(start_of_entry_stats_interval);
+
+  data = geoip_get_client_history_dirreq(now, GEOIP_CLIENT_CONNECT);
+  format_iso_time(written, now);
   out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
                                     0600, &open_file);
   if (!out)
     goto done;
-  if (fprintf(out, "entry-stats-end %s (%d s)\nentry-ips %s\n",
-              written, REQUEST_HIST_PERIOD, data ? data : "") < 0)
+  if (fprintf(out, "entry-stats-end %s (%u s)\nentry-ips %s\n",
+              written, (unsigned) (now - start_of_entry_stats_interval),
+              data ? data : "") < 0)
     goto done;
 
+  start_of_entry_stats_interval = now;
+
   finish_writing_to_file(open_file);
   open_file = NULL;
  done:
@@ -1071,7 +1080,6 @@ dump_entry_stats(void)
     abort_writing_to_file(open_file);
   tor_free(filename);
   tor_free(data);
-#endif
 }
 
 /** Helper used to implement GETINFO ip-to-country/... controller command. */
diff --git a/src/or/main.c b/src/or/main.c
index 403ae93..16136cb 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -830,7 +830,7 @@ run_scheduled_events(time_t now)
   static time_t time_to_clean_caches = 0;
   static time_t time_to_recheck_bandwidth = 0;
   static time_t time_to_check_for_expired_networkstatus = 0;
-  static time_t time_to_dump_buffer_stats = 0;
+  static time_t time_to_write_stats_files = 0;
   static time_t time_to_retry_dns_init = 0;
   or_options_t *options = get_options();
   int i;
@@ -958,10 +958,44 @@ run_scheduled_events(time_t now)
     time_to_check_for_expired_networkstatus = now + CHECK_EXPIRED_NS_INTERVAL;
   }
 
-  if (time_to_dump_buffer_stats < now) {
-    if (get_options()->CellStatistics && time_to_dump_buffer_stats)
-      dump_buffer_stats();
-    time_to_dump_buffer_stats = now + DUMP_BUFFER_STATS_INTERVAL;
+  /* 1g. Check whether we should write statistics to disk.
+   */
+  if (time_to_write_stats_files >= 0 && time_to_write_stats_files < now) {
+#define WRITE_STATS_INTERVAL (24*60*60)
+    or_options_t *options = get_options();
+    if (options->CellStatistics || options->DirReqStatistics ||
+        options->EntryStatistics || options->ExitPortStatistics) {
+      if (!time_to_write_stats_files) {
+        /* Initialize stats. */
+        if (options->CellStatistics)
+          rep_hist_buffer_stats_init(now);
+        if (options->DirReqStatistics)
+          geoip_dirreq_stats_init(now);
+        if (options->EntryStatistics)
+          geoip_entry_stats_init(now);
+        if (options->ExitPortStatistics)
+          rep_hist_exit_stats_init(now);
+        log_notice(LD_CONFIG, "Configured to measure statistics. Look for "
+                   "the *-stats files that will first be written to the "
+                   "data directory in %d hours from now.",
+                   WRITE_STATS_INTERVAL / (60 * 60));
+        time_to_write_stats_files = now + WRITE_STATS_INTERVAL;
+      } else {
+        /* Write stats to disk. */
+        time_to_write_stats_files += WRITE_STATS_INTERVAL;
+        if (options->CellStatistics)
+          rep_hist_buffer_stats_write(time_to_write_stats_files);
+        if (options->DirReqStatistics)
+          geoip_dirreq_stats_write(time_to_write_stats_files);
+        if (options->EntryStatistics)
+          geoip_entry_stats_write(time_to_write_stats_files);
+        if (options->ExitPortStatistics)
+          rep_hist_exit_stats_write(time_to_write_stats_files);
+      }
+    } else {
+      /* Never write stats to disk */
+      time_to_write_stats_files = -1;
+    }
   }
 
   /* Remove old information from rephist and the rend cache. */
diff --git a/src/or/or.h b/src/or/or.h
index 9a0f51f..98ab860 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -3739,6 +3739,11 @@ void geoip_start_dirreq(uint64_t dirreq_id, size_t response_size,
 void geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type,
                                dirreq_state_t new_state);
 
+void geoip_dirreq_stats_init(time_t now);
+void geoip_dirreq_stats_write(time_t now);
+void geoip_entry_stats_init(time_t now);
+void geoip_entry_stats_write(time_t now);
+
 /********************************* hibernate.c **********************/
 
 int accounting_parse_options(or_options_t *options, int validate_only);
@@ -4076,11 +4081,11 @@ void rep_hist_note_extend_failed(const char *from_name, const char *to_name);
 void rep_hist_dump_stats(time_t now, int severity);
 void rep_hist_note_bytes_read(size_t num_bytes, time_t when);
 void rep_hist_note_bytes_written(size_t num_bytes, time_t when);
-void rep_hist_note_exit_bytes_read(uint16_t port, size_t num_bytes,
-                                   time_t now);
-void rep_hist_note_exit_bytes_written(uint16_t port, size_t num_bytes,
-                                      time_t now);
-void rep_hist_note_exit_stream_opened(uint16_t port, time_t now);
+void rep_hist_note_exit_bytes_read(uint16_t port, size_t num_bytes);
+void rep_hist_note_exit_bytes_written(uint16_t port, size_t num_bytes);
+void rep_hist_note_exit_stream_opened(uint16_t port);
+void rep_hist_exit_stats_init(time_t now);
+void rep_hist_exit_stats_write(time_t now);
 int rep_hist_bandwidth_assess(void);
 char *rep_hist_get_bandwidth_lines(int for_extrainfo);
 void rep_hist_update_state(or_state_t *state);
@@ -4132,9 +4137,10 @@ void hs_usage_note_fetch_successful(const char *service_id, time_t now);
 void hs_usage_write_statistics_to_file(time_t now);
 void hs_usage_free_all(void);
 
-#define DUMP_BUFFER_STATS_INTERVAL (24*60*60)
-void add_circ_to_buffer_stats(circuit_t *circ, time_t end_of_interval);
-void dump_buffer_stats(void);
+void rep_hist_buffer_stats_init(time_t now);
+void rep_hist_buffer_stats_add_circ(circuit_t *circ,
+                                    time_t end_of_interval);
+void rep_hist_buffer_stats_write(time_t now);
 
 /********************************* rendclient.c ***************************/
 
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 3e4ba67..44bf940 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -1321,8 +1321,6 @@ rep_hist_note_bytes_read(size_t num_bytes, time_t when)
 }
 
 /* Some constants */
-/** How long are the intervals for measuring exit stats? */
-#define EXIT_STATS_INTERVAL_SEC (24 * 60 * 60)
 /** To what multiple should byte numbers be rounded up? */
 #define EXIT_STATS_ROUND_UP_BYTES 1024
 /** To what multiple should stream counts be rounded up? */
@@ -1344,10 +1342,14 @@ static uint64_t *exit_bytes_written = NULL;
 /** Number of streams opened in current period by exit port */
 static uint32_t *exit_streams = NULL;
 
-/** Set up arrays for exit port statistics. */
-static void
-exit_stats_init(void)
+/** When does the current exit stats period end? */
+static time_t start_of_exit_stats_interval;
+
+/** Initialize exit port stats. */
+void
+rep_hist_exit_stats_init(time_t now)
 {
+  start_of_exit_stats_interval = now;
   exit_bytes_read = tor_malloc_zero(EXIT_STATS_NUM_PORTS *
                                     sizeof(uint64_t));
   exit_bytes_written = tor_malloc_zero(EXIT_STATS_NUM_PORTS *
@@ -1356,12 +1358,9 @@ exit_stats_init(void)
                                  sizeof(uint32_t));
 }
 
-/** When does the current exit stats period end? */
-static time_t end_of_current_exit_stats_period = 0;
-
 /** Write exit stats for the current period to disk and reset counters. */
-static void
-write_exit_stats(time_t when)
+void
+rep_hist_exit_stats_write(time_t now)
 {
   char t[ISO_TIME_LEN+1];
   int r, i, comma;
@@ -1372,98 +1371,93 @@ write_exit_stats(time_t when)
   open_file_t *open_file = NULL;
   FILE *out = NULL;
 
-  log_debug(LD_HIST, "Considering writing exit port statistics to disk..");
-  if (!exit_bytes_read)
-    exit_stats_init();
-  while (when > end_of_current_exit_stats_period) {
-    format_iso_time(t, end_of_current_exit_stats_period);
-    log_info(LD_HIST, "Writing exit port statistics to disk for period "
-             "ending at %s.", t);
-
-    if (!open_file) {
-      out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
-                                        0600, &open_file);
-      if (!out) {
-        log_warn(LD_HIST, "Couldn't open '%s'.", filename);
-        goto done;
-      }
-    }
+  format_iso_time(t, now);
+  log_info(LD_HIST, "Writing exit port statistics to disk for period "
+           "ending at %s.", t);
 
-    /* written yyyy-mm-dd HH:MM:SS (n s) */
-    if (fprintf(out, "exit-stats-end %s (%d s)\n", t,
-                EXIT_STATS_INTERVAL_SEC) < 0)
+  if (!open_file) {
+    out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
+                                      0600, &open_file);
+    if (!out) {
+      log_warn(LD_HIST, "Couldn't open '%s'.", filename);
       goto done;
-
-    /* Count the total number of bytes, so that we can attribute all
-     * observations below a threshold of 1 / EXIT_STATS_THRESHOLD_RECIPROCAL
-     * of all bytes to a special port 'other'. */
-    total_bytes = 0;
-    for (i = 1; i < EXIT_STATS_NUM_PORTS; i++) {
-      total_bytes += exit_bytes_read[i];
-      total_bytes += exit_bytes_written[i];
-    }
-    threshold_bytes = total_bytes / EXIT_STATS_THRESHOLD_RECIPROCAL;
-
-    /* kibibytes-(read|written) port=kibibytes,.. */
-    for (r = 0; r < 2; r++) {
-      b = r ? exit_bytes_read : exit_bytes_written;
-      tor_assert(b);
-      if (fprintf(out, "%s ",
-                  r ? "exit-kibibytes-read"
-                    : "exit-kibibytes-written") < 0)
-        goto done;
-
-      comma = 0;
-      other_bytes = 0;
-      for (i = 1; i < EXIT_STATS_NUM_PORTS; i++) {
-        if (b[i] > 0) {
-          if (exit_bytes_read[i] + exit_bytes_written[i] > threshold_bytes) {
-            uint64_t num = round_uint64_to_next_multiple_of(b[i],
-                                                EXIT_STATS_ROUND_UP_BYTES);
-            num /= 1024;
-            if (fprintf(out, "%s%d="U64_FORMAT,
-                        comma++ ? "," : "", i,
-                        U64_PRINTF_ARG(num)) < 0)
-              goto done;
-          } else
-            other_bytes += b[i];
-        }
-      }
-      other_bytes = round_uint64_to_next_multiple_of(other_bytes,
-                                         EXIT_STATS_ROUND_UP_BYTES);
-      other_bytes /= 1024;
-      if (fprintf(out, "%sother="U64_FORMAT"\n",
-                  comma ? "," : "", U64_PRINTF_ARG(other_bytes))<0)
-        goto done;
     }
-    /* streams-opened port=num,.. */
-    if (fprintf(out, "exit-streams-opened ") < 0)
+  }
+
+  /* written yyyy-mm-dd HH:MM:SS (n s) */
+  if (fprintf(out, "exit-stats-end %s (%d s)\n", t,
+              (unsigned) (now - start_of_exit_stats_interval)) < 0)
+    goto done;
+
+  /* Count the total number of bytes, so that we can attribute all
+   * observations below a threshold of 1 / EXIT_STATS_THRESHOLD_RECIPROCAL
+   * of all bytes to a special port 'other'. */
+  total_bytes = 0;
+  for (i = 1; i < EXIT_STATS_NUM_PORTS; i++) {
+    total_bytes += exit_bytes_read[i];
+    total_bytes += exit_bytes_written[i];
+  }
+  threshold_bytes = total_bytes / EXIT_STATS_THRESHOLD_RECIPROCAL;
+
+  /* exit-kibibytes-(read|written) port=kibibytes,.. */
+  for (r = 0; r < 2; r++) {
+    b = r ? exit_bytes_read : exit_bytes_written;
+    tor_assert(b);
+    if (fprintf(out, "%s ",
+                r ? "exit-kibibytes-read"
+                  : "exit-kibibytes-written") < 0)
       goto done;
+
     comma = 0;
-    other_streams = 0;
+    other_bytes = 0;
     for (i = 1; i < EXIT_STATS_NUM_PORTS; i++) {
-      if (exit_streams[i] > 0) {
+      if (b[i] > 0) {
         if (exit_bytes_read[i] + exit_bytes_written[i] > threshold_bytes) {
-          uint32_t num = round_uint32_to_next_multiple_of(exit_streams[i],
-                                              EXIT_STATS_ROUND_UP_STREAMS);
-          if (fprintf(out, "%s%d=%u",
-                      comma++ ? "," : "", i, num)<0)
+          uint64_t num = round_uint64_to_next_multiple_of(b[i],
+                                              EXIT_STATS_ROUND_UP_BYTES);
+          num /= 1024;
+          if (fprintf(out, "%s%d="U64_FORMAT,
+                      comma++ ? "," : "", i,
+                      U64_PRINTF_ARG(num)) < 0)
             goto done;
         } else
-          other_streams += exit_streams[i];
+          other_bytes += b[i];
       }
     }
-    other_streams = round_uint32_to_next_multiple_of(other_streams,
-                                         EXIT_STATS_ROUND_UP_STREAMS);
-    if (fprintf(out, "%sother=%u\n",
-                comma ? "," : "", other_streams)<0)
+    other_bytes = round_uint64_to_next_multiple_of(other_bytes,
+                                       EXIT_STATS_ROUND_UP_BYTES);
+    other_bytes /= 1024;
+    if (fprintf(out, "%sother="U64_FORMAT"\n",
+                comma ? "," : "", U64_PRINTF_ARG(other_bytes))<0)
       goto done;
-    /* Reset counters */
-    memset(exit_bytes_read, 0, sizeof(exit_bytes_read));
-    memset(exit_bytes_written, 0, sizeof(exit_bytes_written));
-    memset(exit_streams, 0, sizeof(exit_streams));
-    end_of_current_exit_stats_period += EXIT_STATS_INTERVAL_SEC;
   }
+  /* exit-streams-opened port=num,.. */
+  if (fprintf(out, "exit-streams-opened ") < 0)
+    goto done;
+  comma = 0;
+  other_streams = 0;
+  for (i = 1; i < EXIT_STATS_NUM_PORTS; i++) {
+    if (exit_streams[i] > 0) {
+      if (exit_bytes_read[i] + exit_bytes_written[i] > threshold_bytes) {
+        uint32_t num = round_uint32_to_next_multiple_of(exit_streams[i],
+                                            EXIT_STATS_ROUND_UP_STREAMS);
+        if (fprintf(out, "%s%d=%u",
+                    comma++ ? "," : "", i, num)<0)
+          goto done;
+      } else
+        other_streams += exit_streams[i];
+    }
+  }
+  other_streams = round_uint32_to_next_multiple_of(other_streams,
+                                       EXIT_STATS_ROUND_UP_STREAMS);
+  if (fprintf(out, "%sother=%u\n",
+              comma ? "," : "", other_streams)<0)
+    goto done;
+  /* Reset counters */
+  memset(exit_bytes_read, 0, sizeof(exit_bytes_read));
+  memset(exit_bytes_written, 0, sizeof(exit_bytes_written));
+  memset(exit_streams, 0, sizeof(exit_streams));
+  start_of_exit_stats_interval = now;
 
   if (open_file)
     finish_writing_to_file(open_file);
@@ -1474,59 +1468,36 @@ write_exit_stats(time_t when)
   tor_free(filename);
 }
 
-/** Prepare to add an exit stats observation at second <b>when</b> by
- * checking whether this observation lies in the current observation
- * period; if not, shift the current period forward by one until the
- * reported event fits it and write all results in between to disk. */
-static void
-add_exit_obs(time_t when)
-{
-  if (!exit_bytes_read)
-    exit_stats_init();
-  if (when > end_of_current_exit_stats_period) {
-    if (end_of_current_exit_stats_period)
-      write_exit_stats(when);
-    else
-      end_of_current_exit_stats_period = when + EXIT_STATS_INTERVAL_SEC;
-  }
-}
-
 /** Note that we wrote <b>num_bytes</b> to an exit connection to
- * <b>port</b> in second <b>when</b>. */
+ * <b>port</b>. */
 void
-rep_hist_note_exit_bytes_written(uint16_t port, size_t num_bytes,
-                                 time_t when)
+rep_hist_note_exit_bytes_written(uint16_t port, size_t num_bytes)
 {
   if (!get_options()->ExitPortStatistics)
     return;
-  add_exit_obs(when);
   exit_bytes_written[port] += num_bytes;
   log_debug(LD_HIST, "Written %lu bytes to exit connection to port %d.",
             (unsigned long)num_bytes, port);
 }
 
 /** Note that we read <b>num_bytes</b> from an exit connection to
- * <b>port</b> in second <b>when</b>. */
+ * <b>port</b>. */
 void
-rep_hist_note_exit_bytes_read(uint16_t port, size_t num_bytes,
-                              time_t when)
+rep_hist_note_exit_bytes_read(uint16_t port, size_t num_bytes)
 {
   if (!get_options()->ExitPortStatistics)
     return;
-  add_exit_obs(when);
   exit_bytes_read[port] += num_bytes;
   log_debug(LD_HIST, "Read %lu bytes from exit connection to port %d.",
             (unsigned long)num_bytes, port);
 }
 
-/** Note that we opened an exit stream to <b>port</b> in second
- * <b>when</b>. */
+/** Note that we opened an exit stream to <b>port</b>. */
 void
-rep_hist_note_exit_stream_opened(uint16_t port, time_t when)
+rep_hist_note_exit_stream_opened(uint16_t port)
 {
   if (!get_options()->ExitPortStatistics)
     return;
-  add_exit_obs(when);
   exit_streams[port]++;
   log_debug(LD_HIST, "Opened exit stream to port %d", port);
 }
@@ -2623,7 +2594,14 @@ hs_usage_write_statistics_to_file(time_t now)
 /*** cell statistics ***/
 
 /** Start of the current buffer stats interval. */
-time_t start_of_buffer_stats_interval;
+static time_t start_of_buffer_stats_interval;
+
+/** Initialize buffer stats. */
+void
+rep_hist_buffer_stats_init(time_t now)
+{
+  start_of_buffer_stats_interval = now;
+}
 
 typedef struct circ_buffer_stats_t {
   uint32_t processed_cells;
@@ -2639,7 +2617,7 @@ smartlist_t *circuits_for_buffer_stats = NULL;
  * <b>end_of_interval</b> and reset cell counters in case the circuit
  * remains open in the next measurement interval. */
 void
-add_circ_to_buffer_stats(circuit_t *circ, time_t end_of_interval)
+rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval)
 {
   circ_buffer_stats_t *stat;
   time_t start_of_interval;
@@ -2687,9 +2665,8 @@ _buffer_stats_compare_entries(const void **_a, const void **_b)
 
 /** Append buffer statistics to local file. */
 void
-dump_buffer_stats(void)
+rep_hist_buffer_stats_write(time_t now)
 {
-  time_t now = time(NULL);
   char *filename;
   char written[ISO_TIME_LEN+1];
   open_file_t *open_file = NULL;
@@ -2704,7 +2681,7 @@ dump_buffer_stats(void)
   circuit_t *circ;
   /* add current circuits to stats */
   for (circ = _circuit_get_global_list(); circ; circ = circ->next)
-    add_circ_to_buffer_stats(circ, now);
+    rep_hist_buffer_stats_add_circ(circ, now);
   /* calculate deciles */
   memset(processed_cells, 0, SHARES * sizeof(int));
   memset(circs_in_share, 0, SHARES * sizeof(int));
@@ -2736,7 +2713,7 @@ dump_buffer_stats(void)
     goto done;
   format_iso_time(written, now);
   if (fprintf(out, "cell-stats-end %s (%d s)\n", written,
-              DUMP_BUFFER_STATS_INTERVAL) < 0)
+              (unsigned) (now - start_of_buffer_stats_interval)) < 0)
     goto done;
   for (i = 0; i < SHARES; i++) {
     tor_snprintf(buf, sizeof(buf), "%d", !circs_in_share[i] ? 0 :
-- 
1.5.6.5