aboutsummaryrefslogtreecommitdiff
path: root/src/feature/hs/hs_metrics.c
blob: 4ce91c2b320f4e652ae65211902ca8842403db2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/* Copyright (c) 2020-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * @file hs_metrics.c
 * @brief Onion service metrics exposed through the MetricsPort
 **/

#define HS_METRICS_ENTRY_PRIVATE

#include "orconfig.h"

#include "lib/malloc/malloc.h"
#include "lib/container/smartlist.h"
#include "lib/metrics/metrics_store.h"
#include "lib/log/util_bug.h"

#include "feature/hs/hs_metrics.h"
#include "feature/hs/hs_metrics_entry.h"
#include "feature/hs/hs_service.h"

/** Return a static buffer pointer that contains the port as a string.
 *
 * Subsequent call to this function invalidates the previous buffer. */
static const char *
port_to_str(const uint16_t port)
{
  static char buf[8];
  tor_snprintf(buf, sizeof(buf), "%u", port);
  return buf;
}

/** Add a new metric to the metrics store of the service.
 *
 * <b>metric</b> is the index of the metric in the <b>base_metrics</b> array.
 */
static void
add_metric_with_labels(hs_service_t *service, hs_metrics_key_t metric,
                       bool port_as_label, uint16_t port)
{
  metrics_store_t *store;
  const char **error_reasons = NULL;
  size_t num_error_reasons = 0;

  tor_assert(service);

  if (BUG(metric >= base_metrics_size))
    return;

  store = service->metrics.store;

  /* Check whether the current metric is an error metric, because error metrics
   * require an additional `reason` label. */
  switch (metric) {
    case HS_METRICS_NUM_REJECTED_INTRO_REQ:
      error_reasons = hs_metrics_intro_req_error_reasons;
      num_error_reasons = hs_metrics_intro_req_error_reasons_size;
      break;
    case HS_METRICS_NUM_FAILED_RDV:
      error_reasons = hs_metrics_rend_error_reasons;
      num_error_reasons = hs_metrics_rend_error_reasons_size;
      break;
    /* Fall through for all other metrics, as they don't need a
     * reason label. */
    case HS_METRICS_NUM_INTRODUCTIONS: FALLTHROUGH;
    case HS_METRICS_APP_WRITE_BYTES: FALLTHROUGH;
    case HS_METRICS_APP_READ_BYTES: FALLTHROUGH;
    case HS_METRICS_NUM_ESTABLISHED_RDV: FALLTHROUGH;
    case HS_METRICS_NUM_RDV: FALLTHROUGH;
    case HS_METRICS_NUM_ESTABLISHED_INTRO: FALLTHROUGH;
    case HS_METRICS_POW_NUM_PQUEUE_RDV: FALLTHROUGH;
    case HS_METRICS_POW_SUGGESTED_EFFORT: FALLTHROUGH;
    case HS_METRICS_INTRO_CIRC_BUILD_TIME: FALLTHROUGH;
    case HS_METRICS_REND_CIRC_BUILD_TIME: FALLTHROUGH;
    default:
      break;
  }

  /* We don't need a reason label for this metric */
  if (!num_error_reasons) {
      metrics_store_entry_t *entry = metrics_store_add(
          store, base_metrics[metric].type, base_metrics[metric].name,
          base_metrics[metric].help, base_metrics[metric].bucket_count,
          base_metrics[metric].buckets);

      metrics_store_entry_add_label(entry,
              metrics_format_label("onion", service->onion_address));

      if (port_as_label) {
        metrics_store_entry_add_label(entry,
                metrics_format_label("port", port_to_str(port)));
      }

      return;
  }

  tor_assert(error_reasons);

  /* Add entries with reason as label. We need one metric line per
   * reason. */
  for (size_t i = 0; i < num_error_reasons; ++i) {
    metrics_store_entry_t *entry =
      metrics_store_add(store, base_metrics[metric].type,
                        base_metrics[metric].name,
                        base_metrics[metric].help,
                        base_metrics[metric].bucket_count,
                        base_metrics[metric].buckets);
    /* Add labels to the entry. */
    metrics_store_entry_add_label(entry,
            metrics_format_label("onion", service->onion_address));
    metrics_store_entry_add_label(entry,
            metrics_format_label("reason", error_reasons[i]));

    if (port_as_label) {
      metrics_store_entry_add_label(entry,
              metrics_format_label("port", port_to_str(port)));
    }
  }
}

/** Initialize a metrics store for the given service.
 *
 * Essentially, this goes over the base_metrics array and adds them all to the
 * store set with their label(s) if any. */
static void
init_store(hs_service_t *service)
{
  tor_assert(service);

  for (size_t i = 0; i < base_metrics_size; ++i) {
    /* Add entries with port as label. We need one metric line per port. */
    if (base_metrics[i].port_as_label && service->config.ports) {
      SMARTLIST_FOREACH_BEGIN(service->config.ports,
                              const hs_port_config_t *, p) {
        add_metric_with_labels(service, base_metrics[i].key, true,
                               p->virtual_port);
      } SMARTLIST_FOREACH_END(p);
    } else {
      add_metric_with_labels(service, base_metrics[i].key, false, 0);
    }
  }
}

/** Update the metrics key entry in the store in the given service. The port,
 * if non 0, and the reason label, if non NULL, are used to find the correct
 * metrics entry. The value n is the value used to update the entry. */
void
hs_metrics_update_by_service(const hs_metrics_key_t key,
                             const hs_service_t *service,
                             uint16_t port, const char *reason,
                             int64_t n, int64_t obs, bool reset)
{
  tor_assert(service);

  /* Get the metrics entry in the store. */
  smartlist_t *entries = metrics_store_get_all(service->metrics.store,
                                               base_metrics[key].name);
  if (BUG(!entries)) {
    return;
  }

  /* We need to find the right metrics entry by finding the port label if any.
   *
   * XXX: This is not the most optimal due to the string format. Maybe at some
   * point turn this into a kvline and a map in a metric entry? */
  SMARTLIST_FOREACH_BEGIN(entries, metrics_store_entry_t *, entry) {
    if ((port == 0 ||
         metrics_store_entry_has_label(
             entry, metrics_format_label("port", port_to_str(port)))) &&
        ((!reason || metrics_store_entry_has_label(
                         entry, metrics_format_label("reason", reason))))) {
      if (reset) {
        metrics_store_entry_reset(entry);
      }

      if (metrics_store_entry_is_histogram(entry)) {
        metrics_store_hist_entry_update(entry, n, obs);
      } else {
        metrics_store_entry_update(entry, n);
      }

      break;
    }
  } SMARTLIST_FOREACH_END(entry);
}

/** Update the metrics key entry in the store of a service identified by the
 * given identity public key. The port, if non 0, and the reason label, if non
 * NULL, are used to find the correct metrics entry. The value n is the value
 * used to update the entry.
 *
 * This is used by callsite that have access to the key but not the service
 * object so an extra lookup is done to find the service. */
void
hs_metrics_update_by_ident(const hs_metrics_key_t key,
                           const ed25519_public_key_t *ident_pk,
                           const uint16_t port, const char *reason,
                           int64_t n, int64_t obs, bool reset)
{
  hs_service_t *service;

  if (!ident_pk) {
    /* We can end up here in case this is used from a failure/closing path for
     * which we might not have any identity key attacehed to a circuit or
     * connection yet. Simply don't assume we have one. */
    return;
  }

  service = hs_service_find(ident_pk);
  if (!service) {
    /* This is possible because an onion service client can end up here due to
     * having an identity key onto a connection _to_ an onion service. We
     * can't differentiate that from an actual onion service initiated by a
     * service and thus the only way to know is to lookup the service. */
    return;
  }
  hs_metrics_update_by_service(key, service, port, reason, n, obs, reset);
}

/** Return a list of all the onion service metrics stores. This is the
 * function attached to the .get_metrics() member of the subsys_t. */
const smartlist_t *
hs_metrics_get_stores(void)
{
  /* We can't have the caller to free the returned list so keep it static,
   * simply update it. */
  static smartlist_t *stores_list = NULL;

  smartlist_free(stores_list);
  stores_list = hs_service_get_metrics_stores();
  return stores_list;
}

/** Initialize the metrics store in the given service. */
void
hs_metrics_service_init(hs_service_t *service)
{
  tor_assert(service);

  /* This function is called when we register a service and so it could either
   * be a new service or a service that was just reloaded through a HUP signal
   * for instance. Thus, it is possible that the service has already an
   * initialized store. If so, just return. */
  if (service->metrics.store) {
    return;
  }

  service->metrics.store = metrics_store_new();
  init_store(service);
}

/** Free the metrics store in the given service. */
void
hs_metrics_service_free(hs_service_t *service)
{
  tor_assert(service);

  metrics_store_free(service->metrics.store);
}