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 }