bwhist.c (18085B)
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 { 34 /* Store total from current period. */ 35 b->totals[b->next_max_idx] = b->total_in_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 */ 39 b->next_period += NUM_SECS_BW_SUM_INTERVAL; 40 if (b->next_max_idx == NUM_TOTALS) 41 b->next_max_idx = 0; 42 if (b->num_maxes_set < NUM_TOTALS) 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. */ 51 STATIC void 52 advance_obs(bw_array_t *b) 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>. */ 77 STATIC void 78 add_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. */ 98 STATIC bw_array_t * 99 bw_array_new(void) 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; 106 b->next_period = start + NUM_SECS_BW_SUM_INTERVAL; 107 return b; 108 } 109 110 /** Free storage held by bandwidth array <b>b</b>. */ 111 STATIC void 112 bw_array_free_(bw_array_t *b) 113 { 114 if (!b) { 115 return; 116 } 117 118 tor_free(b); 119 } 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 { 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 148 read_array = bw_array_new(); 149 read_array_ipv6 = bw_array_new(); 150 write_array = bw_array_new(); 151 write_array_ipv6 = bw_array_new(); 152 dir_read_array = bw_array_new(); 153 dir_write_array = bw_array_new(); 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 */ 163 void 164 bwhist_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 */ 182 void 183 bwhist_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 */ 194 void 195 bwhist_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 */ 203 void 204 bwhist_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 */ 217 STATIC uint64_t 218 find_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 */ 240 MOCK_IMPL(int, 241 bwhist_bandwidth_assess,(void)) 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 */ 259 STATIC size_t 260 bwhist_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. */ 280 cutoff = options->RelayBandwidthRate * NUM_SECS_BW_SUM_INTERVAL; 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; 289 tor_assert(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>. */ 305 static void 306 bwhist_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 322 format_iso_time(end, b->next_period-NUM_SECS_BW_SUM_INTERVAL); 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 */ 331 char * 332 bwhist_get_bandwidth_lines(void) 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. */ 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 { 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)); 403 maxval = b->max_total / NUM_SECS_ROLLING_MEASURE; 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. */ 410 void 411 bwhist_update_state(or_state_t *state) 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. */ 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 { 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; 457 b->next_period = start + NUM_SECS_BW_SUM_INTERVAL; 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); 464 mv *= NUM_SECS_ROLLING_MEASURE; 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. */ 512 int 513 bwhist_load_state(or_state_t *state, char **err) 514 { 515 int all_ok = 1; 516 517 /* Assert they already have been malloced */ 518 tor_assert(read_array && write_array); 519 tor_assert(read_array_ipv6 && write_array_ipv6); 520 tor_assert(dir_read_array && dir_write_array); 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 548 void 549 bwhist_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 }