LCOV - code coverage report
Current view: top level - feature/stats - connstats.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 71 87 81.6 %
Date: 2021-11-24 03:28:48 Functions: 18 20 90.0 %

          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             : }

Generated by: LCOV version 1.14