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 }