Tor 0.4.9.0-alpha-dev
bwhist.c
Go to the documentation of this file.
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 bwhist.c
9 * @brief Tracking for relay bandwidth history
10 *
11 * This module handles bandwidth usage history, used by relays to
12 * self-report how much bandwidth they've used for different
13 * purposes over last day or so, in order to generate the
14 * {dirreq-,}{read,write}-history lines in that they publish.
15 **/
16
17#define BWHIST_PRIVATE
18#include "orconfig.h"
19#include "core/or/or.h"
21
22#include "app/config/config.h"
25
29
30/** Shift the current period of b forward by one. */
31STATIC void
33{
34 /* Store total from current period. */
36 /* Store maximum from current period. */
37 b->maxima[b->next_max_idx++] = b->max_total;
38 /* Advance next_period and next_max_idx */
40 if (b->next_max_idx == NUM_TOTALS)
41 b->next_max_idx = 0;
43 ++b->num_maxes_set;
44 /* Reset max_total. */
45 b->max_total = 0;
46 /* Reset total_in_period. */
47 b->total_in_period = 0;
48}
49
50/** Shift the current observation time of <b>b</b> forward by one second. */
51STATIC void
53{
54 int nextidx;
55 uint64_t total;
56
57 /* Calculate the total bandwidth for the last NUM_SECS_ROLLING_MEASURE
58 * seconds; adjust max_total as needed.*/
59 total = b->total_obs + b->obs[b->cur_obs_idx];
60 if (total > b->max_total)
61 b->max_total = total;
62
63 nextidx = b->cur_obs_idx+1;
64 if (nextidx == NUM_SECS_ROLLING_MEASURE)
65 nextidx = 0;
66
67 b->total_obs = total - b->obs[nextidx];
68 b->obs[nextidx]=0;
69 b->cur_obs_idx = nextidx;
70
71 if (++b->cur_obs_time >= b->next_period)
72 commit_max(b);
73}
74
75/** Add <b>n</b> bytes to the number of bytes in <b>b</b> for second
76 * <b>when</b>. */
77STATIC void
78add_obs(bw_array_t *b, time_t when, uint64_t n)
79{
80 if (when < b->cur_obs_time)
81 return; /* Don't record data in the past. */
82
83 /* If we're currently adding observations for an earlier second than
84 * 'when', advance b->cur_obs_time and b->cur_obs_idx by an
85 * appropriate number of seconds, and do all the other housekeeping. */
86 while (when > b->cur_obs_time) {
87 /* Doing this one second at a time is potentially inefficient, if we start
88 with a state file that is very old. Fortunately, it doesn't seem to
89 show up in profiles, so we can just ignore it for now. */
90 advance_obs(b);
91 }
92
93 b->obs[b->cur_obs_idx] += n;
94 b->total_in_period += n;
95}
96
97/** Allocate, initialize, and return a new bw_array. */
100{
101 bw_array_t *b;
102 time_t start;
103 b = tor_malloc_zero(sizeof(bw_array_t));
104 start = time(NULL);
105 b->cur_obs_time = start;
107 return b;
108}
109
110/** Free storage held by bandwidth array <b>b</b>. */
111STATIC void
113{
114 if (!b) {
115 return;
116 }
117
118 tor_free(b);
119}
120
121/** Recent history of bandwidth observations for (all) read operations. */
122static bw_array_t *read_array = NULL;
123/** Recent history of bandwidth observations for IPv6 read operations. */
125/** Recent history of bandwidth observations for (all) write operations. */
127/** Recent history of bandwidth observations for IPv6 write operations. */
129/** Recent history of bandwidth observations for read operations for the
130 directory protocol. */
132/** Recent history of bandwidth observations for write operations for the
133 directory protocol. */
135
136/** Set up structures for bandwidth history, clearing them if they already
137 * exist. */
138void
140{
141 bw_array_free(read_array);
142 bw_array_free(read_array_ipv6);
143 bw_array_free(write_array);
144 bw_array_free(write_array_ipv6);
145 bw_array_free(dir_read_array);
146 bw_array_free(dir_write_array);
147
154}
155
156/** Remember that we read <b>num_bytes</b> bytes in second <b>when</b>.
157 *
158 * Add num_bytes to the current running total for <b>when</b>.
159 *
160 * <b>when</b> can go back to time, but it's safe to ignore calls
161 * earlier than the latest <b>when</b> you've heard of.
162 */
163void
164bwhist_note_bytes_written(uint64_t num_bytes, time_t when, bool ipv6)
165{
166/* Maybe a circular array for recent seconds, and step to a new point
167 * every time a new second shows up. Or simpler is to just to have
168 * a normal array and push down each item every second; it's short.
169 */
170/* When a new second has rolled over, compute the sum of the bytes we've
171 * seen over when-1 to when-1-NUM_SECS_ROLLING_MEASURE, and stick it
172 * somewhere. See bwhist_bandwidth_assess() below.
173 */
174 add_obs(write_array, when, num_bytes);
175 if (ipv6)
176 add_obs(write_array_ipv6, when, num_bytes);
177}
178
179/** Remember that we wrote <b>num_bytes</b> bytes in second <b>when</b>.
180 * (like bwhist_note_bytes_written() above)
181 */
182void
183bwhist_note_bytes_read(uint64_t num_bytes, time_t when, bool ipv6)
184{
185/* if we're smart, we can make this func and the one above share code */
186 add_obs(read_array, when, num_bytes);
187 if (ipv6)
188 add_obs(read_array_ipv6, when, num_bytes);
189}
190
191/** Remember that we wrote <b>num_bytes</b> directory bytes in second
192 * <b>when</b>. (like bwhist_note_bytes_written() above)
193 */
194void
195bwhist_note_dir_bytes_written(uint64_t num_bytes, time_t when)
196{
197 add_obs(dir_write_array, when, num_bytes);
198}
199
200/** Remember that we read <b>num_bytes</b> directory bytes in second
201 * <b>when</b>. (like bwhist_note_bytes_written() above)
202 */
203void
204bwhist_note_dir_bytes_read(uint64_t num_bytes, time_t when)
205{
206 add_obs(dir_read_array, when, num_bytes);
207}
208
209/**
210 * Helper: Return the largest value in b->maxima. (This is equal to the
211 * most bandwidth used in any NUM_SECS_ROLLING_MEASURE period for the last
212 * NUM_SECS_BW_SUM_IS_VALID seconds.)
213 *
214 * Also include the current period if we have been observing it for
215 * at least min_observation_time seconds.
216 */
217STATIC uint64_t
218find_largest_max(bw_array_t *b, int min_observation_time)
219{
220 int i;
221 uint64_t max;
222 time_t period_start = b->next_period - NUM_SECS_BW_SUM_INTERVAL;
223 if (b->cur_obs_time > period_start + min_observation_time)
224 max = b->max_total;
225 else
226 max = 0;
227 for (i=0; i<NUM_TOTALS; ++i) {
228 if (b->maxima[i]>max)
229 max = b->maxima[i];
230 }
231 return max;
232}
233
234/** Find the largest sums in the past NUM_SECS_BW_SUM_IS_VALID (roughly)
235 * seconds. Find one sum for reading and one for writing. They don't have
236 * to be at the same time.
237 *
238 * Return the smaller of these sums, divided by NUM_SECS_ROLLING_MEASURE.
239 */
240MOCK_IMPL(int,
242{
243 uint64_t w,r;
244 int min_obs_time = get_options()->TestingMinTimeToReportBandwidth;
245 r = find_largest_max(read_array, min_obs_time);
246 w = find_largest_max(write_array, min_obs_time);
247 if (r>w)
248 return (int)(((double)w)/NUM_SECS_ROLLING_MEASURE);
249 else
250 return (int)(((double)r)/NUM_SECS_ROLLING_MEASURE);
251}
252
253/** Print the bandwidth history of b (either [dir-]read_array or
254 * [dir-]write_array) into the buffer pointed to by buf. The format is
255 * simply comma separated numbers, from oldest to newest.
256 *
257 * It returns the number of bytes written.
258 */
259STATIC size_t
260bwhist_fill_bandwidth_history(char *buf, size_t len, const bw_array_t *b)
261{
262 char *cp = buf;
263 int i, n;
264 const or_options_t *options = get_options();
265 uint64_t cutoff;
266
267 if (b->num_maxes_set <= b->next_max_idx) {
268 /* We haven't been through the circular array yet; time starts at i=0.*/
269 i = 0;
270 } else {
271 /* We've been around the array at least once. The next i to be
272 overwritten is the oldest. */
273 i = b->next_max_idx;
274 }
275
276 if (options->RelayBandwidthRate) {
277 /* We don't want to report that we used more bandwidth than the max we're
278 * willing to relay; otherwise everybody will know how much traffic
279 * we used ourself. */
281 } else {
282 cutoff = UINT64_MAX;
283 }
284
285 for (n=0; n<b->num_maxes_set; ++n,++i) {
286 uint64_t total;
287 if (i >= NUM_TOTALS)
288 i -= NUM_TOTALS;
290 /* Round the bandwidth used down to the nearest 1k. */
291 total = b->totals[i] & ~0x3ff;
292 if (total > cutoff)
293 total = cutoff;
294
295 if (n==(b->num_maxes_set-1))
296 tor_snprintf(cp, len-(cp-buf), "%"PRIu64, (total));
297 else
298 tor_snprintf(cp, len-(cp-buf), "%"PRIu64",", (total));
299 cp += strlen(cp);
300 }
301 return cp-buf;
302}
303
304/** Encode a single bandwidth history line into <b>buf</b>. */
305static void
306bwhist_get_one_bandwidth_line(buf_t *buf, const char *desc,
307 const bw_array_t *b)
308{
309 /* [dirreq-](read|write)-history yyyy-mm-dd HH:MM:SS (n s) n,n,n... */
310 /* The n,n,n part above. Largest representation of a uint64_t is 20 chars
311 * long, plus the comma. */
312#define MAX_HIST_VALUE_LEN (21*NUM_TOTALS)
313
314 char tmp[MAX_HIST_VALUE_LEN];
315 char end[ISO_TIME_LEN+1];
316
317 size_t slen = bwhist_fill_bandwidth_history(tmp, MAX_HIST_VALUE_LEN, b);
318 /* If we don't have anything to write, skip to the next entry. */
319 if (slen == 0)
320 return;
321
323 buf_add_printf(buf, "%s %s (%d s) %s\n",
324 desc, end, NUM_SECS_BW_SUM_INTERVAL, tmp);
325}
326
327/** Allocate and return lines for representing this server's bandwidth
328 * history in its descriptor. We publish these lines in our extra-info
329 * descriptor.
330 */
331char *
333{
334 buf_t *buf = buf_new();
335
336 bwhist_get_one_bandwidth_line(buf, "write-history", write_array);
337 bwhist_get_one_bandwidth_line(buf, "read-history", read_array);
338 bwhist_get_one_bandwidth_line(buf, "ipv6-write-history", write_array_ipv6);
339 bwhist_get_one_bandwidth_line(buf, "ipv6-read-history", read_array_ipv6);
340 bwhist_get_one_bandwidth_line(buf, "dirreq-write-history", dir_write_array);
341 bwhist_get_one_bandwidth_line(buf, "dirreq-read-history", dir_read_array);
342
343 char *result = buf_extract(buf, NULL);
344 buf_free(buf);
345 return result;
346}
347
348/** Write a single bw_array_t into the Values, Ends, Interval, and Maximum
349 * entries of an or_state_t. Done before writing out a new state file. */
350static void
352 const bw_array_t *b,
353 smartlist_t **s_values,
354 smartlist_t **s_maxima,
355 time_t *s_begins,
356 int *s_interval)
357{
358 int i,j;
359 uint64_t maxval;
360
361 if (*s_values) {
362 SMARTLIST_FOREACH(*s_values, char *, val, tor_free(val));
363 smartlist_free(*s_values);
364 }
365 if (*s_maxima) {
366 SMARTLIST_FOREACH(*s_maxima, char *, val, tor_free(val));
367 smartlist_free(*s_maxima);
368 }
369 if (! server_mode(get_options())) {
370 /* Clients don't need to store bandwidth history persistently;
371 * force these values to the defaults. */
372 /* FFFF we should pull the default out of config.c's state table,
373 * so we don't have two defaults. */
374 if (*s_begins != 0 || *s_interval != 900) {
375 time_t now = time(NULL);
376 time_t save_at = get_options()->AvoidDiskWrites ? now+3600 : now+600;
377 or_state_mark_dirty(state, save_at);
378 }
379 *s_begins = 0;
380 *s_interval = 900;
381 *s_values = smartlist_new();
382 *s_maxima = smartlist_new();
383 return;
384 }
385 *s_begins = b->next_period;
386 *s_interval = NUM_SECS_BW_SUM_INTERVAL;
387
388 *s_values = smartlist_new();
389 *s_maxima = smartlist_new();
390 /* Set i to first position in circular array */
391 i = (b->num_maxes_set <= b->next_max_idx) ? 0 : b->next_max_idx;
392 for (j=0; j < b->num_maxes_set; ++j,++i) {
393 if (i >= NUM_TOTALS)
394 i = 0;
395 smartlist_add_asprintf(*s_values, "%"PRIu64,
396 (b->totals[i] & ~0x3ff));
397 maxval = b->maxima[i] / NUM_SECS_ROLLING_MEASURE;
398 smartlist_add_asprintf(*s_maxima, "%"PRIu64,
399 (maxval & ~0x3ff));
400 }
401 smartlist_add_asprintf(*s_values, "%"PRIu64,
402 (b->total_in_period & ~0x3ff));
404 smartlist_add_asprintf(*s_maxima, "%"PRIu64,
405 (maxval & ~0x3ff));
406}
407
408/** Update <b>state</b> with the newest bandwidth history. Done before
409 * writing out a new state file. */
410void
412{
413#define UPDATE(arrname,st) \
414 bwhist_update_bwhist_state_section(state,\
415 (arrname),\
416 &state->BWHistory ## st ## Values, \
417 &state->BWHistory ## st ## Maxima, \
418 &state->BWHistory ## st ## Ends, \
419 &state->BWHistory ## st ## Interval)
420
421 UPDATE(write_array, Write);
422 UPDATE(read_array, Read);
423 UPDATE(write_array_ipv6, IPv6Write);
424 UPDATE(read_array_ipv6, IPv6Read);
425 UPDATE(dir_write_array, DirWrite);
426 UPDATE(dir_read_array, DirRead);
427
428 if (server_mode(get_options())) {
429 or_state_mark_dirty(state, time(NULL)+(2*3600));
430 }
431#undef UPDATE
432}
433
434/** Load a single bw_array_t from its Values, Ends, Maxima, and Interval
435 * entries in an or_state_t. Done while reading the state file. */
436static int
438 const smartlist_t *s_values,
439 const smartlist_t *s_maxima,
440 const time_t s_begins,
441 const int s_interval)
442{
443 time_t now = time(NULL);
444 int retval = 0;
445 time_t start;
446
447 uint64_t v, mv;
448 int i,ok,ok_m = 0;
449 int have_maxima = s_maxima && s_values &&
450 (smartlist_len(s_values) == smartlist_len(s_maxima));
451
452 if (s_values && s_begins >= now - NUM_SECS_BW_SUM_INTERVAL*NUM_TOTALS) {
453 start = s_begins - s_interval*(smartlist_len(s_values));
454 if (start > now)
455 return 0;
456 b->cur_obs_time = start;
458 SMARTLIST_FOREACH_BEGIN(s_values, const char *, cp) {
459 const char *maxstr = NULL;
460 v = tor_parse_uint64(cp, 10, 0, UINT64_MAX, &ok, NULL);
461 if (have_maxima) {
462 maxstr = smartlist_get(s_maxima, cp_sl_idx);
463 mv = tor_parse_uint64(maxstr, 10, 0, UINT64_MAX, &ok_m, NULL);
465 } else {
466 /* No maxima known; guess average rate to be conservative. */
467 mv = (v / s_interval) * NUM_SECS_ROLLING_MEASURE;
468 }
469 if (!ok) {
470 retval = -1;
471 log_notice(LD_HIST, "Could not parse value '%s' into a number.'",cp);
472 }
473 if (maxstr && !ok_m) {
474 retval = -1;
475 log_notice(LD_HIST, "Could not parse maximum '%s' into a number.'",
476 maxstr);
477 }
478
479 if (start < now) {
480 time_t cur_start = start;
481 time_t actual_interval_len = s_interval;
482 uint64_t cur_val = 0;
483 /* Calculate the average per second. This is the best we can do
484 * because our state file doesn't have per-second resolution. */
485 if (start + s_interval > now)
486 actual_interval_len = now - start;
487 cur_val = v / actual_interval_len;
488 /* This is potentially inefficient, but since we don't do it very
489 * often it should be ok. */
490 while (cur_start < start + actual_interval_len) {
491 add_obs(b, cur_start, cur_val);
492 ++cur_start;
493 }
494 b->max_total = mv;
495 /* This will result in some fairly choppy history if s_interval
496 * is not the same as NUM_SECS_BW_SUM_INTERVAL. XXXX */
497 start += actual_interval_len;
498 }
499 } SMARTLIST_FOREACH_END(cp);
500 }
501
502 /* Clean up maxima and observed */
503 for (i=0; i<NUM_SECS_ROLLING_MEASURE; ++i) {
504 b->obs[i] = 0;
505 }
506 b->total_obs = 0;
507
508 return retval;
509}
510
511/** Set bandwidth history from the state file we just loaded. */
512int
513bwhist_load_state(or_state_t *state, char **err)
514{
515 int all_ok = 1;
516
517 /* Assert they already have been malloced */
521
522#define LOAD(arrname,st) \
523 if (bwhist_load_bwhist_state_section( \
524 (arrname), \
525 state->BWHistory ## st ## Values, \
526 state->BWHistory ## st ## Maxima, \
527 state->BWHistory ## st ## Ends, \
528 state->BWHistory ## st ## Interval)<0) \
529 all_ok = 0
530
531 LOAD(write_array, Write);
532 LOAD(read_array, Read);
533 LOAD(write_array_ipv6, IPv6Write);
534 LOAD(read_array_ipv6, IPv6Read);
535 LOAD(dir_write_array, DirWrite);
536 LOAD(dir_read_array, DirRead);
537
538#undef LOAD
539 if (!all_ok) {
540 *err = tor_strdup("Parsing of bandwidth history values failed");
541 /* and create fresh arrays */
542 bwhist_init();
543 return -1;
544 }
545 return 0;
546}
547
548void
549bwhist_free_all(void)
550{
551 bw_array_free(read_array);
552 bw_array_free(read_array_ipv6);
553 bw_array_free(write_array);
554 bw_array_free(write_array_ipv6);
555 bw_array_free(dir_read_array);
556 bw_array_free(dir_write_array);
557}
void buf_add_printf(buf_t *buf, const char *format,...)
Definition: buffers.c:568
buf_t * buf_new(void)
Definition: buffers.c:365
char * buf_extract(buf_t *buf, size_t *sz_out)
Definition: buffers.c:592
Declaration for bw_array_t structure and related constants.
#define NUM_SECS_BW_SUM_INTERVAL
Definition: bw_array_st.h:19
#define NUM_SECS_ROLLING_MEASURE
Definition: bw_array_st.h:17
#define NUM_TOTALS
Definition: bw_array_st.h:23
void bwhist_note_dir_bytes_written(uint64_t num_bytes, time_t when)
Definition: bwhist.c:195
STATIC void commit_max(bw_array_t *b)
Definition: bwhist.c:32
static bw_array_t * write_array_ipv6
Definition: bwhist.c:128
static int bwhist_load_bwhist_state_section(bw_array_t *b, const smartlist_t *s_values, const smartlist_t *s_maxima, const time_t s_begins, const int s_interval)
Definition: bwhist.c:437
STATIC void bw_array_free_(bw_array_t *b)
Definition: bwhist.c:112
void bwhist_init(void)
Definition: bwhist.c:139
static bw_array_t * dir_read_array
Definition: bwhist.c:131
void bwhist_update_state(or_state_t *state)
Definition: bwhist.c:411
STATIC bw_array_t * write_array
Definition: bwhist.c:126
void bwhist_note_bytes_read(uint64_t num_bytes, time_t when, bool ipv6)
Definition: bwhist.c:183
int bwhist_load_state(or_state_t *state, char **err)
Definition: bwhist.c:513
static bw_array_t * dir_write_array
Definition: bwhist.c:134
static bw_array_t * read_array
Definition: bwhist.c:122
void bwhist_note_dir_bytes_read(uint64_t num_bytes, time_t when)
Definition: bwhist.c:204
void bwhist_note_bytes_written(uint64_t num_bytes, time_t when, bool ipv6)
Definition: bwhist.c:164
static void bwhist_update_bwhist_state_section(or_state_t *state, const bw_array_t *b, smartlist_t **s_values, smartlist_t **s_maxima, time_t *s_begins, int *s_interval)
Definition: bwhist.c:351
STATIC size_t bwhist_fill_bandwidth_history(char *buf, size_t len, const bw_array_t *b)
Definition: bwhist.c:260
static void bwhist_get_one_bandwidth_line(buf_t *buf, const char *desc, const bw_array_t *b)
Definition: bwhist.c:306
static bw_array_t * read_array_ipv6
Definition: bwhist.c:124
int bwhist_bandwidth_assess(void)
Definition: bwhist.c:241
STATIC void advance_obs(bw_array_t *b)
Definition: bwhist.c:52
STATIC bw_array_t * bw_array_new(void)
Definition: bwhist.c:99
char * bwhist_get_bandwidth_lines(void)
Definition: bwhist.c:332
STATIC void add_obs(bw_array_t *b, time_t when, uint64_t n)
Definition: bwhist.c:78
STATIC uint64_t find_largest_max(bw_array_t *b, int min_observation_time)
Definition: bwhist.c:218
Header for feature/stats/bwhist.c.
const or_options_t * get_options(void)
Definition: config.c:944
Header file for config.c.
#define LD_HIST
Definition: log.h:99
#define tor_free(p)
Definition: malloc.h:56
Master header file for Tor-specific functionality.
The or_options_t structure, which represents Tor's configuration.
The or_state_t structure, which represents Tor's state file.
uint64_t tor_parse_uint64(const char *s, int base, uint64_t min, uint64_t max, int *ok, char **next)
Definition: parse_int.c:110
int tor_snprintf(char *str, size_t size, const char *format,...)
Definition: printf.c:27
int server_mode(const or_options_t *options)
Definition: routermode.c:34
Header file for routermode.c.
void smartlist_add_asprintf(struct smartlist_t *sl, const char *pattern,...)
Definition: smartlist.c:36
smartlist_t * smartlist_new(void)
#define SMARTLIST_FOREACH_BEGIN(sl, type, var)
#define SMARTLIST_FOREACH(sl, type, var, cmd)
void or_state_mark_dirty(or_state_t *state, time_t when)
Definition: statefile.c:784
Header for statefile.c.
uint64_t totals[NUM_TOTALS]
Definition: bw_array_st.h:54
int num_maxes_set
Definition: bw_array_st.h:47
uint64_t obs[NUM_SECS_ROLLING_MEASURE]
Definition: bw_array_st.h:31
int cur_obs_idx
Definition: bw_array_st.h:32
uint64_t maxima[NUM_TOTALS]
Definition: bw_array_st.h:51
int next_max_idx
Definition: bw_array_st.h:45
time_t next_period
Definition: bw_array_st.h:42
uint64_t max_total
Definition: bw_array_st.h:36
uint64_t total_obs
Definition: bw_array_st.h:34
time_t cur_obs_time
Definition: bw_array_st.h:33
uint64_t total_in_period
Definition: bw_array_st.h:38
int TestingMinTimeToReportBandwidth
uint64_t RelayBandwidthRate
#define STATIC
Definition: testsupport.h:32
#define MOCK_IMPL(rv, funcname, arglist)
Definition: testsupport.h:133
void format_iso_time(char *buf, time_t t)
Definition: time_fmt.c:326
#define tor_assert(expr)
Definition: util_bug.h:103