Coverage Report

Created: 2026-05-30 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tor/src/feature/stats/bwhist.c
Line
Count
Source
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"
20
#include "feature/stats/bwhist.h"
21
22
#include "app/config/config.h"
23
#include "app/config/statefile.h"
24
#include "feature/relay/routermode.h"
25
26
#include "feature/stats/bw_array_st.h"
27
#include "app/config/or_state_st.h"
28
#include "app/config/or_options_st.h"
29
30
/** Shift the current period of b forward by one. */
31
STATIC void
32
commit_max(bw_array_t *b)
33
0
{
34
  /* Store total from current period. */
35
0
  b->totals[b->next_max_idx] = b->total_in_period;
36
  /* Store maximum from current period. */
37
0
  b->maxima[b->next_max_idx++] = b->max_total;
38
  /* Advance next_period and next_max_idx */
39
0
  b->next_period += NUM_SECS_BW_SUM_INTERVAL;
40
0
  if (b->next_max_idx == NUM_TOTALS)
41
0
    b->next_max_idx = 0;
42
0
  if (b->num_maxes_set < NUM_TOTALS)
43
0
    ++b->num_maxes_set;
44
  /* Reset max_total. */
45
0
  b->max_total = 0;
46
  /* Reset total_in_period. */
47
0
  b->total_in_period = 0;
48
0
}
49
50
/** Shift the current observation time of <b>b</b> forward by one second. */
51
STATIC void
52
advance_obs(bw_array_t *b)
53
0
{
54
0
  int nextidx;
55
0
  uint64_t total;
56
57
  /* Calculate the total bandwidth for the last NUM_SECS_ROLLING_MEASURE
58
   * seconds; adjust max_total as needed.*/
59
0
  total = b->total_obs + b->obs[b->cur_obs_idx];
60
0
  if (total > b->max_total)
61
0
    b->max_total = total;
62
63
0
  nextidx = b->cur_obs_idx+1;
64
0
  if (nextidx == NUM_SECS_ROLLING_MEASURE)
65
0
    nextidx = 0;
66
67
0
  b->total_obs = total - b->obs[nextidx];
68
0
  b->obs[nextidx]=0;
69
0
  b->cur_obs_idx = nextidx;
70
71
0
  if (++b->cur_obs_time >= b->next_period)
72
0
    commit_max(b);
73
0
}
74
75
/** Add <b>n</b> bytes to the number of bytes in <b>b</b> for second
76
 * <b>when</b>. */
77
STATIC void
78
add_obs(bw_array_t *b, time_t when, uint64_t n)
79
0
{
80
0
  if (when < b->cur_obs_time)
81
0
    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
0
  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
0
    advance_obs(b);
91
0
  }
92
93
0
  b->obs[b->cur_obs_idx] += n;
94
0
  b->total_in_period += n;
95
0
}
96
97
/** Allocate, initialize, and return a new bw_array. */
98
STATIC bw_array_t *
99
bw_array_new(void)
100
0
{
101
0
  bw_array_t *b;
102
0
  time_t start;
103
0
  b = tor_malloc_zero(sizeof(bw_array_t));
104
0
  start = time(NULL);
105
0
  b->cur_obs_time = start;
106
0
  b->next_period = start + NUM_SECS_BW_SUM_INTERVAL;
107
0
  return b;
108
0
}
109
110
/** Free storage held by bandwidth array <b>b</b>. */
111
STATIC void
112
bw_array_free_(bw_array_t *b)
113
0
{
114
0
  if (!b) {
115
0
    return;
116
0
  }
117
118
0
  tor_free(b);
119
0
}
120
121
/** Recent history of bandwidth observations for (all) read operations. */
122
static bw_array_t *read_array = NULL;
123
/** Recent history of bandwidth observations for IPv6 read operations. */
124
static bw_array_t *read_array_ipv6 = NULL;
125
/** Recent history of bandwidth observations for (all) write operations. */
126
STATIC bw_array_t *write_array = NULL;
127
/** Recent history of bandwidth observations for IPv6 write operations. */
128
static bw_array_t *write_array_ipv6 = NULL;
129
/** Recent history of bandwidth observations for read operations for the
130
    directory protocol. */
131
static bw_array_t *dir_read_array = NULL;
132
/** Recent history of bandwidth observations for write operations for the
133
    directory protocol. */
134
static bw_array_t *dir_write_array = NULL;
135
136
/** Set up structures for bandwidth history, clearing them if they already
137
 * exist. */
138
void
139
bwhist_init(void)
140
0
{
141
0
  bw_array_free(read_array);
142
0
  bw_array_free(read_array_ipv6);
143
0
  bw_array_free(write_array);
144
0
  bw_array_free(write_array_ipv6);
145
0
  bw_array_free(dir_read_array);
146
0
  bw_array_free(dir_write_array);
147
148
0
  read_array = bw_array_new();
149
0
  read_array_ipv6 = bw_array_new();
150
0
  write_array = bw_array_new();
151
0
  write_array_ipv6 = bw_array_new();
152
0
  dir_read_array = bw_array_new();
153
0
  dir_write_array = bw_array_new();
154
0
}
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
 */
163
void
164
bwhist_note_bytes_written(uint64_t num_bytes, time_t when, bool ipv6)
165
0
{
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
0
  add_obs(write_array, when, num_bytes);
175
0
  if (ipv6)
176
0
    add_obs(write_array_ipv6, when, num_bytes);
177
0
}
178
179
/** Remember that we wrote <b>num_bytes</b> bytes in second <b>when</b>.
180
 * (like bwhist_note_bytes_written() above)
181
 */
182
void
183
bwhist_note_bytes_read(uint64_t num_bytes, time_t when, bool ipv6)
184
0
{
185
/* if we're smart, we can make this func and the one above share code */
186
0
  add_obs(read_array, when, num_bytes);
187
0
  if (ipv6)
188
0
    add_obs(read_array_ipv6, when, num_bytes);
189
0
}
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
 */
194
void
195
bwhist_note_dir_bytes_written(uint64_t num_bytes, time_t when)
196
0
{
197
0
  add_obs(dir_write_array, when, num_bytes);
198
0
}
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
 */
203
void
204
bwhist_note_dir_bytes_read(uint64_t num_bytes, time_t when)
205
0
{
206
0
  add_obs(dir_read_array, when, num_bytes);
207
0
}
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
 */
217
STATIC uint64_t
218
find_largest_max(bw_array_t *b, int min_observation_time)
219
0
{
220
0
  int i;
221
0
  uint64_t max;
222
0
  time_t period_start = b->next_period - NUM_SECS_BW_SUM_INTERVAL;
223
0
  if (b->cur_obs_time > period_start + min_observation_time)
224
0
    max = b->max_total;
225
0
  else
226
0
    max = 0;
227
0
  for (i=0; i<NUM_TOTALS; ++i) {
228
0
    if (b->maxima[i]>max)
229
0
      max = b->maxima[i];
230
0
  }
231
0
  return max;
232
0
}
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
 */
240
MOCK_IMPL(int,
241
bwhist_bandwidth_assess,(void))
242
0
{
243
0
  uint64_t w,r;
244
0
  int min_obs_time = get_options()->TestingMinTimeToReportBandwidth;
245
0
  r = find_largest_max(read_array, min_obs_time);
246
0
  w = find_largest_max(write_array, min_obs_time);
247
0
  if (r>w)
248
0
    return (int)(((double)w)/NUM_SECS_ROLLING_MEASURE);
249
0
  else
250
0
    return (int)(((double)r)/NUM_SECS_ROLLING_MEASURE);
251
0
}
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
 */
259
STATIC size_t
260
bwhist_fill_bandwidth_history(char *buf, size_t len, const bw_array_t *b)
261
0
{
262
0
  char *cp = buf;
263
0
  int i, n;
264
0
  const or_options_t *options = get_options();
265
0
  uint64_t cutoff;
266
267
0
  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
0
    i = 0;
270
0
  } else {
271
    /* We've been around the array at least once.  The next i to be
272
       overwritten is the oldest. */
273
0
    i = b->next_max_idx;
274
0
  }
275
276
0
  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. */
280
0
    cutoff = options->RelayBandwidthRate * NUM_SECS_BW_SUM_INTERVAL;
281
0
  } else {
282
0
    cutoff = UINT64_MAX;
283
0
  }
284
285
0
  for (n=0; n<b->num_maxes_set; ++n,++i) {
286
0
    uint64_t total;
287
0
    if (i >= NUM_TOTALS)
288
0
      i -= NUM_TOTALS;
289
0
    tor_assert(i < NUM_TOTALS);
290
    /* Round the bandwidth used down to the nearest 1k. */
291
0
    total = b->totals[i] & ~0x3ff;
292
0
    if (total > cutoff)
293
0
      total = cutoff;
294
295
0
    if (n==(b->num_maxes_set-1))
296
0
      tor_snprintf(cp, len-(cp-buf), "%"PRIu64, (total));
297
0
    else
298
0
      tor_snprintf(cp, len-(cp-buf), "%"PRIu64",", (total));
299
0
    cp += strlen(cp);
300
0
  }
301
0
  return cp-buf;
302
0
}
303
304
/** Encode a single bandwidth history line into <b>buf</b>. */
305
static void
306
bwhist_get_one_bandwidth_line(buf_t *buf, const char *desc,
307
                              const bw_array_t *b)
308
0
{
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
0
#define MAX_HIST_VALUE_LEN (21*NUM_TOTALS)
313
314
0
  char tmp[MAX_HIST_VALUE_LEN];
315
0
  char end[ISO_TIME_LEN+1];
316
317
0
  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
0
  if (slen == 0)
320
0
    return;
321
322
0
  format_iso_time(end, b->next_period-NUM_SECS_BW_SUM_INTERVAL);
323
0
  buf_add_printf(buf, "%s %s (%d s) %s\n",
324
0
                 desc, end, NUM_SECS_BW_SUM_INTERVAL, tmp);
325
0
}
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
 */
331
char *
332
bwhist_get_bandwidth_lines(void)
333
0
{
334
0
  buf_t *buf = buf_new();
335
336
0
  bwhist_get_one_bandwidth_line(buf, "write-history", write_array);
337
0
  bwhist_get_one_bandwidth_line(buf, "read-history", read_array);
338
0
  bwhist_get_one_bandwidth_line(buf, "ipv6-write-history", write_array_ipv6);
339
0
  bwhist_get_one_bandwidth_line(buf, "ipv6-read-history", read_array_ipv6);
340
0
  bwhist_get_one_bandwidth_line(buf, "dirreq-write-history", dir_write_array);
341
0
  bwhist_get_one_bandwidth_line(buf, "dirreq-read-history", dir_read_array);
342
343
0
  char *result = buf_extract(buf, NULL);
344
0
  buf_free(buf);
345
0
  return result;
346
0
}
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. */
350
static void
351
bwhist_update_bwhist_state_section(or_state_t *state,
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
0
{
358
0
  int i,j;
359
0
  uint64_t maxval;
360
361
0
  if (*s_values) {
362
0
    SMARTLIST_FOREACH(*s_values, char *, val, tor_free(val));
363
0
    smartlist_free(*s_values);
364
0
  }
365
0
  if (*s_maxima) {
366
0
    SMARTLIST_FOREACH(*s_maxima, char *, val, tor_free(val));
367
0
    smartlist_free(*s_maxima);
368
0
  }
369
0
  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
0
    if (*s_begins != 0 || *s_interval != 900) {
375
0
      time_t now = time(NULL);
376
0
      time_t save_at = get_options()->AvoidDiskWrites ? now+3600 : now+600;
377
0
      or_state_mark_dirty(state, save_at);
378
0
    }
379
0
    *s_begins = 0;
380
0
    *s_interval = 900;
381
0
    *s_values = smartlist_new();
382
0
    *s_maxima = smartlist_new();
383
0
    return;
384
0
  }
385
0
  *s_begins = b->next_period;
386
0
  *s_interval = NUM_SECS_BW_SUM_INTERVAL;
387
388
0
  *s_values = smartlist_new();
389
0
  *s_maxima = smartlist_new();
390
  /* Set i to first position in circular array */
391
0
  i = (b->num_maxes_set <= b->next_max_idx) ? 0 : b->next_max_idx;
392
0
  for (j=0; j < b->num_maxes_set; ++j,++i) {
393
0
    if (i >= NUM_TOTALS)
394
0
      i = 0;
395
0
    smartlist_add_asprintf(*s_values, "%"PRIu64,
396
0
                           (b->totals[i] & ~0x3ff));
397
0
    maxval = b->maxima[i] / NUM_SECS_ROLLING_MEASURE;
398
0
    smartlist_add_asprintf(*s_maxima, "%"PRIu64,
399
0
                           (maxval & ~0x3ff));
400
0
  }
401
0
  smartlist_add_asprintf(*s_values, "%"PRIu64,
402
0
                         (b->total_in_period & ~0x3ff));
403
0
  maxval = b->max_total / NUM_SECS_ROLLING_MEASURE;
404
0
  smartlist_add_asprintf(*s_maxima, "%"PRIu64,
405
0
                         (maxval & ~0x3ff));
406
0
}
407
408
/** Update <b>state</b> with the newest bandwidth history. Done before
409
 * writing out a new state file. */
410
void
411
bwhist_update_state(or_state_t *state)
412
0
{
413
0
#define UPDATE(arrname,st) \
414
0
  bwhist_update_bwhist_state_section(state,\
415
0
                                       (arrname),\
416
0
                                       &state->BWHistory ## st ## Values, \
417
0
                                       &state->BWHistory ## st ## Maxima, \
418
0
                                       &state->BWHistory ## st ## Ends, \
419
0
                                       &state->BWHistory ## st ## Interval)
420
421
0
  UPDATE(write_array, Write);
422
0
  UPDATE(read_array, Read);
423
0
  UPDATE(write_array_ipv6, IPv6Write);
424
0
  UPDATE(read_array_ipv6, IPv6Read);
425
0
  UPDATE(dir_write_array, DirWrite);
426
0
  UPDATE(dir_read_array, DirRead);
427
428
0
  if (server_mode(get_options())) {
429
0
    or_state_mark_dirty(state, time(NULL)+(2*3600));
430
0
  }
431
0
#undef UPDATE
432
0
}
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. */
436
static int
437
bwhist_load_bwhist_state_section(bw_array_t *b,
438
                                   const smartlist_t *s_values,
439
                                   const smartlist_t *s_maxima,
440
                                   const time_t s_begins,
441
                                   const int s_interval)
442
0
{
443
0
  time_t now = time(NULL);
444
0
  int retval = 0;
445
0
  time_t start;
446
447
0
  uint64_t v, mv;
448
0
  int i,ok,ok_m = 0;
449
0
  int have_maxima = s_maxima && s_values &&
450
0
    (smartlist_len(s_values) == smartlist_len(s_maxima));
451
452
0
  if (s_values && s_begins >= now - NUM_SECS_BW_SUM_INTERVAL*NUM_TOTALS) {
453
0
    start = s_begins - s_interval*(smartlist_len(s_values));
454
0
    if (start > now)
455
0
      return 0;
456
0
    b->cur_obs_time = start;
457
0
    b->next_period = start + NUM_SECS_BW_SUM_INTERVAL;
458
0
    SMARTLIST_FOREACH_BEGIN(s_values, const char *, cp) {
459
0
        const char *maxstr = NULL;
460
0
        v = tor_parse_uint64(cp, 10, 0, UINT64_MAX, &ok, NULL);
461
0
        if (have_maxima) {
462
0
          maxstr = smartlist_get(s_maxima, cp_sl_idx);
463
0
          mv = tor_parse_uint64(maxstr, 10, 0, UINT64_MAX, &ok_m, NULL);
464
0
          mv *= NUM_SECS_ROLLING_MEASURE;
465
0
        } else {
466
          /* No maxima known; guess average rate to be conservative. */
467
0
          mv = (v / s_interval) * NUM_SECS_ROLLING_MEASURE;
468
0
        }
469
0
        if (!ok) {
470
0
          retval = -1;
471
0
          log_notice(LD_HIST, "Could not parse value '%s' into a number.'",cp);
472
0
        }
473
0
        if (maxstr && !ok_m) {
474
0
          retval = -1;
475
0
          log_notice(LD_HIST, "Could not parse maximum '%s' into a number.'",
476
0
                     maxstr);
477
0
        }
478
479
0
        if (start < now) {
480
0
          time_t cur_start = start;
481
0
          time_t actual_interval_len = s_interval;
482
0
          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
0
          if (start + s_interval > now)
486
0
            actual_interval_len = now - start;
487
0
          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
0
          while (cur_start < start + actual_interval_len) {
491
0
            add_obs(b, cur_start, cur_val);
492
0
            ++cur_start;
493
0
          }
494
0
          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
0
          start += actual_interval_len;
498
0
        }
499
0
    } SMARTLIST_FOREACH_END(cp);
500
0
  }
501
502
  /* Clean up maxima and observed */
503
0
  for (i=0; i<NUM_SECS_ROLLING_MEASURE; ++i) {
504
0
    b->obs[i] = 0;
505
0
  }
506
0
  b->total_obs = 0;
507
508
0
  return retval;
509
0
}
510
511
/** Set bandwidth history from the state file we just loaded. */
512
int
513
bwhist_load_state(or_state_t *state, char **err)
514
0
{
515
0
  int all_ok = 1;
516
517
  /* Assert they already have been malloced */
518
0
  tor_assert(read_array && write_array);
519
0
  tor_assert(read_array_ipv6 && write_array_ipv6);
520
0
  tor_assert(dir_read_array && dir_write_array);
521
522
0
#define LOAD(arrname,st)                                                \
523
0
  if (bwhist_load_bwhist_state_section(                               \
524
0
                                (arrname),                              \
525
0
                                state->BWHistory ## st ## Values,       \
526
0
                                state->BWHistory ## st ## Maxima,       \
527
0
                                state->BWHistory ## st ## Ends,         \
528
0
                                state->BWHistory ## st ## Interval)<0)  \
529
0
    all_ok = 0
530
531
0
  LOAD(write_array, Write);
532
0
  LOAD(read_array, Read);
533
0
  LOAD(write_array_ipv6, IPv6Write);
534
0
  LOAD(read_array_ipv6, IPv6Read);
535
0
  LOAD(dir_write_array, DirWrite);
536
0
  LOAD(dir_read_array, DirRead);
537
538
0
#undef LOAD
539
0
  if (!all_ok) {
540
0
    *err = tor_strdup("Parsing of bandwidth history values failed");
541
    /* and create fresh arrays */
542
0
    bwhist_init();
543
0
    return -1;
544
0
  }
545
0
  return 0;
546
0
}
547
548
void
549
bwhist_free_all(void)
550
0
{
551
0
  bw_array_free(read_array);
552
0
  bw_array_free(read_array_ipv6);
553
0
  bw_array_free(write_array);
554
0
  bw_array_free(write_array_ipv6);
555
0
  bw_array_free(dir_read_array);
556
  bw_array_free(dir_write_array);
557
0
}