Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions central/metrics/custom_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package metrics

import (
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/stackrox/rox/pkg/metrics"
"github.com/stackrox/rox/pkg/sync"
)

/*
Custom Prometheus metrics are user facing metrics, served on the API endpoint.
The metrics are configured via the private section of the 'config' API.

A metric is configured with a list of labels supported by the metric tracker.

Metrics are immutable. To change anything, the metric has to be deleted and
recreated with separate requests.
*/

//go:generate mockgen-wrapper
type CustomRegistry interface {
RegisterMetric(metricName string, category string, period time.Duration, labels []string) error
UnregisterMetric(metricName string) bool
SetTotal(metricName string, labels prometheus.Labels, total int)
}

type customRegistry struct {
prometheus.Registerer
gauges sync.Map // map[metricName string]*prometheus.GaugeVec
}

// MakeCustomRegistry is a CustomRegistry factory.
func MakeCustomRegistry() CustomRegistry {
return &customRegistry{
Registerer: prometheus.NewRegistry(),
}
}

var _ CustomRegistry = (*customRegistry)(nil)

// UnregisterMetric unregister the given metric by name.
func (cr *customRegistry) UnregisterMetric(metricName string) bool {
if gauge, loaded := cr.gauges.LoadAndDelete(metricName); loaded {
return cr.Unregister(gauge.(*prometheus.GaugeVec))
}
return false
}

// RegisterMetric registers a user-defined aggregated metric.
func (cr *customRegistry) RegisterMetric(metricName string, category string, period time.Duration, labels []string) error {
gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: metrics.PrometheusNamespace,
Subsystem: metrics.CentralSubsystem.String(),
Name: metricName,
Help: "The total number of " + category + " aggregated by " + strings.Join(labels, ",") +
" and gathered every " + period.String(),
}, labels)
if _, loaded := cr.gauges.LoadOrStore(metricName, gauge); !loaded {
return cr.Register(gauge)
}
return nil
}

// SetTotal sets the value to the gauge of a metric.
func (cr *customRegistry) SetTotal(metricName string, labels prometheus.Labels, total int) {
if gauge, ok := cr.gauges.Load(metricName); ok {
gauge.(*prometheus.GaugeVec).With(labels).Set(float64(total))
}
}
49 changes: 49 additions & 0 deletions central/metrics/custom_registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package metrics

import (
"testing"
"time"

"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
)

func TestMakeCustomRegistry(t *testing.T) {
cr1 := MakeCustomRegistry()
cr2 := MakeCustomRegistry()
assert.NotSame(t, cr1, cr2)

assert.NoError(t, cr1.RegisterMetric("test", "test", 10*time.Minute, []string{"Test1", "Test2"}))
assert.NoError(t, cr1.RegisterMetric("test", "test", 10*time.Minute, []string{"Test1", "Test2"}))
assert.NoError(t, cr2.RegisterMetric("test", "test", 10*time.Minute, []string{"Test1", "Test2"}))

cr1.SetTotal("test", map[string]string{"Test1": "value1", "Test2": "value2"}, 42)
cr2.SetTotal("test", map[string]string{"Test1": "value1", "Test2": "value2"}, 24)

getMetricValue := func(registry CustomRegistry, metricName string) (float64, error) {
metricValue := &dto.Metric{}
gauge, ok := registry.(*customRegistry).gauges.Load(metricName)
if !ok {
return 0, errors.New("no such metric")
}
err := gauge.(*prometheus.GaugeVec).WithLabelValues("value1", "value2").Write(metricValue)
if err != nil {
return 0, err
}
return metricValue.GetGauge().GetValue(), nil
}

value, err := getMetricValue(cr1, "test")
assert.NoError(t, err)
assert.Equal(t, float64(42), value)

value, err = getMetricValue(cr2, "test")
assert.NoError(t, err)
assert.Equal(t, float64(24), value)

assert.True(t, cr1.UnregisterMetric("test"))
assert.False(t, cr1.UnregisterMetric("test"))
assert.True(t, cr2.UnregisterMetric("test"))
}
82 changes: 82 additions & 0 deletions central/metrics/mocks/custom_registry.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading