[tor-commits] [tor] 02/04: metrics: Add support for histograms.

gitolite role git at cupani.torproject.org
Mon Mar 13 15:23:52 UTC 2023


This is an automated email from the git hooks/post-receive script.

dgoulet pushed a commit to branch main
in repository tor.

commit d1264d11c3320b8a28c3715acca8a8ace1d70569
Author: Gabriela Moldovan <gabi at torproject.org>
AuthorDate: Wed Mar 8 17:51:58 2023 +0000

    metrics: Add support for histograms.
    
    This will enable us to add e.g. circuit build metrics (#40717).
    
    Signed-off-by: Gabriela Moldovan <gabi at torproject.org>
---
 changes/ticket40757                   |   3 +
 src/feature/hs/hs_metrics.c           |   7 +-
 src/feature/hs/hs_metrics_entry.h     |   4 +
 src/feature/relay/relay_metrics.c     | 170 +++++++++++++++++-----------------
 src/lib/metrics/metrics_common.c      |   2 +
 src/lib/metrics/metrics_common.h      |  23 +++++
 src/lib/metrics/metrics_store.c       |   6 +-
 src/lib/metrics/metrics_store.h       |   6 +-
 src/lib/metrics/metrics_store_entry.c | 157 ++++++++++++++++++++++++++++++-
 src/lib/metrics/metrics_store_entry.h |  13 ++-
 src/lib/metrics/prometheus.c          |  70 +++++++++++++-
 src/test/test_metrics.c               | 146 ++++++++++++++++++++++++++++-
 12 files changed, 504 insertions(+), 103 deletions(-)

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 <string.h>
@@ -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 <string.h>
+
 /** 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 <limits.h>
+
 #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

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the tor-commits mailing list