tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

hs_metrics.c (8828B)


      1 /* Copyright (c) 2020-2021, The Tor Project, Inc. */
      2 /* See LICENSE for licensing information */
      3 
      4 /**
      5 * @file hs_metrics.c
      6 * @brief Onion service metrics exposed through the MetricsPort
      7 **/
      8 
      9 #define HS_METRICS_ENTRY_PRIVATE
     10 
     11 #include "orconfig.h"
     12 
     13 #include "lib/malloc/malloc.h"
     14 #include "lib/container/smartlist.h"
     15 #include "lib/metrics/metrics_store.h"
     16 #include "lib/log/util_bug.h"
     17 
     18 #include "feature/hs/hs_metrics.h"
     19 #include "feature/hs/hs_metrics_entry.h"
     20 #include "feature/hs/hs_service.h"
     21 
     22 /** Return a static buffer pointer that contains the port as a string.
     23 *
     24 * Subsequent call to this function invalidates the previous buffer. */
     25 static const char *
     26 port_to_str(const uint16_t port)
     27 {
     28  static char buf[8];
     29  tor_snprintf(buf, sizeof(buf), "%u", port);
     30  return buf;
     31 }
     32 
     33 /** Add a new metric to the metrics store of the service.
     34 *
     35 * <b>metric</b> is the index of the metric in the <b>base_metrics</b> array.
     36 */
     37 static void
     38 add_metric_with_labels(hs_service_t *service, hs_metrics_key_t metric,
     39                       bool port_as_label, uint16_t port)
     40 {
     41  metrics_store_t *store;
     42  const char **error_reasons = NULL;
     43  size_t num_error_reasons = 0;
     44 
     45  tor_assert(service);
     46 
     47  if (BUG(metric >= base_metrics_size))
     48    return;
     49 
     50  store = service->metrics.store;
     51 
     52  /* Check whether the current metric is an error metric, because error metrics
     53   * require an additional `reason` label. */
     54  switch (metric) {
     55    case HS_METRICS_NUM_REJECTED_INTRO_REQ:
     56      error_reasons = hs_metrics_intro_req_error_reasons;
     57      num_error_reasons = hs_metrics_intro_req_error_reasons_size;
     58      break;
     59    case HS_METRICS_NUM_FAILED_RDV:
     60      error_reasons = hs_metrics_rend_error_reasons;
     61      num_error_reasons = hs_metrics_rend_error_reasons_size;
     62      break;
     63    /* Fall through for all other metrics, as they don't need a
     64     * reason label. */
     65    case HS_METRICS_NUM_INTRODUCTIONS: FALLTHROUGH;
     66    case HS_METRICS_APP_WRITE_BYTES: FALLTHROUGH;
     67    case HS_METRICS_APP_READ_BYTES: FALLTHROUGH;
     68    case HS_METRICS_NUM_ESTABLISHED_RDV: FALLTHROUGH;
     69    case HS_METRICS_NUM_RDV: FALLTHROUGH;
     70    case HS_METRICS_NUM_ESTABLISHED_INTRO: FALLTHROUGH;
     71    case HS_METRICS_POW_NUM_PQUEUE_RDV: FALLTHROUGH;
     72    case HS_METRICS_POW_SUGGESTED_EFFORT: FALLTHROUGH;
     73    case HS_METRICS_INTRO_CIRC_BUILD_TIME: FALLTHROUGH;
     74    case HS_METRICS_REND_CIRC_BUILD_TIME: FALLTHROUGH;
     75    default:
     76      break;
     77  }
     78 
     79  /* We don't need a reason label for this metric */
     80  if (!num_error_reasons) {
     81      metrics_store_entry_t *entry = metrics_store_add(
     82          store, base_metrics[metric].type, base_metrics[metric].name,
     83          base_metrics[metric].help, base_metrics[metric].bucket_count,
     84          base_metrics[metric].buckets);
     85 
     86      metrics_store_entry_add_label(entry,
     87              metrics_format_label("onion", service->onion_address));
     88 
     89      if (port_as_label) {
     90        metrics_store_entry_add_label(entry,
     91                metrics_format_label("port", port_to_str(port)));
     92      }
     93 
     94      return;
     95  }
     96 
     97  tor_assert(error_reasons);
     98 
     99  /* Add entries with reason as label. We need one metric line per
    100   * reason. */
    101  for (size_t i = 0; i < num_error_reasons; ++i) {
    102    metrics_store_entry_t *entry =
    103      metrics_store_add(store, base_metrics[metric].type,
    104                        base_metrics[metric].name,
    105                        base_metrics[metric].help,
    106                        base_metrics[metric].bucket_count,
    107                        base_metrics[metric].buckets);
    108    /* Add labels to the entry. */
    109    metrics_store_entry_add_label(entry,
    110            metrics_format_label("onion", service->onion_address));
    111    metrics_store_entry_add_label(entry,
    112            metrics_format_label("reason", error_reasons[i]));
    113 
    114    if (port_as_label) {
    115      metrics_store_entry_add_label(entry,
    116              metrics_format_label("port", port_to_str(port)));
    117    }
    118  }
    119 }
    120 
    121 /** Initialize a metrics store for the given service.
    122 *
    123 * Essentially, this goes over the base_metrics array and adds them all to the
    124 * store set with their label(s) if any. */
    125 static void
    126 init_store(hs_service_t *service)
    127 {
    128  tor_assert(service);
    129 
    130  for (size_t i = 0; i < base_metrics_size; ++i) {
    131    /* Add entries with port as label. We need one metric line per port. */
    132    if (base_metrics[i].port_as_label && service->config.ports) {
    133      SMARTLIST_FOREACH_BEGIN(service->config.ports,
    134                              const hs_port_config_t *, p) {
    135        add_metric_with_labels(service, base_metrics[i].key, true,
    136                               p->virtual_port);
    137      } SMARTLIST_FOREACH_END(p);
    138    } else {
    139      add_metric_with_labels(service, base_metrics[i].key, false, 0);
    140    }
    141  }
    142 }
    143 
    144 /** Update the metrics key entry in the store in the given service. The port,
    145 * if non 0, and the reason label, if non NULL, are used to find the correct
    146 * metrics entry. The value n is the value used to update the entry. */
    147 void
    148 hs_metrics_update_by_service(const hs_metrics_key_t key,
    149                             const hs_service_t *service,
    150                             uint16_t port, const char *reason,
    151                             int64_t n, int64_t obs, bool reset)
    152 {
    153  tor_assert(service);
    154 
    155  /* Get the metrics entry in the store. */
    156  smartlist_t *entries = metrics_store_get_all(service->metrics.store,
    157                                               base_metrics[key].name);
    158  if (BUG(!entries)) {
    159    return;
    160  }
    161 
    162  /* We need to find the right metrics entry by finding the port label if any.
    163   *
    164   * XXX: This is not the most optimal due to the string format. Maybe at some
    165   * point turn this into a kvline and a map in a metric entry? */
    166  SMARTLIST_FOREACH_BEGIN(entries, metrics_store_entry_t *, entry) {
    167    if ((port == 0 ||
    168         metrics_store_entry_has_label(
    169             entry, metrics_format_label("port", port_to_str(port)))) &&
    170        ((!reason || metrics_store_entry_has_label(
    171                         entry, metrics_format_label("reason", reason))))) {
    172      if (reset) {
    173        metrics_store_entry_reset(entry);
    174      }
    175 
    176      if (metrics_store_entry_is_histogram(entry)) {
    177        metrics_store_hist_entry_update(entry, n, obs);
    178      } else {
    179        metrics_store_entry_update(entry, n);
    180      }
    181 
    182      break;
    183    }
    184  } SMARTLIST_FOREACH_END(entry);
    185 }
    186 
    187 /** Update the metrics key entry in the store of a service identified by the
    188 * given identity public key. The port, if non 0, and the reason label, if non
    189 * NULL, are used to find the correct metrics entry. The value n is the value
    190 * used to update the entry.
    191 *
    192 * This is used by callsite that have access to the key but not the service
    193 * object so an extra lookup is done to find the service. */
    194 void
    195 hs_metrics_update_by_ident(const hs_metrics_key_t key,
    196                           const ed25519_public_key_t *ident_pk,
    197                           const uint16_t port, const char *reason,
    198                           int64_t n, int64_t obs, bool reset)
    199 {
    200  hs_service_t *service;
    201 
    202  if (!ident_pk) {
    203    /* We can end up here in case this is used from a failure/closing path for
    204     * which we might not have any identity key attacehed to a circuit or
    205     * connection yet. Simply don't assume we have one. */
    206    return;
    207  }
    208 
    209  service = hs_service_find(ident_pk);
    210  if (!service) {
    211    /* This is possible because an onion service client can end up here due to
    212     * having an identity key onto a connection _to_ an onion service. We
    213     * can't differentiate that from an actual onion service initiated by a
    214     * service and thus the only way to know is to lookup the service. */
    215    return;
    216  }
    217  hs_metrics_update_by_service(key, service, port, reason, n, obs, reset);
    218 }
    219 
    220 /** Return a list of all the onion service metrics stores. This is the
    221 * function attached to the .get_metrics() member of the subsys_t. */
    222 const smartlist_t *
    223 hs_metrics_get_stores(void)
    224 {
    225  /* We can't have the caller to free the returned list so keep it static,
    226   * simply update it. */
    227  static smartlist_t *stores_list = NULL;
    228 
    229  smartlist_free(stores_list);
    230  stores_list = hs_service_get_metrics_stores();
    231  return stores_list;
    232 }
    233 
    234 /** Initialize the metrics store in the given service. */
    235 void
    236 hs_metrics_service_init(hs_service_t *service)
    237 {
    238  tor_assert(service);
    239 
    240  /* This function is called when we register a service and so it could either
    241   * be a new service or a service that was just reloaded through a HUP signal
    242   * for instance. Thus, it is possible that the service has already an
    243   * initialized store. If so, just return. */
    244  if (service->metrics.store) {
    245    return;
    246  }
    247 
    248  service->metrics.store = metrics_store_new();
    249  init_store(service);
    250 }
    251 
    252 /** Free the metrics store in the given service. */
    253 void
    254 hs_metrics_service_free(hs_service_t *service)
    255 {
    256  tor_assert(service);
    257 
    258  metrics_store_free(service->metrics.store);
    259 }