test_metrics.c (14855B)
1 /* Copyright (c) 2020-2021, The Tor Project, Inc. */ 2 /* See LICENSE for licensing information */ 3 4 /** 5 * \file test_metrics.c 6 * \brief Test lib/metrics and feature/metrics functionalities 7 */ 8 9 #define CONFIG_PRIVATE 10 #define CONNECTION_PRIVATE 11 #define MAINLOOP_PRIVATE 12 #define METRICS_STORE_ENTRY_PRIVATE 13 14 #include "test/test.h" 15 #include "test/test_helpers.h" 16 #include "test/log_test_helpers.h" 17 18 #include "app/config/config.h" 19 20 #include "core/mainloop/connection.h" 21 #include "core/mainloop/mainloop.h" 22 #include "core/or/connection_st.h" 23 #include "core/or/policies.h" 24 #include "core/or/port_cfg_st.h" 25 26 #include "feature/metrics/metrics.h" 27 28 #include "lib/encoding/confline.h" 29 #include "lib/metrics/metrics_store.h" 30 31 #include <limits.h> 32 33 #define TEST_METRICS_ENTRY_NAME "entryA" 34 #define TEST_METRICS_ENTRY_HELP "Description of entryA" 35 #define TEST_METRICS_ENTRY_LABEL_1 "label=\"farfadet\"" 36 #define TEST_METRICS_ENTRY_LABEL_2 "label=\"ponki\"" 37 38 #define TEST_METRICS_HIST_ENTRY_NAME "test_hist_entry" 39 #define TEST_METRICS_HIST_ENTRY_HELP "Description of test_hist_entry" 40 41 static void 42 set_metrics_port(or_options_t *options) 43 { 44 const char *port = "MetricsPort 9035"; /* Default to 127.0.0.1 */ 45 const char *policy = "MetricsPortPolicy accept 1.2.3.4"; 46 47 config_get_lines(port, &options->MetricsPort_lines, 0); 48 config_get_lines(policy, &options->MetricsPortPolicy, 0); 49 50 /* Parse and validate policy. */ 51 policies_parse_from_options(options); 52 } 53 54 static void 55 test_config(void *arg) 56 { 57 char *err_msg = NULL; 58 tor_addr_t addr; 59 smartlist_t *ports = smartlist_new(); 60 or_options_t *options = get_options_mutable(); 61 62 (void) arg; 63 64 set_metrics_port(options); 65 66 int ret = metrics_parse_ports(options, ports, &err_msg); 67 tt_int_op(ret, OP_EQ, 0); 68 tt_int_op(smartlist_len(ports), OP_EQ, 1); 69 70 /* Validate the configured port. */ 71 const port_cfg_t *cfg = smartlist_get(ports, 0); 72 tt_assert(tor_addr_eq_ipv4h(&cfg->addr, 0x7f000001)); 73 tt_int_op(cfg->port, OP_EQ, 9035); 74 tt_int_op(cfg->type, OP_EQ, CONN_TYPE_METRICS_LISTENER); 75 76 /* Address of the policy should be permitted. */ 77 tor_addr_from_ipv4h(&addr, 0x01020304); /* 1.2.3.4 */ 78 ret = metrics_policy_permits_address(&addr); 79 tt_int_op(ret, OP_EQ, true); 80 81 /* Anything else, should not. */ 82 tor_addr_from_ipv4h(&addr, 0x01020305); /* 1.2.3.5 */ 83 ret = metrics_policy_permits_address(&addr); 84 tt_int_op(ret, OP_EQ, false); 85 86 done: 87 SMARTLIST_FOREACH(ports, port_cfg_t *, c, port_cfg_free(c)); 88 smartlist_free(ports); 89 or_options_free(options); 90 tor_free(err_msg); 91 } 92 93 static char _c_buf[256]; 94 #define CONTAINS(conn, msg) \ 95 do { \ 96 tt_int_op(buf_datalen(conn->outbuf), OP_EQ, (strlen(msg))); \ 97 memset(_c_buf, 0, sizeof(_c_buf)); \ 98 buf_get_bytes(conn->outbuf, _c_buf, (strlen(msg))); \ 99 tt_str_op(_c_buf, OP_EQ, (msg)); \ 100 tt_int_op(buf_datalen(conn->outbuf), OP_EQ, 0); \ 101 } while (0) 102 103 #define WRITE(conn, msg) \ 104 buf_add(conn->inbuf, (msg), (strlen(msg))); 105 106 /* Free the previous conn object if any and allocate a new connection. In 107 * order to be allowed, set its address to 1.2.3.4 as per the policy. */ 108 #define NEW_ALLOWED_CONN() \ 109 do { \ 110 close_closeable_connections(); \ 111 conn = connection_new(CONN_TYPE_METRICS, AF_INET); \ 112 tor_addr_from_ipv4h(&conn->addr, 0x01020304); \ 113 } while (0) 114 115 static void 116 test_connection(void *arg) 117 { 118 int ret; 119 connection_t *conn = NULL; 120 or_options_t *options = get_options_mutable(); 121 122 (void) arg; 123 124 /* Notice that in this test, we will allocate a new connection at every test 125 * case. This is because the metrics_connection_process_inbuf() marks for 126 * close the connection in case of an error and thus we can't call again an 127 * inbuf process function on a marked for close connection. */ 128 129 tor_init_connection_lists(); 130 131 /* Setup policy. */ 132 set_metrics_port(options); 133 134 /* Set 1.2.3.5 IP, we should get rejected. */ 135 NEW_ALLOWED_CONN(); 136 tor_addr_from_ipv4h(&conn->addr, 0x01020305); 137 ret = metrics_connection_process_inbuf(conn); 138 tt_int_op(ret, OP_EQ, -1); 139 140 /* No HTTP request yet. */ 141 NEW_ALLOWED_CONN(); 142 ret = metrics_connection_process_inbuf(conn); 143 tt_int_op(ret, OP_EQ, 0); 144 connection_free_minimal(conn); 145 146 /* Bad request. */ 147 NEW_ALLOWED_CONN(); 148 WRITE(conn, "HTTP 4.7\r\n\r\n"); 149 ret = metrics_connection_process_inbuf(conn); 150 tt_int_op(ret, OP_EQ, -1); 151 CONTAINS(conn, "HTTP/1.0 400 Bad Request\r\n\r\n"); 152 153 /* Path not found. */ 154 NEW_ALLOWED_CONN(); 155 WRITE(conn, "GET /badpath HTTP/1.0\r\n\r\n"); 156 ret = metrics_connection_process_inbuf(conn); 157 tt_int_op(ret, OP_EQ, -1); 158 CONTAINS(conn, "HTTP/1.0 404 Not Found\r\n\r\n"); 159 160 /* Method not allowed. */ 161 NEW_ALLOWED_CONN(); 162 WRITE(conn, "POST /something HTTP/1.0\r\n\r\n"); 163 ret = metrics_connection_process_inbuf(conn); 164 tt_int_op(ret, OP_EQ, -1); 165 CONTAINS(conn, "HTTP/1.0 405 Method Not Allowed\r\n\r\n"); 166 167 /* Ask for metrics. The content should be above 0. We don't test the 168 * validity of the returned content but it is certainly not an error. */ 169 NEW_ALLOWED_CONN(); 170 WRITE(conn, "GET /metrics HTTP/1.0\r\n\r\n"); 171 ret = metrics_connection_process_inbuf(conn); 172 tt_int_op(ret, OP_EQ, 0); 173 tt_int_op(buf_datalen(conn->outbuf), OP_GT, 0); 174 175 done: 176 or_options_free(options); 177 connection_free_minimal(conn); 178 } 179 180 static void 181 test_prometheus(void *arg) 182 { 183 metrics_store_t *store = NULL; 184 metrics_store_entry_t *entry = NULL; 185 buf_t *buf = buf_new(); 186 char *output = NULL; 187 188 (void) arg; 189 190 /* Fresh new store. No entries. */ 191 store = metrics_store_new(); 192 tt_assert(store); 193 194 /* Add entry and validate its content. */ 195 entry = metrics_store_add(store, METRICS_TYPE_COUNTER, 196 TEST_METRICS_ENTRY_NAME, 197 TEST_METRICS_ENTRY_HELP, 198 0, NULL); 199 tt_assert(entry); 200 metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); 201 202 static const char *expected = 203 "# HELP " TEST_METRICS_ENTRY_NAME " " TEST_METRICS_ENTRY_HELP "\n" 204 "# TYPE " TEST_METRICS_ENTRY_NAME " counter\n" 205 TEST_METRICS_ENTRY_NAME "{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n"; 206 207 metrics_store_get_output(METRICS_FORMAT_PROMETHEUS, store, buf); 208 output = buf_extract(buf, NULL); 209 tt_str_op(expected, OP_EQ, output); 210 211 done: 212 buf_free(buf); 213 tor_free(output); 214 metrics_store_free(store); 215 } 216 217 static void 218 test_prometheus_histogram(void *arg) 219 { 220 metrics_store_t *store = NULL; 221 metrics_store_entry_t *entry = NULL; 222 buf_t *buf = buf_new(); 223 char *output = NULL; 224 const int64_t buckets[] = { 10, 20, 3000 }; 225 226 (void) arg; 227 228 /* Fresh new store. No entries. */ 229 store = metrics_store_new(); 230 tt_assert(store); 231 232 /* Add a histogram entry and validate its content. */ 233 entry = metrics_store_add(store, METRICS_TYPE_HISTOGRAM, 234 TEST_METRICS_HIST_ENTRY_NAME, 235 TEST_METRICS_HIST_ENTRY_HELP, 236 ARRAY_LENGTH(buckets), buckets); 237 tt_assert(entry); 238 metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); 239 240 static const char *expected = 241 "# HELP " TEST_METRICS_HIST_ENTRY_NAME " " 242 TEST_METRICS_HIST_ENTRY_HELP "\n" 243 "# TYPE " TEST_METRICS_HIST_ENTRY_NAME " histogram\n" 244 TEST_METRICS_HIST_ENTRY_NAME "_bucket{" 245 TEST_METRICS_ENTRY_LABEL_1 ",le=\"10.00\"} 0\n" 246 TEST_METRICS_HIST_ENTRY_NAME "_bucket{" 247 TEST_METRICS_ENTRY_LABEL_1 ",le=\"20.00\"} 0\n" 248 TEST_METRICS_HIST_ENTRY_NAME "_bucket{" 249 TEST_METRICS_ENTRY_LABEL_1 ",le=\"3000.00\"} 0\n" 250 TEST_METRICS_HIST_ENTRY_NAME "_bucket{" 251 TEST_METRICS_ENTRY_LABEL_1 ",le=\"+Inf\"} 0\n" 252 TEST_METRICS_HIST_ENTRY_NAME "_sum{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n" 253 TEST_METRICS_HIST_ENTRY_NAME "_count{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n"; 254 255 metrics_store_get_output(METRICS_FORMAT_PROMETHEUS, store, buf); 256 output = buf_extract(buf, NULL); 257 tt_str_op(expected, OP_EQ, output); 258 259 done: 260 buf_free(buf); 261 tor_free(output); 262 metrics_store_free(store); 263 } 264 265 static void 266 test_store(void *arg) 267 { 268 metrics_store_t *store = NULL; 269 metrics_store_entry_t *entry = NULL; 270 const int64_t buckets[] = { 10, 20, 3000 }; 271 const size_t bucket_count = ARRAY_LENGTH(buckets); 272 273 (void) arg; 274 275 /* Fresh new store. No entries. */ 276 store = metrics_store_new(); 277 tt_assert(store); 278 tt_assert(!metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME)); 279 280 /* Add entry and validate its content. */ 281 entry = metrics_store_add(store, METRICS_TYPE_COUNTER, 282 TEST_METRICS_ENTRY_NAME, 283 TEST_METRICS_ENTRY_HELP, 0, NULL); 284 tt_assert(entry); 285 tt_int_op(entry->type, OP_EQ, METRICS_TYPE_COUNTER); 286 tt_str_op(entry->name, OP_EQ, TEST_METRICS_ENTRY_NAME); 287 tt_str_op(entry->help, OP_EQ, TEST_METRICS_ENTRY_HELP); 288 tt_uint_op(entry->u.counter.value, OP_EQ, 0); 289 290 /* Access the entry. */ 291 tt_assert(metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME)); 292 293 /* Add a label to the entry to make it unique. */ 294 metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); 295 tt_int_op(metrics_store_entry_has_label(entry, TEST_METRICS_ENTRY_LABEL_1), 296 OP_EQ, true); 297 298 /* Update entry's value. */ 299 metrics_store_entry_update(entry, 42); 300 tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 42); 301 metrics_store_entry_update(entry, 42); 302 tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 84); 303 metrics_store_entry_reset(entry); 304 tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 0); 305 306 /* Add a new entry of same name but different label. */ 307 /* Add entry and validate its content. */ 308 entry = metrics_store_add(store, METRICS_TYPE_COUNTER, 309 TEST_METRICS_ENTRY_NAME, 310 TEST_METRICS_ENTRY_HELP, 0, NULL); 311 tt_assert(entry); 312 metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_2); 313 314 /* Make sure _both_ entries are there. */ 315 const smartlist_t *entries = 316 metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME); 317 tt_assert(entries); 318 tt_int_op(smartlist_len(entries), OP_EQ, 2); 319 320 /* Add a histogram entry and validate its content. */ 321 entry = metrics_store_add(store, METRICS_TYPE_HISTOGRAM, 322 TEST_METRICS_HIST_ENTRY_NAME, 323 TEST_METRICS_HIST_ENTRY_HELP, 324 bucket_count, buckets); 325 326 tt_assert(entry); 327 tt_int_op(entry->type, OP_EQ, METRICS_TYPE_HISTOGRAM); 328 tt_str_op(entry->name, OP_EQ, TEST_METRICS_HIST_ENTRY_NAME); 329 tt_str_op(entry->help, OP_EQ, TEST_METRICS_HIST_ENTRY_HELP); 330 tt_uint_op(entry->u.histogram.bucket_count, OP_EQ, bucket_count); 331 332 for (size_t i = 0; i < bucket_count; ++i) { 333 tt_uint_op(entry->u.histogram.buckets[i].bucket, OP_EQ, buckets[i]); 334 tt_uint_op(entry->u.histogram.buckets[i].value, OP_EQ, 0); 335 } 336 337 /* Access the entry. */ 338 tt_assert(metrics_store_get_all(store, TEST_METRICS_HIST_ENTRY_NAME)); 339 340 /* Record various observations. */ 341 metrics_store_hist_entry_update(entry, 3, 11); 342 tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 0); 343 tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 3); 344 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 3); 345 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 3); 346 tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 3); 347 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, 11); 348 349 metrics_store_hist_entry_update(entry, 1, 42); 350 tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 0); 351 tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 3); 352 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 4); 353 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 4); 354 tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 4); 355 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, 53); 356 357 /* Ensure this resets all buckets back to 0. */ 358 metrics_store_entry_reset(entry); 359 for (size_t i = 0; i < bucket_count; ++i) { 360 tt_uint_op(entry->u.histogram.buckets[i].bucket, OP_EQ, buckets[i]); 361 tt_uint_op(entry->u.histogram.buckets[i].value, OP_EQ, 0); 362 } 363 364 /* tt_int_op assigns the third argument to a variable of type long, which 365 * overflows on some platforms (e.g. on some 32-bit systems). We disable 366 * these checks for those platforms. */ 367 #if LONG_MAX >= INT64_MAX 368 metrics_store_hist_entry_update(entry, 1, INT64_MAX - 13); 369 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MAX - 13); 370 metrics_store_hist_entry_update(entry, 1, 13); 371 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MAX); 372 /* Uh-oh, the sum of all observations is now greater than INT64_MAX. Make 373 * sure we reset the entry instead of overflowing the sum. */ 374 metrics_store_hist_entry_update(entry, 1, 1); 375 tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 1); 376 tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 1); 377 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); 378 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); 379 tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 1); 380 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, 1); 381 #endif 382 383 #if LONG_MIN <= INT64_MIN 384 metrics_store_entry_reset(entry); 385 /* In practice, we're not going to have negative observations (as we only use 386 * histograms for timings, which are always positive), but technically 387 * prometheus _does_ support negative observations. */ 388 metrics_store_hist_entry_update(entry, 1, INT64_MIN + 13); 389 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MIN + 13); 390 metrics_store_hist_entry_update(entry, 1, -13); 391 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MIN); 392 /* Uh-oh, the sum of all observations is now less than INT64_MIN. Make 393 * sure we reset the entry instead of underflowing the sum. */ 394 metrics_store_hist_entry_update(entry, 1, -1); 395 tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 1); 396 tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 1); 397 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); 398 tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); 399 tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 1); 400 tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, -1); 401 #endif 402 403 done: 404 metrics_store_free(store); 405 } 406 407 struct testcase_t metrics_tests[] = { 408 409 { "config", test_config, TT_FORK, NULL, NULL }, 410 { "connection", test_connection, TT_FORK, NULL, NULL }, 411 { "prometheus", test_prometheus, TT_FORK, NULL, NULL }, 412 { "prometheus_histogram", test_prometheus_histogram, TT_FORK, NULL, NULL }, 413 { "store", test_store, TT_FORK, NULL, NULL }, 414 415 END_OF_TESTCASES 416 };