#include "orconfig.h"
#include "core/or/or.h"
#include "feature/stats/connstats.h"
#include "app/config/config.h"
static time_t start_of_conn_stats_interval;
void
conn_stats_init(time_t now)
{
start_of_conn_stats_interval = now;
}
#define BIDI_THRESHOLD 20480
#define BIDI_FACTOR 10
#define BIDI_INTERVAL 10
static time_t bidi_next_interval = 0;
typedef struct conn_counts_t {
uint32_t below_threshold;
uint32_t mostly_read;
uint32_t mostly_written;
uint32_t both_read_and_written;
} conn_counts_t ;
static conn_counts_t counts;
static conn_counts_t counts_ipv6;
typedef struct bidi_map_entry_t {
HT_ENTRY(bidi_map_entry_t) node;
uint64_t conn_id;
size_t read;
size_t written;
bool is_ipv6;
} bidi_map_entry_t;
static HT_HEAD(bidimap, bidi_map_entry_t) bidi_map =
HT_INITIALIZER();
static int
bidi_map_ent_eq(const bidi_map_entry_t *a, const bidi_map_entry_t *b)
{
return a->conn_id == b->conn_id;
}
static unsigned
bidi_map_ent_hash(const bidi_map_entry_t *entry)
{
return (unsigned) entry->conn_id;
}
HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
bidi_map_ent_eq);
HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_);
void
conn_stats_free_all(void)
{
bidi_map_entry_t **ptr, **next, *ent;
for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) {
ent = *ptr;
next = HT_NEXT_RMV(bidimap, &bidi_map, ptr);
tor_free(ent);
}
HT_CLEAR(bidimap, &bidi_map);
}
void
conn_stats_reset(time_t now)
{
start_of_conn_stats_interval = now;
memset(&counts, 0, sizeof(counts));
memset(&counts_ipv6, 0, sizeof(counts_ipv6));
conn_stats_free_all();
}
void
conn_stats_terminate(void)
{
conn_stats_reset(0);
}
static void
add_entry_to_count(conn_counts_t *cnt, const bidi_map_entry_t *ent)
{
if (ent->read + ent->written < BIDI_THRESHOLD)
cnt->below_threshold++;
else if (ent->read >= ent->written * BIDI_FACTOR)
cnt->mostly_read++;
else if (ent->written >= ent->read * BIDI_FACTOR)
cnt->mostly_written++;
else
cnt->both_read_and_written++;
}
static void
collect_period_statistics(void)
{
bidi_map_entry_t **ptr, **next, *ent;
for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) {
ent = *ptr;
add_entry_to_count(&counts, ent);
if (ent->is_ipv6)
add_entry_to_count(&counts_ipv6, ent);
next = HT_NEXT_RMV(bidimap, &bidi_map, ptr);
tor_free(ent);
}
log_info(LD_GENERAL, "%d below threshold, %d mostly read, "
"%d mostly written, %d both read and written.",
counts.below_threshold, counts.mostly_read, counts.mostly_written,
counts.both_read_and_written);
}
void
conn_stats_note_or_conn_bytes(uint64_t conn_id, size_t num_read,
size_t num_written, time_t when,
bool is_ipv6)
{
if (!start_of_conn_stats_interval)
return;
if (bidi_next_interval == 0)
bidi_next_interval = when + BIDI_INTERVAL;
if (when >= bidi_next_interval) {
collect_period_statistics();
while (when >= bidi_next_interval)
bidi_next_interval += BIDI_INTERVAL;
}
if (num_read > 0 || num_written > 0) {
bidi_map_entry_t *entry, lookup;
lookup.conn_id = conn_id;
entry = HT_FIND(bidimap, &bidi_map, &lookup);
if (entry) {
entry->written += num_written;
entry->read += num_read;
entry->is_ipv6 |= is_ipv6;
} else {
entry = tor_malloc_zero(sizeof(bidi_map_entry_t));
entry->conn_id = conn_id;
entry->written = num_written;
entry->read = num_read;
entry->is_ipv6 = is_ipv6;
HT_INSERT(bidimap, &bidi_map, entry);
}
}
}
char *
conn_stats_format(time_t now)
{
char *result, written_at[ISO_TIME_LEN+1];
if (!start_of_conn_stats_interval)
return NULL;
tor_assert(now >= start_of_conn_stats_interval);
format_iso_time(written_at, now);
tor_asprintf(&result,
"conn-bi-direct %s (%d s) "
"%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n"
"ipv6-conn-bi-direct %s (%d s) "
"%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n",
written_at,
(unsigned) (now - start_of_conn_stats_interval),
counts.below_threshold,
counts.mostly_read,
counts.mostly_written,
counts.both_read_and_written,
written_at,
(unsigned) (now - start_of_conn_stats_interval),
counts_ipv6.below_threshold,
counts_ipv6.mostly_read,
counts_ipv6.mostly_written,
counts_ipv6.both_read_and_written);
return result;
}
time_t
conn_stats_save(time_t now)
{
char *str = NULL;
if (!start_of_conn_stats_interval)
return 0;
if (start_of_conn_stats_interval + WRITE_STATS_INTERVAL > now)
goto done;
str = conn_stats_format(now);
conn_stats_reset(now);
if (!check_or_create_data_subdir("stats")) {
write_to_data_subdir("stats", "conn-stats", str, "connection statistics");
}
done:
tor_free(str);
return start_of_conn_stats_interval + WRITE_STATS_INTERVAL;
}