github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/resource-recommend/recommender/recommenders/percentile_recommender_test.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package recommenders
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/resource"
    27  	"k8s.io/apimachinery/pkg/types"
    28  
    29  	"github.com/kubewharf/katalyst-api/pkg/apis/recommendation/v1alpha1"
    30  	"github.com/kubewharf/katalyst-core/pkg/controller/resource-recommend/oom"
    31  	datasourcetypes "github.com/kubewharf/katalyst-core/pkg/util/resource-recommend/types/datasource"
    32  	customtypes "github.com/kubewharf/katalyst-core/pkg/util/resource-recommend/types/error"
    33  	processortypes "github.com/kubewharf/katalyst-core/pkg/util/resource-recommend/types/processor"
    34  	recommendationtypes "github.com/kubewharf/katalyst-core/pkg/util/resource-recommend/types/recommendation"
    35  )
    36  
    37  func TestRecommend(t *testing.T) {
    38  	recommendation1 := &recommendationtypes.Recommendation{
    39  		NamespacedName: types.NamespacedName{
    40  			Name:      "name1",
    41  			Namespace: "namespace1",
    42  		},
    43  		Config: recommendationtypes.Config{
    44  			Containers: []recommendationtypes.Container{
    45  				{
    46  					ContainerName: "container1",
    47  					ContainerConfigs: []recommendationtypes.ContainerConfig{
    48  						{
    49  							ControlledResource:    v1.ResourceCPU,
    50  							ResourceBufferPercent: 10,
    51  						},
    52  						{
    53  							ControlledResource:    v1.ResourceMemory,
    54  							ResourceBufferPercent: 10,
    55  						},
    56  					},
    57  				},
    58  			},
    59  			TargetRef: v1alpha1.CrossVersionObjectReference{
    60  				Kind: "deployment",
    61  				Name: "workload1",
    62  			},
    63  		},
    64  	}
    65  
    66  	r := &PercentileRecommender{
    67  		DataProcessor: dummyDataProcessor{},
    68  		OomRecorder:   dummyOomRecorder{},
    69  	}
    70  	err := r.Recommend(recommendation1)
    71  	if err != nil {
    72  		t.Errorf("Unexpected error: %v", err)
    73  	}
    74  
    75  	if recommendation1.Recommendations[0].Requests.Target.Cpu().String() != "1100" || recommendation1.Recommendations[0].Requests.Target.Memory().String() != "2Ki" {
    76  		t.Errorf("Recommendations mismatch.")
    77  	}
    78  }
    79  
    80  func TestGetCpuTargetPercentileEstimationWithUsageBuffer(t *testing.T) {
    81  	recommender := &PercentileRecommender{
    82  		DataProcessor: dummyDataProcessor{},
    83  		OomRecorder:   dummyOomRecorder{},
    84  	}
    85  	taskKey := &processortypes.ProcessKey{
    86  		ResourceRecommendNamespacedName: types.NamespacedName{
    87  			Name:      "name1",
    88  			Namespace: "namespace1",
    89  		},
    90  		Metric: &datasourcetypes.Metric{
    91  			Namespace:     "namespace1",
    92  			Kind:          "deployment1",
    93  			WorkloadName:  "workload1",
    94  			ContainerName: "container1",
    95  			Resource:      v1.ResourceCPU,
    96  		},
    97  	}
    98  	resourceBufferPercentage := 0.1
    99  	cpuQuantity, err := recommender.getCpuTargetPercentileEstimationWithUsageBuffer(taskKey, resourceBufferPercentage)
   100  	if err != nil {
   101  		t.Errorf("Expected no error, but got: %v", err)
   102  	}
   103  
   104  	expectedCpuQuantity := resource.NewMilliQuantity(int64(1100*1000), resource.DecimalSI) // 设置期望的CPU数量
   105  	if !cpuQuantity.Equal(*expectedCpuQuantity) {
   106  		t.Errorf("Expected cpu quantity %s, but got %s", cpuQuantity.String(), expectedCpuQuantity.String())
   107  	}
   108  }
   109  
   110  func TestGetMemTargetPercentileEstimationWithUsageBuffer(t *testing.T) {
   111  	recommender := &PercentileRecommender{
   112  		DataProcessor: dummyDataProcessor{},
   113  		OomRecorder:   dummyOomRecorder{},
   114  	}
   115  	taskKey := &processortypes.ProcessKey{
   116  		ResourceRecommendNamespacedName: types.NamespacedName{
   117  			Name:      "name1",
   118  			Namespace: "namespace1",
   119  		},
   120  		Metric: &datasourcetypes.Metric{
   121  			Namespace:     "namespace1",
   122  			Kind:          "deployment1",
   123  			WorkloadName:  "workload1",
   124  			ContainerName: "container1",
   125  			Resource:      v1.ResourceMemory,
   126  		},
   127  	}
   128  	resourceBufferPercentage := 0.1
   129  	memQuantity, err := recommender.getMemTargetPercentileEstimationWithUsageBuffer(taskKey, resourceBufferPercentage)
   130  	if err != nil {
   131  		t.Errorf("Expected no error, but got: %v", err)
   132  	}
   133  
   134  	expectedMemQuantity := resource.NewQuantity((1100/1024+1)*1024, resource.BinarySI) // 设置期望的内存数量
   135  	if !memQuantity.Equal(*expectedMemQuantity) {
   136  		t.Errorf("Expected memory quantity %s, but got %s", expectedMemQuantity.String(), memQuantity.String())
   137  	}
   138  }
   139  
   140  func TestScaleOnOOM(t *testing.T) {
   141  	oomRecords := []oom.OOMRecord{
   142  		{
   143  			Pod:       "workload-name-pod-1",
   144  			Container: "container-1",
   145  			Namespace: "namespace-1",
   146  			OOMAt:     time.Now(),
   147  			Memory:    *resource.NewQuantity(1024, resource.BinarySI),
   148  		},
   149  		{
   150  			Pod:       "workload-name-pod-2",
   151  			Container: "container-2",
   152  			Namespace: "namespace-2",
   153  			OOMAt:     time.Now().Add(-time.Hour * 24 * 8), // too old event
   154  			Memory:    *resource.NewQuantity(2048, resource.BinarySI),
   155  		},
   156  	}
   157  
   158  	r := &PercentileRecommender{}
   159  	namespace := "namespace-1"
   160  	workloadName := "workload-name"
   161  	containerName := "container-1"
   162  
   163  	quantityPointer := r.ScaleOnOOM(oomRecords, namespace, workloadName, containerName)
   164  
   165  	if quantityPointer == nil {
   166  		t.Errorf("Expected non-nil quantity, got nil")
   167  		return
   168  	}
   169  	quantity := *quantityPointer
   170  
   171  	expectedValuePointer := r.getMemQuantity(float64(oomRecords[0].Memory.Value()) + OOMMinBumpUp)
   172  	if expectedValuePointer == nil {
   173  		t.Errorf("got expectedValuePointer is nil")
   174  		return
   175  	}
   176  	expectedValue := *expectedValuePointer
   177  	if !quantity.Equal(expectedValue) {
   178  		t.Errorf("Expected value %s, got %s", expectedValue.String(), quantity.String())
   179  		return
   180  	}
   181  }
   182  
   183  type dummyDataProcessor struct{}
   184  
   185  func (d dummyDataProcessor) Run(_ context.Context) {
   186  	return
   187  }
   188  
   189  func (d dummyDataProcessor) Register(_ *processortypes.ProcessConfig) *customtypes.CustomError {
   190  	return nil
   191  }
   192  
   193  func (d dummyDataProcessor) Cancel(_ *processortypes.ProcessKey) *customtypes.CustomError {
   194  	return nil
   195  }
   196  
   197  func (d dummyDataProcessor) QueryProcessedValues(_ *processortypes.ProcessKey) (float64, error) {
   198  	return 1000, nil
   199  }
   200  
   201  type dummyOomRecorder struct{}
   202  
   203  func (d dummyOomRecorder) ListOOMRecords() []oom.OOMRecord {
   204  	return nil
   205  }
   206  
   207  func (d dummyOomRecorder) ScaleOnOOM(_ []oom.OOMRecord, _, _, _ string) *resource.Quantity {
   208  	return nil
   209  }
   210  
   211  func TestPercentileRecommender_getMemQuantity(t *testing.T) {
   212  	tests := []struct {
   213  		name                string
   214  		memRecommendedValue float64
   215  		wantQuantity        *resource.Quantity
   216  	}{
   217  		{
   218  			name:                "testKi",
   219  			memRecommendedValue: 1100,
   220  			wantQuantity:        resource.NewQuantity(2*1024, resource.BinarySI),
   221  		},
   222  		{
   223  			name:                "testMi",
   224  			memRecommendedValue: 2 * 1024 * 1024,
   225  			wantQuantity:        resource.NewQuantity(2*1024*1024, resource.BinarySI),
   226  		},
   227  		{
   228  			name:                "testMiNotDivisible",
   229  			memRecommendedValue: 2.66 * 1024 * 1024,
   230  			wantQuantity:        resource.NewQuantity(3*1024*1024, resource.BinarySI),
   231  		},
   232  	}
   233  	for _, tt := range tests {
   234  		t.Run(tt.name, func(t *testing.T) {
   235  			r := &PercentileRecommender{}
   236  			if gotQuantity := r.getMemQuantity(tt.memRecommendedValue); !reflect.DeepEqual(gotQuantity, tt.wantQuantity) {
   237  				t.Errorf("getMemQuantity() = %v, want %v", gotQuantity, tt.wantQuantity)
   238  			}
   239  		})
   240  	}
   241  }