@@ -182,6 +182,11 @@ type testCase struct {
182
182
DefaultPodTemplatePath * string
183
183
// Labels can be used to enable or disable workloads inside this test case.
184
184
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
185
190
}
186
191
187
192
func (tc * testCase ) collectsMetrics () bool {
@@ -214,6 +219,73 @@ type workload struct {
214
219
Params params
215
220
// Labels can be used to enable or disable a workload.
216
221
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 )
217
289
}
218
290
219
291
type params struct {
@@ -881,6 +953,38 @@ func setupClusterForWorkload(tCtx ktesting.TContext, configPath string, featureG
881
953
return mustSetupCluster (tCtx , cfg , featureGates , outOfTreePluginRegistry )
882
954
}
883
955
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
+
884
988
func runWorkload (tCtx ktesting.TContext , tc * testCase , w * workload , informerFactory informers.SharedInformerFactory ) []DataItem {
885
989
b , benchmarking := tCtx .TB ().(* testing.B )
886
990
if benchmarking {
@@ -1033,7 +1137,12 @@ func runWorkload(tCtx ktesting.TContext, tc *testCase, w *workload, informerFact
1033
1137
collectorWG .Wait ()
1034
1138
mu .Lock ()
1035
1139
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
+ }
1037
1146
}
1038
1147
mu .Unlock ()
1039
1148
}
@@ -1432,6 +1541,11 @@ func getTestCases(path string) ([]*testCase, error) {
1432
1541
if err := getSpecFromFile (& path , & testCases ); err != nil {
1433
1542
return nil , fmt .Errorf ("parsing test cases error: %w" , err )
1434
1543
}
1544
+ for _ , tc := range testCases {
1545
+ for _ , w := range tc .Workloads {
1546
+ w .setDefaults (tc .DefaultThresholdMetricSelector )
1547
+ }
1548
+ }
1435
1549
return testCases , nil
1436
1550
}
1437
1551
@@ -1463,6 +1577,12 @@ func validateTestCases(testCases []*testCase) error {
1463
1577
// TODO(#93795): make sure each workload within a test case has a unique
1464
1578
// name? The name is used to identify the stats in benchmark reports.
1465
1579
// 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
+ }
1466
1586
}
1467
1587
return nil
1468
1588
}
0 commit comments