1 /* Copyright 2004 Roger Dingledine, Nick Mathewson. */
2 /* See LICENSE for licensing information */
4 const char rephist_c_id
[] = "$Id$";
8 * \brief Basic history functionality for reputation module.
13 static void bw_arrays_init(void);
14 static void predicted_ports_init(void);
16 /** History of an OR-\>OR link. */
17 typedef struct link_history_t
{
18 /** When did we start tracking this list? */
20 /** When did we most recently note a change to this link */
22 /** How many times did extending from OR1 to OR2 succeed? */
23 unsigned long n_extend_ok
;
24 /** How many times did extending from OR1 to OR2 fail? */
25 unsigned long n_extend_fail
;
28 /** History of an OR. */
29 typedef struct or_history_t
{
30 /** When did we start tracking this OR? */
32 /** When did we most recently note a change to this OR? */
34 /** How many times did we successfully connect? */
35 unsigned long n_conn_ok
;
36 /** How many times did we try to connect and fail?*/
37 unsigned long n_conn_fail
;
38 /** How many seconds have we been connected to this OR before
41 /** How many seconds have we been unable to connect to this OR before
43 unsigned long downtime
;
44 /** If nonzero, we have been connected since this time. */
46 /** If nonzero, we have been unable to connect since this time. */
48 /** Map from hex OR2 identity digest to a link_history_t for the link
49 * from this OR to OR2. */
50 strmap_t
*link_history_map
;
53 /** Map from hex OR identity digest to or_history_t. */
54 static strmap_t
*history_map
= NULL
;
56 /** Return the or_history_t for the named OR, creating it if necessary.
58 static or_history_t
*get_or_history(const char* id
)
61 char hexid
[HEX_DIGEST_LEN
+1];
62 base16_encode(hexid
, HEX_DIGEST_LEN
+1, id
, DIGEST_LEN
);
64 if (!strcmp(hexid
, "0000000000000000000000000000000000000000"))
67 hist
= (or_history_t
*) strmap_get(history_map
, hexid
);
69 hist
= tor_malloc_zero(sizeof(or_history_t
));
70 hist
->link_history_map
= strmap_new();
71 hist
->since
= hist
->changed
= time(NULL
);
72 strmap_set(history_map
, hexid
, hist
);
77 /** Return the link_history_t for the link from the first named OR to
78 * the second, creating it if necessary. (ORs are identified by
81 static link_history_t
*get_link_history(const char *from_id
,
85 link_history_t
*lhist
;
86 char to_hexid
[HEX_DIGEST_LEN
+1];
87 orhist
= get_or_history(from_id
);
90 base16_encode(to_hexid
, HEX_DIGEST_LEN
+1, to_id
, DIGEST_LEN
);
91 if (!strcmp(to_hexid
, "0000000000000000000000000000000000000000"))
93 lhist
= (link_history_t
*) strmap_get(orhist
->link_history_map
, to_hexid
);
95 lhist
= tor_malloc_zero(sizeof(link_history_t
));
96 lhist
->since
= lhist
->changed
= time(NULL
);
97 strmap_set(orhist
->link_history_map
, to_hexid
, lhist
);
103 _free_link_history(void *val
)
109 free_or_history(or_history_t
*hist
)
111 strmap_free(hist
->link_history_map
, _free_link_history
);
115 /** Update an or_history_t object <b>hist</b> so that its uptime/downtime
116 * count is up-to-date as of <b>when</b>.
118 static void update_or_history(or_history_t
*hist
, time_t when
)
121 if (hist
->up_since
) {
122 tor_assert(!hist
->down_since
);
123 hist
->uptime
+= (when
- hist
->up_since
);
124 hist
->up_since
= when
;
125 } else if (hist
->down_since
) {
126 hist
->downtime
+= (when
- hist
->down_since
);
127 hist
->down_since
= when
;
131 /** Initialize the static data structures for tracking history.
133 void rep_hist_init(void)
135 history_map
= strmap_new();
137 predicted_ports_init();
140 /** Remember that an attempt to connect to the OR with identity digest
141 * <b>id</b> failed at <b>when</b>.
143 void rep_hist_note_connect_failed(const char* id
, time_t when
)
146 hist
= get_or_history(id
);
150 if (hist
->up_since
) {
151 hist
->uptime
+= (when
- hist
->up_since
);
154 if (!hist
->down_since
)
155 hist
->down_since
= when
;
156 hist
->changed
= when
;
159 /** Remember that an attempt to connect to the OR with identity digest
160 * <b>id</b> succeeded at <b>when</b>.
162 void rep_hist_note_connect_succeeded(const char* id
, time_t when
)
165 hist
= get_or_history(id
);
169 if (hist
->down_since
) {
170 hist
->downtime
+= (when
- hist
->down_since
);
171 hist
->down_since
= 0;
174 hist
->up_since
= when
;
175 hist
->changed
= when
;
178 /** Remember that we intentionally closed our connection to the OR
179 * with identity digest <b>id</b> at <b>when</b>.
181 void rep_hist_note_disconnect(const char* id
, time_t when
)
184 hist
= get_or_history(id
);
188 if (hist
->up_since
) {
189 hist
->uptime
+= (when
- hist
->up_since
);
192 hist
->changed
= when
;
195 /** Remember that our connection to the OR with identity digest
196 * <b>id</b> had an error and stopped working at <b>when</b>.
198 void rep_hist_note_connection_died(const char* id
, time_t when
)
202 /* XXXX009 Well, everybody has an ID now. Hm. */
203 /* If conn has no nickname, it's either an OP, or it is an OR
204 * which didn't complete its handshake (or did and was unapproved).
209 hist
= get_or_history(id
);
212 if (hist
->up_since
) {
213 hist
->uptime
+= (when
- hist
->up_since
);
216 if (!hist
->down_since
)
217 hist
->down_since
= when
;
218 hist
->changed
= when
;
221 /** Remember that we successfully extended from the OR with identity
222 * digest <b>from_id</b> to the OR with identity digest
225 void rep_hist_note_extend_succeeded(const char *from_id
,
228 link_history_t
*hist
;
229 /* log_fn(LOG_WARN, "EXTEND SUCCEEDED: %s->%s",from_name,to_name); */
230 hist
= get_link_history(from_id
, to_id
);
234 hist
->changed
= time(NULL
);
237 /** Remember that we tried to extend from the OR with identity digest
238 * <b>from_id</b> to the OR with identity digest <b>to_name</b>, but
241 void rep_hist_note_extend_failed(const char *from_id
, const char *to_id
)
243 link_history_t
*hist
;
244 /* log_fn(LOG_WARN, "EXTEND FAILED: %s->%s",from_name,to_name); */
245 hist
= get_link_history(from_id
, to_id
);
248 ++hist
->n_extend_fail
;
249 hist
->changed
= time(NULL
);
252 /** Log all the reliability data we have remembered, with the chosen
255 void rep_hist_dump_stats(time_t now
, int severity
)
257 strmap_iter_t
*lhist_it
;
258 strmap_iter_t
*orhist_it
;
259 const char *name1
, *name2
, *hexdigest1
, *hexdigest2
;
260 or_history_t
*or_history
;
261 link_history_t
*link_history
;
262 void *or_history_p
, *link_history_p
;
267 unsigned long upt
, downt
;
270 rep_history_clean(now
-24*60*60);
272 log(severity
, "--------------- Dumping history information:");
274 for (orhist_it
= strmap_iter_init(history_map
); !strmap_iter_done(orhist_it
);
275 orhist_it
= strmap_iter_next(history_map
,orhist_it
)) {
276 strmap_iter_get(orhist_it
, &hexdigest1
, &or_history_p
);
277 or_history
= (or_history_t
*) or_history_p
;
279 if ((r
= router_get_by_hexdigest(hexdigest1
)))
284 update_or_history(or_history
, now
);
285 upt
= or_history
->uptime
;
286 downt
= or_history
->downtime
;
288 uptime
= ((double)upt
) / (upt
+downt
);
293 "OR %s [%s]: %ld/%ld good connections; uptime %ld/%ld sec (%.2f%%)",
295 or_history
->n_conn_ok
, or_history
->n_conn_fail
+or_history
->n_conn_ok
,
296 upt
, upt
+downt
, uptime
*100.0);
298 if (!strmap_isempty(or_history
->link_history_map
)) {
299 strlcpy(buffer
, " Extend attempts: ", sizeof(buffer
));
300 len
= strlen(buffer
);
301 for (lhist_it
= strmap_iter_init(or_history
->link_history_map
);
302 !strmap_iter_done(lhist_it
);
303 lhist_it
= strmap_iter_next(or_history
->link_history_map
, lhist_it
)) {
304 strmap_iter_get(lhist_it
, &hexdigest2
, &link_history_p
);
305 if ((r
= router_get_by_hexdigest(hexdigest2
)))
310 link_history
= (link_history_t
*) link_history_p
;
312 ret
= tor_snprintf(buffer
+len
, 2048-len
, "%s(%ld/%ld); ", name2
,
313 link_history
->n_extend_ok
,
314 link_history
->n_extend_ok
+link_history
->n_extend_fail
);
320 log(severity
, "%s", buffer
);
325 /** Remove history info for routers/links that haven't changed since
327 void rep_history_clean(time_t before
)
329 or_history_t
*or_history
;
330 link_history_t
*link_history
;
331 void *or_history_p
, *link_history_p
;
332 strmap_iter_t
*orhist_it
, *lhist_it
;
333 const char *hd1
, *hd2
;
335 orhist_it
= strmap_iter_init(history_map
);
336 while (!strmap_iter_done(orhist_it
)) {
337 strmap_iter_get(orhist_it
, &hd1
, &or_history_p
);
338 or_history
= or_history_p
;
339 if (or_history
->changed
< before
) {
340 free_or_history(or_history
);
341 orhist_it
= strmap_iter_next_rmv(history_map
, orhist_it
);
344 for (lhist_it
= strmap_iter_init(or_history
->link_history_map
);
345 !strmap_iter_done(lhist_it
); ) {
346 strmap_iter_get(lhist_it
, &hd2
, &link_history_p
);
347 link_history
= link_history_p
;
348 if (link_history
->changed
< before
) {
349 tor_free(link_history
);
350 lhist_it
= strmap_iter_next_rmv(or_history
->link_history_map
,lhist_it
);
353 lhist_it
= strmap_iter_next(or_history
->link_history_map
,lhist_it
);
355 orhist_it
= strmap_iter_next(history_map
, orhist_it
);
360 void write_rep_history(const char *filename
)
365 or_history_t
*or_history
;
366 link_history_t
*link_history
;
367 strmap_iter_t
*lhist_it
;
368 strmap_iter_t
*orhist_it
;
369 void *or_history_p
, *link_history_p
;
372 tmpfile
= tor_malloc(strlen(filename
)+5);
373 tor_snprintf(tmpfile
, strlen(filename
)+5, "%s_tmp", filename
);
375 f
= fopen(tmpfile
, "w");
377 for (orhist_it
= strmap_iter_init(history_map
); !strmap_iter_done(orhist_it
);
378 orhist_it
= strmap_iter_next(history_map
,orhist_it
)) {
379 strmap_iter_get(orhist_it
, &name1
, &or_history_p
);
380 or_history
= (or_history_t
*) or_history_p
;
381 fprintf(f
, "link %s connected:u%ld failed:%uld uptime:%uld",
382 name1
, or_history
->since1
,
389 replace_file(filename
, tmpfile
);
396 #define NUM_SECS_ROLLING_MEASURE 10
397 #define NUM_SECS_BW_SUM_IS_VALID (24*60*60) /* one day */
398 #define NUM_SECS_BW_SUM_INTERVAL (15*60)
399 #define NUM_TOTALS (NUM_SECS_BW_SUM_IS_VALID/NUM_SECS_BW_SUM_INTERVAL)
402 * Structure to track bandwidth use, and remember the maxima for a given
405 typedef struct bw_array_t
{
406 /** Observation array: Total number of bytes transferred in each of the last
407 * NUM_SECS_ROLLING_MEASURE seconds. This is used as a circular array. */
408 int obs
[NUM_SECS_ROLLING_MEASURE
];
409 int cur_obs_idx
; /**< Current position in obs. */
410 time_t cur_obs_time
; /**< Time represented in obs[cur_obs_idx] */
411 int total_obs
; /**< Total for all members of obs except obs[cur_obs_idx] */
412 int max_total
; /**< Largest value that total_obs has taken on in the current
414 int total_in_period
; /**< Total bytes transferred in the current period. */
416 /** When does the next period begin? */
418 /** Where in 'maxima' should the maximum bandwidth usage for the current
419 * period be stored? */
421 /** How many values in maxima/totals have been set ever? */
423 /** Circular array of the maximum
424 * bandwidth-per-NUM_SECS_ROLLING_MEASURE usage for the last
425 * NUM_TOTALS periods */
426 int maxima
[NUM_TOTALS
];
427 /** Circular array of the total bandwidth usage for the last NUM_TOTALS
429 int totals
[NUM_TOTALS
];
432 /** Shift the current period of b forward by one.
434 static void commit_max(bw_array_t
*b
) {
435 /* Store total from current period. */
436 b
->totals
[b
->next_max_idx
] = b
->total_in_period
;
437 /* Store maximum from current period. */
438 b
->maxima
[b
->next_max_idx
++] = b
->max_total
;
439 /* Advance next_period and next_max_idx */
440 b
->next_period
+= NUM_SECS_BW_SUM_INTERVAL
;
441 if (b
->next_max_idx
== NUM_TOTALS
)
443 if (b
->num_maxes_set
< NUM_TOTALS
)
445 /* Reset max_total. */
447 /* Reset total_in_period. */
448 b
->total_in_period
= 0;
451 /** Shift the current observation time of 'b' forward by one second.
453 static INLINE
void advance_obs(bw_array_t
*b
) {
457 /* Calculate the total bandwidth for the last NUM_SECS_ROLLING_MEASURE
458 * seconds; adjust max_total as needed.*/
459 total
= b
->total_obs
+ b
->obs
[b
->cur_obs_idx
];
460 if (total
> b
->max_total
)
461 b
->max_total
= total
;
463 nextidx
= b
->cur_obs_idx
+1;
464 if (nextidx
== NUM_SECS_ROLLING_MEASURE
)
467 b
->total_obs
= total
- b
->obs
[nextidx
];
469 b
->cur_obs_idx
= nextidx
;
471 if (++b
->cur_obs_time
>= b
->next_period
)
475 /** Add 'n' bytes to the number of bytes in b for second 'when'.
477 static INLINE
void add_obs(bw_array_t
*b
, time_t when
, int n
) {
478 /* Don't record data in the past. */
479 if (when
<b
->cur_obs_time
)
481 /* If we're currently adding observations for an earlier second than
482 * 'when', advance b->cur_obs_time and b->cur_obs_idx by an
483 * appropriate number of seconds, and do all the other housekeeping */
484 while (when
>b
->cur_obs_time
)
487 b
->obs
[b
->cur_obs_idx
] += n
;
488 b
->total_in_period
+= n
;
491 /** Allocate, initialize, and return a new bw_array.
493 static bw_array_t
*bw_array_new(void) {
496 b
= tor_malloc_zero(sizeof(bw_array_t
));
498 b
->cur_obs_time
= start
;
499 b
->next_period
= start
+ NUM_SECS_BW_SUM_INTERVAL
;
503 static bw_array_t
*read_array
= NULL
;
504 static bw_array_t
*write_array
= NULL
;
506 /** Set up read_array and write_array
508 static void bw_arrays_init(void)
510 read_array
= bw_array_new();
511 write_array
= bw_array_new();
514 /** We read <b>num_bytes</b> more bytes in second <b>when</b>.
516 * Add num_bytes to the current running total for <b>when</b>.
518 * <b>when</b> can go back to time, but it's safe to ignore calls
519 * earlier than the latest <b>when</b> you've heard of.
521 void rep_hist_note_bytes_written(int num_bytes
, time_t when
) {
522 /* Maybe a circular array for recent seconds, and step to a new point
523 * every time a new second shows up. Or simpler is to just to have
524 * a normal array and push down each item every second; it's short.
526 /* When a new second has rolled over, compute the sum of the bytes we've
527 * seen over when-1 to when-1-NUM_SECS_ROLLING_MEASURE, and stick it
528 * somewhere. See rep_hist_bandwidth_assess() below.
530 add_obs(write_array
, when
, num_bytes
);
533 /** We wrote <b>num_bytes</b> more bytes in second <b>when</b>.
534 * (like rep_hist_note_bytes_written() above)
536 void rep_hist_note_bytes_read(int num_bytes
, time_t when
) {
537 /* if we're smart, we can make this func and the one above share code */
538 add_obs(read_array
, when
, num_bytes
);
541 /** Helper: Return the largest value in b->maxima. (This is equal to the
542 * most bandwidth used in any NUM_SECS_ROLLING_MEASURE period for the last
543 * NUM_SECS_BW_SUM_IS_VALID seconds.)
545 static int find_largest_max(bw_array_t
*b
)
549 for (i
=0; i
<NUM_TOTALS
; ++i
) {
550 if (b
->maxima
[i
]>max
)
557 * Find the largest sums in the past NUM_SECS_BW_SUM_IS_VALID (roughly)
558 * seconds. Find one sum for reading and one for writing. They don't have
559 * to be at the same time).
561 * Return the smaller of these sums, divided by NUM_SECS_ROLLING_MEASURE.
563 int rep_hist_bandwidth_assess(void) {
565 r
= find_largest_max(read_array
);
566 w
= find_largest_max(write_array
);
568 return (int)(w
/(double)NUM_SECS_ROLLING_MEASURE
);
570 return (int)(r
/(double)NUM_SECS_ROLLING_MEASURE
);
576 * Allocate and return lines for representing this server's bandwidth
577 * history in its descriptor.
580 rep_hist_get_bandwidth_lines(void)
583 char t
[ISO_TIME_LEN
+1];
588 /* opt (read|write)-history yyyy-mm-dd HH:MM:SS (n s) n,n,n,n,n... */
589 len
= (60+12*NUM_TOTALS
)*2;
590 buf
= tor_malloc_zero(len
);
593 b
= r
?read_array
:write_array
;
595 format_iso_time(t
, b
->next_period
-NUM_SECS_BW_SUM_INTERVAL
);
596 tor_snprintf(cp
, len
-(cp
-buf
), "opt %s %s (%d s) ", r
?"read-history ":"write-history", t
,
597 NUM_SECS_BW_SUM_INTERVAL
);
600 if (b
->num_maxes_set
<= b
->next_max_idx
)
601 /* We haven't been through the circular array yet; time starts at i=0.*/
604 /* We've been around the array at least once. The next i to be
605 overwritten is the oldest. */
608 for (n
=0; n
<b
->num_maxes_set
; ++n
,++i
) {
609 while (i
>= NUM_TOTALS
) i
-= NUM_TOTALS
;
610 if (n
==(b
->num_maxes_set
-1))
611 tor_snprintf(cp
, len
-(cp
-buf
), "%d", b
->totals
[i
]);
613 tor_snprintf(cp
, len
-(cp
-buf
), "%d,", b
->totals
[i
]);
616 strlcat(cp
, "\n", len
-(cp
-buf
));
622 /** A list of port numbers that have been used recently. */
623 static smartlist_t
*predicted_ports_list
=NULL
;
624 /** The corresponding most recently used time for each port. */
625 static smartlist_t
*predicted_ports_times
=NULL
;
627 static void add_predicted_port(uint16_t port
, time_t now
) {
628 uint16_t *tmp_port
= tor_malloc(sizeof(uint16_t));
629 time_t *tmp_time
= tor_malloc(sizeof(time_t));
632 smartlist_add(predicted_ports_list
, tmp_port
);
633 smartlist_add(predicted_ports_times
, tmp_time
);
636 static void predicted_ports_init(void) {
637 predicted_ports_list
= smartlist_create();
638 predicted_ports_times
= smartlist_create();
639 add_predicted_port(80, time(NULL
)); /* add one to kickstart us */
642 /** Remember that <b>port</b> has been asked for as of time <b>now</b>.
643 * This is used for predicting what sorts of streams we'll make in the
644 * future and making circuits to anticipate that.
646 void rep_hist_note_used_port(uint16_t port
, time_t now
) {
651 tor_assert(predicted_ports_list
);
652 tor_assert(predicted_ports_times
);
654 if (!port
) /* record nothing */
657 for (i
= 0; i
< smartlist_len(predicted_ports_list
); ++i
) {
658 tmp_port
= smartlist_get(predicted_ports_list
, i
);
659 tmp_time
= smartlist_get(predicted_ports_times
, i
);
660 if (*tmp_port
== port
) {
665 /* it's not there yet; we need to add it */
666 add_predicted_port(port
, now
);
669 #define PREDICTED_CIRCS_RELEVANCE_TIME (3600) /* 1 hour */
671 /** Return a pointer to the list of port numbers that
672 * are likely to be asked for in the near future.
674 * The caller promises not to mess with it.
676 smartlist_t
*rep_hist_get_predicted_ports(time_t now
) {
681 tor_assert(predicted_ports_list
);
682 tor_assert(predicted_ports_times
);
684 /* clean out obsolete entries */
685 for (i
= 0; i
< smartlist_len(predicted_ports_list
); ++i
) {
686 tmp_time
= smartlist_get(predicted_ports_times
, i
);
687 if (*tmp_time
+ PREDICTED_CIRCS_RELEVANCE_TIME
< now
) {
688 tmp_port
= smartlist_get(predicted_ports_list
, i
);
689 log_fn(LOG_DEBUG
, "Expiring predicted port %d", *tmp_port
);
690 smartlist_del(predicted_ports_list
, i
);
691 smartlist_del(predicted_ports_times
, i
);
697 return predicted_ports_list
;
700 /** The last time at which we needed an internal circ. */
701 static time_t predicted_hidserv_time
= 0;
702 /** The last time we needed an internal circ with good uptime. */
703 static time_t predicted_hidserv_uptime_time
= 0;
704 /** The last time we needed an internal circ with good capacity. */
705 static time_t predicted_hidserv_capacity_time
= 0;
707 /** Remember that we used an internal circ at time <b>now</b>. */
708 void rep_hist_note_used_hidserv(time_t now
, int need_uptime
, int need_capacity
) {
709 predicted_hidserv_time
= now
;
711 predicted_hidserv_uptime_time
= now
;
713 predicted_hidserv_capacity_time
= now
;
716 /** Return 1 if we've used an internal circ recently; else return 0. */
717 int rep_hist_get_predicted_hidserv(time_t now
, int *need_uptime
, int *need_capacity
) {
718 if (!predicted_hidserv_time
) /* initialize it */
719 predicted_hidserv_time
= now
;
720 if (predicted_hidserv_time
+ PREDICTED_CIRCS_RELEVANCE_TIME
< now
)
721 return 0; /* too long ago */
722 if (predicted_hidserv_uptime_time
+ PREDICTED_CIRCS_RELEVANCE_TIME
< now
)
724 if (predicted_hidserv_capacity_time
+ PREDICTED_CIRCS_RELEVANCE_TIME
< now
)
730 void rep_hist_note_used_resolve(time_t now
) { }
731 int rep_hist_get_predicted_resolve(time_t now
) { return 0; }