Skip to content

Commit 51498ff

Browse files
authored
feat: Add native histogram support (#131)
This commit add the support for prometheus native histogram in place of classic histograms See: https://prometheus.io/docs/specs/native_histograms/ This feature is still experimental so disabled by default. Signed-off-by: julien.girard <[email protected]>
1 parent 0b35444 commit 51498ff

File tree

4 files changed

+138
-13
lines changed

4 files changed

+138
-13
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Inspired by [github.com/zsais/go-gin-prometheus](https://github.com/zsais/go-gin
3434
- [Ignore](#ignore)
3535
- [Token](#token)
3636
- [Bucket size](#bucket-size)
37+
- [Native histogram](#native-histogram)
3738
- [Troubleshooting](#troubleshooting)
3839
- [The instrumentation doesn't seem to work](#the-instrumentation-doesnt-seem-to-work)
3940

@@ -324,6 +325,28 @@ p := ginprom.New(
324325
r.Use(p.Instrument())
325326
```
326327

328+
### Native histogram
329+
330+
Configure ginprom to use native histogram instead of classical histograms.
331+
Refers to: https://prometheus.io/docs/specs/native_histograms/
332+
333+
Default values:
334+
- BucketFactor : 1.1
335+
- MaxBucketNumber: 100
336+
- MinResetDuration : 1 Hour
337+
338+
```go
339+
r := gin.New()
340+
p := ginprom.New(
341+
ginprom.Engine(r),
342+
ginprom.NativeHistogram(true),
343+
ginprom.NativeHistogramBucketFactor(1.1),
344+
ginprom.NativeHistogramMaxBucketNumber(100),
345+
ginprom.NativeHistogramMinResetDuration(1 * time.Hour),
346+
)
347+
r.Use(p.Instrument())
348+
```
349+
327350
## Troubleshooting
328351

329352
### The instrumentation doesn't seem to work

options.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package ginprom
22

33
import (
4+
"time"
5+
46
"github.com/gin-gonic/gin"
57
"github.com/prometheus/client_golang/prometheus"
68
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -158,3 +160,27 @@ func CustomCounterLabels(labels []string, f func(c *gin.Context) map[string]stri
158160
p.customCounterLabels = labels
159161
}
160162
}
163+
164+
func NativeHistogram(nh bool) PrometheusOption {
165+
return func(p *Prometheus) {
166+
p.nativeHistogram = nh
167+
}
168+
}
169+
170+
func NativeHistogramBucketFactor(nhbf float64) PrometheusOption {
171+
return func(p *Prometheus) {
172+
p.NativeHistogramBucketFactor = nhbf
173+
}
174+
}
175+
176+
func NativeHistogramMaxBucketNumber(nhmbn uint32) PrometheusOption {
177+
return func(p *Prometheus) {
178+
p.NativeHistogramMaxBucketNumber = nhmbn
179+
}
180+
}
181+
182+
func NativeHistogramMinResetDuration(nhmrd time.Duration) PrometheusOption {
183+
return func(p *Prometheus) {
184+
p.NativeHistogramMinResetDuration = nhmrd
185+
}
186+
}

prom.go

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type Prometheus struct {
6767
customCounterLabelsProvider func(c *gin.Context) map[string]string
6868
customCounterLabels []string
6969
customHistograms pmapHistogram
70+
nativeHistogram bool
7071

7172
MetricsPath string
7273
Namespace string
@@ -80,6 +81,10 @@ type Prometheus struct {
8081
RequestPathFunc func(c *gin.Context) string
8182
HandlerOpts promhttp.HandlerOpts
8283

84+
NativeHistogramBucketFactor float64
85+
NativeHistogramMaxBucketNumber uint32
86+
NativeHistogramMinResetDuration time.Duration
87+
8388
RequestCounterMetricName string
8489
RequestDurationMetricName string
8590
RequestSizeMetricName string
@@ -224,12 +229,29 @@ func (p *Prometheus) AddCustomHistogramValue(name string, labelValues []string,
224229
func (p *Prometheus) AddCustomHistogram(name, help string, labels []string) {
225230
p.customHistograms.Lock()
226231
defer p.customHistograms.Unlock()
227-
g := prometheus.NewHistogramVec(prometheus.HistogramOpts{
228-
Namespace: p.Namespace,
229-
Subsystem: p.Subsystem,
230-
Name: name,
231-
Help: help,
232-
}, labels)
232+
233+
var reqDurOpts prometheus.HistogramOpts
234+
if p.nativeHistogram {
235+
reqDurOpts = prometheus.HistogramOpts{
236+
Namespace: p.Namespace,
237+
Subsystem: p.Subsystem,
238+
NativeHistogramBucketFactor: p.NativeHistogramBucketFactor,
239+
NativeHistogramMaxBucketNumber: p.NativeHistogramMaxBucketNumber,
240+
NativeHistogramMinResetDuration: p.NativeHistogramMinResetDuration,
241+
Name: name,
242+
Help: help,
243+
}
244+
245+
} else {
246+
reqDurOpts = prometheus.HistogramOpts{
247+
Namespace: p.Namespace,
248+
Subsystem: p.Subsystem,
249+
Name: name,
250+
Help: help,
251+
}
252+
}
253+
254+
g := prometheus.NewHistogramVec(reqDurOpts, labels)
233255
p.customHistograms.values[name] = *g
234256
p.mustRegister(g)
235257
}
@@ -254,6 +276,11 @@ func New(options ...PrometheusOption) *Prometheus {
254276
RequestDurationMetricName: defaultReqDurMetricName,
255277
RequestSizeMetricName: defaultReqSzMetricName,
256278
ResponseSizeMetricName: defaultResSzMetricName,
279+
nativeHistogram: false,
280+
// Grafana Mimir recommended parameters: https://grafana.com/docs/mimir/latest/send/native-histograms/
281+
NativeHistogramBucketFactor: 1.1,
282+
NativeHistogramMaxBucketNumber: 100,
283+
NativeHistogramMinResetDuration: 1 * time.Hour,
257284
}
258285
p.customGauges.values = make(map[string]prometheus.GaugeVec)
259286
p.customCounters.values = make(map[string]prometheus.CounterVec)
@@ -292,13 +319,29 @@ func (p *Prometheus) register() {
292319
)
293320
p.mustRegister(p.reqCnt)
294321

295-
p.reqDur = prometheus.NewHistogramVec(prometheus.HistogramOpts{
296-
Namespace: p.Namespace,
297-
Subsystem: p.Subsystem,
298-
Buckets: p.BucketsSize,
299-
Name: p.RequestDurationMetricName,
300-
Help: "The HTTP request latency bucket.",
301-
}, []string{"method", "path", "host"})
322+
var reqDurOpts prometheus.HistogramOpts
323+
if p.nativeHistogram {
324+
reqDurOpts = prometheus.HistogramOpts{
325+
Namespace: p.Namespace,
326+
Subsystem: p.Subsystem,
327+
NativeHistogramBucketFactor: p.NativeHistogramBucketFactor,
328+
NativeHistogramMaxBucketNumber: p.NativeHistogramMaxBucketNumber,
329+
NativeHistogramMinResetDuration: p.NativeHistogramMinResetDuration,
330+
Name: p.RequestDurationMetricName,
331+
Help: "The HTTP request latency bucket.",
332+
}
333+
334+
} else {
335+
reqDurOpts = prometheus.HistogramOpts{
336+
Namespace: p.Namespace,
337+
Subsystem: p.Subsystem,
338+
Buckets: p.BucketsSize,
339+
Name: p.RequestDurationMetricName,
340+
Help: "The HTTP request latency bucket.",
341+
}
342+
}
343+
344+
p.reqDur = prometheus.NewHistogramVec(reqDurOpts, []string{"method", "path", "host"})
302345
p.mustRegister(p.reqDur)
303346

304347
p.reqSz = prometheus.NewSummary(

prom_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/gin-gonic/gin"
1212
"github.com/prometheus/client_golang/prometheus"
1313
"github.com/prometheus/client_golang/prometheus/promhttp"
14+
io_prometheus_client "github.com/prometheus/client_model/go"
1415
"github.com/stretchr/testify/assert"
1516
)
1617

@@ -495,6 +496,38 @@ func TestCustomHistogram(t *testing.T) {
495496
})
496497
}
497498

499+
func TestCustomNativeHistogram(t *testing.T) {
500+
r := gin.New()
501+
registry := prometheus.NewRegistry()
502+
p := New(Engine(r), Registry(registry), NativeHistogram(true))
503+
p.AddCustomHistogram("custom_histogram", "test histogram", []string{"url", "method"})
504+
r.Use(p.Instrument())
505+
defer unregister(p)
506+
507+
err := p.AddCustomHistogramValue("custom_histogram", []string{"http://example.com/status", "GET"}, 0.45)
508+
assert.Nil(t, err)
509+
510+
mfs, err := registry.Gather()
511+
assert.Nil(t, err)
512+
513+
found := false
514+
515+
for _, mf := range mfs {
516+
if mf.GetType() == io_prometheus_client.MetricType_HISTOGRAM {
517+
for _, m := range mf.Metric {
518+
if mf.GetName() == "gin_gonic_custom_histogram" {
519+
found = true
520+
assert.Equal(t, int32(3), m.GetHistogram().GetSchema())
521+
assert.Equal(t, uint64(0x1), m.GetHistogram().GetSampleCount())
522+
assert.Equal(t, 0.45, m.GetHistogram().GetSampleSum())
523+
}
524+
}
525+
}
526+
}
527+
528+
assert.True(t, found)
529+
}
530+
498531
func TestIgnore(t *testing.T) {
499532
r := gin.New()
500533
ipath := "/ping"

0 commit comments

Comments
 (0)