Skip to content

Commit 7b55340

Browse files
committed
Collect some of scheduling metrics and scheduling throughput
In addition to getting overall performance measurements from golang benchmark, collect metrics that provides information about insides of the scheduler itself. This is a first step towards improving what we collect about the scheduler. Metrics in question: - scheduler_scheduling_algorithm_predicate_evaluation_seconds - scheduler_scheduling_algorithm_priority_evaluation_seconds - scheduler_binding_duration_seconds - scheduler_e2e_scheduling_duration_seconds Scheduling throughput is computed on the fly inside perfScheduling.
1 parent c5d7574 commit 7b55340

File tree

6 files changed

+616
-4
lines changed

6 files changed

+616
-4
lines changed

staging/src/k8s.io/component-base/metrics/testutil/BUILD

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
"//staging/src/k8s.io/apimachinery/pkg/version:go_default_library",
1414
"//staging/src/k8s.io/component-base/metrics:go_default_library",
1515
"//vendor/github.com/prometheus/client_golang/prometheus/testutil:go_default_library",
16+
"//vendor/github.com/prometheus/client_model/go:go_default_library",
1617
"//vendor/github.com/prometheus/common/expfmt:go_default_library",
1718
"//vendor/github.com/prometheus/common/model:go_default_library",
1819
],
@@ -34,7 +35,14 @@ filegroup(
3435

3536
go_test(
3637
name = "go_default_test",
37-
srcs = ["testutil_test.go"],
38+
srcs = [
39+
"metrics_test.go",
40+
"testutil_test.go",
41+
],
3842
embed = [":go_default_library"],
39-
deps = ["//staging/src/k8s.io/component-base/metrics:go_default_library"],
43+
deps = [
44+
"//staging/src/k8s.io/component-base/metrics:go_default_library",
45+
"//vendor/github.com/prometheus/client_model/go:go_default_library",
46+
"//vendor/k8s.io/utils/pointer:go_default_library",
47+
],
4048
)

staging/src/k8s.io/component-base/metrics/testutil/metrics.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ package testutil
1919
import (
2020
"fmt"
2121
"io"
22+
"math"
2223
"reflect"
24+
"sort"
2325
"strings"
2426

27+
dto "github.com/prometheus/client_model/go"
2528
"github.com/prometheus/common/expfmt"
2629
"github.com/prometheus/common/model"
30+
31+
"k8s.io/component-base/metrics"
2732
)
2833

2934
var (
@@ -178,3 +183,140 @@ func ValidateMetrics(metrics Metrics, metricName string, expectedLabels ...strin
178183
}
179184
return nil
180185
}
186+
187+
// Histogram wraps prometheus histogram DTO (data transfer object)
188+
type Histogram struct {
189+
*dto.Histogram
190+
}
191+
192+
// GetHistogramFromGatherer collects a metric from a gatherer implementing k8s.io/component-base/metrics.Gatherer interface.
193+
// Used only for testing purposes where we need to gather metrics directly from a running binary (without metrics endpoint).
194+
func GetHistogramFromGatherer(gatherer metrics.Gatherer, metricName string) (Histogram, error) {
195+
var metricFamily *dto.MetricFamily
196+
m, err := gatherer.Gather()
197+
if err != nil {
198+
return Histogram{}, err
199+
}
200+
for _, mFamily := range m {
201+
if mFamily.Name != nil && *mFamily.Name == metricName {
202+
metricFamily = mFamily
203+
break
204+
}
205+
}
206+
207+
if metricFamily == nil {
208+
return Histogram{}, fmt.Errorf("Metric %q not found", metricName)
209+
}
210+
211+
if metricFamily.GetMetric() == nil {
212+
return Histogram{}, fmt.Errorf("Metric %q is empty", metricName)
213+
}
214+
215+
if len(metricFamily.GetMetric()) == 0 {
216+
return Histogram{}, fmt.Errorf("Metric %q is empty", metricName)
217+
}
218+
219+
return Histogram{
220+
// Histograms are stored under the first index (based on observation).
221+
// Given there's only one histogram registered per each metric name, accessing
222+
// the first index is sufficient.
223+
metricFamily.GetMetric()[0].GetHistogram(),
224+
}, nil
225+
}
226+
227+
func uint64Ptr(u uint64) *uint64 {
228+
return &u
229+
}
230+
231+
// Bucket of a histogram
232+
type bucket struct {
233+
upperBound float64
234+
count float64
235+
}
236+
237+
func bucketQuantile(q float64, buckets []bucket) float64 {
238+
if q < 0 {
239+
return math.Inf(-1)
240+
}
241+
if q > 1 {
242+
return math.Inf(+1)
243+
}
244+
245+
if len(buckets) < 2 {
246+
return math.NaN()
247+
}
248+
249+
rank := q * buckets[len(buckets)-1].count
250+
b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
251+
252+
if b == 0 {
253+
return buckets[0].upperBound * (rank / buckets[0].count)
254+
}
255+
256+
// linear approximation of b-th bucket
257+
brank := rank - buckets[b-1].count
258+
bSize := buckets[b].upperBound - buckets[b-1].upperBound
259+
bCount := buckets[b].count - buckets[b-1].count
260+
261+
return buckets[b-1].upperBound + bSize*(brank/bCount)
262+
}
263+
264+
// Quantile computes q-th quantile of a cumulative histogram.
265+
// It's expected the histogram is valid (by calling Validate)
266+
func (hist *Histogram) Quantile(q float64) float64 {
267+
buckets := []bucket{}
268+
269+
for _, bckt := range hist.Bucket {
270+
buckets = append(buckets, bucket{
271+
count: float64(*bckt.CumulativeCount),
272+
upperBound: *bckt.UpperBound,
273+
})
274+
}
275+
276+
// bucketQuantile expects the upper bound of the last bucket to be +inf
277+
// buckets[len(buckets)-1].upperBound = math.Inf(+1)
278+
279+
return bucketQuantile(q, buckets)
280+
}
281+
282+
// Average computes histogram's average value
283+
func (hist *Histogram) Average() float64 {
284+
return *hist.SampleSum / float64(*hist.SampleCount)
285+
}
286+
287+
// Clear clears all fields of the wrapped histogram
288+
func (hist *Histogram) Clear() {
289+
if hist.SampleCount != nil {
290+
*hist.SampleCount = 0
291+
}
292+
if hist.SampleSum != nil {
293+
*hist.SampleSum = 0
294+
}
295+
for _, b := range hist.Bucket {
296+
if b.CumulativeCount != nil {
297+
*b.CumulativeCount = 0
298+
}
299+
}
300+
}
301+
302+
// Validate makes sure the wrapped histogram has all necessary fields set and with valid values.
303+
func (hist *Histogram) Validate() error {
304+
if hist.SampleCount == nil || *hist.SampleCount == 0 {
305+
return fmt.Errorf("nil or empty histogram SampleCount")
306+
}
307+
308+
if hist.SampleSum == nil || *hist.SampleSum == 0 {
309+
return fmt.Errorf("nil or empty histogram SampleSum")
310+
}
311+
312+
for _, bckt := range hist.Bucket {
313+
if bckt == nil {
314+
return fmt.Errorf("empty histogram bucket")
315+
}
316+
if bckt.UpperBound == nil || *bckt.UpperBound < 0 {
317+
return fmt.Errorf("nil or negative histogram bucket UpperBound")
318+
}
319+
}
320+
321+
return nil
322+
}

0 commit comments

Comments
 (0)