Skip to content

Commit 767d2a3

Browse files
committed
Allow to set scheduling throughput thresholds in scheduler_perf tests
1 parent 7ec344d commit 767d2a3

File tree

1 file changed

+121
-1
lines changed

1 file changed

+121
-1
lines changed

test/integration/scheduler_perf/scheduler_perf.go

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ type testCase struct {
182182
DefaultPodTemplatePath *string
183183
// Labels can be used to enable or disable workloads inside this test case.
184184
Labels []string
185+
// DefaultThresholdMetricSelector defines default metric used for threshold comparison.
186+
// It is only populated to workloads without their ThresholdMetricSelector set.
187+
// If nil, the default metric is set to "SchedulingThroughput".
188+
// Optional
189+
DefaultThresholdMetricSelector *thresholdMetricSelector
185190
}
186191

187192
func (tc *testCase) collectsMetrics() bool {
@@ -214,6 +219,73 @@ type workload struct {
214219
Params params
215220
// Labels can be used to enable or disable a workload.
216221
Labels []string
222+
// Threshold is compared to average value of metric specified using thresholdMetricSelector.
223+
// The comparison is performed for op with CollectMetrics set to true.
224+
// If the measured value is below the threshold, the workload's test case will fail.
225+
// If set to zero, the threshold check is disabled.
226+
// Optional
227+
Threshold float64
228+
// ThresholdMetricSelector defines to what metric the Threshold should be compared.
229+
// If nil, the metric is set to DefaultThresholdMetricSelector of the testCase.
230+
// If DefaultThresholdMetricSelector is nil, the metric is set to "SchedulingThroughput".
231+
// Optional
232+
ThresholdMetricSelector *thresholdMetricSelector
233+
}
234+
235+
func (w *workload) isValid(mcc *metricsCollectorConfig) error {
236+
if w.Threshold < 0 {
237+
return fmt.Errorf("invalid Threshold=%f; should be non-negative", w.Threshold)
238+
}
239+
240+
return w.ThresholdMetricSelector.isValid(mcc)
241+
}
242+
243+
func (w *workload) setDefaults(testCaseThresholdMetricSelector *thresholdMetricSelector) {
244+
if w.ThresholdMetricSelector != nil {
245+
return
246+
}
247+
if testCaseThresholdMetricSelector != nil {
248+
w.ThresholdMetricSelector = testCaseThresholdMetricSelector
249+
return
250+
}
251+
// By defult, SchedulingThroughput should be compared with the threshold.
252+
w.ThresholdMetricSelector = &thresholdMetricSelector{
253+
Name: "SchedulingThroughput",
254+
}
255+
}
256+
257+
// thresholdMetricSelector defines the name and labels of metric to compare with threshold.
258+
type thresholdMetricSelector struct {
259+
// Name of the metric is compared to "Metric" field in DataItem labels.
260+
Name string
261+
// Labels of the metric. All of them needs to match the metric's labels to assume equality.
262+
Labels map[string]string
263+
// ExpectLower defines whether the threshold should denote the maximum allowable value of the metric.
264+
// If false, the threshold defines minimum allowable value.
265+
// Optional
266+
ExpectLower bool
267+
}
268+
269+
func (ms thresholdMetricSelector) isValid(mcc *metricsCollectorConfig) error {
270+
if ms.Name == "SchedulingThroughput" {
271+
return nil
272+
}
273+
274+
if mcc == nil {
275+
mcc = &defaultMetricsCollectorConfig
276+
}
277+
278+
labels, ok := mcc.Metrics[ms.Name]
279+
if !ok {
280+
return fmt.Errorf("the metric %v is targeted, but it's not collected during the test. Make sure the MetricsCollectorConfig is valid", ms.Name)
281+
}
282+
283+
for _, labelsComb := range uniqueLVCombos(labels) {
284+
if labelsMatch(labelsComb, ms.Labels) {
285+
return nil
286+
}
287+
}
288+
return fmt.Errorf("no matching labels found for metric %v", ms.Name)
217289
}
218290

219291
type params struct {
@@ -881,6 +953,38 @@ func setupClusterForWorkload(tCtx ktesting.TContext, configPath string, featureG
881953
return mustSetupCluster(tCtx, cfg, featureGates, outOfTreePluginRegistry)
882954
}
883955

956+
func labelsMatch(actualLabels, requiredLabels map[string]string) bool {
957+
for requiredLabel, requiredValue := range requiredLabels {
958+
actualValue, ok := actualLabels[requiredLabel]
959+
if !ok || requiredValue != actualValue {
960+
return false
961+
}
962+
}
963+
return true
964+
}
965+
966+
func valueWithinThreshold(value, threshold float64, expectLower bool) bool {
967+
if expectLower {
968+
return value < threshold
969+
}
970+
return value > threshold
971+
}
972+
973+
func compareMetricWithThreshold(items []DataItem, threshold float64, metricSelector thresholdMetricSelector) error {
974+
if threshold == 0 {
975+
return nil
976+
}
977+
for _, item := range items {
978+
if item.Labels["Metric"] == metricSelector.Name && labelsMatch(item.Labels, metricSelector.Labels) && !valueWithinThreshold(item.Data["Average"], threshold, metricSelector.ExpectLower) {
979+
if metricSelector.ExpectLower {
980+
return fmt.Errorf("expected %s Average to be lower: got %f, want %f", metricSelector.Name, item.Data["Average"], threshold)
981+
}
982+
return fmt.Errorf("expected %s Average to be higher: got %f, want %f", metricSelector.Name, item.Data["Average"], threshold)
983+
}
984+
}
985+
return nil
986+
}
987+
884988
func runWorkload(tCtx ktesting.TContext, tc *testCase, w *workload, informerFactory informers.SharedInformerFactory) []DataItem {
885989
b, benchmarking := tCtx.TB().(*testing.B)
886990
if benchmarking {
@@ -1033,7 +1137,12 @@ func runWorkload(tCtx ktesting.TContext, tc *testCase, w *workload, informerFact
10331137
collectorWG.Wait()
10341138
mu.Lock()
10351139
for _, collector := range collectors {
1036-
dataItems = append(dataItems, collector.collect()...)
1140+
items := collector.collect()
1141+
dataItems = append(dataItems, items...)
1142+
err := compareMetricWithThreshold(items, w.Threshold, *w.ThresholdMetricSelector)
1143+
if err != nil {
1144+
tCtx.Errorf("op %d: %s", opIndex, err)
1145+
}
10371146
}
10381147
mu.Unlock()
10391148
}
@@ -1432,6 +1541,11 @@ func getTestCases(path string) ([]*testCase, error) {
14321541
if err := getSpecFromFile(&path, &testCases); err != nil {
14331542
return nil, fmt.Errorf("parsing test cases error: %w", err)
14341543
}
1544+
for _, tc := range testCases {
1545+
for _, w := range tc.Workloads {
1546+
w.setDefaults(tc.DefaultThresholdMetricSelector)
1547+
}
1548+
}
14351549
return testCases, nil
14361550
}
14371551

@@ -1463,6 +1577,12 @@ func validateTestCases(testCases []*testCase) error {
14631577
// TODO(#93795): make sure each workload within a test case has a unique
14641578
// name? The name is used to identify the stats in benchmark reports.
14651579
// TODO(#94404): check for unused template parameters? Probably a typo.
1580+
for _, w := range tc.Workloads {
1581+
err := w.isValid(tc.MetricsCollectorConfig)
1582+
if err != nil {
1583+
return err
1584+
}
1585+
}
14661586
}
14671587
return nil
14681588
}

0 commit comments

Comments
 (0)