summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/misc-reason6
-rw-r--r--changes/stats-tests4
-rw-r--r--doc/spec/control-spec.txt2
-rw-r--r--doc/spec/tor-spec.txt5
-rw-r--r--doc/tor.1.txt13
-rw-r--r--src/or/connection.c6
-rw-r--r--src/or/or.h2
-rw-r--r--src/or/reasons.c6
-rw-r--r--src/or/relay.c1
-rw-r--r--src/or/rephist.c271
-rw-r--r--src/or/rephist.h15
-rw-r--r--src/test/test.c65
12 files changed, 245 insertions, 151 deletions
diff --git a/changes/misc-reason b/changes/misc-reason
new file mode 100644
index 0000000000..9d173fd3c1
--- /dev/null
+++ b/changes/misc-reason
@@ -0,0 +1,6 @@
+ o Minor bugfixes:
+ - Exit nodes didn't recognize EHOSTUNREACH as a plausible error
+ code, and sent back END_STREAM_REASON_MISC. They now send a a
+ new stream ending reason: END_STREAM_REASON_NOROUTE. Also update
+ the spec to reflect this new reason. Bugfix on 0.1.0.1-rc; fixes
+ bug 1793.
diff --git a/changes/stats-tests b/changes/stats-tests
new file mode 100644
index 0000000000..9298843469
--- /dev/null
+++ b/changes/stats-tests
@@ -0,0 +1,4 @@
+ o Code simplifications and refactoring:
+ - New unit tests for exit-port history statistics; refactored exit
+ statistics code to be more easily tested.
+
diff --git a/doc/spec/control-spec.txt b/doc/spec/control-spec.txt
index 333b1a36a2..31ce35074b 100644
--- a/doc/spec/control-spec.txt
+++ b/doc/spec/control-spec.txt
@@ -1049,7 +1049,7 @@
Reason = "MISC" / "RESOLVEFAILED" / "CONNECTREFUSED" /
"EXITPOLICY" / "DESTROY" / "DONE" / "TIMEOUT" /
- "HIBERNATING" / "INTERNAL"/ "RESOURCELIMIT" /
+ "NOROUTE" / "HIBERNATING" / "INTERNAL"/ "RESOURCELIMIT" /
"CONNRESET" / "TORPROTOCOL" / "NOTDIRECTORY" / "END"
The "REASON" field is provided only for FAILED, CLOSED, and DETACHED
diff --git a/doc/spec/tor-spec.txt b/doc/spec/tor-spec.txt
index f448f6da2c..2b1223ba7a 100644
--- a/doc/spec/tor-spec.txt
+++ b/doc/spec/tor-spec.txt
@@ -835,7 +835,8 @@ see tor-design.pdf.
6 -- REASON_DONE (Anonymized TCP connection was closed)
7 -- REASON_TIMEOUT (Connection timed out, or OR timed out
while connecting)
- 8 -- (unallocated) [**]
+ 8 -- REASON_NOROUTE (Routing error while attempting to
+ contact destination)
9 -- REASON_HIBERNATING (OR is temporarily hibernating)
10 -- REASON_INTERNAL (Internal error at the OR)
11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)
@@ -857,8 +858,6 @@ see tor-design.pdf.
[*] Older versions of Tor also send this reason when connections are
reset.
- [**] Due to a bug in versions of Tor through 0095, error reason 8 must
- remain allocated until that version is obsolete.
--- [The rest of this section describes unimplemented functionality.]
diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index db623c8da9..3b7e30bdfb 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -965,23 +965,20 @@ is non-zero):
**CellStatistics** **0**|**1**::
When this option is enabled, Tor writes statistics on the mean time that
- cells spend in circuit queues to disk every 24 hours. Cannot be changed
- while Tor is running. (Default: 0)
+ cells spend in circuit queues to disk every 24 hours. (Default: 0)
**DirReqStatistics** **0**|**1**::
When this option is enabled, Tor writes statistics on the number and
- response time of network status requests to disk every 24 hours. Cannot be
- changed while Tor is running. (Default: 0)
+ response time of network status requests to disk every 24 hours.
+ (Default: 0)
**EntryStatistics** **0**|**1**::
When this option is enabled, Tor writes statistics on the number of
- directly connecting clients to disk every 24 hours. Cannot be changed while
- Tor is running. (Default: 0)
+ directly connecting clients to disk every 24 hours. (Default: 0)
**ExitPortStatistics** **0**|**1**::
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. Cannot be
- changed while Tor is running. (Default: 0)
+ bytes and opened stream per exit port to disk every 24 hours. (Default: 0)
**ExtraInfoStatistics** **0**|**1**::
When this option is enabled, Tor includes previously gathered statistics in
diff --git a/src/or/connection.c b/src/or/connection.c
index 9956172600..a16eb37a14 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -2096,15 +2096,13 @@ 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);
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);
rep_hist_note_bytes_written(num_written, now);
}
+ if (conn->type == CONN_TYPE_EXIT)
+ rep_hist_note_exit_bytes(conn->port, num_written, num_read);
if (connection_counts_as_relayed_traffic(conn, now)) {
global_relayed_read_bucket -= (int)num_read;
diff --git a/src/or/or.h b/src/or/or.h
index e1f4541a7e..4098cd284e 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -551,7 +551,7 @@ typedef enum {
#define END_STREAM_REASON_DESTROY 5
#define END_STREAM_REASON_DONE 6
#define END_STREAM_REASON_TIMEOUT 7
-/* 8 is unallocated for historical reasons. */
+#define END_STREAM_REASON_NOROUTE 8
#define END_STREAM_REASON_HIBERNATING 9
#define END_STREAM_REASON_INTERNAL 10
#define END_STREAM_REASON_RESOURCELIMIT 11
diff --git a/src/or/reasons.c b/src/or/reasons.c
index 2dd5fe9463..8bce3625d8 100644
--- a/src/or/reasons.c
+++ b/src/or/reasons.c
@@ -28,6 +28,7 @@ stream_end_reason_to_control_string(int reason)
case END_STREAM_REASON_DESTROY: return "DESTROY";
case END_STREAM_REASON_DONE: return "DONE";
case END_STREAM_REASON_TIMEOUT: return "TIMEOUT";
+ case END_STREAM_REASON_NOROUTE: return "NOROUTE";
case END_STREAM_REASON_HIBERNATING: return "HIBERNATING";
case END_STREAM_REASON_INTERNAL: return "INTERNAL";
case END_STREAM_REASON_RESOURCELIMIT: return "RESOURCELIMIT";
@@ -62,6 +63,7 @@ stream_end_reason_to_string(int reason)
case END_STREAM_REASON_DESTROY: return "destroyed";
case END_STREAM_REASON_DONE: return "closed normally";
case END_STREAM_REASON_TIMEOUT: return "gave up (timeout)";
+ case END_STREAM_REASON_NOROUTE: return "no route to host";
case END_STREAM_REASON_HIBERNATING: return "server is hibernating";
case END_STREAM_REASON_INTERNAL: return "internal error at server";
case END_STREAM_REASON_RESOURCELIMIT: return "server out of resources";
@@ -104,6 +106,8 @@ stream_end_reason_to_socks5_response(int reason)
return SOCKS5_SUCCEEDED;
case END_STREAM_REASON_TIMEOUT:
return SOCKS5_TTL_EXPIRED;
+ case END_STREAM_REASON_NOROUTE:
+ return SOCKS5_HOST_UNREACHABLE;
case END_STREAM_REASON_RESOURCELIMIT:
return SOCKS5_GENERAL_ERROR;
case END_STREAM_REASON_HIBERNATING:
@@ -164,6 +168,8 @@ errno_to_stream_end_reason(int e)
S_CASE(ENOTCONN):
S_CASE(ENETUNREACH):
return END_STREAM_REASON_INTERNAL;
+ E_CASE(EHOSTUNREACH):
+ return END_STREAM_REASON_NOROUTE;
S_CASE(ECONNREFUSED):
return END_STREAM_REASON_CONNECTREFUSED;
S_CASE(ECONNRESET):
diff --git a/src/or/relay.c b/src/or/relay.c
index b2de91da85..6d0080fad6 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -787,6 +787,7 @@ connection_ap_process_end_not_open(
case END_STREAM_REASON_RESOLVEFAILED:
case END_STREAM_REASON_TIMEOUT:
case END_STREAM_REASON_MISC:
+ case END_STREAM_REASON_NOROUTE:
if (client_dns_incr_failures(conn->socks_request->address)
< MAX_RESOLVE_FAILURES) {
/* We haven't retried too many times; reattach the connection. */
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 72addde5b5..b6a19d4b16 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -1889,6 +1889,16 @@ rep_hist_exit_stats_init(time_t now)
sizeof(uint32_t));
}
+/** Reset counters for exit port statistics. */
+void
+rep_hist_reset_exit_stats(time_t now)
+{
+ start_of_exit_stats_interval = now;
+ memset(exit_bytes_read, 0, EXIT_STATS_NUM_PORTS * sizeof(uint64_t));
+ memset(exit_bytes_written, 0, EXIT_STATS_NUM_PORTS * sizeof(uint64_t));
+ memset(exit_streams, 0, EXIT_STATS_NUM_PORTS * sizeof(uint32_t));
+}
+
/** Stop collecting exit port stats in a way that we can re-start doing
* so in rep_hist_exit_stats_init(). */
void
@@ -1900,164 +1910,173 @@ rep_hist_exit_stats_term(void)
tor_free(exit_streams);
}
-/** Write exit stats to $DATADIR/stats/exit-stats, reset counters, and
- * return when we would next want to write exit stats. */
-time_t
-rep_hist_exit_stats_write(time_t now)
+/** Return a newly allocated string containing the exit port statistics
+ * until <b>now</b>, or NULL if we're not collecting exit stats. */
+char *
+rep_hist_format_exit_stats(time_t now)
{
+ int i;
+ uint64_t total_bytes = 0, threshold_bytes, other_read = 0,
+ other_written = 0;
+ uint32_t other_streams = 0;
+ char *buf;
+ smartlist_t *written_strings, *read_strings, *streams_strings;
+ char *written_string, *read_string, *streams_string;
char t[ISO_TIME_LEN+1];
- int r, i, comma;
- uint64_t *b, total_bytes, threshold_bytes, other_bytes;
- uint32_t other_streams;
-
- char *statsdir = NULL, *filename = NULL;
- open_file_t *open_file = NULL;
- FILE *out = NULL;
+ char *result;
if (!start_of_exit_stats_interval)
- return 0; /* Not initialized. */
- if (start_of_exit_stats_interval + WRITE_STATS_INTERVAL > now)
- goto done; /* Not ready to write. */
+ return NULL; /* Not initialized. */
- statsdir = get_datadir_fname("stats");
- if (check_private_dir(statsdir, CPD_CREATE) < 0)
- goto done;
- filename = get_datadir_fname2("stats", "exit-stats");
- format_iso_time(t, now);
- 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;
- }
- }
-
- /* 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
+ /* Count total number of bytes, so that we can attribute observations
+ * below or equal to 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_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;
- }
- /* exit-streams-opened port=num,.. */
- if (fprintf(out, "exit-streams-opened ") < 0)
- goto done;
- comma = 0;
- other_streams = 0;
+ /* Add observations of all ports above the threshold to smartlists and
+ * join them to single strings. Also count bytes and streams of ports
+ * below or equal to the threshold. */
+ written_strings = smartlist_create();
+ read_strings = smartlist_create();
+ streams_strings = smartlist_create();
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) {
+ if (exit_bytes_read[i] + exit_bytes_written[i] > threshold_bytes) {
+ if (exit_bytes_written[i] > 0) {
+ uint64_t num = round_uint64_to_next_multiple_of(
+ exit_bytes_written[i], EXIT_STATS_ROUND_UP_BYTES);
+ num /= 1024;
+ buf = NULL;
+ tor_asprintf(&buf, "%d="U64_FORMAT, i, U64_PRINTF_ARG(num));
+ smartlist_add(written_strings, buf);
+ }
+ if (exit_bytes_read[i] > 0) {
+ uint64_t num = round_uint64_to_next_multiple_of(
+ exit_bytes_read[i], EXIT_STATS_ROUND_UP_BYTES);
+ num /= 1024;
+ buf = NULL;
+ tor_asprintf(&buf, "%d="U64_FORMAT, i, U64_PRINTF_ARG(num));
+ smartlist_add(read_strings, buf);
+ }
+ if (exit_streams[i] > 0) {
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];
+ EXIT_STATS_ROUND_UP_STREAMS);
+ buf = NULL;
+ tor_asprintf(&buf, "%d=%u", i, num);
+ smartlist_add(streams_strings, buf);
+ }
+ } else {
+ other_read += exit_bytes_read[i];
+ other_written += exit_bytes_written[i];
+ other_streams += exit_streams[i];
}
}
+ other_written = round_uint64_to_next_multiple_of(other_written,
+ EXIT_STATS_ROUND_UP_BYTES);
+ other_written /= 1024;
+ buf = NULL;
+ tor_asprintf(&buf, "other="U64_FORMAT, U64_PRINTF_ARG(other_written));
+ smartlist_add(written_strings, buf);
+ other_read = round_uint64_to_next_multiple_of(other_read,
+ EXIT_STATS_ROUND_UP_BYTES);
+ other_read /= 1024;
+ buf = NULL;
+ tor_asprintf(&buf, "other="U64_FORMAT, U64_PRINTF_ARG(other_read));
+ smartlist_add(read_strings, buf);
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)
+ EXIT_STATS_ROUND_UP_STREAMS);
+ buf = NULL;
+ tor_asprintf(&buf, "other=%u", other_streams);
+ smartlist_add(streams_strings, buf);
+ written_string = smartlist_join_strings(written_strings, ",", 0, NULL);
+ read_string = smartlist_join_strings(read_strings, ",", 0, NULL);
+ streams_string = smartlist_join_strings(streams_strings, ",", 0, NULL);
+ SMARTLIST_FOREACH(written_strings, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(read_strings, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(streams_strings, char *, cp, tor_free(cp));
+ smartlist_free(written_strings);
+ smartlist_free(read_strings);
+ smartlist_free(streams_strings);
+
+ /* Put everything together. */
+ format_iso_time(t, now);
+ tor_asprintf(&result, "exit-stats-end %s (%d s)\n"
+ "exit-kibibytes-written %s\n"
+ "exit-kibibytes-read %s\n"
+ "exit-streams-opened %s\n",
+ t, (unsigned) (now - start_of_exit_stats_interval),
+ written_string,
+ read_string,
+ streams_string);
+ tor_free(written_string);
+ tor_free(read_string);
+ tor_free(streams_string);
+ return result;
+}
+
+/** If 24 hours have passed since the beginning of the current exit port
+ * stats period, write exit stats to $DATADIR/stats/exit-stats (possibly
+ * overwriting an existing file) and reset counters. Return when we would
+ * next want to write exit stats or 0 if we never want to write. */
+time_t
+rep_hist_exit_stats_write(time_t now)
+{
+ char *statsdir = NULL, *filename = NULL, *str = NULL;
+
+ if (!start_of_exit_stats_interval)
+ return 0; /* Not initialized. */
+ if (start_of_exit_stats_interval + WRITE_STATS_INTERVAL > now)
+ goto done; /* Not ready to write. */
+
+ log_info(LD_HIST, "Writing exit port statistics to disk.");
+
+ /* Generate history string. */
+ str = rep_hist_format_exit_stats(now);
+
+ /* Reset counters. */
+ rep_hist_reset_exit_stats(now);
+
+ /* Try to write to disk. */
+ statsdir = get_datadir_fname("stats");
+ if (check_private_dir(statsdir, CPD_CREATE) < 0) {
+ log_warn(LD_HIST, "Unable to create stats/ directory!");
goto done;
- /* Reset counters */
- memset(exit_bytes_read, 0, EXIT_STATS_NUM_PORTS * sizeof(uint64_t));
- memset(exit_bytes_written, 0, EXIT_STATS_NUM_PORTS * sizeof(uint64_t));
- memset(exit_streams, 0, EXIT_STATS_NUM_PORTS * sizeof(uint32_t));
- start_of_exit_stats_interval = now;
+ }
+ filename = get_datadir_fname2("stats", "exit-stats");
+ if (write_str_to_file(filename, str, 0) < 0)
+ log_warn(LD_HIST, "Unable to write exit port statistics to disk!");
- if (open_file)
- finish_writing_to_file(open_file);
- open_file = NULL;
done:
- if (open_file)
- abort_writing_to_file(open_file);
- tor_free(filename);
+ tor_free(str);
tor_free(statsdir);
+ tor_free(filename);
return start_of_exit_stats_interval + WRITE_STATS_INTERVAL;
}
-/** Note that we wrote <b>num_bytes</b> to an exit connection to
- * <b>port</b>. */
+/** Note that we wrote <b>num_written</b> bytes and read <b>num_read</b>
+ * bytes to/from an exit connection to <b>port</b>. */
void
-rep_hist_note_exit_bytes_written(uint16_t port, size_t num_bytes)
+rep_hist_note_exit_bytes(uint16_t port, size_t num_written,
+ size_t num_read)
{
- if (!get_options()->ExitPortStatistics)
- return;
- if (!exit_bytes_written)
- return; /* Not initialized */
- 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>. */
-void
-rep_hist_note_exit_bytes_read(uint16_t port, size_t num_bytes)
-{
- if (!get_options()->ExitPortStatistics)
- return;
- if (!exit_bytes_read)
- return; /* Not initialized */
- exit_bytes_read[port] += num_bytes;
- log_debug(LD_HIST, "Read %lu bytes from exit connection to port %d.",
- (unsigned long)num_bytes, port);
+ if (!start_of_exit_stats_interval)
+ return; /* Not initialized. */
+ exit_bytes_written[port] += num_written;
+ exit_bytes_read[port] += num_read;
+ log_debug(LD_HIST, "Written %lu bytes and read %lu bytes to/from an "
+ "exit connection to port %d.",
+ (unsigned long)num_written, (unsigned long)num_read, port);
}
/** Note that we opened an exit stream to <b>port</b>. */
void
rep_hist_note_exit_stream_opened(uint16_t port)
{
- if (!get_options()->ExitPortStatistics)
- return;
- if (!exit_streams)
- return; /* Not initialized */
+ if (!start_of_exit_stats_interval)
+ return; /* Not initialized. */
exit_streams[port]++;
log_debug(LD_HIST, "Opened exit stream to port %d", port);
}
diff --git a/src/or/rephist.h b/src/or/rephist.h
index fe45a81a3b..f655500eb8 100644
--- a/src/or/rephist.h
+++ b/src/or/rephist.h
@@ -23,12 +23,6 @@ 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);
-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);
-time_t rep_hist_exit_stats_write(time_t now);
-void rep_hist_exit_stats_term(void);
int rep_hist_bandwidth_assess(void);
char *rep_hist_get_bandwidth_lines(int for_extrainfo);
void rep_hist_update_state(or_state_t *state);
@@ -71,6 +65,15 @@ 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);
+void rep_hist_exit_stats_init(time_t now);
+void rep_hist_reset_exit_stats(time_t now);
+void rep_hist_exit_stats_term(void);
+char *rep_hist_format_exit_stats(time_t now);
+time_t rep_hist_exit_stats_write(time_t now);
+void rep_hist_note_exit_bytes(uint16_t port, size_t num_written,
+ size_t num_read);
+void rep_hist_note_exit_stream_opened(uint16_t port);
+
void rep_hist_buffer_stats_init(time_t now);
void rep_hist_buffer_stats_add_circ(circuit_t *circ,
time_t end_of_interval);
diff --git a/src/test/test.c b/src/test/test.c
index c1d2ecbfb9..5a37f1da8d 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -70,6 +70,7 @@ int have_failed = 0;
/** Temporary directory (set up by setup_directory) under which we store all
* our files during testing. */
static char temp_dir[256];
+static pid_t temp_dir_setup_in_pid = 0;
/** Select and create the temporary directory we'll use to run our unit tests.
* Store it in <b>temp_dir</b>. Exit immediately if we can't create it.
@@ -96,6 +97,7 @@ setup_directory(void)
exit(1);
}
is_setup = 1;
+ temp_dir_setup_in_pid = getpid();
}
/** Return a filename relative to our testing temporary directory */
@@ -109,11 +111,16 @@ get_fname(const char *name)
}
/** Remove all files stored under the temporary directory, and the directory
- * itself. */
+ * itself. Called by atexit(). */
static void
remove_directory(void)
{
- smartlist_t *elements = tor_listdir(temp_dir);
+ smartlist_t *elements;
+ if (getpid() != temp_dir_setup_in_pid) {
+ /* Only clean out the tempdir when the main process is exiting. */
+ return;
+ }
+ elements = tor_listdir(temp_dir);
if (elements) {
SMARTLIST_FOREACH(elements, const char *, cp,
{
@@ -1149,6 +1156,57 @@ test_geoip(void)
tor_free(s);
}
+/** Run unit tests for stats code. */
+static void
+test_stats(void)
+{
+ time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */
+ char *s = NULL;
+
+ /* We shouldn't collect exit stats without initializing them. */
+ rep_hist_note_exit_stream_opened(80);
+ rep_hist_note_exit_bytes(80, 100, 10000);
+ s = rep_hist_format_exit_stats(now + 86400);
+ test_assert(!s);
+
+ /* Initialize stats, note some streams and bytes, and generate history
+ * string. */
+ rep_hist_exit_stats_init(now);
+ rep_hist_note_exit_stream_opened(80);
+ rep_hist_note_exit_bytes(80, 100, 10000);
+ rep_hist_note_exit_stream_opened(443);
+ rep_hist_note_exit_bytes(443, 100, 10000);
+ rep_hist_note_exit_bytes(443, 100, 10000);
+ s = rep_hist_format_exit_stats(now + 86400);
+ test_streq("exit-stats-end 2010-08-12 13:27:30 (86400 s)\n"
+ "exit-kibibytes-written 80=1,443=1,other=0\n"
+ "exit-kibibytes-read 80=10,443=20,other=0\n"
+ "exit-streams-opened 80=4,443=4,other=0\n", s);
+ tor_free(s);
+
+ /* Stop collecting stats, add some bytes, and ensure we don't generate
+ * a history string. */
+ rep_hist_exit_stats_term();
+ rep_hist_note_exit_bytes(80, 100, 10000);
+ s = rep_hist_format_exit_stats(now + 86400);
+ test_assert(!s);
+
+ /* Re-start stats, add some bytes, reset stats, and see what history we
+ * get when observing no streams or bytes at all. */
+ rep_hist_exit_stats_init(now);
+ rep_hist_note_exit_stream_opened(80);
+ rep_hist_note_exit_bytes(80, 100, 10000);
+ rep_hist_reset_exit_stats(now);
+ s = rep_hist_format_exit_stats(now + 86400);
+ test_streq("exit-stats-end 2010-08-12 13:27:30 (86400 s)\n"
+ "exit-kibibytes-written other=0\n"
+ "exit-kibibytes-read other=0\n"
+ "exit-streams-opened other=0\n", s);
+
+ done:
+ tor_free(s);
+}
+
static void *
legacy_test_setup(const struct testcase_t *testcase)
{
@@ -1181,6 +1239,8 @@ const struct testcase_setup_t legacy_setup = {
test_ ## group ## _ ## name }
#define DISABLED(name) \
{ #name, legacy_test_helper, TT_SKIP, &legacy_setup, name }
+#define FORK(name) \
+ { #name, legacy_test_helper, TT_FORK, &legacy_setup, test_ ## name }
static struct testcase_t test_array[] = {
ENT(buffers),
@@ -1189,6 +1249,7 @@ static struct testcase_t test_array[] = {
ENT(policies),
ENT(rend_fns),
ENT(geoip),
+ FORK(stats),
DISABLED(bench_aes),
DISABLED(bench_dmap),