mirror of
https://github.com/kubernetes/client-go.git
synced 2025-07-31 23:00:26 +00:00
Use generics for workqueue metrics
The workqueue implementation was recently updated to be strongly typed, using Go generics. However the metrics implementation was not updated, and continued using interface{}. This translated to unnecessary memory allocations when invoking the queueMetrics interface methods to track queue operation. We can avoid these extra heap allocations by using generics for the metrics implementation as well. Signed-off-by: Antonin Bas <antonin.bas@broadcom.com> Kubernetes-commit: 1aec7568e111f5855121e3afacacf431e5f95948
This commit is contained in:
parent
ca4a13f6de
commit
5b31113588
@ -26,10 +26,10 @@ import (
|
|||||||
// This file provides abstractions for setting the provider (e.g., prometheus)
|
// This file provides abstractions for setting the provider (e.g., prometheus)
|
||||||
// of metrics.
|
// of metrics.
|
||||||
|
|
||||||
type queueMetrics interface {
|
type queueMetrics[T comparable] interface {
|
||||||
add(item t)
|
add(item T)
|
||||||
get(item t)
|
get(item T)
|
||||||
done(item t)
|
done(item T)
|
||||||
updateUnfinishedWork()
|
updateUnfinishedWork()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ func (noopMetric) Set(float64) {}
|
|||||||
func (noopMetric) Observe(float64) {}
|
func (noopMetric) Observe(float64) {}
|
||||||
|
|
||||||
// defaultQueueMetrics expects the caller to lock before setting any metrics.
|
// defaultQueueMetrics expects the caller to lock before setting any metrics.
|
||||||
type defaultQueueMetrics struct {
|
type defaultQueueMetrics[T comparable] struct {
|
||||||
clock clock.Clock
|
clock clock.Clock
|
||||||
|
|
||||||
// current depth of a workqueue
|
// current depth of a workqueue
|
||||||
@ -81,15 +81,15 @@ type defaultQueueMetrics struct {
|
|||||||
latency HistogramMetric
|
latency HistogramMetric
|
||||||
// how long processing an item from a workqueue takes
|
// how long processing an item from a workqueue takes
|
||||||
workDuration HistogramMetric
|
workDuration HistogramMetric
|
||||||
addTimes map[t]time.Time
|
addTimes map[T]time.Time
|
||||||
processingStartTimes map[t]time.Time
|
processingStartTimes map[T]time.Time
|
||||||
|
|
||||||
// how long have current threads been working?
|
// how long have current threads been working?
|
||||||
unfinishedWorkSeconds SettableGaugeMetric
|
unfinishedWorkSeconds SettableGaugeMetric
|
||||||
longestRunningProcessor SettableGaugeMetric
|
longestRunningProcessor SettableGaugeMetric
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultQueueMetrics) add(item t) {
|
func (m *defaultQueueMetrics[T]) add(item T) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ func (m *defaultQueueMetrics) add(item t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultQueueMetrics) get(item t) {
|
func (m *defaultQueueMetrics[T]) get(item T) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func (m *defaultQueueMetrics) get(item t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultQueueMetrics) done(item t) {
|
func (m *defaultQueueMetrics[T]) done(item T) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -125,7 +125,7 @@ func (m *defaultQueueMetrics) done(item t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultQueueMetrics) updateUnfinishedWork() {
|
func (m *defaultQueueMetrics[T]) updateUnfinishedWork() {
|
||||||
// Note that a summary metric would be better for this, but prometheus
|
// Note that a summary metric would be better for this, but prometheus
|
||||||
// doesn't seem to have non-hacky ways to reset the summary metrics.
|
// doesn't seem to have non-hacky ways to reset the summary metrics.
|
||||||
var total float64
|
var total float64
|
||||||
@ -141,15 +141,15 @@ func (m *defaultQueueMetrics) updateUnfinishedWork() {
|
|||||||
m.longestRunningProcessor.Set(oldest)
|
m.longestRunningProcessor.Set(oldest)
|
||||||
}
|
}
|
||||||
|
|
||||||
type noMetrics struct{}
|
type noMetrics[T any] struct{}
|
||||||
|
|
||||||
func (noMetrics) add(item t) {}
|
func (noMetrics[T]) add(item T) {}
|
||||||
func (noMetrics) get(item t) {}
|
func (noMetrics[T]) get(item T) {}
|
||||||
func (noMetrics) done(item t) {}
|
func (noMetrics[T]) done(item T) {}
|
||||||
func (noMetrics) updateUnfinishedWork() {}
|
func (noMetrics[T]) updateUnfinishedWork() {}
|
||||||
|
|
||||||
// Gets the time since the specified start in seconds.
|
// Gets the time since the specified start in seconds.
|
||||||
func (m *defaultQueueMetrics) sinceInSeconds(start time.Time) float64 {
|
func (m *defaultQueueMetrics[T]) sinceInSeconds(start time.Time) float64 {
|
||||||
return m.clock.Since(start).Seconds()
|
return m.clock.Since(start).Seconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,28 +210,15 @@ func (_ noopMetricsProvider) NewRetriesMetric(name string) CounterMetric {
|
|||||||
return noopMetric{}
|
return noopMetric{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalMetricsFactory = queueMetricsFactory{
|
var globalMetricsProvider MetricsProvider = noopMetricsProvider{}
|
||||||
metricsProvider: noopMetricsProvider{},
|
|
||||||
}
|
|
||||||
|
|
||||||
type queueMetricsFactory struct {
|
var setGlobalMetricsProviderOnce sync.Once
|
||||||
metricsProvider MetricsProvider
|
|
||||||
|
|
||||||
onlyOnce sync.Once
|
func newQueueMetrics[T comparable](mp MetricsProvider, name string, clock clock.Clock) queueMetrics[T] {
|
||||||
}
|
|
||||||
|
|
||||||
func (f *queueMetricsFactory) setProvider(mp MetricsProvider) {
|
|
||||||
f.onlyOnce.Do(func() {
|
|
||||||
f.metricsProvider = mp
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *queueMetricsFactory) newQueueMetrics(name string, clock clock.Clock) queueMetrics {
|
|
||||||
mp := f.metricsProvider
|
|
||||||
if len(name) == 0 || mp == (noopMetricsProvider{}) {
|
if len(name) == 0 || mp == (noopMetricsProvider{}) {
|
||||||
return noMetrics{}
|
return noMetrics[T]{}
|
||||||
}
|
}
|
||||||
return &defaultQueueMetrics{
|
return &defaultQueueMetrics[T]{
|
||||||
clock: clock,
|
clock: clock,
|
||||||
depth: mp.NewDepthMetric(name),
|
depth: mp.NewDepthMetric(name),
|
||||||
adds: mp.NewAddsMetric(name),
|
adds: mp.NewAddsMetric(name),
|
||||||
@ -239,8 +226,8 @@ func (f *queueMetricsFactory) newQueueMetrics(name string, clock clock.Clock) qu
|
|||||||
workDuration: mp.NewWorkDurationMetric(name),
|
workDuration: mp.NewWorkDurationMetric(name),
|
||||||
unfinishedWorkSeconds: mp.NewUnfinishedWorkSecondsMetric(name),
|
unfinishedWorkSeconds: mp.NewUnfinishedWorkSecondsMetric(name),
|
||||||
longestRunningProcessor: mp.NewLongestRunningProcessorSecondsMetric(name),
|
longestRunningProcessor: mp.NewLongestRunningProcessorSecondsMetric(name),
|
||||||
addTimes: map[t]time.Time{},
|
addTimes: map[T]time.Time{},
|
||||||
processingStartTimes: map[t]time.Time{},
|
processingStartTimes: map[T]time.Time{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +238,7 @@ func newRetryMetrics(name string, provider MetricsProvider) retryMetrics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
provider = globalMetricsFactory.metricsProvider
|
provider = globalMetricsProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
return &defaultRetryMetrics{
|
return &defaultRetryMetrics{
|
||||||
@ -262,5 +249,7 @@ func newRetryMetrics(name string, provider MetricsProvider) retryMetrics {
|
|||||||
// SetProvider sets the metrics provider for all subsequently created work
|
// SetProvider sets the metrics provider for all subsequently created work
|
||||||
// queues. Only the first call has an effect.
|
// queues. Only the first call has an effect.
|
||||||
func SetProvider(metricsProvider MetricsProvider) {
|
func SetProvider(metricsProvider MetricsProvider) {
|
||||||
globalMetricsFactory.setProvider(metricsProvider)
|
setGlobalMetricsProviderOnce.Do(func() {
|
||||||
|
globalMetricsProvider = metricsProvider
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,9 @@ type testMetrics struct {
|
|||||||
updateCalled chan<- struct{}
|
updateCalled chan<- struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *testMetrics) add(item t) { m.added++ }
|
func (m *testMetrics) add(item any) { m.added++ }
|
||||||
func (m *testMetrics) get(item t) { m.gotten++ }
|
func (m *testMetrics) get(item any) { m.gotten++ }
|
||||||
func (m *testMetrics) done(item t) { m.finished++ }
|
func (m *testMetrics) done(item any) { m.finished++ }
|
||||||
func (m *testMetrics) updateUnfinishedWork() { m.updateCalled <- struct{}{} }
|
func (m *testMetrics) updateUnfinishedWork() { m.updateCalled <- struct{}{} }
|
||||||
|
|
||||||
func TestMetricShutdown(t *testing.T) {
|
func TestMetricShutdown(t *testing.T) {
|
||||||
|
@ -138,13 +138,9 @@ func NewNamed(name string) *Type {
|
|||||||
// newQueueWithConfig constructs a new named workqueue
|
// newQueueWithConfig constructs a new named workqueue
|
||||||
// with the ability to customize different properties for testing purposes
|
// with the ability to customize different properties for testing purposes
|
||||||
func newQueueWithConfig[T comparable](config TypedQueueConfig[T], updatePeriod time.Duration) *Typed[T] {
|
func newQueueWithConfig[T comparable](config TypedQueueConfig[T], updatePeriod time.Duration) *Typed[T] {
|
||||||
var metricsFactory *queueMetricsFactory
|
metricsProvider := globalMetricsProvider
|
||||||
if config.MetricsProvider != nil {
|
if config.MetricsProvider != nil {
|
||||||
metricsFactory = &queueMetricsFactory{
|
metricsProvider = config.MetricsProvider
|
||||||
metricsProvider: config.MetricsProvider,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metricsFactory = &globalMetricsFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Clock == nil {
|
if config.Clock == nil {
|
||||||
@ -158,12 +154,12 @@ func newQueueWithConfig[T comparable](config TypedQueueConfig[T], updatePeriod t
|
|||||||
return newQueue(
|
return newQueue(
|
||||||
config.Clock,
|
config.Clock,
|
||||||
config.Queue,
|
config.Queue,
|
||||||
metricsFactory.newQueueMetrics(config.Name, config.Clock),
|
newQueueMetrics[T](metricsProvider, config.Name, config.Clock),
|
||||||
updatePeriod,
|
updatePeriod,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newQueue[T comparable](c clock.WithTicker, queue Queue[T], metrics queueMetrics, updatePeriod time.Duration) *Typed[T] {
|
func newQueue[T comparable](c clock.WithTicker, queue Queue[T], metrics queueMetrics[T], updatePeriod time.Duration) *Typed[T] {
|
||||||
t := &Typed[T]{
|
t := &Typed[T]{
|
||||||
clock: c,
|
clock: c,
|
||||||
queue: queue,
|
queue: queue,
|
||||||
@ -176,7 +172,7 @@ func newQueue[T comparable](c clock.WithTicker, queue Queue[T], metrics queueMet
|
|||||||
|
|
||||||
// Don't start the goroutine for a type of noMetrics so we don't consume
|
// Don't start the goroutine for a type of noMetrics so we don't consume
|
||||||
// resources unnecessarily
|
// resources unnecessarily
|
||||||
if _, ok := metrics.(noMetrics); !ok {
|
if _, ok := metrics.(noMetrics[T]); !ok {
|
||||||
go t.updateUnfinishedWorkLoop()
|
go t.updateUnfinishedWorkLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +205,7 @@ type Typed[t comparable] struct {
|
|||||||
shuttingDown bool
|
shuttingDown bool
|
||||||
drain bool
|
drain bool
|
||||||
|
|
||||||
metrics queueMetrics
|
metrics queueMetrics[t]
|
||||||
|
|
||||||
unfinishedWorkUpdatePeriod time.Duration
|
unfinishedWorkUpdatePeriod time.Duration
|
||||||
clock clock.WithTicker
|
clock clock.WithTicker
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package workqueue_test
|
package workqueue_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -460,3 +461,20 @@ func mustGarbageCollect(t *testing.T, i interface{}) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkQueue(b *testing.B) {
|
||||||
|
keys := make([]string, 100)
|
||||||
|
for idx := range keys {
|
||||||
|
keys[idx] = fmt.Sprintf("key-%d", idx)
|
||||||
|
}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
b.StopTimer()
|
||||||
|
q := workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[string]{})
|
||||||
|
b.StartTimer()
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
q.Add(keys[j])
|
||||||
|
key, _ := q.Get()
|
||||||
|
q.Done(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user