pkg/trimaran/loadvariationriskbalancing/collector.go (96 lines of code) (raw):
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package loadvariationriskbalancing
import (
"sync"
"time"
"github.com/paypal/load-watcher/pkg/watcher"
loadwatcherapi "github.com/paypal/load-watcher/pkg/watcher/api"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
pluginConfig "sigs.k8s.io/scheduler-plugins/pkg/apis/config"
"sigs.k8s.io/scheduler-plugins/pkg/apis/config/v1beta1"
)
const (
metricsUpdateIntervalSeconds = 30
)
// Collector : get data from load watcher, encapsulating the load watcher and its operations
//
// Currently, the Collector owns its own load watcher and is used solely by the LoadVariationRiskBalancing
// plugin. Other Trimaran plugins, such as the TargetLoadPacking, have their own load watchers. The reason
// being that the Trimaran plugins have different, potentially conflicting, objectives. Thus, it is recommended
// not to enable them concurrently. As such, they are currently designed to each have its own load-watcher.
// If a need arises in the future to enable multiple Trimaran plugins, a restructuring to have a single Collector,
// serving the multiple plugins, may be beneficial for performance reasons.
type Collector struct {
// load watcher client
client loadwatcherapi.Client
// data collected by load watcher
metrics watcher.WatcherMetrics
// plugin arguments
args *pluginConfig.LoadVariationRiskBalancingArgs
// for safe access to metrics
mu sync.RWMutex
}
// newCollector : create an instance of a data collector
func newCollector(obj runtime.Object) (*Collector, error) {
// get the plugin arguments
args := getArgs(obj)
klog.V(4).Infof("Using LoadVariationRiskBalancingArgs: MetricProvider.Type=%q, MetricProvider.Address=%q,"+
" SafeVarianceMargin=%f, SafeVarianceSensitivity=%f, WatcherAddress=%q",
args.MetricProvider.Type, args.MetricProvider.Address, args.SafeVarianceMargin,
args.SafeVarianceSensitivity, args.WatcherAddress)
var client loadwatcherapi.Client
if args.WatcherAddress != "" {
client, _ = loadwatcherapi.NewServiceClient(args.WatcherAddress)
} else {
opts := watcher.MetricsProviderOpts{
Name: string(args.MetricProvider.Type),
Address: args.MetricProvider.Address,
AuthToken: args.MetricProvider.Token,
}
client, _ = loadwatcherapi.NewLibraryClient(opts)
}
collector := &Collector{
client: client,
args: args,
}
// populate metrics before returning
err := collector.updateMetrics()
if err != nil {
klog.Warningf("unable to populate metrics initially: %v", err)
}
// start periodic updates
go func() {
metricsUpdaterTicker := time.NewTicker(time.Second * metricsUpdateIntervalSeconds)
for range metricsUpdaterTicker.C {
err = collector.updateMetrics()
if err != nil {
klog.Warningf("unable to update metrics: %v", err)
}
}
}()
return collector, nil
}
// getAllMetrics : get all metrics from watcher
func (collector *Collector) getAllMetrics() *watcher.WatcherMetrics {
collector.mu.RLock()
metrics := collector.metrics
collector.mu.RUnlock()
return &metrics
}
// getNodeMetrics : get metrics for a node from watcher
func (collector *Collector) getNodeMetrics(nodeName string) []watcher.Metric {
allMetrics := collector.getAllMetrics()
// Check if node is new (no metrics yet) or metrics are unavailable due to 404 or 500
if _, ok := allMetrics.Data.NodeMetricsMap[nodeName]; !ok {
klog.Errorf("unable to find metrics for node %v", nodeName)
return nil
}
return allMetrics.Data.NodeMetricsMap[nodeName].Metrics
}
// getArgs : get configured args
func getArgs(obj runtime.Object) *pluginConfig.LoadVariationRiskBalancingArgs {
// cast object into plugin arguments object
args, ok := obj.(*pluginConfig.LoadVariationRiskBalancingArgs)
if !ok {
klog.Errorf("want args to be of type LoadVariationRiskBalancingArgs, got %T, using defaults", obj)
args = &pluginConfig.LoadVariationRiskBalancingArgs{
MetricProvider: pluginConfig.MetricProviderSpec{
Type: v1beta1.DefaultMetricProviderType,
},
SafeVarianceMargin: v1beta1.DefaultSafeVarianceMargin,
SafeVarianceSensitivity: v1beta1.DefaultSafeVarianceSensitivity,
}
return args
}
return args
}
// updateMetrics : request to load watcher to update all metrics
func (collector *Collector) updateMetrics() error {
metrics, err := collector.client.GetLatestWatcherMetrics()
if err != nil {
klog.Errorf("load watcher client failed: %v", err)
return err
}
collector.mu.Lock()
collector.metrics = *metrics
collector.mu.Unlock()
return nil
}