Line data Source code
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 2 : conn_stats_init(time_t now)
31 : {
32 2 : start_of_conn_stats_interval = now;
33 2 : }
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 4 : bidi_map_ent_eq(const bidi_map_entry_t *a, const bidi_map_entry_t *b)
94 : {
95 4 : 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 12 : bidi_map_ent_hash(const bidi_map_entry_t *entry)
101 : {
102 12 : return (unsigned) entry->conn_id;
103 : }
104 :
105 654 : HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
106 : bidi_map_ent_eq);
107 239 : 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 237 : conn_stats_free_all(void)
113 : {
114 237 : bidi_map_entry_t **ptr, **next, *ent;
115 240 : for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) {
116 3 : ent = *ptr;
117 3 : next = HT_NEXT_RMV(bidimap, &bidi_map, ptr);
118 3 : tor_free(ent);
119 : }
120 237 : HT_CLEAR(bidimap, &bidi_map);
121 237 : }
122 :
123 : /** Reset counters for conn statistics. */
124 : void
125 2 : conn_stats_reset(time_t now)
126 : {
127 2 : start_of_conn_stats_interval = now;
128 2 : memset(&counts, 0, sizeof(counts));
129 2 : memset(&counts_ipv6, 0, sizeof(counts_ipv6));
130 2 : conn_stats_free_all();
131 2 : }
132 :
133 : /** Stop collecting connection stats in a way that we can re-start doing
134 : * so in conn_stats_init(). */
135 : void
136 1 : conn_stats_terminate(void)
137 : {
138 1 : conn_stats_reset(0);
139 1 : }
140 :
141 : /**
142 : * Record a single entry @a ent in the counts structure @a cnt.
143 : */
144 : static void
145 1 : add_entry_to_count(conn_counts_t *cnt, const bidi_map_entry_t *ent)
146 : {
147 1 : if (ent->read + ent->written < BIDI_THRESHOLD)
148 0 : cnt->below_threshold++;
149 1 : else if (ent->read >= ent->written * BIDI_FACTOR)
150 0 : cnt->mostly_read++;
151 1 : else if (ent->written >= ent->read * BIDI_FACTOR)
152 1 : cnt->mostly_written++;
153 : else
154 0 : cnt->both_read_and_written++;
155 1 : }
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 1 : collect_period_statistics(void)
164 : {
165 1 : bidi_map_entry_t **ptr, **next, *ent;
166 2 : for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) {
167 1 : ent = *ptr;
168 1 : add_entry_to_count(&counts, ent);
169 1 : if (ent->is_ipv6)
170 0 : add_entry_to_count(&counts_ipv6, ent);
171 1 : next = HT_NEXT_RMV(bidimap, &bidi_map, ptr);
172 1 : tor_free(ent);
173 : }
174 1 : 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 1 : }
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 10 : 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 10 : if (!start_of_conn_stats_interval)
190 : return;
191 : /* Initialize */
192 8 : if (bidi_next_interval == 0)
193 1 : bidi_next_interval = when + BIDI_INTERVAL;
194 : /* Sum up last period's statistics */
195 8 : if (when >= bidi_next_interval) {
196 1 : collect_period_statistics();
197 2 : while (when >= bidi_next_interval)
198 1 : bidi_next_interval += BIDI_INTERVAL;
199 : }
200 : /* Add this connection's bytes. */
201 8 : if (num_read > 0 || num_written > 0) {
202 8 : bidi_map_entry_t *entry, lookup;
203 8 : lookup.conn_id = conn_id;
204 8 : entry = HT_FIND(bidimap, &bidi_map, &lookup);
205 8 : if (entry) {
206 4 : entry->written += num_written;
207 4 : entry->read += num_read;
208 4 : entry->is_ipv6 |= is_ipv6;
209 : } else {
210 4 : entry = tor_malloc_zero(sizeof(bidi_map_entry_t));
211 4 : entry->conn_id = conn_id;
212 4 : entry->written = num_written;
213 4 : entry->read = num_read;
214 4 : entry->is_ipv6 = is_ipv6;
215 4 : 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 4 : conn_stats_format(time_t now)
225 : {
226 4 : char *result, written_at[ISO_TIME_LEN+1];
227 :
228 4 : if (!start_of_conn_stats_interval)
229 : return NULL; /* Not initialized. */
230 :
231 2 : tor_assert(now >= start_of_conn_stats_interval);
232 :
233 2 : format_iso_time(written_at, now);
234 2 : 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 2 : 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 0 : conn_stats_save(time_t now)
261 : {
262 0 : char *str = NULL;
263 :
264 0 : if (!start_of_conn_stats_interval)
265 : return 0; /* Not initialized. */
266 0 : if (start_of_conn_stats_interval + WRITE_STATS_INTERVAL > now)
267 0 : goto done; /* Not ready to write */
268 :
269 : /* Generate history string. */
270 0 : str = conn_stats_format(now);
271 :
272 : /* Reset counters. */
273 0 : conn_stats_reset(now);
274 :
275 : /* Try to write to disk. */
276 0 : if (!check_or_create_data_subdir("stats")) {
277 0 : write_to_data_subdir("stats", "conn-stats", str, "connection statistics");
278 : }
279 :
280 0 : done:
281 0 : tor_free(str);
282 0 : return start_of_conn_stats_interval + WRITE_STATS_INTERVAL;
283 : }
|