diff --git a/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/seatsecs_test.go b/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/seatsecs_test.go new file mode 100644 index 00000000000..b0a98b2570b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/seatsecs_test.go @@ -0,0 +1,45 @@ +/* +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 queueset + +import ( + "fmt" + "math" + "testing" + "time" +) + +func TestSeatSecondsString(t *testing.T) { + digits := math.Log10(ssScale) + expectFmt := fmt.Sprintf("%%%d.%dfss", int(digits+2), int(digits)) + testCases := []struct { + ss SeatSeconds + expect string + }{ + {ss: SeatSeconds(1), expect: fmt.Sprintf(expectFmt, 1.0/ssScale)}, + {ss: 0, expect: "0.00000000ss"}, + {ss: SeatsTimesDuration(1, time.Second), expect: "1.00000000ss"}, + {ss: SeatsTimesDuration(123, 100*time.Millisecond), expect: "12.30000000ss"}, + {ss: SeatsTimesDuration(1203, 10*time.Millisecond), expect: "12.03000000ss"}, + } + for _, testCase := range testCases { + actualStr := testCase.ss.String() + if actualStr != testCase.expect { + t.Errorf("SeatSeonds(%d) formatted as %q rather than expected %q", uint64(testCase.ss), actualStr, testCase.expect) + } + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/types.go b/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/types.go index 7213460bbfb..842ea071616 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/types.go +++ b/staging/src/k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset/types.go @@ -18,6 +18,8 @@ package queueset import ( "context" + "fmt" + "math" "time" genericrequest "k8s.io/apiserver/pkg/endpoints/request" @@ -134,3 +136,39 @@ func (q *queue) dump(includeDetails bool) debug.QueueDump { SeatsInUse: q.seatsInUse, } } + +// SeatSeconds is a measure of work, in units of seat-seconds, using a fixed-point representation. +// `SeatSeconds(n)` represents `n/ssScale` seat-seconds. +// The constants `ssScale` and `ssScaleDigits` are private to the implementation here, +// no other code should use them. +type SeatSeconds uint64 + +// MaxSeatsSeconds is the maximum representable value of SeatSeconds +const MaxSeatSeconds = SeatSeconds(math.MaxUint64) + +// MinSeatSeconds is the lowest representable value of SeatSeconds +const MinSeatSeconds = SeatSeconds(0) + +// SeatsTimeDuration produces the SeatSeconds value for the given factors. +// This is intended only to produce small values, increments in work +// rather than amount of work done since process start. +func SeatsTimesDuration(seats float64, duration time.Duration) SeatSeconds { + return SeatSeconds(math.Round(seats * float64(duration/time.Nanosecond) / (1e9 / ssScale))) +} + +// ToFloat converts to a floating-point representation. +// This conversion may lose precision. +func (ss SeatSeconds) ToFloat() float64 { + return float64(ss) / ssScale +} + +// String converts to a string. +// This is suitable for large as well as small values. +func (ss SeatSeconds) String() string { + const div = SeatSeconds(ssScale) + quo := ss / div + rem := ss - quo*div + return fmt.Sprintf("%d.%08dss", quo, rem) +} + +const ssScale = 1e8