Adds a FixedPrecision Counter and Gauge
This commit is contained in:
parent
ab31273ace
commit
2cf055074f
151
fixed_precision.go
Normal file
151
fixed_precision.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Package prom exports a collection of prometheus.Metrics that use int64 and
|
||||||
|
// atomic store and add for maximum speed.
|
||||||
|
//
|
||||||
|
// In testing it provides a 10x speedup compared to
|
||||||
|
// github.com/prometheus/client_golang/prometheus.Metrics.
|
||||||
|
package prom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCounter returns prometheus.Counter backed by a fixed-precision int64 that
|
||||||
|
// uses atomic operations.
|
||||||
|
func NewCounter(opts prometheus.CounterOpts, prec uint) prometheus.Counter {
|
||||||
|
return NewFixedPrecisionCounter(opts, prec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGauge returns prometheus.Gauge backed by a fixed-precision int64 that
|
||||||
|
// uses atomic operations.
|
||||||
|
func NewGauge(opts prometheus.GaugeOpts, prec uint) prometheus.Gauge {
|
||||||
|
return NewFixedPrecisionGauge(opts, prec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixedPrecisionGauge implements a prometheus Gauge/Counter metric that uses atomic
|
||||||
|
// adds and stores for speed.
|
||||||
|
type FixedPrecisionGauge struct {
|
||||||
|
val int64
|
||||||
|
prec uint
|
||||||
|
|
||||||
|
desc *prometheus.Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFixedPrecisionGauge returns a populated fixed-precision counter.
|
||||||
|
func NewFixedPrecisionGauge(opts prometheus.GaugeOpts, prec uint) *FixedPrecisionGauge {
|
||||||
|
desc := prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
opts.Help,
|
||||||
|
nil,
|
||||||
|
opts.ConstLabels,
|
||||||
|
)
|
||||||
|
return &FixedPrecisionGauge{
|
||||||
|
desc: desc,
|
||||||
|
prec: uint(math.Pow10(int(prec))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set stores the value in the counter.
|
||||||
|
func (fpg *FixedPrecisionGauge) Set(val float64) {
|
||||||
|
atomic.StoreInt64(&fpg.val, int64(val)*int64(fpg.prec))
|
||||||
|
}
|
||||||
|
|
||||||
|
// add maps delta into the appropriate precision and adds it to val.
|
||||||
|
func (fpg *FixedPrecisionGauge) add(delta int64) {
|
||||||
|
atomic.AddInt64(&fpg.val, delta*int64(fpg.prec))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc adds 1 to the counter.
|
||||||
|
func (fpg *FixedPrecisionGauge) Inc() {
|
||||||
|
fpg.add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec decrements 1 from the counter.
|
||||||
|
func (fpg *FixedPrecisionGauge) Dec() {
|
||||||
|
fpg.add(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add generically adds delta to the value stored by counter.
|
||||||
|
func (fpg *FixedPrecisionGauge) Add(delta float64) {
|
||||||
|
atomic.AddInt64(&fpg.val, int64(delta*float64(fpg.prec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub is the inverse of Add.
|
||||||
|
func (fpg *FixedPrecisionGauge) Sub(val float64) {
|
||||||
|
fpg.Add(val * -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write is implemented to be useful as a prometheus counter.
|
||||||
|
func (fpg *FixedPrecisionGauge) Write(out *dto.Metric) error {
|
||||||
|
f := float64(atomic.LoadInt64(&fpg.val)) / float64(fpg.prec)
|
||||||
|
out.Counter = &dto.Counter{Value: &f}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns a float64 representation of the current value stored.
|
||||||
|
func (fpg *FixedPrecisionGauge) Value() float64 {
|
||||||
|
return float64(atomic.LoadInt64(&fpg.val)) / float64(fpg.prec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following three methods exist to make this behave with Prometheus
|
||||||
|
|
||||||
|
// Desc returns this FixedPrecisionGauge's prometheus description.
|
||||||
|
func (fpg *FixedPrecisionGauge) Desc() *prometheus.Desc {
|
||||||
|
return fpg.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe sends the counter's description to the chan
|
||||||
|
func (fpg *FixedPrecisionGauge) Describe(dc chan<- *prometheus.Desc) {
|
||||||
|
dc <- fpg.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect sends the counter value to the chan
|
||||||
|
func (fpg *FixedPrecisionGauge) Collect(mc chan<- prometheus.Metric) {
|
||||||
|
mc <- fpg
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToCurrentTime sets the Gauge to the current Unix time in seconds.
|
||||||
|
//
|
||||||
|
// Beware that if precision is set too high (greater than 9) it can overflow
|
||||||
|
// the underlying int64.
|
||||||
|
func (fpg *FixedPrecisionGauge) SetToCurrentTime() {
|
||||||
|
fpg.Set(float64(time.Now().Unix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixedPrecisionCounter embeds FixedPrecisionGauge and enforces the same
|
||||||
|
// guarantees as a prometheus.Counter where negative adds panic.
|
||||||
|
type FixedPrecisionCounter struct {
|
||||||
|
FixedPrecisionGauge
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFixedPrecisionCounter creates a FixedPrecisionCounter based on the
|
||||||
|
// provided Opts. It matches prometheus.Counter behavior by panicking if adding
|
||||||
|
// negative numbers.
|
||||||
|
func NewFixedPrecisionCounter(opts prometheus.CounterOpts, prec uint) *FixedPrecisionCounter {
|
||||||
|
desc := prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
opts.Help,
|
||||||
|
nil,
|
||||||
|
opts.ConstLabels,
|
||||||
|
)
|
||||||
|
return &FixedPrecisionCounter{
|
||||||
|
FixedPrecisionGauge: FixedPrecisionGauge{
|
||||||
|
desc: desc,
|
||||||
|
prec: uint(math.Pow10(int(prec))),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the given value to the counter. It matches prometheus.Counter
|
||||||
|
// behavior by panicking if the value is < 0.
|
||||||
|
func (fpc *FixedPrecisionCounter) Add(v float64) {
|
||||||
|
if v < 0 {
|
||||||
|
panic("counter cannot decrease in value")
|
||||||
|
}
|
||||||
|
fpc.FixedPrecisionGauge.Add(v)
|
||||||
|
}
|
133
fixed_precision_test.go
Normal file
133
fixed_precision_test.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package prom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoPrecisionGauge(t *testing.T) {
|
||||||
|
c := NewFixedPrecisionGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
c.Inc()
|
||||||
|
var want float64 = 1
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %v, got %v.", expected, got)
|
||||||
|
}
|
||||||
|
c.Add(0.9999999999999999999)
|
||||||
|
want = 2
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %v, got %v.", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Sub(0.9999999999999999999 * 3)
|
||||||
|
want = -1
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %v, got %v.", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &dto.Metric{}
|
||||||
|
c.Write(m)
|
||||||
|
|
||||||
|
if expected, got := `counter:<value:-1 > `, m.String(); expected != got {
|
||||||
|
t.Errorf("expected %q, got %q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixedPrecisionAdd(t *testing.T) {
|
||||||
|
c := NewFixedPrecisionGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, 3)
|
||||||
|
|
||||||
|
c.Inc()
|
||||||
|
want := 1.0
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %f, got %f.", expected, got)
|
||||||
|
}
|
||||||
|
c.Add(42.3)
|
||||||
|
want = 43.3
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %f, got %f.", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &dto.Metric{}
|
||||||
|
c.Write(m)
|
||||||
|
|
||||||
|
if expected, got := `counter:<value:43.3 > `, m.String(); expected != got {
|
||||||
|
t.Errorf("expected %q, got %q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixedPrecisionSub(t *testing.T) {
|
||||||
|
c := NewFixedPrecisionGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, 3)
|
||||||
|
|
||||||
|
c.Dec()
|
||||||
|
var want float64 = -1
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %f, got %f.", expected, got)
|
||||||
|
}
|
||||||
|
c.Sub(42.3)
|
||||||
|
want = -43.3
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %f, got %f.", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &dto.Metric{}
|
||||||
|
c.Write(m)
|
||||||
|
|
||||||
|
if expected, got := `counter:<value:-43.3 > `, m.String(); expected != got {
|
||||||
|
t.Errorf("expected %q, got %q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetToCurrentTime(t *testing.T) {
|
||||||
|
precs := []uint{}
|
||||||
|
var i uint
|
||||||
|
for i = 0; i < 10; i++ {
|
||||||
|
precs = append(precs, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prec := range precs {
|
||||||
|
c := NewFixedPrecisionGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, prec)
|
||||||
|
|
||||||
|
c.SetToCurrentTime()
|
||||||
|
n := time.Now()
|
||||||
|
|
||||||
|
delta := math.Abs(c.Value() - float64(n.Unix()))
|
||||||
|
if !(delta <= 1) {
|
||||||
|
t.Fatalf("SetToCurrentTime at %v precision was off from time.Now(): got: %v, want: <= 1", prec, delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounterDirection(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e == nil {
|
||||||
|
t.Fatalf("did not panic and shold have")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c := NewFixedPrecisionCounter(prometheus.CounterOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, 3)
|
||||||
|
c.Add(1)
|
||||||
|
var want float64 = 1
|
||||||
|
if expected, got := want, c.Value(); expected != got {
|
||||||
|
t.Errorf("Expected %f, got %f.", expected, got)
|
||||||
|
}
|
||||||
|
c.Add(-1)
|
||||||
|
}
|
33
interface_test.go
Normal file
33
interface_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package prom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGauginess(t *testing.T) {
|
||||||
|
g := NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, 3)
|
||||||
|
|
||||||
|
switch g.(type) {
|
||||||
|
case prometheus.Gauge:
|
||||||
|
default:
|
||||||
|
t.Fatalf("FixedPrecision is not a prometheus.Gauge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounteriness(t *testing.T) {
|
||||||
|
c := NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "test",
|
||||||
|
Help: "test help",
|
||||||
|
}, 3)
|
||||||
|
|
||||||
|
switch c.(type) {
|
||||||
|
case prometheus.Counter:
|
||||||
|
default:
|
||||||
|
t.Fatalf("FixedPrecision is not a prometheus.Counter")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user