diff --git a/changes/ticket40757 b/changes/ticket40757 new file mode 100644 index 0000000000..e2d8c1ed47 --- /dev/null +++ b/changes/ticket40757 @@ -0,0 +1,3 @@ + o Minor features (metrics): + - Add support for histograms. + Part of ticket 40757. diff --git a/src/feature/hs/hs_metrics.c b/src/feature/hs/hs_metrics.c index 4c6d957cf8..38cdb49e40 100644 --- a/src/feature/hs/hs_metrics.c +++ b/src/feature/hs/hs_metrics.c @@ -76,7 +76,8 @@ add_metric_with_labels(hs_service_t *service, hs_metrics_key_t 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].help, base_metrics[metric].bucket_count, + base_metrics[metric].buckets); metrics_store_entry_add_label(entry, metrics_format_label("onion", service->onion_address)); @@ -97,7 +98,9 @@ add_metric_with_labels(hs_service_t *service, hs_metrics_key_t metric, metrics_store_entry_t *entry = metrics_store_add(store, base_metrics[metric].type, base_metrics[metric].name, - base_metrics[metric].help); + 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)); diff --git a/src/feature/hs/hs_metrics_entry.h b/src/feature/hs/hs_metrics_entry.h index 07693972c0..b966f0c226 100644 --- a/src/feature/hs/hs_metrics_entry.h +++ b/src/feature/hs/hs_metrics_entry.h @@ -70,6 +70,10 @@ typedef struct hs_metrics_entry_t { const char *name; /* Metrics output help comment. */ const char *help; + /* The buckets, if the metric type is METRICS_TYPE_HISTOGRAM. */ + const int64_t *buckets; + /* The number of buckets, if the metric type is METRICS_TYPE_HISTOGRAM. */ + size_t bucket_count; /* True iff a port label should be added to the metrics entry. */ bool port_as_label; } hs_metrics_entry_t; diff --git a/src/feature/relay/relay_metrics.c b/src/feature/relay/relay_metrics.c index cdf34a3404..99204931fc 100644 --- a/src/feature/relay/relay_metrics.c +++ b/src/feature/relay/relay_metrics.c @@ -222,8 +222,8 @@ fill_circuits_values(void) { const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_NUM_CIRCUITS]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "opened")); @@ -255,57 +255,57 @@ fill_relay_flags(void) const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_RELAY_FLAGS]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Fast")); metrics_store_entry_update(sentry, is_fast); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Exit")); metrics_store_entry_update(sentry, is_exit); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Authority")); metrics_store_entry_update(sentry, is_authority); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Stable")); metrics_store_entry_update(sentry, is_stable); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "HSDir")); metrics_store_entry_update(sentry, is_hs_dir); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Running")); metrics_store_entry_update(sentry, is_running); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "V2Dir")); metrics_store_entry_update(sentry, is_v2_dir); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Sybil")); metrics_store_entry_update(sentry, is_sybil); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "Guard")); metrics_store_entry_update(sentry, is_guard); @@ -317,15 +317,15 @@ fill_traffic_values(void) { const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_NUM_TRAFFIC]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("direction", "read")); metrics_store_entry_update(sentry, get_bytes_read()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("direction", "written")); metrics_store_entry_update(sentry, get_bytes_written()); @@ -336,57 +336,57 @@ static void fill_dos_values(void) { const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_NUM_DOS]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "circuit_rejected")); metrics_store_entry_update(sentry, dos_get_num_cc_rejected()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "circuit_killed_max_cell")); metrics_store_entry_update(sentry, stats_n_circ_max_cell_reached); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "circuit_killed_max_cell_outq")); metrics_store_entry_update(sentry, stats_n_circ_max_cell_outq_reached); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "marked_address")); metrics_store_entry_update(sentry, dos_get_num_cc_marked_addr()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "marked_address_maxq")); metrics_store_entry_update(sentry, dos_get_num_cc_marked_addr_maxq()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "conn_rejected")); metrics_store_entry_update(sentry, dos_get_num_conn_addr_connect_rejected()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "concurrent_conn_rejected")); metrics_store_entry_update(sentry, dos_get_num_conn_addr_rejected()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "single_hop_refused")); metrics_store_entry_update(sentry, dos_get_num_single_hop_refused()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("type", "introduce2_rejected")); metrics_store_entry_update(sentry, hs_dos_get_intro2_rejected_count()); @@ -399,8 +399,8 @@ fill_cc_counters_values(void) const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_CC_COUNTERS]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "starvation")); metrics_store_entry_add_label(sentry, @@ -408,7 +408,7 @@ fill_cc_counters_values(void) metrics_store_entry_update(sentry, congestion_control_get_num_rtt_reset()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "clock_stalls")); metrics_store_entry_add_label(sentry, @@ -417,7 +417,7 @@ fill_cc_counters_values(void) congestion_control_get_num_clock_stalls()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "flow_control")); metrics_store_entry_add_label(sentry, @@ -426,7 +426,7 @@ fill_cc_counters_values(void) cc_stats_flow_num_xoff_sent); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "flow_control")); metrics_store_entry_add_label(sentry, @@ -435,7 +435,7 @@ fill_cc_counters_values(void) cc_stats_flow_num_xon_sent); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_limits")); metrics_store_entry_add_label(sentry, @@ -443,7 +443,7 @@ fill_cc_counters_values(void) metrics_store_entry_update(sentry, cc_stats_vegas_above_delta); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_limits")); metrics_store_entry_add_label(sentry, @@ -451,7 +451,7 @@ fill_cc_counters_values(void) metrics_store_entry_update(sentry, cc_stats_vegas_above_ss_cwnd_max); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_limits")); metrics_store_entry_add_label(sentry, @@ -459,7 +459,7 @@ fill_cc_counters_values(void) metrics_store_entry_update(sentry, cc_stats_vegas_below_ss_inc_floor); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_circuits")); metrics_store_entry_add_label(sentry, @@ -467,7 +467,7 @@ fill_cc_counters_values(void) metrics_store_entry_update(sentry, cc_stats_circs_created); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_circuits")); metrics_store_entry_add_label(sentry, @@ -475,7 +475,7 @@ fill_cc_counters_values(void) metrics_store_entry_update(sentry, cc_stats_circs_closed); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_circuits")); metrics_store_entry_add_label(sentry, @@ -490,8 +490,8 @@ fill_cc_gauges_values(void) const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_CC_GAUGES]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "slow_start_exit")); metrics_store_entry_add_label(sentry, @@ -500,7 +500,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_exit_ss_cwnd_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "slow_start_exit")); metrics_store_entry_add_label(sentry, @@ -509,7 +509,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_exit_ss_bdp_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "slow_start_exit")); metrics_store_entry_add_label(sentry, @@ -518,7 +518,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_exit_ss_inc_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "on_circ_close")); metrics_store_entry_add_label(sentry, @@ -527,7 +527,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_circ_close_cwnd_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "on_circ_close")); metrics_store_entry_add_label(sentry, @@ -536,7 +536,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_circ_close_ss_cwnd_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "buffers")); metrics_store_entry_add_label(sentry, @@ -545,7 +545,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_flow_xon_outbuf_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "buffers")); metrics_store_entry_add_label(sentry, @@ -554,7 +554,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_flow_xoff_outbuf_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_backoff")); metrics_store_entry_add_label(sentry, @@ -563,7 +563,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_csig_blocked_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_backoff")); metrics_store_entry_add_label(sentry, @@ -572,7 +572,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_gamma_drop_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_backoff")); metrics_store_entry_add_label(sentry, @@ -581,7 +581,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_delta_drop_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_backoff")); metrics_store_entry_add_label(sentry, @@ -590,7 +590,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_ss_csig_blocked_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_cwnd_update")); metrics_store_entry_add_label(sentry, @@ -599,7 +599,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_csig_alpha_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_cwnd_update")); metrics_store_entry_add_label(sentry, @@ -608,7 +608,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_csig_beta_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_cwnd_update")); metrics_store_entry_add_label(sentry, @@ -617,7 +617,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_csig_delta_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_estimates")); metrics_store_entry_add_label(sentry, @@ -626,7 +626,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_ss_queue_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_estimates")); metrics_store_entry_add_label(sentry, @@ -635,7 +635,7 @@ fill_cc_gauges_values(void) tor_llround(cc_stats_vegas_queue_ma)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "cc_estimates")); metrics_store_entry_add_label(sentry, @@ -659,16 +659,16 @@ fill_streams_values(void) { const relay_metrics_entry_t *rentry = &base_metrics[RELAY_METRICS_NUM_STREAMS]; - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); fill_single_stream_value(sentry, RELAY_COMMAND_BEGIN); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_stream_value(sentry, RELAY_COMMAND_BEGIN_DIR); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_stream_value(sentry, RELAY_COMMAND_RESOLVE); } @@ -704,31 +704,31 @@ fill_conn_counter_values(void) if (i == 10) { continue; } - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "initiated", "created", AF_INET, rep_hist_get_conn_created(false, i, AF_INET)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "initiated", "created", AF_INET6, rep_hist_get_conn_created(false, i, AF_INET6)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "received", "created", AF_INET, rep_hist_get_conn_created(true, i, AF_INET)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "received", "created", AF_INET6, rep_hist_get_conn_created(true, i, AF_INET6)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "received", "rejected", AF_INET, rep_hist_get_conn_rejected(i, AF_INET)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "received", "rejected", AF_INET6, rep_hist_get_conn_rejected(i, AF_INET6)); @@ -748,21 +748,21 @@ fill_conn_gauge_values(void) if (i == 10) { continue; } - metrics_store_entry_t *sentry = - metrics_store_add(the_store, rentry->type, rentry->name, rentry->help); + metrics_store_entry_t *sentry = metrics_store_add( + the_store, rentry->type, rentry->name, rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "initiated", "opened", AF_INET, rep_hist_get_conn_opened(false, i, AF_INET)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "initiated", "opened", AF_INET6, rep_hist_get_conn_opened(false, i, AF_INET6)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "received", "opened", AF_INET, rep_hist_get_conn_opened(true, i, AF_INET)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); fill_single_connection_value(sentry, i, "received", "opened", AF_INET6, rep_hist_get_conn_opened(true, i, AF_INET6)); } @@ -777,7 +777,7 @@ fill_tcp_exhaustion_values(void) &base_metrics[RELAY_METRICS_NUM_TCP_EXHAUSTION]; sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_update(sentry, rep_hist_get_n_tcp_exhaustion()); } @@ -835,7 +835,7 @@ fill_dns_error_values(void) for (size_t j = 0; j < num_errors; j++) { sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, record_label); metrics_store_entry_add_label(sentry, metrics_format_label("reason", errors[j].name)); @@ -849,7 +849,7 @@ fill_dns_error_values(void) /* Put in the DNS errors, unfortunately not per-type for now. */ for (size_t j = 0; j < num_errors; j++) { sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("reason", errors[j].name)); metrics_store_entry_update(sentry, @@ -873,7 +873,7 @@ fill_dns_query_values(void) char *record_label = tor_strdup(metrics_format_label("record", dns_types[i].name)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, record_label); metrics_store_entry_update(sentry, rep_hist_get_n_dns_request(dns_types[i].type)); @@ -882,7 +882,7 @@ fill_dns_query_values(void) #endif sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_update(sentry, rep_hist_get_n_dns_request(0)); } @@ -895,13 +895,13 @@ fill_global_bw_limit_values(void) &base_metrics[RELAY_METRICS_NUM_GLOBAL_RW_LIMIT]; sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("side", "read")); metrics_store_entry_update(sentry, rep_hist_get_n_read_limit_reached()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("side", "write")); metrics_store_entry_update(sentry, rep_hist_get_n_write_limit_reached()); @@ -916,13 +916,13 @@ fill_socket_values(void) &base_metrics[RELAY_METRICS_NUM_SOCKETS]; sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("state", "opened")); metrics_store_entry_update(sentry, get_n_open_sockets()); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_update(sentry, get_max_sockets()); } @@ -940,7 +940,7 @@ fill_onionskins_values(void) char *type_label = tor_strdup(metrics_format_label("type", handshake_type_to_str(t))); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, type_label); metrics_store_entry_add_label(sentry, metrics_format_label("action", "processed")); @@ -948,7 +948,7 @@ fill_onionskins_values(void) rep_hist_get_circuit_n_handshake_assigned(t)); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, type_label); metrics_store_entry_add_label(sentry, metrics_format_label("action", "dropped")); @@ -967,25 +967,25 @@ fill_oom_values(void) &base_metrics[RELAY_METRICS_NUM_OOM_BYTES]; sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("subsys", "cell")); metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_cell); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("subsys", "dns")); metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_dns); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("subsys", "geoip")); metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_geoip); sentry = metrics_store_add(the_store, rentry->type, rentry->name, - rentry->help); + rentry->help, 0, NULL); metrics_store_entry_add_label(sentry, metrics_format_label("subsys", "hsdir")); metrics_store_entry_update(sentry, oom_stats_n_bytes_removed_hsdir); diff --git a/src/lib/metrics/metrics_common.c b/src/lib/metrics/metrics_common.c index f3f7e22d88..5836a581f1 100644 --- a/src/lib/metrics/metrics_common.c +++ b/src/lib/metrics/metrics_common.c @@ -24,6 +24,8 @@ metrics_type_to_str(const metrics_type_t type) return "counter"; case METRICS_TYPE_GAUGE: return "gauge"; + case METRICS_TYPE_HISTOGRAM: + return "histogram"; default: tor_assert_unreached(); } diff --git a/src/lib/metrics/metrics_common.h b/src/lib/metrics/metrics_common.h index 3644ad3d50..6c8e8bdbdc 100644 --- a/src/lib/metrics/metrics_common.h +++ b/src/lib/metrics/metrics_common.h @@ -10,6 +10,7 @@ #define TOR_LIB_METRICS_METRICS_COMMON_H #include "lib/cc/torint.h" +#include "lib/container/smartlist.h" /** Helper macro that must be used to construct the right namespaced metrics * name. A name is a string so stringify the result. */ @@ -28,8 +29,18 @@ typedef enum { METRICS_TYPE_COUNTER, /* Can go up or down. */ METRICS_TYPE_GAUGE, + /* Cumulative counters for multiple observation buckets. */ + METRICS_TYPE_HISTOGRAM, } metrics_type_t; +typedef struct metrics_histogram_bucket_t { + /* The value of the counter of this bucket. */ + uint64_t value; + /* Technically, this should be a floating point value, but in practice, we + * can make do with integer buckets. */ + int64_t bucket; +} metrics_histogram_bucket_t; + /** Metric counter object (METRICS_TYPE_COUNTER). */ typedef struct metrics_counter_t { uint64_t value; @@ -40,6 +51,18 @@ typedef struct metrics_gauge_t { int64_t value; } metrics_gauge_t; +/** Metric histogram object (METRICS_TYPE_HISTOGRAM). */ +typedef struct metrics_histogram_t { + /* The observation buckets. */ + metrics_histogram_bucket_t *buckets; + /* The number of observation buckets. */ + size_t bucket_count; + /* The sum of all observations */ + int64_t sum; + /* The total number of observations */ + uint64_t count; +} metrics_histogram_t; + const char *metrics_type_to_str(const metrics_type_t type); /* Helpers. */ diff --git a/src/lib/metrics/metrics_store.c b/src/lib/metrics/metrics_store.c index b017e97688..db80ca029b 100644 --- a/src/lib/metrics/metrics_store.c +++ b/src/lib/metrics/metrics_store.c @@ -107,7 +107,9 @@ metrics_store_get_all(const metrics_store_t *store, const char *name) * unique identifier. The help string can be omitted. */ metrics_store_entry_t * metrics_store_add(metrics_store_t *store, metrics_type_t type, - const char *name, const char *help) + const char *name, const char *help, size_t bucket_count, + const int64_t *buckets) + { smartlist_t *entries; metrics_store_entry_t *entry; @@ -120,7 +122,7 @@ metrics_store_add(metrics_store_t *store, metrics_type_t type, entries = smartlist_new(); strmap_set(store->entries, name, entries); } - entry = metrics_store_entry_new(type, name, help); + entry = metrics_store_entry_new(type, name, help, bucket_count, buckets); smartlist_add(entries, entry); return entry; diff --git a/src/lib/metrics/metrics_store.h b/src/lib/metrics/metrics_store.h index d85f484bd6..d06c87303c 100644 --- a/src/lib/metrics/metrics_store.h +++ b/src/lib/metrics/metrics_store.h @@ -26,8 +26,10 @@ metrics_store_t *metrics_store_new(void); /* Modifiers. */ metrics_store_entry_t *metrics_store_add(metrics_store_t *store, - metrics_type_t type, - const char *name, const char *help); + metrics_type_t type, const char *name, + const char *help, size_t bucket_count, + const int64_t *buckets); + void metrics_store_reset(metrics_store_t *store); /* Accessors. */ diff --git a/src/lib/metrics/metrics_store_entry.c b/src/lib/metrics/metrics_store_entry.c index 971d9379bd..649fd660c3 100644 --- a/src/lib/metrics/metrics_store_entry.c +++ b/src/lib/metrics/metrics_store_entry.c @@ -6,6 +6,7 @@ * @brief Metrics store entry which contains the gathered data. **/ +#include "metrics_common.h" #define METRICS_STORE_ENTRY_PRIVATE #include @@ -22,10 +23,11 @@ * Public API. */ -/** Return newly allocated store entry of type COUNTER. */ +/** Return newly allocated store entry of the specified type. */ metrics_store_entry_t * metrics_store_entry_new(const metrics_type_t type, const char *name, - const char *help) + const char *help, size_t bucket_count, + const int64_t *buckets) { metrics_store_entry_t *entry = tor_malloc_zero(sizeof(*entry)); @@ -38,6 +40,18 @@ metrics_store_entry_new(const metrics_type_t type, const char *name, entry->help = tor_strdup(help); } + if (type == METRICS_TYPE_HISTOGRAM && bucket_count > 0) { + tor_assert(buckets); + + entry->u.histogram.bucket_count = bucket_count; + entry->u.histogram.buckets = + tor_malloc_zero(sizeof(metrics_histogram_bucket_t) * bucket_count); + + for (size_t i = 0; i < bucket_count; ++i) { + entry->u.histogram.buckets[i].bucket = buckets[i]; + } + } + return entry; } @@ -52,6 +66,11 @@ metrics_store_entry_free_(metrics_store_entry_t *entry) smartlist_free(entry->labels); tor_free(entry->name); tor_free(entry->help); + + if (entry->type == METRICS_TYPE_HISTOGRAM) { + tor_free(entry->u.histogram.buckets); + } + tor_free(entry); } @@ -61,6 +80,11 @@ metrics_store_entry_update(metrics_store_entry_t *entry, const int64_t value) { tor_assert(entry); + /* Histogram values are updated using metrics_store_hist_entry_update */ + if (BUG(entry->type == METRICS_TYPE_HISTOGRAM)) { + return; + } + switch (entry->type) { case METRICS_TYPE_COUNTER: /* Counter can ONLY be positive. */ @@ -73,6 +97,43 @@ metrics_store_entry_update(metrics_store_entry_t *entry, const int64_t value) /* Gauge can increment or decrement. And can be positive or negative. */ entry->u.gauge.value += value; break; + case METRICS_TYPE_HISTOGRAM: + tor_assert_unreached(); + } +} + +/** Update a store entry with value for the specified observation obs. + * + * Note: entry **must** be a histogram. */ +void +metrics_store_hist_entry_update(metrics_store_entry_t *entry, + const int64_t value, const int64_t obs) +{ + if (BUG(entry->type != METRICS_TYPE_HISTOGRAM)) { + return; + } + + /* Counter can ONLY be positive for histograms. */ + if (BUG(value < 0)) { + return; + } + + /* If we're about to overflow or underflow the sum, reset all counters back + * to 0 before recording the observation. */ + if (PREDICT_UNLIKELY( + (obs > 0 && entry->u.histogram.sum > INT64_MAX - obs) || + (obs < 0 && entry->u.histogram.sum < INT64_MIN - obs))) { + metrics_store_entry_reset(entry); + } + + entry->u.histogram.count += value; + entry->u.histogram.sum += obs; + + for (size_t i = 0; i < entry->u.histogram.bucket_count; ++i) { + metrics_histogram_bucket_t *hb = &entry->u.histogram.buckets[i]; + if (obs <= hb->bucket) { + hb->value += value; + } } } @@ -81,8 +142,22 @@ void metrics_store_entry_reset(metrics_store_entry_t *entry) { tor_assert(entry); - /* Everything back to 0. */ - memset(&entry->u, 0, sizeof(entry->u)); + + switch (entry->type) { + case METRICS_TYPE_COUNTER: FALLTHROUGH; + case METRICS_TYPE_GAUGE: + /* Everything back to 0. */ + memset(&entry->u, 0, sizeof(entry->u)); + break; + case METRICS_TYPE_HISTOGRAM: + for (size_t i = 0; i < entry->u.histogram.bucket_count; ++i) { + metrics_histogram_bucket_t *hb = &entry->u.histogram.buckets[i]; + hb->value = 0; + } + entry->u.histogram.sum = 0; + entry->u.histogram.count = 0; + break; + } } /** Return store entry value. */ @@ -91,6 +166,11 @@ metrics_store_entry_get_value(const metrics_store_entry_t *entry) { tor_assert(entry); + /* Histogram values are accessed using metrics_store_hist_entry_get_value. */ + if (BUG(entry->type == METRICS_TYPE_HISTOGRAM)) { + return 0; + } + switch (entry->type) { case METRICS_TYPE_COUNTER: if (entry->u.counter.value > INT64_MAX) { @@ -99,6 +179,9 @@ metrics_store_entry_get_value(const metrics_store_entry_t *entry) return entry->u.counter.value; case METRICS_TYPE_GAUGE: return entry->u.gauge.value; + case METRICS_TYPE_HISTOGRAM: + tor_assert_unreached(); + return 0; } // LCOV_EXCL_START @@ -106,6 +189,35 @@ metrics_store_entry_get_value(const metrics_store_entry_t *entry) // LCOV_EXCL_STOP } +/** Return store entry value for the specified bucket. + * + * Note: entry **must** be a histogram. */ +uint64_t +metrics_store_hist_entry_get_value(const metrics_store_entry_t *entry, + const int64_t bucket) +{ + tor_assert(entry); + + if (BUG(entry->type != METRICS_TYPE_HISTOGRAM)) { + return 0; + } + + for (size_t i = 0; i <= entry->u.histogram.bucket_count; ++i) { + metrics_histogram_bucket_t hb = entry->u.histogram.buckets[i]; + if (bucket == hb.bucket) { + if (hb.value > INT64_MAX) { + return INT64_MAX; + } else { + return hb.value; + } + } + } + + tor_assertf_nonfatal(false, "attempted to get the value of non-existent " + "bucket %" PRId64, bucket); + return 0; +} + /** Add a label into the given entry.*/ void metrics_store_entry_add_label(metrics_store_entry_t *entry, @@ -147,3 +259,40 @@ metrics_store_find_entry_with_label(const smartlist_t *entries, return NULL; } + +/** Return true iff the specified entry is a histogram. */ +bool +metrics_store_entry_is_histogram(const metrics_store_entry_t *entry) +{ + if (entry->type == METRICS_TYPE_HISTOGRAM) { + return true; + } + + return false; +} + +/** Return the total number of observations for the specified histogram. */ +uint64_t +metrics_store_hist_entry_get_count(const metrics_store_entry_t *entry) +{ + tor_assert(entry); + + if (BUG(entry->type != METRICS_TYPE_HISTOGRAM)) { + return 0; + } + + return entry->u.histogram.count; +} + +/** Return the sum of all observations for the specified histogram. */ +int64_t +metrics_store_hist_entry_get_sum(const metrics_store_entry_t *entry) +{ + tor_assert(entry); + + if (BUG(entry->type != METRICS_TYPE_HISTOGRAM)) { + return 0; + } + + return entry->u.histogram.sum; +} diff --git a/src/lib/metrics/metrics_store_entry.h b/src/lib/metrics/metrics_store_entry.h index 0e09e099fe..53fa437406 100644 --- a/src/lib/metrics/metrics_store_entry.h +++ b/src/lib/metrics/metrics_store_entry.h @@ -38,6 +38,7 @@ struct metrics_store_entry_t { union { metrics_counter_t counter; metrics_gauge_t gauge; + metrics_histogram_t histogram; } u; }; @@ -48,7 +49,9 @@ typedef struct metrics_store_entry_t metrics_store_entry_t; /* Allocators. */ metrics_store_entry_t *metrics_store_entry_new(const metrics_type_t type, const char *name, - const char *help); + const char *help, + size_t bucket_count, + const int64_t *buckets); void metrics_store_entry_free_(metrics_store_entry_t *entry); #define metrics_store_entry_free(entry) \ @@ -56,10 +59,16 @@ void metrics_store_entry_free_(metrics_store_entry_t *entry); /* Accessors. */ int64_t metrics_store_entry_get_value(const metrics_store_entry_t *entry); +uint64_t metrics_store_hist_entry_get_value(const metrics_store_entry_t *entry, + const int64_t bucket); bool metrics_store_entry_has_label(const metrics_store_entry_t *entry, const char *label); metrics_store_entry_t *metrics_store_find_entry_with_label( const smartlist_t *entries, const char *label); +bool metrics_store_entry_is_histogram(const metrics_store_entry_t *entry); +uint64_t metrics_store_hist_entry_get_count( + const metrics_store_entry_t *entry); +int64_t metrics_store_hist_entry_get_sum(const metrics_store_entry_t *entry); /* Modifiers. */ void metrics_store_entry_add_label(metrics_store_entry_t *entry, @@ -67,5 +76,7 @@ void metrics_store_entry_add_label(metrics_store_entry_t *entry, void metrics_store_entry_reset(metrics_store_entry_t *entry); void metrics_store_entry_update(metrics_store_entry_t *entry, const int64_t value); +void metrics_store_hist_entry_update(metrics_store_entry_t *entry, + const int64_t value, const int64_t obs); #endif /* !defined(TOR_LIB_METRICS_METRICS_STORE_ENTRY_H) */ diff --git a/src/lib/metrics/prometheus.c b/src/lib/metrics/prometheus.c index aac23ac92e..2f98f8ebb6 100644 --- a/src/lib/metrics/prometheus.c +++ b/src/lib/metrics/prometheus.c @@ -17,6 +17,8 @@ #include "lib/metrics/prometheus.h" +#include + /** Return a static buffer containing all the labels properly formatted * for the output as a string. * @@ -33,13 +35,54 @@ format_labels(smartlist_t *labels) } line = smartlist_join_strings(labels, ",", 0, NULL); - tor_snprintf(buf, sizeof(buf), "{%s}", line); + tor_snprintf(buf, sizeof(buf), "%s", line); end: tor_free(line); return buf; } +/** Write the string representation of the histogram entry to the specified + * buffer. + * + * Note: entry **must** be a histogram. + */ +static void +format_histogram(const metrics_store_entry_t *entry, buf_t *data) +{ + tor_assert(entry->type == METRICS_TYPE_HISTOGRAM); + + const char *labels = format_labels(entry->labels); + + for (size_t i = 0; i < entry->u.histogram.bucket_count; ++i) { + metrics_histogram_bucket_t hb = entry->u.histogram.buckets[i]; + if (strlen(labels) > 0) { + buf_add_printf(data, "%s_bucket{%s,le=\"%.2f\"} %" PRIi64 "\n", + entry->name, labels, (double)hb.bucket, hb.value); + } else { + buf_add_printf(data, "%s_bucket{le=\"%.2f\"} %" PRIi64 "\n", + entry->name, (double)hb.bucket, hb.value); + } + } + + if (strlen(labels) > 0) { + buf_add_printf(data, "%s_bucket{%s,le=\"+Inf\"} %" PRIi64 "\n", + entry->name, labels, + metrics_store_hist_entry_get_count(entry)); + buf_add_printf(data, "%s_sum{%s} %" PRIi64 "\n", entry->name, labels, + metrics_store_hist_entry_get_sum(entry)); + buf_add_printf(data, "%s_count{%s} %" PRIi64 "\n", entry->name, labels, + metrics_store_hist_entry_get_count(entry)); + } else { + buf_add_printf(data, "%s_bucket{le=\"+Inf\"} %" PRIi64 "\n", entry->name, + metrics_store_hist_entry_get_count(entry)); + buf_add_printf(data, "%s_sum %" PRIi64 "\n", entry->name, + metrics_store_hist_entry_get_sum(entry)); + buf_add_printf(data, "%s_count %" PRIi64 "\n", entry->name, + metrics_store_hist_entry_get_count(entry)); + } +} + /** Format the given entry in to the buffer data. */ void prometheus_format_store_entry(const metrics_store_entry_t *entry, buf_t *data, @@ -53,7 +96,26 @@ prometheus_format_store_entry(const metrics_store_entry_t *entry, buf_t *data, buf_add_printf(data, "# TYPE %s %s\n", entry->name, metrics_type_to_str(entry->type)); } - buf_add_printf(data, "%s%s %" PRIi64 "\n", entry->name, - format_labels(entry->labels), - metrics_store_entry_get_value(entry)); + + switch (entry->type) { + case METRICS_TYPE_COUNTER: FALLTHROUGH; + case METRICS_TYPE_GAUGE: + { + const char *labels = format_labels(entry->labels); + if (strlen(labels) > 0) { + buf_add_printf(data, "%s{%s} %" PRIi64 "\n", entry->name, + labels, + metrics_store_entry_get_value(entry)); + } else { + buf_add_printf(data, "%s %" PRIi64 "\n", entry->name, + metrics_store_entry_get_value(entry)); + } + break; + } + case METRICS_TYPE_HISTOGRAM: + format_histogram(entry, data); + break; + default: + tor_assert_unreached(); + } } diff --git a/src/test/test_metrics.c b/src/test/test_metrics.c index ba1a763f0c..0bf072dbfc 100644 --- a/src/test/test_metrics.c +++ b/src/test/test_metrics.c @@ -28,11 +28,16 @@ #include "lib/encoding/confline.h" #include "lib/metrics/metrics_store.h" +#include + #define TEST_METRICS_ENTRY_NAME "entryA" #define TEST_METRICS_ENTRY_HELP "Description of entryA" #define TEST_METRICS_ENTRY_LABEL_1 "label=\"farfadet\"" #define TEST_METRICS_ENTRY_LABEL_2 "label=\"ponki\"" +#define TEST_METRICS_HIST_ENTRY_NAME "test_hist_entry" +#define TEST_METRICS_HIST_ENTRY_HELP "Description of test_hist_entry" + static void set_metrics_port(or_options_t *options) { @@ -189,7 +194,8 @@ test_prometheus(void *arg) /* Add entry and validate its content. */ entry = metrics_store_add(store, METRICS_TYPE_COUNTER, TEST_METRICS_ENTRY_NAME, - TEST_METRICS_ENTRY_HELP); + TEST_METRICS_ENTRY_HELP, + 0, NULL); tt_assert(entry); metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); @@ -208,11 +214,61 @@ test_prometheus(void *arg) metrics_store_free(store); } +static void +test_prometheus_histogram(void *arg) +{ + metrics_store_t *store = NULL; + metrics_store_entry_t *entry = NULL; + buf_t *buf = buf_new(); + char *output = NULL; + const int64_t buckets[] = { 10, 20, 3000 }; + + (void) arg; + + /* Fresh new store. No entries. */ + store = metrics_store_new(); + tt_assert(store); + + /* Add a histogram entry and validate its content. */ + entry = metrics_store_add(store, METRICS_TYPE_HISTOGRAM, + TEST_METRICS_HIST_ENTRY_NAME, + TEST_METRICS_HIST_ENTRY_HELP, + ARRAY_LENGTH(buckets), buckets); + tt_assert(entry); + metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1); + + static const char *expected = + "# HELP " TEST_METRICS_HIST_ENTRY_NAME " " + TEST_METRICS_HIST_ENTRY_HELP "\n" + "# TYPE " TEST_METRICS_HIST_ENTRY_NAME " histogram\n" + TEST_METRICS_HIST_ENTRY_NAME "_bucket{" + TEST_METRICS_ENTRY_LABEL_1 ",le=\"10.00\"} 0\n" + TEST_METRICS_HIST_ENTRY_NAME "_bucket{" + TEST_METRICS_ENTRY_LABEL_1 ",le=\"20.00\"} 0\n" + TEST_METRICS_HIST_ENTRY_NAME "_bucket{" + TEST_METRICS_ENTRY_LABEL_1 ",le=\"3000.00\"} 0\n" + TEST_METRICS_HIST_ENTRY_NAME "_bucket{" + TEST_METRICS_ENTRY_LABEL_1 ",le=\"+Inf\"} 0\n" + TEST_METRICS_HIST_ENTRY_NAME "_sum{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n" + TEST_METRICS_HIST_ENTRY_NAME "_count{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n"; + + metrics_store_get_output(METRICS_FORMAT_PROMETHEUS, store, buf); + output = buf_extract(buf, NULL); + tt_str_op(expected, OP_EQ, output); + + done: + buf_free(buf); + tor_free(output); + metrics_store_free(store); +} + static void test_store(void *arg) { metrics_store_t *store = NULL; metrics_store_entry_t *entry = NULL; + const int64_t buckets[] = { 10, 20, 3000 }; + const size_t bucket_count = ARRAY_LENGTH(buckets); (void) arg; @@ -224,7 +280,7 @@ test_store(void *arg) /* Add entry and validate its content. */ entry = metrics_store_add(store, METRICS_TYPE_COUNTER, TEST_METRICS_ENTRY_NAME, - TEST_METRICS_ENTRY_HELP); + TEST_METRICS_ENTRY_HELP, 0, NULL); tt_assert(entry); tt_int_op(entry->type, OP_EQ, METRICS_TYPE_COUNTER); tt_str_op(entry->name, OP_EQ, TEST_METRICS_ENTRY_NAME); @@ -251,7 +307,7 @@ test_store(void *arg) /* Add entry and validate its content. */ entry = metrics_store_add(store, METRICS_TYPE_COUNTER, TEST_METRICS_ENTRY_NAME, - TEST_METRICS_ENTRY_HELP); + TEST_METRICS_ENTRY_HELP, 0, NULL); tt_assert(entry); metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_2); @@ -261,6 +317,89 @@ test_store(void *arg) tt_assert(entries); tt_int_op(smartlist_len(entries), OP_EQ, 2); + /* Add a histogram entry and validate its content. */ + entry = metrics_store_add(store, METRICS_TYPE_HISTOGRAM, + TEST_METRICS_HIST_ENTRY_NAME, + TEST_METRICS_HIST_ENTRY_HELP, + bucket_count, buckets); + + tt_assert(entry); + tt_int_op(entry->type, OP_EQ, METRICS_TYPE_HISTOGRAM); + tt_str_op(entry->name, OP_EQ, TEST_METRICS_HIST_ENTRY_NAME); + tt_str_op(entry->help, OP_EQ, TEST_METRICS_HIST_ENTRY_HELP); + tt_uint_op(entry->u.histogram.bucket_count, OP_EQ, bucket_count); + + for (size_t i = 0; i < bucket_count; ++i) { + tt_uint_op(entry->u.histogram.buckets[i].bucket, OP_EQ, buckets[i]); + tt_uint_op(entry->u.histogram.buckets[i].value, OP_EQ, 0); + } + + /* Access the entry. */ + tt_assert(metrics_store_get_all(store, TEST_METRICS_HIST_ENTRY_NAME)); + + /* Record various observations. */ + metrics_store_hist_entry_update(entry, 3, 11); + tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 0); + tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 3); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 3); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 3); + tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 3); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, 11); + + metrics_store_hist_entry_update(entry, 1, 42); + tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 0); + tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 3); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 4); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 4); + tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 4); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, 53); + + /* Ensure this resets all buckets back to 0. */ + metrics_store_entry_reset(entry); + for (size_t i = 0; i < bucket_count; ++i) { + tt_uint_op(entry->u.histogram.buckets[i].bucket, OP_EQ, buckets[i]); + tt_uint_op(entry->u.histogram.buckets[i].value, OP_EQ, 0); + } + + /* tt_int_op assigns the third argument to a variable of type long, which + * overflows on some platforms (e.g. on some 32-bit systems). We disable + * these checks for those platforms. */ +#if LONG_MAX >= INT64_MAX + metrics_store_hist_entry_update(entry, 1, INT64_MAX - 13); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MAX - 13); + metrics_store_hist_entry_update(entry, 1, 13); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MAX); + /* Uh-oh, the sum of all observations is now greater than INT64_MAX. Make + * sure we reset the entry instead of overflowing the sum. */ + metrics_store_hist_entry_update(entry, 1, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, 1); +#endif + +#if LONG_MIN <= INT64_MIN + metrics_store_entry_reset(entry); + /* In practice, we're not going to have negative observations (as we only use + * histograms for timings, which are always positive), but technically + * prometheus _does_ support negative observations. */ + metrics_store_hist_entry_update(entry, 1, INT64_MIN + 13); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MIN + 13); + metrics_store_hist_entry_update(entry, 1, -13); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, INT64_MIN); + /* Uh-oh, the sum of all observations is now less than INT64_MIN. Make + * sure we reset the entry instead of underflowing the sum. */ + metrics_store_hist_entry_update(entry, 1, -1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 10), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 20), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_value(entry, 3000), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_count(entry), OP_EQ, 1); + tt_int_op(metrics_store_hist_entry_get_sum(entry), OP_EQ, -1); +#endif + done: metrics_store_free(store); } @@ -270,6 +409,7 @@ struct testcase_t metrics_tests[] = { { "config", test_config, TT_FORK, NULL, NULL }, { "connection", test_connection, TT_FORK, NULL, NULL }, { "prometheus", test_prometheus, TT_FORK, NULL, NULL }, + { "prometheus_histogram", test_prometheus_histogram, TT_FORK, NULL, NULL }, { "store", test_store, TT_FORK, NULL, NULL }, END_OF_TESTCASES