|
- package main
-
- import (
- "context"
- "fmt"
- "github.com/prometheus/client_golang/api"
- v1 "github.com/prometheus/client_golang/api/prometheus/v1"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
- "github.com/prometheus/common/model"
- "sync"
- "time"
- )
-
- type State struct {
- config Config
- weights map[string]int
- spaces map[string]int64
- inflights map[string]int64
- lock sync.RWMutex
- }
-
- type TargetState struct {
- Target ServerSpec
- Weight int
- FreeSpace int64
- Inflight int64
- }
-
- var state *State
-
- func initState() {
- c, err := NewConfigFromFile("config.yaml")
- if err != nil {
- log.WithError(err).Fatal("Unable to read config")
- }
-
- state = &State{
- config: c,
- }
- }
-
- func (state *State) GetListenAddr() string {
- state.lock.RLock()
- defer state.lock.RUnlock()
- return state.config.Listen
- }
-
- func (state *State) getPromAddr() string {
- state.lock.RLock()
- defer state.lock.RUnlock()
- return state.config.PromAddr
- }
-
- func (state *State) MakeClient() (v1.API, error) {
- addr := state.getPromAddr()
- log.WithField("prometheus", addr).Info("Making new prom client...")
- client, err := api.NewClient(api.Config{
- Address: addr,
- })
-
- if err != nil {
- return nil, err
- }
-
- c := v1.NewAPI(client)
-
- return c, nil
- }
-
- func (state *State) GetTargets() []TargetState {
- state.lock.RLock()
- defer state.lock.RUnlock()
-
- var states []TargetState
-
- for _, targetSpec := range state.config.Targets {
- w := 0
-
- if val, ok := state.weights[targetSpec.Id]; ok {
- w = val
- }
-
- space := int64(0)
-
- if val, ok := state.spaces[targetSpec.Id]; ok {
- space = val
- }
-
- inflights := int64(0)
-
- if val, ok := state.inflights[targetSpec.Id]; ok {
- inflights = val
- }
-
- states = append(states, TargetState{
- Target: targetSpec,
- Weight: w,
- FreeSpace: space,
- Inflight: inflights,
- })
- }
-
- return states
- }
-
- var (
- weightsGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
- Name: "offload_dispatcher_target_weights",
- Help: "Weights of the targets in selection",
- }, []string{"target"})
- )
-
- func (state *State) updateTargets(targets []TargetState) {
-
- newWeights := make(map[string]int)
- newInflights := make(map[string]int64)
- newSpaces := make(map[string]int64)
-
- for _, t := range targets {
- newWeights[t.Target.Id] = t.Weight
- newInflights[t.Target.Id] = t.Inflight
- newSpaces[t.Target.Id] = t.FreeSpace
- weightsGauge.WithLabelValues(t.Target.Id).Set(float64(t.Weight))
- }
-
- state.lock.Lock()
- defer state.lock.Unlock()
-
- state.weights = newWeights
- state.inflights = newInflights
- state.spaces = newSpaces
- }
-
- func (state *State) UpdateThread() {
- log.Info("Starting update thread...")
- v1api, err := state.MakeClient()
- if err != nil {
- log.WithError(err).Fatal("Unable to make prom client")
- }
- for {
- log.Info("Starting update run...")
- ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
-
- targets := state.GetTargets()
-
- for i, target := range targets {
- {
- result, warnings, err := v1api.Query(ctx, fmt.Sprintf("minio_cluster_capacity_usable_free_bytes{job=\"%s\"}", target.Target.JobName), time.Now(), v1.WithTimeout(5*time.Second))
- if err != nil {
- log.WithField("target", target.Target.Id).WithError(err).Error("Error querying Prometheus for free space")
- continue
- }
- if len(warnings) > 0 {
- log.WithField("target", target.Target.Id).Warnf("Free space warnings: %v\n", warnings)
- }
-
- resultVec := result.(model.Vector)
- if resultVec.Len() > 0 {
- resultVal := resultVec[0]
- log.WithField("target", target.Target.Id).Debugf("Computed free space: %v", resultVal.Value)
- targets[i].FreeSpace = int64(resultVal.Value)
- }
- }
-
- {
- result, warnings, err := v1api.Query(ctx, fmt.Sprintf("sum(minio_s3_requests_inflight_total{job=\"%s\"})", target.Target.JobName), time.Now(), v1.WithTimeout(5*time.Second))
- if err != nil {
- log.WithField("target", target.Target.Id).WithError(err).Error("Error querying Prometheus for inflights")
- continue
- }
- if len(warnings) > 0 {
- log.WithField("target", target.Target.Id).Warnf("Inflight warnings: %v\n", warnings)
- }
-
- resultVec := result.(model.Vector)
- if resultVec.Len() > 0 {
- resultVal := resultVec[0]
- log.WithField("target", target.Target.Id).Debugf("Computed inflight: %v", resultVal.Value)
- targets[i].Inflight = int64(resultVal.Value)
- }
- }
-
- }
-
- cancel()
-
- inflightsSum := int64(0)
- inflightsCount := 0
- for _, t := range targets {
- inflightsSum = inflightsSum + t.Inflight
- inflightsCount = inflightsCount + 1
- }
- averageInflights := float64(inflightsSum) / float64(inflightsCount)
- log.Debugf("Average inflights: %v", averageInflights)
-
- anyNonZero := false
- for i, t := range targets {
- if t.FreeSpace > int64(t.Target.MinimumFreeSpace) {
- targets[i].Weight = int(int64(averageInflights) - int64(t.Inflight))
-
- // Clamp to 0
- if targets[i].Weight < 0 {
- targets[i].Weight = 0
- }
-
- // Record if we found anything nonzero
- if targets[i].Weight > 0 {
- anyNonZero = true
- }
- } else {
- targets[i].Weight = 0
- }
-
- }
- // Failsafe, if nobody is eligible, everybody is (that has space)
- if !anyNonZero {
- for i, t := range targets {
- if t.FreeSpace > int64(t.Target.MinimumFreeSpace) {
- targets[i].Weight = 1
- }
- }
- }
-
- for _, t := range targets {
- log.WithField("target", t.Target.Id).Debugf("Computed weight: %v", t.Weight)
- }
-
- state.updateTargets(targets)
-
- time.Sleep(15 * time.Second)
- }
- }
|