vendor: revendor metrics-server, custom-metrics-apiserver

This commit is contained in:
Sergiusz Urbaniak 2020-10-28 15:52:52 +01:00
parent 752ce84723
commit 523aa52367
1010 changed files with 91458 additions and 29107 deletions

View file

@ -45,7 +45,7 @@ import (
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
"k8s.io/klog/v2"
fctypesv1a1 "k8s.io/api/flowcontrol/v1alpha1"
fcclientv1a1 "k8s.io/client-go/kubernetes/typed/flowcontrol/v1alpha1"
@ -84,7 +84,8 @@ type RequestDigest struct {
// this type and cfgMeal follow the convention that the suffix
// "Locked" means that the caller must hold the configController lock.
type configController struct {
queueSetFactory fq.QueueSetFactory
queueSetFactory fq.QueueSetFactory
obsPairGenerator metrics.TimedObserverPairGenerator
// configQueue holds `(interface{})(0)` when the configuration
// objects need to be reprocessed.
@ -144,6 +145,9 @@ type priorityLevelState struct {
// number of goroutines between Controller::Match and calling the
// returned StartFunction
numPending int
// Observers tracking number waiting, executing
obsPair metrics.TimedObserverPair
}
// NewTestableController is extra flexible to facilitate testing
@ -152,104 +156,123 @@ func newTestableController(
flowcontrolClient fcclientv1a1.FlowcontrolV1alpha1Interface,
serverConcurrencyLimit int,
requestWaitLimit time.Duration,
obsPairGenerator metrics.TimedObserverPairGenerator,
queueSetFactory fq.QueueSetFactory,
) *configController {
cfgCtl := &configController{
cfgCtlr := &configController{
queueSetFactory: queueSetFactory,
obsPairGenerator: obsPairGenerator,
serverConcurrencyLimit: serverConcurrencyLimit,
requestWaitLimit: requestWaitLimit,
flowcontrolClient: flowcontrolClient,
priorityLevelStates: make(map[string]*priorityLevelState),
}
klog.V(2).Infof("NewTestableController with serverConcurrencyLimit=%d, requestWaitLimit=%s", serverConcurrencyLimit, requestWaitLimit)
cfgCtl.initializeConfigController(informerFactory)
cfgCtlr.initializeConfigController(informerFactory)
// ensure the data structure reflects the mandatory config
cfgCtl.lockAndDigestConfigObjects(nil, nil)
return cfgCtl
cfgCtlr.lockAndDigestConfigObjects(nil, nil)
return cfgCtlr
}
// initializeConfigController sets up the controller that processes
// config API objects.
func (cfgCtl *configController) initializeConfigController(informerFactory kubeinformers.SharedInformerFactory) {
cfgCtl.configQueue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 8*time.Hour), "priority_and_fairness_config_queue")
func (cfgCtlr *configController) initializeConfigController(informerFactory kubeinformers.SharedInformerFactory) {
cfgCtlr.configQueue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 8*time.Hour), "priority_and_fairness_config_queue")
fci := informerFactory.Flowcontrol().V1alpha1()
pli := fci.PriorityLevelConfigurations()
fsi := fci.FlowSchemas()
cfgCtl.plLister = pli.Lister()
cfgCtl.plInformerSynced = pli.Informer().HasSynced
cfgCtl.fsLister = fsi.Lister()
cfgCtl.fsInformerSynced = fsi.Informer().HasSynced
cfgCtlr.plLister = pli.Lister()
cfgCtlr.plInformerSynced = pli.Informer().HasSynced
cfgCtlr.fsLister = fsi.Lister()
cfgCtlr.fsInformerSynced = fsi.Informer().HasSynced
pli.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pl := obj.(*fctypesv1a1.PriorityLevelConfiguration)
klog.V(7).Infof("Triggered API priority and fairness config reloading due to creation of PLC %s", pl.Name)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
},
UpdateFunc: func(oldObj, newObj interface{}) {
newPL := newObj.(*fctypesv1a1.PriorityLevelConfiguration)
oldPL := oldObj.(*fctypesv1a1.PriorityLevelConfiguration)
if !apiequality.Semantic.DeepEqual(oldPL.Spec, newPL.Spec) {
klog.V(7).Infof("Triggered API priority and fairness config reloading due to spec update of PLC %s", newPL.Name)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
}
},
DeleteFunc: func(obj interface{}) {
name, _ := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
klog.V(7).Infof("Triggered API priority and fairness config reloading due to deletion of PLC %s", name)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
}})
fsi.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fs := obj.(*fctypesv1a1.FlowSchema)
klog.V(7).Infof("Triggered API priority and fairness config reloading due to creation of FS %s", fs.Name)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
},
UpdateFunc: func(oldObj, newObj interface{}) {
newFS := newObj.(*fctypesv1a1.FlowSchema)
oldFS := oldObj.(*fctypesv1a1.FlowSchema)
if !apiequality.Semantic.DeepEqual(oldFS.Spec, newFS.Spec) {
klog.V(7).Infof("Triggered API priority and fairness config reloading due to spec update of FS %s", newFS.Name)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
}
},
DeleteFunc: func(obj interface{}) {
name, _ := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
klog.V(7).Infof("Triggered API priority and fairness config reloading due to deletion of FS %s", name)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
}})
}
func (cfgCtl *configController) Run(stopCh <-chan struct{}) error {
defer cfgCtl.configQueue.ShutDown()
// MaintainObservations keeps the observers from
// metrics.PriorityLevelConcurrencyObserverPairGenerator from falling
// too far behind
func (cfgCtlr *configController) MaintainObservations(stopCh <-chan struct{}) {
wait.Until(cfgCtlr.updateObservations, 10*time.Second, stopCh)
}
func (cfgCtlr *configController) updateObservations() {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
for _, plc := range cfgCtlr.priorityLevelStates {
if plc.queues != nil {
plc.queues.UpdateObservations()
}
}
}
func (cfgCtlr *configController) Run(stopCh <-chan struct{}) error {
defer cfgCtlr.configQueue.ShutDown()
klog.Info("Starting API Priority and Fairness config controller")
if ok := cache.WaitForCacheSync(stopCh, cfgCtl.plInformerSynced, cfgCtl.fsInformerSynced); !ok {
if ok := cache.WaitForCacheSync(stopCh, cfgCtlr.plInformerSynced, cfgCtlr.fsInformerSynced); !ok {
return fmt.Errorf("Never achieved initial sync")
}
klog.Info("Running API Priority and Fairness config worker")
wait.Until(cfgCtl.runWorker, time.Second, stopCh)
wait.Until(cfgCtlr.runWorker, time.Second, stopCh)
klog.Info("Shutting down API Priority and Fairness config worker")
return nil
}
func (cfgCtl *configController) runWorker() {
for cfgCtl.processNextWorkItem() {
func (cfgCtlr *configController) runWorker() {
for cfgCtlr.processNextWorkItem() {
}
}
func (cfgCtl *configController) processNextWorkItem() bool {
obj, shutdown := cfgCtl.configQueue.Get()
func (cfgCtlr *configController) processNextWorkItem() bool {
obj, shutdown := cfgCtlr.configQueue.Get()
if shutdown {
return false
}
func(obj interface{}) {
defer cfgCtl.configQueue.Done(obj)
if !cfgCtl.syncOne() {
cfgCtl.configQueue.AddRateLimited(obj)
defer cfgCtlr.configQueue.Done(obj)
if !cfgCtlr.syncOne() {
cfgCtlr.configQueue.AddRateLimited(obj)
} else {
cfgCtl.configQueue.Forget(obj)
cfgCtlr.configQueue.Forget(obj)
}
}(obj)
@ -259,19 +282,19 @@ func (cfgCtl *configController) processNextWorkItem() bool {
// syncOne attempts to sync all the API Priority and Fairness config
// objects. It either succeeds and returns `true` or logs an error
// and returns `false`.
func (cfgCtl *configController) syncOne() bool {
func (cfgCtlr *configController) syncOne() bool {
all := labels.Everything()
newPLs, err := cfgCtl.plLister.List(all)
newPLs, err := cfgCtlr.plLister.List(all)
if err != nil {
klog.Errorf("Unable to list PriorityLevelConfiguration objects: %s", err.Error())
return false
}
newFSs, err := cfgCtl.fsLister.List(all)
newFSs, err := cfgCtlr.fsLister.List(all)
if err != nil {
klog.Errorf("Unable to list FlowSchema objects: %s", err.Error())
return false
}
err = cfgCtl.digestConfigObjects(newPLs, newFSs)
err = cfgCtlr.digestConfigObjects(newPLs, newFSs)
if err == nil {
return true
}
@ -288,7 +311,7 @@ func (cfgCtl *configController) syncOne() bool {
// FlowSchemas --- with the work dvided among the passes according to
// those dependencies.
type cfgMeal struct {
cfgCtl *configController
cfgCtlr *configController
newPLStates map[string]*priorityLevelState
@ -315,9 +338,9 @@ type fsStatusUpdate struct {
}
// digestConfigObjects is given all the API objects that configure
// cfgCtl and writes its consequent new configState.
func (cfgCtl *configController) digestConfigObjects(newPLs []*fctypesv1a1.PriorityLevelConfiguration, newFSs []*fctypesv1a1.FlowSchema) error {
fsStatusUpdates := cfgCtl.lockAndDigestConfigObjects(newPLs, newFSs)
// cfgCtlr and writes its consequent new configState.
func (cfgCtlr *configController) digestConfigObjects(newPLs []*fctypesv1a1.PriorityLevelConfiguration, newFSs []*fctypesv1a1.FlowSchema) error {
fsStatusUpdates := cfgCtlr.lockAndDigestConfigObjects(newPLs, newFSs)
var errs []error
for _, fsu := range fsStatusUpdates {
enc, err := json.Marshal(fsu.condition)
@ -326,7 +349,7 @@ func (cfgCtl *configController) digestConfigObjects(newPLs []*fctypesv1a1.Priori
panic(fmt.Sprintf("Failed to json.Marshall(%#+v): %s", fsu.condition, err.Error()))
}
klog.V(4).Infof("Writing Condition %s to FlowSchema %s because its previous value was %s", string(enc), fsu.flowSchema.Name, fcfmt.Fmt(fsu.oldValue))
_, err = cfgCtl.flowcontrolClient.FlowSchemas().Patch(context.TODO(), fsu.flowSchema.Name, apitypes.StrategicMergePatchType, []byte(fmt.Sprintf(`{"status": {"conditions": [ %s ] } }`, string(enc))), metav1.PatchOptions{FieldManager: "api-priority-and-fairness-config-consumer-v1"}, "status")
_, err = cfgCtlr.flowcontrolClient.FlowSchemas().Patch(context.TODO(), fsu.flowSchema.Name, apitypes.StrategicMergePatchType, []byte(fmt.Sprintf(`{"status": {"conditions": [ %s ] } }`, string(enc))), metav1.PatchOptions{FieldManager: "api-priority-and-fairness-config-consumer-v1"}, "status")
if err != nil {
errs = append(errs, errors.Wrap(err, fmt.Sprintf("failed to set a status.condition for FlowSchema %s", fsu.flowSchema.Name)))
}
@ -337,11 +360,11 @@ func (cfgCtl *configController) digestConfigObjects(newPLs []*fctypesv1a1.Priori
return apierrors.NewAggregate(errs)
}
func (cfgCtl *configController) lockAndDigestConfigObjects(newPLs []*fctypesv1a1.PriorityLevelConfiguration, newFSs []*fctypesv1a1.FlowSchema) []fsStatusUpdate {
cfgCtl.lock.Lock()
defer cfgCtl.lock.Unlock()
func (cfgCtlr *configController) lockAndDigestConfigObjects(newPLs []*fctypesv1a1.PriorityLevelConfiguration, newFSs []*fctypesv1a1.FlowSchema) []fsStatusUpdate {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
meal := cfgMeal{
cfgCtl: cfgCtl,
cfgCtlr: cfgCtlr,
newPLStates: make(map[string]*priorityLevelState),
}
@ -351,16 +374,16 @@ func (cfgCtl *configController) lockAndDigestConfigObjects(newPLs []*fctypesv1a1
// Supply missing mandatory PriorityLevelConfiguration objects
if !meal.haveExemptPL {
meal.imaginePL(fcboot.MandatoryPriorityLevelConfigurationExempt, cfgCtl.requestWaitLimit)
meal.imaginePL(fcboot.MandatoryPriorityLevelConfigurationExempt, cfgCtlr.requestWaitLimit)
}
if !meal.haveCatchAllPL {
meal.imaginePL(fcboot.MandatoryPriorityLevelConfigurationCatchAll, cfgCtl.requestWaitLimit)
meal.imaginePL(fcboot.MandatoryPriorityLevelConfigurationCatchAll, cfgCtlr.requestWaitLimit)
}
meal.finishQueueSetReconfigsLocked()
// The new config has been constructed
cfgCtl.priorityLevelStates = meal.newPLStates
cfgCtlr.priorityLevelStates = meal.newPLStates
klog.V(5).Infof("Switched to new API Priority and Fairness configuration")
return meal.fsStatusUpdates
}
@ -369,11 +392,11 @@ func (cfgCtl *configController) lockAndDigestConfigObjects(newPLs []*fctypesv1a1
// Pretend broken ones do not exist.
func (meal *cfgMeal) digestNewPLsLocked(newPLs []*fctypesv1a1.PriorityLevelConfiguration) {
for _, pl := range newPLs {
state := meal.cfgCtl.priorityLevelStates[pl.Name]
state := meal.cfgCtlr.priorityLevelStates[pl.Name]
if state == nil {
state = &priorityLevelState{}
state = &priorityLevelState{obsPair: meal.cfgCtlr.obsPairGenerator.Generate(1, 1, []string{pl.Name})}
}
qsCompleter, err := qscOfPL(meal.cfgCtl.queueSetFactory, state.queues, pl, meal.cfgCtl.requestWaitLimit)
qsCompleter, err := queueSetCompleterForPL(meal.cfgCtlr.queueSetFactory, state.queues, pl, meal.cfgCtlr.requestWaitLimit, state.obsPair)
if err != nil {
klog.Warningf("Ignoring PriorityLevelConfiguration object %s because its spec (%s) is broken: %s", pl.Name, fcfmt.Fmt(pl.Spec), err)
continue
@ -439,8 +462,8 @@ func (meal *cfgMeal) digestFlowSchemasLocked(newFSs []*fctypesv1a1.FlowSchema) {
fsSeq = append(fsSeq, fcboot.MandatoryFlowSchemaCatchAll)
}
meal.cfgCtl.flowSchemas = fsSeq
if klog.V(5) {
meal.cfgCtlr.flowSchemas = fsSeq
if klog.V(5).Enabled() {
for _, fs := range fsSeq {
klog.Infof("Using FlowSchema %s", fcfmt.Fmt(fs))
}
@ -453,7 +476,7 @@ func (meal *cfgMeal) digestFlowSchemasLocked(newFSs []*fctypesv1a1.FlowSchema) {
// queues, otherwise start the quiescing process if that has not
// already been started.
func (meal *cfgMeal) processOldPLsLocked() {
for plName, plState := range meal.cfgCtl.priorityLevelStates {
for plName, plState := range meal.cfgCtlr.priorityLevelStates {
if meal.newPLStates[plName] != nil {
// Still desired and already updated
continue
@ -476,9 +499,9 @@ func (meal *cfgMeal) processOldPLsLocked() {
}
}
var err error
plState.qsCompleter, err = qscOfPL(meal.cfgCtl.queueSetFactory, plState.queues, plState.pl, meal.cfgCtl.requestWaitLimit)
plState.qsCompleter, err = queueSetCompleterForPL(meal.cfgCtlr.queueSetFactory, plState.queues, plState.pl, meal.cfgCtlr.requestWaitLimit, plState.obsPair)
if err != nil {
// This can not happen because qscOfPL already approved this config
// This can not happen because queueSetCompleterForPL already approved this config
panic(fmt.Sprintf("%s from name=%q spec=%s", err, plName, fcfmt.Fmt(plState.pl.Spec)))
}
if plState.pl.Spec.Limited != nil {
@ -509,7 +532,7 @@ func (meal *cfgMeal) finishQueueSetReconfigsLocked() {
// The use of math.Ceil here means that the results might sum
// to a little more than serverConcurrencyLimit but the
// difference will be negligible.
concurrencyLimit := int(math.Ceil(float64(meal.cfgCtl.serverConcurrencyLimit) * float64(plState.pl.Spec.Limited.AssuredConcurrencyShares) / meal.shareSum))
concurrencyLimit := int(math.Ceil(float64(meal.cfgCtlr.serverConcurrencyLimit) * float64(plState.pl.Spec.Limited.AssuredConcurrencyShares) / meal.shareSum))
metrics.UpdateSharedConcurrencyLimit(plName, concurrencyLimit)
if plState.queues == nil {
@ -521,10 +544,11 @@ func (meal *cfgMeal) finishQueueSetReconfigsLocked() {
}
}
// qscOfPL returns a pointer to an appropriate QueuingConfig or nil
// if no limiting is called for. Returns nil and an error if the given
// queueSetCompleterForPL returns an appropriate QueueSetCompleter for the
// given priority level configuration. Returns nil if that config
// does not call for limiting. Returns nil and an error if the given
// object is malformed in a way that is a problem for this package.
func qscOfPL(qsf fq.QueueSetFactory, queues fq.QueueSet, pl *fctypesv1a1.PriorityLevelConfiguration, requestWaitLimit time.Duration) (fq.QueueSetCompleter, error) {
func queueSetCompleterForPL(qsf fq.QueueSetFactory, queues fq.QueueSet, pl *fctypesv1a1.PriorityLevelConfiguration, requestWaitLimit time.Duration, intPair metrics.TimedObserverPair) (fq.QueueSetCompleter, error) {
if (pl.Spec.Type == fctypesv1a1.PriorityLevelEnablementExempt) != (pl.Spec.Limited == nil) {
return nil, errors.New("broken union structure at the top")
}
@ -553,7 +577,7 @@ func qscOfPL(qsf fq.QueueSetFactory, queues fq.QueueSet, pl *fctypesv1a1.Priorit
if queues != nil {
qsc, err = queues.BeginConfigChange(qcQS)
} else {
qsc, err = qsf.BeginConstruction(qcQS)
qsc, err = qsf.BeginConstruction(qcQS, intPair)
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("priority level %q has QueuingConfiguration %#+v, which is invalid", pl.Name, qcAPI))
@ -594,9 +618,11 @@ func (meal *cfgMeal) presyncFlowSchemaStatus(fs *fctypesv1a1.FlowSchema, isDangl
}
// imaginePL adds a priority level based on one of the mandatory ones
// that does not actually exist (right now) as a real API object.
func (meal *cfgMeal) imaginePL(proto *fctypesv1a1.PriorityLevelConfiguration, requestWaitLimit time.Duration) {
klog.V(3).Infof("No %s PriorityLevelConfiguration found, imagining one", proto.Name)
qsCompleter, err := qscOfPL(meal.cfgCtl.queueSetFactory, nil, proto, requestWaitLimit)
obsPair := meal.cfgCtlr.obsPairGenerator.Generate(1, 1, []string{proto.Name})
qsCompleter, err := queueSetCompleterForPL(meal.cfgCtlr.queueSetFactory, nil, proto, requestWaitLimit, obsPair)
if err != nil {
// This can not happen because proto is one of the mandatory
// objects and these are not erroneous
@ -605,6 +631,7 @@ func (meal *cfgMeal) imaginePL(proto *fctypesv1a1.PriorityLevelConfiguration, re
meal.newPLStates[proto.Name] = &priorityLevelState{
pl: proto,
qsCompleter: qsCompleter,
obsPair: obsPair,
}
if proto.Spec.Limited != nil {
meal.shareSum += float64(proto.Spec.Limited.AssuredConcurrencyShares)
@ -624,14 +651,14 @@ func (immediateRequest) Finish(execute func()) bool {
// The returned bool indicates whether the request is exempt from
// limitation. The startWaitingTime is when the request started
// waiting in its queue, or `Time{}` if this did not happen.
func (cfgCtl *configController) startRequest(ctx context.Context, rd RequestDigest) (fs *fctypesv1a1.FlowSchema, pl *fctypesv1a1.PriorityLevelConfiguration, isExempt bool, req fq.Request, startWaitingTime time.Time) {
func (cfgCtlr *configController) startRequest(ctx context.Context, rd RequestDigest, queueNoteFn fq.QueueNoteFn) (fs *fctypesv1a1.FlowSchema, pl *fctypesv1a1.PriorityLevelConfiguration, isExempt bool, req fq.Request, startWaitingTime time.Time) {
klog.V(7).Infof("startRequest(%#+v)", rd)
cfgCtl.lock.Lock()
defer cfgCtl.lock.Unlock()
for _, fs := range cfgCtl.flowSchemas {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
for _, fs := range cfgCtlr.flowSchemas {
if matchesFlowSchema(rd, fs) {
plName := fs.Spec.PriorityLevelConfiguration.Name
plState := cfgCtl.priorityLevelStates[plName]
plState := cfgCtlr.priorityLevelStates[plName]
if plState.pl.Spec.Type == fctypesv1a1.PriorityLevelEnablementExempt {
klog.V(7).Infof("startRequest(%#+v) => fsName=%q, distMethod=%#+v, plName=%q, immediate", rd, fs.Name, fs.Spec.DistinguisherMethod, plName)
return fs, plState.pl, true, immediateRequest{}, time.Time{}
@ -641,16 +668,17 @@ func (cfgCtl *configController) startRequest(ctx context.Context, rd RequestDige
numQueues = plState.pl.Spec.Limited.LimitResponse.Queuing.Queues
}
var flowDistinguisher string
var hashValue uint64
if numQueues > 1 {
flowDistinguisher := computeFlowDistinguisher(rd, fs.Spec.DistinguisherMethod)
flowDistinguisher = computeFlowDistinguisher(rd, fs.Spec.DistinguisherMethod)
hashValue = hashFlowID(fs.Name, flowDistinguisher)
}
startWaitingTime = time.Now()
klog.V(7).Infof("startRequest(%#+v) => fsName=%q, distMethod=%#+v, plName=%q, numQueues=%d", rd, fs.Name, fs.Spec.DistinguisherMethod, plName, numQueues)
req, idle := plState.queues.StartRequest(ctx, hashValue, fs.Name, rd.RequestInfo, rd.User)
req, idle := plState.queues.StartRequest(ctx, hashValue, flowDistinguisher, fs.Name, rd.RequestInfo, rd.User, queueNoteFn)
if idle {
cfgCtl.maybeReapLocked(plName, plState)
cfgCtlr.maybeReapLocked(plName, plState)
}
return fs, plState.pl, false, req, startWaitingTime
}
@ -659,7 +687,7 @@ func (cfgCtl *configController) startRequest(ctx context.Context, rd RequestDige
// FlowSchema that matches everything. If somehow control reaches
// here, panic with some relevant information.
var catchAll *fctypesv1a1.FlowSchema
for _, fs := range cfgCtl.flowSchemas {
for _, fs := range cfgCtlr.flowSchemas {
if fs.Name == fctypesv1a1.FlowSchemaNameCatchAll {
catchAll = fs
}
@ -668,10 +696,10 @@ func (cfgCtl *configController) startRequest(ctx context.Context, rd RequestDige
}
// Call this after getting a clue that the given priority level is undesired and idle
func (cfgCtl *configController) maybeReap(plName string) {
cfgCtl.lock.Lock()
defer cfgCtl.lock.Unlock()
plState := cfgCtl.priorityLevelStates[plName]
func (cfgCtlr *configController) maybeReap(plName string) {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
plState := cfgCtlr.priorityLevelStates[plName]
if plState == nil {
klog.V(7).Infof("plName=%s, plState==nil", plName)
return
@ -684,17 +712,17 @@ func (cfgCtl *configController) maybeReap(plName string) {
}
}
klog.V(3).Infof("Triggered API priority and fairness config reloading because priority level %s is undesired and idle", plName)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
}
// Call this if both (1) plState.queues is non-nil and reported being
// idle, and (2) cfgCtl's lock has not been released since then.
func (cfgCtl *configController) maybeReapLocked(plName string, plState *priorityLevelState) {
// idle, and (2) cfgCtlr's lock has not been released since then.
func (cfgCtlr *configController) maybeReapLocked(plName string, plState *priorityLevelState) {
if !(plState.quiescing && plState.numPending == 0) {
return
}
klog.V(3).Infof("Triggered API priority and fairness config reloading because priority level %s is undesired and idle", plName)
cfgCtl.configQueue.Add(0)
cfgCtlr.configQueue.Add(0)
}
// computeFlowDistinguisher extracts the flow distinguisher according to the given method

View file

@ -0,0 +1,290 @@
/*
Copyright 2019 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 flowcontrol
import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"text/tabwriter"
"time"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/server/mux"
)
const (
queryIncludeRequestDetails = "includeRequestDetails"
)
func (cfgCtlr *configController) Install(c *mux.PathRecorderMux) {
// TODO(yue9944882): handle "Accept" header properly
// debugging dumps a CSV content for three levels of granularity
// 1. row per priority-level
c.UnlistedHandleFunc("/debug/api_priority_and_fairness/dump_priority_levels", cfgCtlr.dumpPriorityLevels)
// 2. row per queue
c.UnlistedHandleFunc("/debug/api_priority_and_fairness/dump_queues", cfgCtlr.dumpQueues)
// 3. row per request
c.UnlistedHandleFunc("/debug/api_priority_and_fairness/dump_requests", cfgCtlr.dumpRequests)
}
func (cfgCtlr *configController) dumpPriorityLevels(w http.ResponseWriter, r *http.Request) {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
tabWriter := tabwriter.NewWriter(w, 8, 0, 1, ' ', 0)
columnHeaders := []string{
"PriorityLevelName", // 1
"ActiveQueues", // 2
"IsIdle", // 3
"IsQuiescing", // 4
"WaitingRequests", // 5
"ExecutingRequests", // 6
}
tabPrint(tabWriter, rowForHeaders(columnHeaders))
endLine(tabWriter)
for _, plState := range cfgCtlr.priorityLevelStates {
if plState.queues == nil {
tabPrint(tabWriter, row(
plState.pl.Name, // 1
"<none>", // 2
"<none>", // 3
"<none>", // 4
"<none>", // 5
"<none>", // 6
))
endLine(tabWriter)
continue
}
queueSetDigest := plState.queues.Dump(false)
activeQueueNum := 0
for _, q := range queueSetDigest.Queues {
if len(q.Requests) > 0 {
activeQueueNum++
}
}
tabPrint(tabWriter, rowForPriorityLevel(
plState.pl.Name, // 1
activeQueueNum, // 2
plState.queues.IsIdle(), // 3
plState.quiescing, // 4
queueSetDigest.Waiting, // 5
queueSetDigest.Executing, // 6
))
endLine(tabWriter)
}
runtime.HandleError(tabWriter.Flush())
}
func (cfgCtlr *configController) dumpQueues(w http.ResponseWriter, r *http.Request) {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
tabWriter := tabwriter.NewWriter(w, 8, 0, 1, ' ', 0)
columnHeaders := []string{
"PriorityLevelName", // 1
"Index", // 2
"PendingRequests", // 3
"ExecutingRequests", // 4
"VirtualStart", // 5
}
tabPrint(tabWriter, rowForHeaders(columnHeaders))
endLine(tabWriter)
for _, plState := range cfgCtlr.priorityLevelStates {
if plState.queues == nil {
tabPrint(tabWriter, row(
plState.pl.Name, // 1
"<none>", // 2
"<none>", // 3
"<none>", // 4
"<none>", // 5
))
endLine(tabWriter)
continue
}
queueSetDigest := plState.queues.Dump(false)
for i, q := range queueSetDigest.Queues {
tabPrint(tabWriter, rowForQueue(
plState.pl.Name, // 1
i, // 2
len(q.Requests), // 3
q.ExecutingRequests, // 4
q.VirtualStart, // 5
))
endLine(tabWriter)
}
}
runtime.HandleError(tabWriter.Flush())
}
func (cfgCtlr *configController) dumpRequests(w http.ResponseWriter, r *http.Request) {
cfgCtlr.lock.Lock()
defer cfgCtlr.lock.Unlock()
includeRequestDetails := len(r.URL.Query().Get(queryIncludeRequestDetails)) > 0
tabWriter := tabwriter.NewWriter(w, 8, 0, 1, ' ', 0)
tabPrint(tabWriter, rowForHeaders([]string{
"PriorityLevelName", // 1
"FlowSchemaName", // 2
"QueueIndex", // 3
"RequestIndexInQueue", // 4
"FlowDistingsher", // 5
"ArriveTime", // 6
}))
if includeRequestDetails {
continueLine(tabWriter)
tabPrint(tabWriter, rowForHeaders([]string{
"UserName", // 7
"Verb", // 8
"APIPath", // 9
"Namespace", // 10
"Name", // 11
"APIVersion", // 12
"Resource", // 13
"SubResource", // 14
}))
}
endLine(tabWriter)
for _, plState := range cfgCtlr.priorityLevelStates {
if plState.queues == nil {
tabPrint(tabWriter, row(
plState.pl.Name, // 1
"<none>", // 2
"<none>", // 3
"<none>", // 4
"<none>", // 5
"<none>", // 6
))
if includeRequestDetails {
continueLine(tabWriter)
tabPrint(tabWriter, row(
"<none>", // 7
"<none>", // 8
"<none>", // 9
"<none>", // 10
"<none>", // 11
"<none>", // 12
"<none>", // 13
"<none>", // 14
))
}
endLine(tabWriter)
continue
}
queueSetDigest := plState.queues.Dump(includeRequestDetails)
for iq, q := range queueSetDigest.Queues {
for ir, r := range q.Requests {
tabPrint(tabWriter, rowForRequest(
plState.pl.Name, // 1
r.MatchedFlowSchema, // 2
iq, // 3
ir, // 4
r.FlowDistinguisher, // 5
r.ArriveTime, // 6
))
if includeRequestDetails {
continueLine(tabWriter)
tabPrint(tabWriter, rowForRequestDetails(
r.UserName, // 7
r.RequestInfo.Verb, // 8
r.RequestInfo.Path, // 9
r.RequestInfo.Namespace, // 10
r.RequestInfo.Name, // 11
schema.GroupVersion{
Group: r.RequestInfo.APIGroup,
Version: r.RequestInfo.APIVersion,
}.String(), // 12
r.RequestInfo.Resource, // 13
r.RequestInfo.Subresource, // 14
))
}
endLine(tabWriter)
}
}
}
runtime.HandleError(tabWriter.Flush())
}
func tabPrint(w io.Writer, row string) {
_, err := fmt.Fprint(w, row)
runtime.HandleError(err)
}
func continueLine(w io.Writer) {
_, err := fmt.Fprint(w, ",\t")
runtime.HandleError(err)
}
func endLine(w io.Writer) {
_, err := fmt.Fprint(w, "\n")
runtime.HandleError(err)
}
func rowForHeaders(headers []string) string {
return row(headers...)
}
func rowForPriorityLevel(plName string, activeQueues int, isIdle, isQuiescing bool, waitingRequests, executingRequests int) string {
return row(
plName,
strconv.Itoa(activeQueues),
strconv.FormatBool(isIdle),
strconv.FormatBool(isQuiescing),
strconv.Itoa(waitingRequests),
strconv.Itoa(executingRequests),
)
}
func rowForQueue(plName string, index, waitingRequests, executingRequests int, virtualStart float64) string {
return row(
plName,
strconv.Itoa(index),
strconv.Itoa(waitingRequests),
strconv.Itoa(executingRequests),
fmt.Sprintf("%.4f", virtualStart),
)
}
func rowForRequest(plName, fsName string, queueIndex, requestIndex int, flowDistinguisher string, arriveTime time.Time) string {
return row(
plName,
fsName,
strconv.Itoa(queueIndex),
strconv.Itoa(requestIndex),
flowDistinguisher,
arriveTime.UTC().Format(time.RFC3339Nano),
)
}
func rowForRequestDetails(username, verb, path, namespace, name, apiVersion, resource, subResource string) string {
return row(
username,
verb,
path,
namespace,
name,
apiVersion,
resource,
subResource,
)
}
func row(columns ...string) string {
return strings.Join(columns, ",\t")
}

View file

@ -22,12 +22,13 @@ import (
"time"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apiserver/pkg/server/mux"
"k8s.io/apiserver/pkg/util/flowcontrol/counter"
fq "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing"
fqs "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset"
"k8s.io/apiserver/pkg/util/flowcontrol/metrics"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/klog"
"k8s.io/klog/v2"
fctypesv1a1 "k8s.io/api/flowcontrol/v1alpha1"
fcclientv1a1 "k8s.io/client-go/kubernetes/typed/flowcontrol/v1alpha1"
@ -37,20 +38,30 @@ import (
type Interface interface {
// Handle takes care of queuing and dispatching a request
// characterized by the given digest. The given `noteFn` will be
// invoked with the results of request classification. If Handle
// decides that the request should be executed then `execute()`
// will be invoked once to execute the request; otherwise
// `execute()` will not be invoked.
// invoked with the results of request classification. If the
// request is queued then `queueNoteFn` will be called twice,
// first with `true` and then with `false`; otherwise
// `queueNoteFn` will not be called at all. If Handle decides
// that the request should be executed then `execute()` will be
// invoked once to execute the request; otherwise `execute()` will
// not be invoked.
Handle(ctx context.Context,
requestDigest RequestDigest,
noteFn func(fs *fctypesv1a1.FlowSchema, pl *fctypesv1a1.PriorityLevelConfiguration),
queueNoteFn fq.QueueNoteFn,
execFn func(),
)
// MaintainObservations is a helper for maintaining statistics.
MaintainObservations(stopCh <-chan struct{})
// Run monitors config objects from the main apiservers and causes
// any needed changes to local behavior. This method ceases
// activity and returns after the given channel is closed.
Run(stopCh <-chan struct{}) error
// Install installs debugging endpoints to the web-server.
Install(c *mux.PathRecorderMux)
}
// This request filter implements https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190228-priority-and-fairness.md
@ -68,6 +79,7 @@ func New(
flowcontrolClient,
serverConcurrencyLimit,
requestWaitLimit,
metrics.PriorityLevelConcurrencyObserverPairGenerator,
fqs.NewQueueSetFactory(&clock.RealClock{}, grc),
)
}
@ -78,15 +90,17 @@ func NewTestable(
flowcontrolClient fcclientv1a1.FlowcontrolV1alpha1Interface,
serverConcurrencyLimit int,
requestWaitLimit time.Duration,
obsPairGenerator metrics.TimedObserverPairGenerator,
queueSetFactory fq.QueueSetFactory,
) Interface {
return newTestableController(informerFactory, flowcontrolClient, serverConcurrencyLimit, requestWaitLimit, queueSetFactory)
return newTestableController(informerFactory, flowcontrolClient, serverConcurrencyLimit, requestWaitLimit, obsPairGenerator, queueSetFactory)
}
func (cfgCtl *configController) Handle(ctx context.Context, requestDigest RequestDigest,
func (cfgCtlr *configController) Handle(ctx context.Context, requestDigest RequestDigest,
noteFn func(fs *fctypesv1a1.FlowSchema, pl *fctypesv1a1.PriorityLevelConfiguration),
queueNoteFn fq.QueueNoteFn,
execFn func()) {
fs, pl, isExempt, req, startWaitingTime := cfgCtl.startRequest(ctx, requestDigest)
fs, pl, isExempt, req, startWaitingTime := cfgCtlr.startRequest(ctx, requestDigest, queueNoteFn)
queued := startWaitingTime != time.Time{}
noteFn(fs, pl)
if req == nil {
@ -113,6 +127,6 @@ func (cfgCtl *configController) Handle(ctx context.Context, requestDigest Reques
}
klog.V(7).Infof("Handle(%#+v) => fsName=%q, distMethod=%#+v, plName=%q, isExempt=%v, queued=%v, Finish() => idle=%v", requestDigest, fs.Name, fs.Spec.DistinguisherMethod, pl.Name, isExempt, queued, idle)
if idle {
cfgCtl.maybeReap(pl.Name)
cfgCtlr.maybeReap(pl.Name)
}
}

View file

@ -0,0 +1,47 @@
/*
Copyright 2016 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 debug
import (
"k8s.io/apiserver/pkg/endpoints/request"
"time"
)
// QueueSetDump is an instant dump of queue-set.
type QueueSetDump struct {
Queues []QueueDump
Waiting int
Executing int
}
// QueueDump is an instant dump of one queue in a queue-set.
type QueueDump struct {
Requests []RequestDump
VirtualStart float64
ExecutingRequests int
}
// RequestDump is an instant dump of one requests pending in the queue.
type RequestDump struct {
MatchedFlowSchema string
FlowDistinguisher string
ArriveTime time.Time
StartTime time.Time
// request details
UserName string
RequestInfo request.RequestInfo
}

View file

@ -0,0 +1,180 @@
/*
Copyright 2019 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 fairqueuing
import (
"math"
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apiserver/pkg/util/flowcontrol/metrics"
)
// Integrator computes the moments of some variable X over time as
// read from a particular clock. The integrals start when the
// Integrator is created, and ends at the latest operation on the
// Integrator. As a `metrics.TimedObserver` this fixes X1=1 and
// ignores attempts to change X1.
type Integrator interface {
metrics.TimedObserver
GetResults() IntegratorResults
// Return the results of integrating to now, and reset integration to start now
Reset() IntegratorResults
}
// IntegratorResults holds statistical abstracts of the integration
type IntegratorResults struct {
Duration float64 //seconds
Average float64 //time-weighted
Deviation float64 //standard deviation: sqrt(avg((value-avg)^2))
Min, Max float64
}
// Equal tests for semantic equality.
// This considers all NaN values to be equal to each other.
func (x *IntegratorResults) Equal(y *IntegratorResults) bool {
return x == y || x != nil && y != nil && x.Duration == y.Duration && x.Min == y.Min && x.Max == y.Max && (x.Average == y.Average || math.IsNaN(x.Average) && math.IsNaN(y.Average)) && (x.Deviation == y.Deviation || math.IsNaN(x.Deviation) && math.IsNaN(y.Deviation))
}
type integrator struct {
clock clock.PassiveClock
sync.Mutex
lastTime time.Time
x float64
moments Moments
min, max float64
}
// NewIntegrator makes one that uses the given clock
func NewIntegrator(clock clock.PassiveClock) Integrator {
return &integrator{
clock: clock,
lastTime: clock.Now(),
}
}
func (igr *integrator) SetX1(x1 float64) {
}
func (igr *integrator) Set(x float64) {
igr.Lock()
igr.setLocked(x)
igr.Unlock()
}
func (igr *integrator) setLocked(x float64) {
igr.updateLocked()
igr.x = x
if x < igr.min {
igr.min = x
}
if x > igr.max {
igr.max = x
}
}
func (igr *integrator) Add(deltaX float64) {
igr.Lock()
igr.setLocked(igr.x + deltaX)
igr.Unlock()
}
func (igr *integrator) updateLocked() {
now := igr.clock.Now()
dt := now.Sub(igr.lastTime).Seconds()
igr.lastTime = now
igr.moments = igr.moments.Add(ConstantMoments(dt, igr.x))
}
func (igr *integrator) GetResults() IntegratorResults {
igr.Lock()
defer igr.Unlock()
return igr.getResultsLocked()
}
func (igr *integrator) Reset() IntegratorResults {
igr.Lock()
defer igr.Unlock()
results := igr.getResultsLocked()
igr.moments = Moments{}
igr.min = igr.x
igr.max = igr.x
return results
}
func (igr *integrator) getResultsLocked() (results IntegratorResults) {
igr.updateLocked()
results.Min, results.Max = igr.min, igr.max
results.Duration = igr.moments.ElapsedSeconds
results.Average, results.Deviation = igr.moments.AvgAndStdDev()
return
}
// Moments are the integrals of the 0, 1, and 2 powers of some
// variable X over some range of time.
type Moments struct {
ElapsedSeconds float64 // integral of dt
IntegralX float64 // integral of x dt
IntegralXX float64 // integral of x*x dt
}
// ConstantMoments is for a constant X
func ConstantMoments(dt, x float64) Moments {
return Moments{
ElapsedSeconds: dt,
IntegralX: x * dt,
IntegralXX: x * x * dt,
}
}
// Add combines over two ranges of time
func (igr Moments) Add(ogr Moments) Moments {
return Moments{
ElapsedSeconds: igr.ElapsedSeconds + ogr.ElapsedSeconds,
IntegralX: igr.IntegralX + ogr.IntegralX,
IntegralXX: igr.IntegralXX + ogr.IntegralXX,
}
}
// Sub finds the difference between a range of time and a subrange
func (igr Moments) Sub(ogr Moments) Moments {
return Moments{
ElapsedSeconds: igr.ElapsedSeconds - ogr.ElapsedSeconds,
IntegralX: igr.IntegralX - ogr.IntegralX,
IntegralXX: igr.IntegralXX - ogr.IntegralXX,
}
}
// AvgAndStdDev returns the average and standard devation
func (igr Moments) AvgAndStdDev() (float64, float64) {
if igr.ElapsedSeconds <= 0 {
return math.NaN(), math.NaN()
}
avg := igr.IntegralX / igr.ElapsedSeconds
// standard deviation is sqrt( average( (x - xbar)^2 ) )
// = sqrt( Integral( x^2 + xbar^2 -2*x*xbar dt ) / Duration )
// = sqrt( ( Integral( x^2 dt ) + Duration * xbar^2 - 2*xbar*Integral(x dt) ) / Duration)
// = sqrt( Integral(x^2 dt)/Duration - xbar^2 )
variance := igr.IntegralXX/igr.ElapsedSeconds - avg*avg
if variance >= 0 {
return avg, math.Sqrt(variance)
}
return avg, math.NaN()
}

View file

@ -19,6 +19,9 @@ package fairqueuing
import (
"context"
"time"
"k8s.io/apiserver/pkg/util/flowcontrol/debug"
"k8s.io/apiserver/pkg/util/flowcontrol/metrics"
)
// QueueSetFactory is used to create QueueSet objects. Creation, like
@ -28,7 +31,7 @@ import (
// before committing to a concurrency allotment for the second.
type QueueSetFactory interface {
// BeginConstruction does the first phase of creating a QueueSet
BeginConstruction(QueuingConfig) (QueueSetCompleter, error)
BeginConstruction(QueuingConfig, metrics.TimedObserverPair) (QueueSetCompleter, error)
}
// QueueSetCompleter finishes the two-step process of creating or
@ -66,19 +69,33 @@ type QueueSet interface {
IsIdle() bool
// StartRequest begins the process of handling a request. If the
// request gets queued and the number of queues is greater than
// 1 then Wait uses the given hashValue as the source of entropy
// as it shuffle-shards the request into a queue. The descr1 and
// descr2 values play no role in the logic but appear in log
// messages. This method always returns quickly (without waiting
// for the request to be dequeued). If this method returns a nil
// Request value then caller should reject the request and the
// returned bool indicates whether the QueueSet was idle at the
// moment of the return. Otherwise idle==false and the client
// must call the Wait method of the Request exactly once.
StartRequest(ctx context.Context, hashValue uint64, fsName string, descr1, descr2 interface{}) (req Request, idle bool)
// request gets queued and the number of queues is greater than 1
// then StartRequest uses the given hashValue as the source of
// entropy as it shuffle-shards the request into a queue. The
// descr1 and descr2 values play no role in the logic but appear
// in log messages. This method always returns quickly (without
// waiting for the request to be dequeued). If this method
// returns a nil Request value then caller should reject the
// request and the returned bool indicates whether the QueueSet
// was idle at the moment of the return. Otherwise idle==false
// and the client must call the Finish method of the Request
// exactly once.
StartRequest(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn QueueNoteFn) (req Request, idle bool)
// UpdateObservations makes sure any time-based statistics have
// caught up with the current clock reading
UpdateObservations()
// Dump saves and returns the instant internal state of the queue-set.
// Note that dumping process will stop the queue-set from proceeding
// any requests.
// For debugging only.
Dump(includeRequestDetails bool) debug.QueueSetDump
}
// QueueNoteFn is called when a request enters and leaves a queue
type QueueNoteFn func(inQueue bool)
// Request represents the remainder of the handling of one request
type Request interface {
// Finish determines whether to execute or reject the request and
@ -116,10 +133,3 @@ type DispatchingConfig struct {
// ConcurrencyLimit is the maximum number of requests of this QueueSet that may be executing at a time
ConcurrencyLimit int
}
// EmptyHandler is used to notify the callee when all the queues
// of a QueueSet have been drained.
type EmptyHandler interface {
// HandleEmpty is called to deliver the notification
HandleEmpty()
}

View file

@ -28,11 +28,12 @@ import (
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/util/flowcontrol/counter"
"k8s.io/apiserver/pkg/util/flowcontrol/debug"
fq "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing"
"k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/promise/lockingpromise"
"k8s.io/apiserver/pkg/util/flowcontrol/metrics"
"k8s.io/apiserver/pkg/util/shufflesharding"
"k8s.io/klog"
"k8s.io/klog/v2"
)
const nsTimeFmt = "2006-01-02 15:04:05.000000000"
@ -48,6 +49,7 @@ type queueSetFactory struct {
// the fields `factory` and `theSet` is non-nil.
type queueSetCompleter struct {
factory *queueSetFactory
obsPair metrics.TimedObserverPair
theSet *queueSet
qCfg fq.QueuingConfig
dealer *shufflesharding.Dealer
@ -66,6 +68,7 @@ type queueSet struct {
clock clock.PassiveClock
counter counter.GoRoutineCounter
estimatedServiceTime float64
obsPair metrics.TimedObserverPair
lock sync.Mutex
@ -115,13 +118,14 @@ func NewQueueSetFactory(c clock.PassiveClock, counter counter.GoRoutineCounter)
}
}
func (qsf *queueSetFactory) BeginConstruction(qCfg fq.QueuingConfig) (fq.QueueSetCompleter, error) {
func (qsf *queueSetFactory) BeginConstruction(qCfg fq.QueuingConfig, obsPair metrics.TimedObserverPair) (fq.QueueSetCompleter, error) {
dealer, err := checkConfig(qCfg)
if err != nil {
return nil, err
}
return &queueSetCompleter{
factory: qsf,
obsPair: obsPair,
qCfg: qCfg,
dealer: dealer}, nil
}
@ -147,6 +151,7 @@ func (qsc *queueSetCompleter) Complete(dCfg fq.DispatchingConfig) fq.QueueSet {
clock: qsc.factory.clock,
counter: qsc.factory.counter,
estimatedServiceTime: 60,
obsPair: qsc.obsPair,
qCfg: qsc.qCfg,
virtualTime: 0,
lastRealTime: qsc.factory.clock.Now(),
@ -202,6 +207,12 @@ func (qs *queueSet) setConfiguration(qCfg fq.QueuingConfig, dealer *shuffleshard
qs.qCfg = qCfg
qs.dCfg = dCfg
qs.dealer = dealer
qll := qCfg.QueueLengthLimit
if qll < 1 {
qll = 1
}
qs.obsPair.RequestsWaiting.SetX1(float64(qll))
qs.obsPair.RequestsExecuting.SetX1(float64(dCfg.ConcurrencyLimit))
qs.dispatchAsMuchAsPossibleLocked()
}
@ -221,7 +232,7 @@ const (
// executing at each point where there is a change in that quantity,
// because the metrics --- and only the metrics --- track that
// quantity per FlowSchema.
func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, fsName string, descr1, descr2 interface{}) (fq.Request, bool) {
func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (fq.Request, bool) {
qs.lockAndSyncTime()
defer qs.lock.Unlock()
var req *request
@ -235,7 +246,7 @@ func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, fsName s
metrics.AddReject(qs.qCfg.Name, fsName, "concurrency-limit")
return nil, qs.isIdleLocked()
}
req = qs.dispatchSansQueueLocked(ctx, fsName, descr1, descr2)
req = qs.dispatchSansQueueLocked(ctx, flowDistinguisher, fsName, descr1, descr2)
return req, false
}
@ -246,7 +257,7 @@ func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, fsName s
// 3) Reject current request if there is not enough concurrency shares and
// we are at max queue length
// 4) If not rejected, create a request and enqueue
req = qs.timeoutOldRequestsAndRejectOrEnqueueLocked(ctx, hashValue, fsName, descr1, descr2)
req = qs.timeoutOldRequestsAndRejectOrEnqueueLocked(ctx, hashValue, flowDistinguisher, fsName, descr1, descr2, queueNoteFn)
// req == nil means that the request was rejected - no remaining
// concurrency shares and at max queue length already
if req == nil {
@ -294,6 +305,12 @@ func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, fsName s
return req, false
}
func (req *request) NoteQueued(inQueue bool) {
if req.queueNoteFn != nil {
req.queueNoteFn(inQueue)
}
}
func (req *request) Finish(execFn func()) bool {
exec, idle := req.wait()
if !exec {
@ -398,7 +415,7 @@ func (qs *queueSet) getVirtualTimeRatioLocked() float64 {
// returns the enqueud request on a successful enqueue
// returns nil in the case that there is no available concurrency or
// the queuelengthlimit has been reached
func (qs *queueSet) timeoutOldRequestsAndRejectOrEnqueueLocked(ctx context.Context, hashValue uint64, fsName string, descr1, descr2 interface{}) *request {
func (qs *queueSet) timeoutOldRequestsAndRejectOrEnqueueLocked(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) *request {
// Start with the shuffle sharding, to pick a queue.
queueIdx := qs.chooseQueueIndexLocked(hashValue, descr1, descr2)
queue := qs.queues[queueIdx]
@ -410,14 +427,16 @@ func (qs *queueSet) timeoutOldRequestsAndRejectOrEnqueueLocked(ctx context.Conte
// Create a request and enqueue
req := &request{
qs: qs,
fsName: fsName,
ctx: ctx,
decision: lockingpromise.NewWriteOnce(&qs.lock, qs.counter),
arrivalTime: qs.clock.Now(),
queue: queue,
descr1: descr1,
descr2: descr2,
qs: qs,
fsName: fsName,
flowDistinguisher: flowDistinguisher,
ctx: ctx,
decision: lockingpromise.NewWriteOnce(&qs.lock, qs.counter),
arrivalTime: qs.clock.Now(),
queue: queue,
descr1: descr1,
descr2: descr2,
queueNoteFn: queueNoteFn,
}
if ok := qs.rejectOrEnqueueLocked(req); !ok {
return nil
@ -439,7 +458,7 @@ func (qs *queueSet) chooseQueueIndexLocked(hashValue uint64, descr1, descr2 inte
bestQueueIdx, bestQueueLen = queueIdx, thisLen
}
})
klog.V(6).Infof("QS(%s): For request %#+v %#+v chose queue %d, had %d waiting & %d executing", qs.qCfg.Name, descr1, descr2, bestQueueIdx, bestQueueLen, qs.queues[bestQueueIdx].requestsExecuting)
klog.V(6).Infof("QS(%s) at r=%s v=%.9fs: For request %#+v %#+v chose queue %d, had %d waiting & %d executing", qs.qCfg.Name, qs.clock.Now().Format(nsTimeFmt), qs.virtualTime, descr1, descr2, bestQueueIdx, bestQueueLen, qs.queues[bestQueueIdx].requestsExecuting)
return bestQueueIdx
}
@ -461,6 +480,7 @@ func (qs *queueSet) removeTimedOutRequestsFromQueueLocked(queue *queue, fsName s
// get index for timed out requests
timeoutIdx = i
metrics.AddRequestsInQueues(qs.qCfg.Name, req.fsName, -1)
req.NoteQueued(false)
} else {
break
}
@ -473,6 +493,7 @@ func (qs *queueSet) removeTimedOutRequestsFromQueueLocked(queue *queue, fsName s
queue.requests = reqs[removeIdx:]
// decrement the # of requestsEnqueued
qs.totRequestsWaiting -= removeIdx
qs.obsPair.RequestsWaiting.Add(float64(-removeIdx))
}
}
@ -496,16 +517,19 @@ func (qs *queueSet) rejectOrEnqueueLocked(request *request) bool {
// enqueues a request into its queue.
func (qs *queueSet) enqueueLocked(request *request) {
queue := request.queue
now := qs.clock.Now()
if len(queue.requests) == 0 && queue.requestsExecuting == 0 {
// the queues virtual start time is set to the virtual time.
queue.virtualStart = qs.virtualTime
if klog.V(6) {
klog.Infof("QS(%s) at r=%s v=%.9fs: initialized queue %d virtual start time due to request %#+v %#+v", qs.qCfg.Name, qs.clock.Now().Format(nsTimeFmt), queue.virtualStart, queue.index, request.descr1, request.descr2)
if klog.V(6).Enabled() {
klog.Infof("QS(%s) at r=%s v=%.9fs: initialized queue %d virtual start time due to request %#+v %#+v", qs.qCfg.Name, now.Format(nsTimeFmt), queue.virtualStart, queue.index, request.descr1, request.descr2)
}
}
queue.Enqueue(request)
qs.totRequestsWaiting++
metrics.AddRequestsInQueues(qs.qCfg.Name, request.fsName, 1)
request.NoteQueued(true)
qs.obsPair.RequestsWaiting.Add(1)
}
// dispatchAsMuchAsPossibleLocked runs a loop, as long as there
@ -523,22 +547,24 @@ func (qs *queueSet) dispatchAsMuchAsPossibleLocked() {
}
}
func (qs *queueSet) dispatchSansQueueLocked(ctx context.Context, fsName string, descr1, descr2 interface{}) *request {
func (qs *queueSet) dispatchSansQueueLocked(ctx context.Context, flowDistinguisher, fsName string, descr1, descr2 interface{}) *request {
now := qs.clock.Now()
req := &request{
qs: qs,
fsName: fsName,
ctx: ctx,
startTime: now,
decision: lockingpromise.NewWriteOnce(&qs.lock, qs.counter),
arrivalTime: now,
descr1: descr1,
descr2: descr2,
qs: qs,
fsName: fsName,
flowDistinguisher: flowDistinguisher,
ctx: ctx,
startTime: now,
decision: lockingpromise.NewWriteOnce(&qs.lock, qs.counter),
arrivalTime: now,
descr1: descr1,
descr2: descr2,
}
req.decision.SetLocked(decisionExecute)
qs.totRequestsExecuting++
metrics.AddRequestsExecuting(qs.qCfg.Name, fsName, 1)
if klog.V(5) {
qs.obsPair.RequestsExecuting.Add(1)
if klog.V(5).Enabled() {
klog.Infof("QS(%s) at r=%s v=%.9fs: immediate dispatch of request %q %#+v %#+v, qs will have %d executing", qs.qCfg.Name, now.Format(nsTimeFmt), qs.virtualTime, fsName, descr1, descr2, qs.totRequestsExecuting)
}
return req
@ -567,8 +593,11 @@ func (qs *queueSet) dispatchLocked() bool {
qs.totRequestsExecuting++
queue.requestsExecuting++
metrics.AddRequestsInQueues(qs.qCfg.Name, request.fsName, -1)
request.NoteQueued(false)
metrics.AddRequestsExecuting(qs.qCfg.Name, request.fsName, 1)
if klog.V(6) {
qs.obsPair.RequestsWaiting.Add(-1)
qs.obsPair.RequestsExecuting.Add(1)
if klog.V(6).Enabled() {
klog.Infof("QS(%s) at r=%s v=%.9fs: dispatching request %#+v %#+v from queue %d with virtual start time %.9fs, queue will have %d waiting & %d executing", qs.qCfg.Name, request.startTime.Format(nsTimeFmt), qs.virtualTime, request.descr1, request.descr2, queue.index, queue.virtualStart, len(queue.requests), queue.requestsExecuting)
}
// When a request is dequeued for service -> qs.virtualStart += G
@ -596,6 +625,8 @@ func (qs *queueSet) cancelWait(req *request) {
queue.requests = append(queue.requests[:i], queue.requests[i+1:]...)
qs.totRequestsWaiting--
metrics.AddRequestsInQueues(qs.qCfg.Name, req.fsName, -1)
req.NoteQueued(false)
qs.obsPair.RequestsWaiting.Add(-1)
break
}
}
@ -647,17 +678,19 @@ func (qs *queueSet) finishRequestAndDispatchAsMuchAsPossible(req *request) bool
// previously dispatched request has completed it's service. This
// callback updates important state in the queueSet
func (qs *queueSet) finishRequestLocked(r *request) {
now := qs.clock.Now()
qs.totRequestsExecuting--
metrics.AddRequestsExecuting(qs.qCfg.Name, r.fsName, -1)
qs.obsPair.RequestsExecuting.Add(-1)
if r.queue == nil {
if klog.V(6) {
klog.Infof("QS(%s) at r=%s v=%.9fs: request %#+v %#+v finished, qs will have %d executing", qs.qCfg.Name, qs.clock.Now().Format(nsTimeFmt), qs.virtualTime, r.descr1, r.descr2, qs.totRequestsExecuting)
if klog.V(6).Enabled() {
klog.Infof("QS(%s) at r=%s v=%.9fs: request %#+v %#+v finished, qs will have %d executing", qs.qCfg.Name, now.Format(nsTimeFmt), qs.virtualTime, r.descr1, r.descr2, qs.totRequestsExecuting)
}
return
}
S := qs.clock.Since(r.startTime).Seconds()
S := now.Sub(r.startTime).Seconds()
// When a request finishes being served, and the actual service time was S,
// the queues virtual start time is decremented by G - S.
@ -666,8 +699,8 @@ func (qs *queueSet) finishRequestLocked(r *request) {
// request has finished, remove from requests executing
r.queue.requestsExecuting--
if klog.V(6) {
klog.Infof("QS(%s) at r=%s v=%.9fs: request %#+v %#+v finished, adjusted queue %d virtual start time to %.9fs due to service time %.9fs, queue will have %d waiting & %d executing", qs.qCfg.Name, qs.clock.Now().Format(nsTimeFmt), qs.virtualTime, r.descr1, r.descr2, r.queue.index, r.queue.virtualStart, S, len(r.queue.requests), r.queue.requestsExecuting)
if klog.V(6).Enabled() {
klog.Infof("QS(%s) at r=%s v=%.9fs: request %#+v %#+v finished, adjusted queue %d virtual start time to %.9fs due to service time %.9fs, queue will have %d waiting & %d executing", qs.qCfg.Name, now.Format(nsTimeFmt), qs.virtualTime, r.descr1, r.descr2, r.queue.index, r.queue.virtualStart, S, len(r.queue.requests), r.queue.requestsExecuting)
}
// If there are more queues than desired and this one has no
@ -709,3 +742,22 @@ func (qs *queueSet) preCreateOrUnblockGoroutine() {
func (qs *queueSet) goroutineDoneOrBlocked() {
qs.counter.Add(-1)
}
func (qs *queueSet) UpdateObservations() {
qs.obsPair.RequestsWaiting.Add(0)
qs.obsPair.RequestsExecuting.Add(0)
}
func (qs *queueSet) Dump(includeRequestDetails bool) debug.QueueSetDump {
qs.lock.Lock()
defer qs.lock.Unlock()
d := debug.QueueSetDump{
Queues: make([]debug.QueueDump, len(qs.queues)),
Waiting: qs.totRequestsWaiting,
Executing: qs.totRequestsExecuting,
}
for i, q := range qs.queues {
d.Queues[i] = q.dump(includeRequestDetails)
}
return d
}

View file

@ -20,15 +20,21 @@ import (
"context"
"time"
genericrequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/util/flowcontrol/debug"
fq "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing"
"k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/promise"
)
// request is a temporary container for "requests" with additional
// tracking fields required for the functionality FQScheduler
type request struct {
qs *queueSet
fsName string
ctx context.Context
ctx context.Context
qs *queueSet
flowDistinguisher string
fsName string
// The relevant queue. Is nil if this request did not go through
// a queue.
@ -53,6 +59,8 @@ type request struct {
// Indicates whether client has called Request::Wait()
waitStarted bool
queueNoteFn fq.QueueNoteFn
}
// queue is an array of requests with additional metadata required for
@ -94,3 +102,27 @@ func (q *queue) GetVirtualFinish(J int, G float64) float64 {
jg := float64(J+1) * float64(G)
return jg + q.virtualStart
}
func (q *queue) dump(includeDetails bool) debug.QueueDump {
digest := make([]debug.RequestDump, len(q.requests))
for i, r := range q.requests {
// dump requests.
digest[i].MatchedFlowSchema = r.fsName
digest[i].FlowDistinguisher = r.flowDistinguisher
digest[i].ArriveTime = r.arrivalTime
digest[i].StartTime = r.startTime
if includeDetails {
userInfo, _ := genericrequest.UserFrom(r.ctx)
digest[i].UserName = userInfo.GetName()
requestInfo, ok := genericrequest.RequestInfoFrom(r.ctx)
if ok {
digest[i].RequestInfo = *requestInfo
}
}
}
return debug.QueueDump{
VirtualStart: q.virtualStart,
Requests: digest,
ExecutingRequests: q.requestsExecuting,
}
}

View file

@ -21,6 +21,7 @@ import (
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
compbasemetrics "k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
basemetricstestutil "k8s.io/component-base/metrics/testutil"
@ -32,8 +33,11 @@ const (
)
const (
requestKind = "request_kind"
priorityLevel = "priorityLevel"
flowSchema = "flowSchema"
phase = "phase"
mark = "mark"
)
var (
@ -69,6 +73,14 @@ func GatherAndCompare(expected string, metricNames ...string) error {
return basemetricstestutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expected), metricNames...)
}
// Registerables is a slice of Registerable
type Registerables []compbasemetrics.Registerable
// Append adds more
func (rs Registerables) Append(more ...compbasemetrics.Registerable) Registerables {
return append(rs, more...)
}
var (
apiserverRejectedRequestsTotal = compbasemetrics.NewCounterVec(
&compbasemetrics.CounterOpts{
@ -88,6 +100,47 @@ var (
},
[]string{priorityLevel, flowSchema},
)
// PriorityLevelConcurrencyObserverPairGenerator creates pairs that observe concurrency for priority levels
PriorityLevelConcurrencyObserverPairGenerator = NewSampleAndWaterMarkHistogramsPairGenerator(clock.RealClock{}, time.Millisecond,
&compbasemetrics.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "priority_level_request_count_samples",
Help: "Periodic observations of the number of requests",
Buckets: []float64{0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1},
StabilityLevel: compbasemetrics.ALPHA,
},
&compbasemetrics.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "priority_level_request_count_watermarks",
Help: "Watermarks of the number of requests",
Buckets: []float64{0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1},
StabilityLevel: compbasemetrics.ALPHA,
},
[]string{priorityLevel})
// ReadWriteConcurrencyObserverPairGenerator creates pairs that observe concurrency broken down by mutating vs readonly
ReadWriteConcurrencyObserverPairGenerator = NewSampleAndWaterMarkHistogramsPairGenerator(clock.RealClock{}, time.Millisecond,
&compbasemetrics.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "read_vs_write_request_count_samples",
Help: "Periodic observations of the number of requests",
Buckets: []float64{0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1},
StabilityLevel: compbasemetrics.ALPHA,
},
&compbasemetrics.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "read_vs_write_request_count_watermarks",
Help: "Watermarks of the number of requests",
Buckets: []float64{0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1},
StabilityLevel: compbasemetrics.ALPHA,
},
[]string{requestKind})
apiserverCurrentInqueueRequests = compbasemetrics.NewGaugeVec(
&compbasemetrics.GaugeOpts{
Namespace: namespace,
@ -145,7 +198,7 @@ var (
},
[]string{priorityLevel, flowSchema},
)
metrics = []compbasemetrics.Registerable{
metrics = Registerables{
apiserverRejectedRequestsTotal,
apiserverDispatchedRequestsTotal,
apiserverCurrentInqueueRequests,
@ -154,7 +207,9 @@ var (
apiserverCurrentExecutingRequests,
apiserverRequestWaitingSeconds,
apiserverRequestExecutionSeconds,
}
}.
Append(PriorityLevelConcurrencyObserverPairGenerator.metrics()...).
Append(ReadWriteConcurrencyObserverPairGenerator.metrics()...)
)
// AddRequestsInQueues adds the given delta to the gauge of the # of requests in the queues of the specified flowSchema and priorityLevel

View file

@ -0,0 +1,192 @@
/*
Copyright 2019 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 metrics
import (
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
compbasemetrics "k8s.io/component-base/metrics"
"k8s.io/klog/v2"
)
const (
labelNameMark = "mark"
labelValueLo = "low"
labelValueHi = "high"
labelNamePhase = "phase"
labelValueWaiting = "waiting"
labelValueExecuting = "executing"
)
// SampleAndWaterMarkPairGenerator makes pairs of TimedObservers that
// track samples and watermarks.
type SampleAndWaterMarkPairGenerator struct {
urGenerator SampleAndWaterMarkObserverGenerator
}
var _ TimedObserverPairGenerator = SampleAndWaterMarkPairGenerator{}
// NewSampleAndWaterMarkHistogramsPairGenerator makes a new pair generator
func NewSampleAndWaterMarkHistogramsPairGenerator(clock clock.PassiveClock, samplePeriod time.Duration, sampleOpts, waterMarkOpts *compbasemetrics.HistogramOpts, labelNames []string) SampleAndWaterMarkPairGenerator {
return SampleAndWaterMarkPairGenerator{
urGenerator: NewSampleAndWaterMarkHistogramsGenerator(clock, samplePeriod, sampleOpts, waterMarkOpts, append([]string{labelNamePhase}, labelNames...)),
}
}
// Generate makes a new pair
func (spg SampleAndWaterMarkPairGenerator) Generate(waiting1, executing1 float64, labelValues []string) TimedObserverPair {
return TimedObserverPair{
RequestsWaiting: spg.urGenerator.Generate(0, waiting1, append([]string{labelValueWaiting}, labelValues...)),
RequestsExecuting: spg.urGenerator.Generate(0, executing1, append([]string{labelValueExecuting}, labelValues...)),
}
}
func (spg SampleAndWaterMarkPairGenerator) metrics() Registerables {
return spg.urGenerator.metrics()
}
// SampleAndWaterMarkObserverGenerator creates TimedObservers that
// populate histograms of samples and low- and high-water-marks. The
// generator has a samplePeriod, and the histograms get an observation
// every samplePeriod.
type SampleAndWaterMarkObserverGenerator struct {
*sampleAndWaterMarkObserverGenerator
}
type sampleAndWaterMarkObserverGenerator struct {
clock clock.PassiveClock
samplePeriod time.Duration
samples *compbasemetrics.HistogramVec
waterMarks *compbasemetrics.HistogramVec
}
var _ TimedObserverGenerator = (*sampleAndWaterMarkObserverGenerator)(nil)
// NewSampleAndWaterMarkHistogramsGenerator makes a new one
func NewSampleAndWaterMarkHistogramsGenerator(clock clock.PassiveClock, samplePeriod time.Duration, sampleOpts, waterMarkOpts *compbasemetrics.HistogramOpts, labelNames []string) SampleAndWaterMarkObserverGenerator {
return SampleAndWaterMarkObserverGenerator{
&sampleAndWaterMarkObserverGenerator{
clock: clock,
samplePeriod: samplePeriod,
samples: compbasemetrics.NewHistogramVec(sampleOpts, labelNames),
waterMarks: compbasemetrics.NewHistogramVec(waterMarkOpts, append([]string{labelNameMark}, labelNames...)),
}}
}
func (swg *sampleAndWaterMarkObserverGenerator) quantize(when time.Time) int64 {
return when.UnixNano() / int64(swg.samplePeriod)
}
// Generate makes a new TimedObserver
func (swg *sampleAndWaterMarkObserverGenerator) Generate(x, x1 float64, labelValues []string) TimedObserver {
relX := x / x1
when := swg.clock.Now()
return &sampleAndWaterMarkHistograms{
sampleAndWaterMarkObserverGenerator: swg,
labelValues: labelValues,
loLabelValues: append([]string{labelValueLo}, labelValues...),
hiLabelValues: append([]string{labelValueHi}, labelValues...),
x1: x1,
sampleAndWaterMarkAccumulator: sampleAndWaterMarkAccumulator{
lastSet: when,
lastSetInt: swg.quantize(when),
x: x,
relX: relX,
loRelX: relX,
hiRelX: relX,
}}
}
func (swg *sampleAndWaterMarkObserverGenerator) metrics() Registerables {
return Registerables{swg.samples, swg.waterMarks}
}
type sampleAndWaterMarkHistograms struct {
*sampleAndWaterMarkObserverGenerator
labelValues []string
loLabelValues, hiLabelValues []string
sync.Mutex
x1 float64
sampleAndWaterMarkAccumulator
}
type sampleAndWaterMarkAccumulator struct {
lastSet time.Time
lastSetInt int64 // lastSet / samplePeriod
x float64
relX float64 // x / x1
loRelX, hiRelX float64
}
var _ TimedObserver = (*sampleAndWaterMarkHistograms)(nil)
func (saw *sampleAndWaterMarkHistograms) Add(deltaX float64) {
saw.innerSet(func() {
saw.x += deltaX
})
}
func (saw *sampleAndWaterMarkHistograms) Set(x float64) {
saw.innerSet(func() {
saw.x = x
})
}
func (saw *sampleAndWaterMarkHistograms) SetX1(x1 float64) {
saw.innerSet(func() {
saw.x1 = x1
})
}
func (saw *sampleAndWaterMarkHistograms) innerSet(updateXOrX1 func()) {
saw.Lock()
when := saw.clock.Now()
whenInt := saw.quantize(when)
acc := saw.sampleAndWaterMarkAccumulator
wellOrdered := !when.Before(acc.lastSet)
if wellOrdered {
updateXOrX1()
saw.relX = saw.x / saw.x1
if acc.lastSetInt < whenInt {
saw.loRelX, saw.hiRelX = acc.relX, acc.relX
saw.lastSetInt = whenInt
}
if saw.relX < saw.loRelX {
saw.loRelX = saw.relX
} else if saw.relX > saw.hiRelX {
saw.hiRelX = saw.relX
}
saw.lastSet = when
}
saw.Unlock()
if !wellOrdered {
lastSetS := acc.lastSet.Format(time.RFC3339Nano)
whenS := when.Format(time.RFC3339Nano)
klog.Fatalf("Time went backwards from %s to %s for labelValues=%#+v", lastSetS, whenS, saw.labelValues)
panic(append([]string{lastSetS, whenS}, saw.labelValues...))
}
for acc.lastSetInt < whenInt {
saw.samples.WithLabelValues(saw.labelValues...).Observe(acc.relX)
saw.waterMarks.WithLabelValues(saw.loLabelValues...).Observe(acc.loRelX)
saw.waterMarks.WithLabelValues(saw.hiLabelValues...).Observe(acc.hiRelX)
acc.lastSetInt++
acc.loRelX, acc.hiRelX = acc.relX, acc.relX
}
}

View file

@ -0,0 +1,52 @@
/*
Copyright 2019 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 metrics
// TimedObserver gets informed about the values assigned to a variable
// `X float64` over time, and reports on the ratio `X/X1`.
type TimedObserver interface {
// Add notes a change to the variable
Add(deltaX float64)
// Set notes a setting of the variable
Set(x float64)
// SetX1 changes the value to use for X1
SetX1(x1 float64)
}
// TimedObserverGenerator creates related observers that are
// differentiated by a series of label values
type TimedObserverGenerator interface {
Generate(x, x1 float64, labelValues []string) TimedObserver
}
// TimedObserverPair is a corresponding pair of observers, one for the
// number of requests waiting in queue(s) and one for the number of
// requests being executed
type TimedObserverPair struct {
// RequestsWaiting is given observations of the number of currently queued requests
RequestsWaiting TimedObserver
// RequestsExecuting is given observations of the number of requests currently executing
RequestsExecuting TimedObserver
}
// TimedObserverPairGenerator generates pairs
type TimedObserverPairGenerator interface {
Generate(waiting1, executing1 float64, labelValues []string) TimedObserverPair
}

View file

@ -20,8 +20,8 @@ import (
"encoding/json"
"github.com/go-openapi/spec"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/compiler"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
yaml "gopkg.in/yaml.v2"
"k8s.io/kube-openapi/pkg/util/proto"

View file

@ -26,7 +26,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/net"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
@ -51,7 +50,7 @@ type GenericWebhook struct {
// Otherwise it returns false for an immediate fail.
func DefaultShouldRetry(err error) bool {
// these errors indicate a transient error that should be retried.
if net.IsConnectionReset(err) || apierrors.IsInternalError(err) || apierrors.IsTimeout(err) || apierrors.IsTooManyRequests(err) {
if utilnet.IsConnectionReset(err) || apierrors.IsInternalError(err) || apierrors.IsTimeout(err) || apierrors.IsTooManyRequests(err) {
return true
}
// if the error sends the Retry-After header, we respect it as an explicit confirmation we should retry.

View file

@ -26,7 +26,7 @@ import (
"time"
"golang.org/x/net/websocket"
"k8s.io/klog"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/util/runtime"
)

View file

@ -63,7 +63,7 @@ type Reader struct {
protocols map[string]ReaderProtocolConfig
selectedProtocol string
handleCrash func() // overridable for testing
handleCrash func(additionalHandlers ...func(interface{})) // overridable for testing
}
// NewReader creates a WebSocket pipe that will copy the contents of r to a provided
@ -78,7 +78,7 @@ func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig
err: make(chan error),
ping: ping,
protocols: protocols,
handleCrash: func() { runtime.HandleCrash() },
handleCrash: runtime.HandleCrash,
}
}