mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-27 14:39:40 -08:00
144 lines
5 KiB
Go
144 lines
5 KiB
Go
|
// Copyright 2013 Prometheus Team
|
||
|
// 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 retrieval
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// The default increment for exponential backoff when querying a target.
|
||
|
DEFAULT_BACKOFF_VALUE = 2
|
||
|
// The base units for the exponential backoff.
|
||
|
DEFAULT_BACKOFF_VALUE_UNIT = time.Second
|
||
|
// The maximum allowed backoff time.
|
||
|
MAXIMUM_BACKOFF_VALUE = 30 * time.Minute
|
||
|
)
|
||
|
|
||
|
// A basic interface only useful in testing contexts for dispensing the time
|
||
|
// in a controlled manner.
|
||
|
type instantProvider interface {
|
||
|
// The current instant.
|
||
|
Now() time.Time
|
||
|
}
|
||
|
|
||
|
// timer is a simple means for fluently wrapping around standard Go timekeeping
|
||
|
// mechanisms to enhance testability without compromising code readability.
|
||
|
//
|
||
|
// A timer is sufficient for use on bare initialization. A provider should be
|
||
|
// set only for test contexts. When not provided, a timer emits the current
|
||
|
// system time.
|
||
|
type timer struct {
|
||
|
// The underlying means through which time is provided, if supplied.
|
||
|
provider instantProvider
|
||
|
}
|
||
|
|
||
|
// Emit the current instant.
|
||
|
func (t timer) Now() time.Time {
|
||
|
if t.provider == nil {
|
||
|
return time.Now()
|
||
|
}
|
||
|
return t.provider.Now()
|
||
|
}
|
||
|
|
||
|
// scheduler is an interface that various scheduling strategies must fulfill
|
||
|
// in order to set the scheduling order for a target.
|
||
|
//
|
||
|
// Target takes advantage of this type by embedding an instance of scheduler
|
||
|
// in each Target instance itself. The emitted scheduler.ScheduledFor() is
|
||
|
// the basis for sorting the order of pending queries.
|
||
|
//
|
||
|
// This type is described as an interface to maximize testability.
|
||
|
type scheduler interface {
|
||
|
// ScheduledFor emits the earliest time at which the given object is allowed
|
||
|
// to be run. This time may or not be a reflection of the earliest parameter
|
||
|
// provided in Reschedule; that is up to the underlying strategy
|
||
|
// implementations.
|
||
|
ScheduledFor() time.Time
|
||
|
// Instruct the scheduled item to re-schedule itself given new state data and
|
||
|
// the earliest time at which the outside system thinks the operation should
|
||
|
// be scheduled for.
|
||
|
Reschedule(earliest time.Time, future TargetState)
|
||
|
}
|
||
|
|
||
|
// healthScheduler is an implementation of scheduler that uses health data
|
||
|
// provided by the target field as well as unreachability counts to determine
|
||
|
// when to next schedule an operation.
|
||
|
//
|
||
|
// The type is almost capable of being used with default initialization, except
|
||
|
// that a target field must be provided for which the system compares current
|
||
|
// health against future proposed values.
|
||
|
type healthScheduler struct {
|
||
|
scheduledFor time.Time
|
||
|
target healthReporter
|
||
|
timer timer
|
||
|
unreachableCount int
|
||
|
}
|
||
|
|
||
|
func (s healthScheduler) ScheduledFor() time.Time {
|
||
|
return s.scheduledFor
|
||
|
}
|
||
|
|
||
|
// Reschedule, like the protocol described in scheduler, uses the current and
|
||
|
// proposed future health state to determine how and when a given subject is to
|
||
|
// be scheduled.
|
||
|
//
|
||
|
// If a subject has been at given moment marked as unhealthy, an exponential
|
||
|
// backoff scheme is applied to it. The reason for this backoff is to ensure
|
||
|
// that known-healthy targets can consume valuable request queuing resources
|
||
|
// first. Depending on the retrieval interval and number of consecutive
|
||
|
// unhealthy markings, the query of these unhealthy individuals may come before
|
||
|
// the healthy ones for a short time to help ensure expeditious retrieval.
|
||
|
// The inflection point that drops these to the back of the queue is beneficial
|
||
|
// to save resources in the long-run.
|
||
|
//
|
||
|
// If a subject is healthy, its next scheduling opportunity is set to
|
||
|
// earliest, for this ensures fair querying of all remaining healthy targets and
|
||
|
// removes bias in the ordering. In order for the anti-bias value to have any
|
||
|
// value, the earliest opportunity should be set to a value that is constant
|
||
|
// for a given batch of subjects who are to be scraped on a given interval.
|
||
|
func (s *healthScheduler) Reschedule(e time.Time, f TargetState) {
|
||
|
currentState := s.target.State()
|
||
|
// XXX: Handle metrics surrounding health.
|
||
|
switch currentState {
|
||
|
case UNKNOWN, UNREACHABLE:
|
||
|
switch f {
|
||
|
case ALIVE:
|
||
|
s.unreachableCount = 0
|
||
|
break
|
||
|
case UNREACHABLE:
|
||
|
s.unreachableCount++
|
||
|
break
|
||
|
}
|
||
|
case ALIVE:
|
||
|
switch f {
|
||
|
case UNREACHABLE:
|
||
|
s.unreachableCount++
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if s.unreachableCount == 0 {
|
||
|
s.scheduledFor = e
|
||
|
} else {
|
||
|
backoff := MAXIMUM_BACKOFF_VALUE
|
||
|
exponential := time.Duration(math.Pow(DEFAULT_BACKOFF_VALUE, float64(s.unreachableCount))) * DEFAULT_BACKOFF_VALUE_UNIT
|
||
|
if backoff > exponential {
|
||
|
backoff = exponential
|
||
|
}
|
||
|
|
||
|
s.scheduledFor = s.timer.Now().Add(backoff)
|
||
|
}
|
||
|
}
|