connstats.c (9148B)
1 /* Copyright (c) 2001 Matej Pfajfar. 2 * Copyright (c) 2001-2004, Roger Dingledine. 3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. 4 * Copyright (c) 2007-2021, The Tor Project, Inc. */ 5 /* See LICENSE for licensing information */ 6 7 /** 8 * @file connstats.c 9 * @brief Count bidirectional vs one-way connections. 10 * 11 * Connection statistics, use to track one-way and bidirectional connections. 12 * 13 * Note that this code counts concurrent connections in each 14 * BIDI_INTERVAL-second interval, not total connections. It can tell you what 15 * fraction of connections are bidirectional at each time, not necessarily 16 * what number are bidirectional. 17 **/ 18 19 #include "orconfig.h" 20 #include "core/or/or.h" 21 #include "feature/stats/connstats.h" 22 #include "app/config/config.h" 23 24 /** Start of the current connection stats interval or 0 if we're not 25 * collecting connection statistics. */ 26 static time_t start_of_conn_stats_interval; 27 28 /** Initialize connection stats. */ 29 void 30 conn_stats_init(time_t now) 31 { 32 start_of_conn_stats_interval = now; 33 } 34 35 /** Count connections on which we read and wrote less than this many bytes 36 * as "below threshold." */ 37 #define BIDI_THRESHOLD 20480 38 39 /** Count connections that we read or wrote at least this factor as many 40 * bytes from/to than we wrote or read to/from as mostly reading or 41 * writing. */ 42 #define BIDI_FACTOR 10 43 44 /** Interval length in seconds for considering read and written bytes for 45 * connection stats. */ 46 #define BIDI_INTERVAL 10 47 48 /** Start of next BIDI_INTERVAL second interval. */ 49 static time_t bidi_next_interval = 0; 50 51 /** A single grouped set of connection type counts. */ 52 typedef struct conn_counts_t { 53 /** Number of connections that we read and wrote less than BIDI_THRESHOLD 54 * bytes from/to in BIDI_INTERVAL seconds. */ 55 uint32_t below_threshold; 56 57 /** Number of connections that we read at least BIDI_FACTOR times more 58 * bytes from than we wrote to in BIDI_INTERVAL seconds. */ 59 uint32_t mostly_read; 60 61 /** Number of connections that we wrote at least BIDI_FACTOR times more 62 * bytes to than we read from in BIDI_INTERVAL seconds. */ 63 uint32_t mostly_written; 64 65 /** Number of connections that we read and wrote at least BIDI_THRESHOLD 66 * bytes from/to, but not BIDI_FACTOR times more in either direction in 67 * BIDI_INTERVAL seconds. */ 68 uint32_t both_read_and_written; 69 } conn_counts_t ; 70 71 /** A collection of connection counts, over all OR connections. */ 72 static conn_counts_t counts; 73 /** A collection of connection counts, over IPv6 OR connections only. */ 74 static conn_counts_t counts_ipv6; 75 76 /** Entry in a map from connection ID to the number of read and written 77 * bytes on this connection in a BIDI_INTERVAL second interval. */ 78 typedef struct bidi_map_entry_t { 79 HT_ENTRY(bidi_map_entry_t) node; 80 uint64_t conn_id; /**< Connection ID */ 81 size_t read; /**< Number of read bytes */ 82 size_t written; /**< Number of written bytes */ 83 bool is_ipv6; /**< True if this is an IPv6 connection */ 84 } bidi_map_entry_t; 85 86 /** Map of OR connections together with the number of read and written 87 * bytes in the current BIDI_INTERVAL second interval. */ 88 static HT_HEAD(bidimap, bidi_map_entry_t) bidi_map = 89 HT_INITIALIZER(); 90 91 /** Hashtable helper: return true if @a a and @a b have the same key. */ 92 static int 93 bidi_map_ent_eq(const bidi_map_entry_t *a, const bidi_map_entry_t *b) 94 { 95 return a->conn_id == b->conn_id; 96 } 97 98 /** Hashtable helper: compute a digest for the key of @a entry. */ 99 static unsigned 100 bidi_map_ent_hash(const bidi_map_entry_t *entry) 101 { 102 return (unsigned) entry->conn_id; 103 } 104 105 HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, 106 bidi_map_ent_eq); 107 HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, 108 bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_); 109 110 /** Release all storage held in connstats.c */ 111 void 112 conn_stats_free_all(void) 113 { 114 bidi_map_entry_t **ptr, **next, *ent; 115 for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) { 116 ent = *ptr; 117 next = HT_NEXT_RMV(bidimap, &bidi_map, ptr); 118 tor_free(ent); 119 } 120 HT_CLEAR(bidimap, &bidi_map); 121 } 122 123 /** Reset counters for conn statistics. */ 124 void 125 conn_stats_reset(time_t now) 126 { 127 start_of_conn_stats_interval = now; 128 memset(&counts, 0, sizeof(counts)); 129 memset(&counts_ipv6, 0, sizeof(counts_ipv6)); 130 conn_stats_free_all(); 131 } 132 133 /** Stop collecting connection stats in a way that we can re-start doing 134 * so in conn_stats_init(). */ 135 void 136 conn_stats_terminate(void) 137 { 138 conn_stats_reset(0); 139 } 140 141 /** 142 * Record a single entry @a ent in the counts structure @a cnt. 143 */ 144 static void 145 add_entry_to_count(conn_counts_t *cnt, const bidi_map_entry_t *ent) 146 { 147 if (ent->read + ent->written < BIDI_THRESHOLD) 148 cnt->below_threshold++; 149 else if (ent->read >= ent->written * BIDI_FACTOR) 150 cnt->mostly_read++; 151 else if (ent->written >= ent->read * BIDI_FACTOR) 152 cnt->mostly_written++; 153 else 154 cnt->both_read_and_written++; 155 } 156 157 /** 158 * Count all the connection information we've received during the current 159 * period in 'bidimap', and store that information in the appropriate count 160 * structures. 161 **/ 162 static void 163 collect_period_statistics(void) 164 { 165 bidi_map_entry_t **ptr, **next, *ent; 166 for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) { 167 ent = *ptr; 168 add_entry_to_count(&counts, ent); 169 if (ent->is_ipv6) 170 add_entry_to_count(&counts_ipv6, ent); 171 next = HT_NEXT_RMV(bidimap, &bidi_map, ptr); 172 tor_free(ent); 173 } 174 log_info(LD_GENERAL, "%d below threshold, %d mostly read, " 175 "%d mostly written, %d both read and written.", 176 counts.below_threshold, counts.mostly_read, counts.mostly_written, 177 counts.both_read_and_written); 178 } 179 180 /** We read <b>num_read</b> bytes and wrote <b>num_written</b> from/to OR 181 * connection <b>conn_id</b> in second <b>when</b>. If this is the first 182 * observation in a new interval, sum up the last observations. Add bytes 183 * for this connection. */ 184 void 185 conn_stats_note_or_conn_bytes(uint64_t conn_id, size_t num_read, 186 size_t num_written, time_t when, 187 bool is_ipv6) 188 { 189 if (!start_of_conn_stats_interval) 190 return; 191 /* Initialize */ 192 if (bidi_next_interval == 0) 193 bidi_next_interval = when + BIDI_INTERVAL; 194 /* Sum up last period's statistics */ 195 if (when >= bidi_next_interval) { 196 collect_period_statistics(); 197 while (when >= bidi_next_interval) 198 bidi_next_interval += BIDI_INTERVAL; 199 } 200 /* Add this connection's bytes. */ 201 if (num_read > 0 || num_written > 0) { 202 bidi_map_entry_t *entry, lookup; 203 lookup.conn_id = conn_id; 204 entry = HT_FIND(bidimap, &bidi_map, &lookup); 205 if (entry) { 206 entry->written += num_written; 207 entry->read += num_read; 208 entry->is_ipv6 |= is_ipv6; 209 } else { 210 entry = tor_malloc_zero(sizeof(bidi_map_entry_t)); 211 entry->conn_id = conn_id; 212 entry->written = num_written; 213 entry->read = num_read; 214 entry->is_ipv6 = is_ipv6; 215 HT_INSERT(bidimap, &bidi_map, entry); 216 } 217 } 218 } 219 220 /** Return a newly allocated string containing the connection statistics 221 * until <b>now</b>, or NULL if we're not collecting conn stats. Caller must 222 * ensure start_of_conn_stats_interval is in the past. */ 223 char * 224 conn_stats_format(time_t now) 225 { 226 char *result, written_at[ISO_TIME_LEN+1]; 227 228 if (!start_of_conn_stats_interval) 229 return NULL; /* Not initialized. */ 230 231 tor_assert(now >= start_of_conn_stats_interval); 232 233 format_iso_time(written_at, now); 234 tor_asprintf(&result, 235 "conn-bi-direct %s (%d s) " 236 "%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n" 237 "ipv6-conn-bi-direct %s (%d s) " 238 "%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n", 239 written_at, 240 (unsigned) (now - start_of_conn_stats_interval), 241 counts.below_threshold, 242 counts.mostly_read, 243 counts.mostly_written, 244 counts.both_read_and_written, 245 written_at, 246 (unsigned) (now - start_of_conn_stats_interval), 247 counts_ipv6.below_threshold, 248 counts_ipv6.mostly_read, 249 counts_ipv6.mostly_written, 250 counts_ipv6.both_read_and_written); 251 252 return result; 253 } 254 255 /** If 24 hours have passed since the beginning of the current conn stats 256 * period, write conn stats to $DATADIR/stats/conn-stats (possibly 257 * overwriting an existing file) and reset counters. Return when we would 258 * next want to write conn stats or 0 if we never want to write. */ 259 time_t 260 conn_stats_save(time_t now) 261 { 262 char *str = NULL; 263 264 if (!start_of_conn_stats_interval) 265 return 0; /* Not initialized. */ 266 if (start_of_conn_stats_interval + WRITE_STATS_INTERVAL > now) 267 goto done; /* Not ready to write */ 268 269 /* Generate history string. */ 270 str = conn_stats_format(now); 271 272 /* Reset counters. */ 273 conn_stats_reset(now); 274 275 /* Try to write to disk. */ 276 if (!check_or_create_data_subdir("stats")) { 277 write_to_data_subdir("stats", "conn-stats", str, "connection statistics"); 278 } 279 280 done: 281 tor_free(str); 282 return start_of_conn_stats_interval + WRITE_STATS_INTERVAL; 283 }