Commit 4e29f334 authored by Karsten Loesing's avatar Karsten Loesing
Browse files

Write all statistics to disk exactly every 24 hours.

parent 10fbc998
......@@ -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
......
......@@ -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);
......
......@@ -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;
}
......
......@@ -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);
}
......
......@@ -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 */
......
......@@ -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. */
......
......@@ -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. */
......
......@@ -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 ***************************/
......
......@@ -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));