Skip to content

Allow to use custom registerer for metrics #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 23, 2025
Merged
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: 44 additions & 27 deletions metrics/metric_observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,15 @@ import (

"github.com/openshift-online/async-routine"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var _ async.RoutinesObserver = (*metricObserver)(nil)

type metricObserver struct{}

var (
runningRoutines = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "async_routine_manager_routines",
Help: "Number of running routines.",
},
)

runningRoutinesByName = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "async_routine_manager_routine_instances",
Help: "Number of running instances of a given routine.",
},
[]string{"routine_name", "data"},
)
)

func init() {
prometheus.MustRegister(runningRoutines)
prometheus.MustRegister(runningRoutinesByName)
type metricObserver struct {
registerer prometheus.Registerer
runningRoutines prometheus.Gauge
runningRoutinesByName *prometheus.GaugeVec
}

func mapToString(m map[string]string) string {
Expand All @@ -55,13 +38,13 @@ func mapToString(m map[string]string) string {
}

func (m *metricObserver) RoutineStarted(routine async.AsyncRoutine) {
runningRoutinesByName.
m.runningRoutinesByName.
With(prometheus.Labels{"routine_name": routine.Name(), "data": mapToString(routine.GetData())}).
Inc()
}

func (m *metricObserver) RoutineFinished(routine async.AsyncRoutine) {
runningRoutinesByName.
m.runningRoutinesByName.
With(prometheus.Labels{"routine_name": routine.Name(), "data": mapToString(routine.GetData())}).
Dec()
}
Expand All @@ -70,12 +53,46 @@ func (m *metricObserver) RoutineExceededTimebox(routine async.AsyncRoutine) {
}

func (m *metricObserver) RunningRoutineCount(count int) {
runningRoutines.Set(float64(count))
m.runningRoutines.Set(float64(count))
}

func (m *metricObserver) RunningRoutineByNameCount(name string, count int) {
}

func NewMetricObserver() async.RoutinesObserver {
return &metricObserver{}
// MetricOption defines options for the metrics observer.
type MetricOption func(*metricObserver)

// WithRegisterer configures the Prometheus registry where to register metrics.
func WithRegisterer(r prometheus.Registerer) MetricOption {
return func(observer *metricObserver) {
observer.registerer = r
}
}

// NewMetricObserver returns an observer which tracks metrics for the async
// routines.
func NewMetricObserver(opts ...MetricOption) async.RoutinesObserver {
observer := &metricObserver{
registerer: prometheus.DefaultRegisterer,
}

for _, opt := range opts {
opt(observer)
}

observer.runningRoutines = promauto.With(observer.registerer).NewGauge(
prometheus.GaugeOpts{
Name: "async_routine_manager_routines",
Help: "Number of running routines.",
},
)
observer.runningRoutinesByName = promauto.With(observer.registerer).NewGaugeVec(
prometheus.GaugeOpts{
Name: "async_routine_manager_routine_instances",
Help: "Number of running instances of a given routine.",
},
[]string{"routine_name", "data"},
)

return observer
}
13 changes: 13 additions & 0 deletions metrics/metric_observer_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package metrics

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestMetricsObserver(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Metrics Suite")
}
125 changes: 115 additions & 10 deletions metrics/metric_observer_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,124 @@
package metrics

import (
"testing"
"bytes"

. "github.com/onsi/ginkgo/v2/dsl/core"
. "github.com/onsi/ginkgo/v2/dsl/table"
. "github.com/onsi/gomega"
"github.com/openshift-online/async-routine"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"go.uber.org/mock/gomock"
)

func TestPrometheusMetrics(t *testing.T) {
problems, err := testutil.GatherAndLint(prometheus.DefaultGatherer)
if err != nil {
t.Fatal(err)
}
var _ = Describe("Metrics Observer", func() {
DescribeTable("Updates metrics",
func(updateFn func(async.RoutinesObserver), expected string) {
registry := prometheus.NewPedanticRegistry()
observer := NewMetricObserver(WithRegisterer(registry))

for _, p := range problems {
t.Errorf("found linting issue: %s: %s", p.Metric, p.Text)
}
}
updateFn(observer)

err := testutil.GatherAndCompare(registry, bytes.NewBufferString(expected))
Expect(err).NotTo(HaveOccurred())

problems, err := testutil.GatherAndLint(registry)
Expect(err).NotTo(HaveOccurred())
Expect(problems).To(BeEmpty())
},
Entry("from initial state",
func(async.RoutinesObserver) {},
`# HELP async_routine_manager_routines Number of running routines.
# TYPE async_routine_manager_routines gauge
async_routine_manager_routines 0
`,
),
Entry("when 1 routine is started",
func(observer async.RoutinesObserver) {
ctrl := gomock.NewController(GinkgoT())

routine := async.NewMockAsyncRoutine(ctrl)
routine.EXPECT().
Name().
Return("test").
Times(1)
routine.EXPECT().
GetData().
Return(nil).
Times(1)

observer.RoutineStarted(routine)
observer.RunningRoutineCount(1)
},
`# HELP async_routine_manager_routine_instances Number of running instances of a given routine.
# TYPE async_routine_manager_routine_instances gauge
async_routine_manager_routine_instances{data="",routine_name="test"} 1
# HELP async_routine_manager_routines Number of running routines.
# TYPE async_routine_manager_routines gauge
async_routine_manager_routines 1
`),
Entry("when 2 routines (1 with data) are started",
func(observer async.RoutinesObserver) {
ctrl := gomock.NewController(GinkgoT())

routine := async.NewMockAsyncRoutine(ctrl)
routine.EXPECT().
Name().
Return("test").
Times(1)
routine.EXPECT().
GetData().
Return(nil).
Times(1)
observer.RoutineStarted(routine)

routine2 := async.NewMockAsyncRoutine(ctrl)
routine2.EXPECT().
Name().
Return("test2")
routine2.EXPECT().
GetData().
Return(map[string]string{"foo": "bar"}).
Times(1)

observer.RoutineStarted(routine2)
observer.RunningRoutineCount(2)
},
`# HELP async_routine_manager_routine_instances Number of running instances of a given routine.
# TYPE async_routine_manager_routine_instances gauge
async_routine_manager_routine_instances{data="",routine_name="test"} 1
async_routine_manager_routine_instances{data="foo=bar",routine_name="test2"} 1
# HELP async_routine_manager_routines Number of running routines.
# TYPE async_routine_manager_routines gauge
async_routine_manager_routines 2
`,
),
Entry("when 1 routine is started then stopped",
func(observer async.RoutinesObserver) {
ctrl := gomock.NewController(GinkgoT())

routine := async.NewMockAsyncRoutine(ctrl)
routine.EXPECT().
Name().
Return("test").
Times(2)
routine.EXPECT().
GetData().
Return(nil).
Times(2)

observer.RoutineStarted(routine)
observer.RoutineFinished(routine)
observer.RunningRoutineCount(0)
},
`# HELP async_routine_manager_routine_instances Number of running instances of a given routine.
# TYPE async_routine_manager_routine_instances gauge
async_routine_manager_routine_instances{data="",routine_name="test"} 0
# HELP async_routine_manager_routines Number of running routines.
# TYPE async_routine_manager_routines gauge
async_routine_manager_routines 0
`,
),
)
})