From 5bc0599b90d03fcaa7b74120dbe099d1c21439d1 Mon Sep 17 00:00:00 2001 From: Bartlomiej Plotka Date: Wed, 2 Oct 2024 10:17:06 +0200 Subject: [PATCH] feat: Made sure summaries actually give output quantile objectives as well; improved flag docs and tests for complex type series calculation. (#93) Signed-off-by: bwplotka --- metrics/serve.go | 25 ++++++++++++++++++++----- metrics/serve_test.go | 37 +++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/metrics/serve.go b/metrics/serve.go index 4c8677b..e8a4c2d 100644 --- a/metrics/serve.go +++ b/metrics/serve.go @@ -81,7 +81,8 @@ func (c *Collector) UpdateNotifyCh() chan struct{} { type Config struct { MetricCount, GaugeMetricCount, CounterMetricCount, HistogramMetricCount, NativeHistogramMetricCount, SummaryMetricCount int - HistogramBuckets int + + HistogramBuckets, SummaryObjectives int LabelCount, SeriesCount int MaxSeriesCount, MinSeriesCount int @@ -103,14 +104,16 @@ func NewConfigFromFlags(flagReg func(name, help string) *kingpin.FlagClause) *Co IntVar(&cfg.GaugeMetricCount) flagReg("counter-metric-count", "Number of counter metrics to serve.").Default("200"). IntVar(&cfg.CounterMetricCount) - flagReg("histogram-metric-count", "Number of explicit (classic) histogram metrics to serve.").Default("10"). + flagReg("histogram-metric-count", "Number of explicit (classic) histogram metrics to serve. Use -histogram-metric-bucket-count to control number of buckets. Note that the overall number of series for a single classic histogram metric is equal to 2 (count and sum) + + 1 (+Inf bucket).").Default("10"). IntVar(&cfg.HistogramMetricCount) - flagReg("histogram-metric-bucket-count", "Number of explicit buckets (classic) histogram metrics.").Default("8"). + flagReg("histogram-metric-bucket-count", "Number of explicit buckets (classic) histogram metrics, excluding +Inf bucket.").Default("7"). IntVar(&cfg.HistogramBuckets) flagReg("native-histogram-metric-count", "Number of native (exponential) histogram metrics to serve.").Default("0"). IntVar(&cfg.NativeHistogramMetricCount) - flagReg("summary-metric-count", "Number of summary metrics to serve.").Default("0"). + flagReg("summary-metric-count", "Number of summary metrics to serve. Use -summary-metric-objective-count to control number of quantile objectives. Note that the overall number of series for a single summary metric is equal to 2 (count and sum) + .").Default("0"). IntVar(&cfg.SummaryMetricCount) + flagReg("summary-metric-objective-count", "Number of objectives in the summary metrics to serve.").Default("2"). + IntVar(&cfg.SummaryObjectives) flagReg("label-count", "Number of labels per-metric.").Default("10"). IntVar(&cfg.LabelCount) @@ -242,10 +245,22 @@ func (c *Collector) recreateMetrics(unsafeGetState readOnlyStateFn) { c.nativeHistograms[id] = histogram } + // Mimic some quantile objectives. + objectives := map[float64]float64{} + if c.cfg.SummaryObjectives > 0 { + parts := 100 / c.cfg.SummaryObjectives + for i := 0; i < c.cfg.SummaryObjectives; i++ { + q := parts * (i + 1) + if q == 100 { + q = 99 + } + objectives[float64(q)/100.0] = float64(100-q) / 1000.0 + } + } for id := range c.summaries { mName := fmt.Sprintf("avalanche_summary_metric_%s_%v_%v", strings.Repeat("m", c.cfg.MetricLength), s.metricCycle, id) summary := prometheus.NewSummaryVec( - prometheus.SummaryOpts{Name: mName, Help: help(mName)}, + prometheus.SummaryOpts{Name: mName, Help: help(mName), Objectives: objectives}, append([]string{"series_id", "cycle_id"}, c.labelKeys...), ) c.summaries[id] = summary diff --git a/metrics/serve_test.go b/metrics/serve_test.go index e5388a4..d789578 100644 --- a/metrics/serve_test.go +++ b/metrics/serve_test.go @@ -15,6 +15,7 @@ package metrics import ( "fmt" + "math" "strconv" "testing" "time" @@ -40,6 +41,8 @@ func countSeries(t *testing.T, registry *prometheus.Registry) (seriesCount int) return seriesCount } +// countSeriesTypes gives exact count of all types. For complex types that are represented by counters in Prometheus +// data model (and text exposition formats), we count all individual resulting series. func countSeriesTypes(t *testing.T, registry *prometheus.Registry) (gauges, counters, histograms, nhistograms, summaries int) { t.Helper() @@ -54,13 +57,20 @@ func countSeriesTypes(t *testing.T, registry *prometheus.Registry) (gauges, coun case io_prometheus_client.MetricType_COUNTER: counters++ case io_prometheus_client.MetricType_HISTOGRAM: - if len(m.GetHistogram().Bucket) == 0 { + if bkts := len(m.GetHistogram().Bucket); bkts == 0 { nhistograms++ } else { - histograms++ + histograms += 2 // count and sum. + histograms += len(m.GetHistogram().GetBucket()) + if m.GetHistogram().GetBucket()[bkts-1].GetUpperBound() != math.Inf(+1) { + // In the proto model we don't put explicit +Inf bucket, unless there is an exemplar, + // but it will appear as series in text format and Prometheus model. Account for that. + histograms++ + } } case io_prometheus_client.MetricType_SUMMARY: - summaries++ + summaries += 2 // count and sum. + summaries += len(m.GetSummary().GetQuantile()) default: t.Fatalf("unknown metric type found %v", mf.GetType()) } @@ -74,9 +84,10 @@ func TestRunMetrics(t *testing.T) { GaugeMetricCount: 200, CounterMetricCount: 200, HistogramMetricCount: 10, - HistogramBuckets: 8, + HistogramBuckets: 7, NativeHistogramMetricCount: 10, SummaryMetricCount: 10, + SummaryObjectives: 2, MinSeriesCount: 0, MaxSeriesCount: 1000, @@ -102,9 +113,9 @@ func TestRunMetrics(t *testing.T) { g, c, h, nh, s := countSeriesTypes(t, reg) assert.Equal(t, testCfg.GaugeMetricCount*testCfg.SeriesCount, g) assert.Equal(t, testCfg.CounterMetricCount*testCfg.SeriesCount, c) - assert.Equal(t, testCfg.HistogramMetricCount*testCfg.SeriesCount, h) + assert.Equal(t, (2+testCfg.HistogramBuckets+1)*testCfg.HistogramMetricCount*testCfg.SeriesCount, h) assert.Equal(t, testCfg.NativeHistogramMetricCount*testCfg.SeriesCount, nh) - assert.Equal(t, testCfg.SummaryMetricCount*testCfg.SeriesCount, s) + assert.Equal(t, (2+testCfg.SummaryObjectives)*testCfg.SummaryMetricCount*testCfg.SeriesCount, s) } func TestRunMetrics_ValueChange_SeriesCountSame(t *testing.T) { @@ -112,9 +123,10 @@ func TestRunMetrics_ValueChange_SeriesCountSame(t *testing.T) { GaugeMetricCount: 200, CounterMetricCount: 200, HistogramMetricCount: 10, - HistogramBuckets: 8, + HistogramBuckets: 7, NativeHistogramMetricCount: 10, SummaryMetricCount: 10, + SummaryObjectives: 2, MinSeriesCount: 0, MaxSeriesCount: 1000, @@ -145,9 +157,9 @@ func TestRunMetrics_ValueChange_SeriesCountSame(t *testing.T) { g, c, h, nh, s := countSeriesTypes(t, reg) assert.Equal(t, testCfg.GaugeMetricCount*testCfg.SeriesCount, g) assert.Equal(t, testCfg.CounterMetricCount*testCfg.SeriesCount, c) - assert.Equal(t, testCfg.HistogramMetricCount*testCfg.SeriesCount, h) + assert.Equal(t, (2+testCfg.HistogramBuckets+1)*testCfg.HistogramMetricCount*testCfg.SeriesCount, h) assert.Equal(t, testCfg.NativeHistogramMetricCount*testCfg.SeriesCount, nh) - assert.Equal(t, testCfg.SummaryMetricCount*testCfg.SeriesCount, s) + assert.Equal(t, (2+testCfg.SummaryObjectives)*testCfg.SummaryMetricCount*testCfg.SeriesCount, s) } } @@ -184,9 +196,10 @@ func TestRunMetrics_SeriesChurn(t *testing.T) { GaugeMetricCount: 200, CounterMetricCount: 200, HistogramMetricCount: 10, - HistogramBuckets: 8, + HistogramBuckets: 7, NativeHistogramMetricCount: 10, SummaryMetricCount: 10, + SummaryObjectives: 2, MinSeriesCount: 0, MaxSeriesCount: 1000, @@ -220,9 +233,9 @@ func TestRunMetrics_SeriesChurn(t *testing.T) { g, c, h, nh, s := countSeriesTypes(t, reg) assert.Equal(t, testCfg.GaugeMetricCount*testCfg.SeriesCount, g) assert.Equal(t, testCfg.CounterMetricCount*testCfg.SeriesCount, c) - assert.Equal(t, testCfg.HistogramMetricCount*testCfg.SeriesCount, h) + assert.Equal(t, (2+testCfg.HistogramBuckets+1)*testCfg.HistogramMetricCount*testCfg.SeriesCount, h) assert.Equal(t, testCfg.NativeHistogramMetricCount*testCfg.SeriesCount, nh) - assert.Equal(t, testCfg.SummaryMetricCount*testCfg.SeriesCount, s) + assert.Equal(t, (2+testCfg.SummaryObjectives)*testCfg.SummaryMetricCount*testCfg.SeriesCount, s) gotCycleID := currentCycleID(t, reg) require.Greater(t, gotCycleID, cycleID)