github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/resource/plugins/cpumem/schedule/schedule_test.go (about) 1 package schedule 2 3 import ( 4 "strconv" 5 "testing" 6 7 "github.com/docker/go-units" 8 "github.com/projecteru2/core/resource/plugins/cpumem/types" 9 "github.com/stretchr/testify/assert" 10 ) 11 12 func TestGetFullCPUPlans(t *testing.T) { 13 h := newHost(types.CPUMap{ 14 "0": 400, 15 "1": 200, 16 "2": 400, 17 }, 100, -1) 18 cpuPlans := h.getFullCPUPlans(h.fullCores, 2) 19 assert.Equal(t, 5, len(cpuPlans)) 20 assert.ElementsMatch(t, cpuPlans, []types.CPUMap{ 21 {"0": 100, "1": 100}, 22 {"0": 100, "2": 100}, 23 {"0": 100, "2": 100}, 24 {"0": 100, "2": 100}, 25 {"1": 100, "2": 100}, 26 }) 27 28 h = newHost(types.CPUMap{ 29 "0": 200, 30 "1": 200, 31 "2": 200, 32 }, 100, -1) 33 cpuPlans = h.getFullCPUPlans(h.fullCores, 2) 34 assert.EqualValues(t, 3, len(cpuPlans)) 35 assert.ElementsMatch(t, cpuPlans, []types.CPUMap{ 36 {"0": 100, "1": 100}, 37 {"0": 100, "2": 100}, 38 {"1": 100, "2": 100}, 39 }) 40 } 41 42 func TestGetCPUPlansWithAffinity(t *testing.T) { 43 // 1.7 -> 1.0 44 cpuMap := types.CPUMap{ 45 "0": 0, 46 "1": 30, 47 "2": 0, 48 } 49 originCPUMap := types.CPUMap{ 50 "0": 100, 51 "1": 30, 52 "2": 40, 53 } 54 resourceInfo := &types.NodeResourceInfo{ 55 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 56 Usage: &types.NodeResource{}, 57 } 58 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 59 cpuPlans := GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 1}) 60 assert.Equal(t, 1, len(cpuPlans)) 61 assert.Equal(t, cpuPlans[0].CPUMap, types.CPUMap{"0": 100}) 62 63 // 1.7 -> 1.2 64 cpuMap = types.CPUMap{ 65 "0": 0, 66 "1": 30, 67 "2": 0, 68 } 69 originCPUMap = types.CPUMap{ 70 "0": 100, 71 "1": 30, 72 "2": 40, 73 } 74 75 resourceInfo = &types.NodeResourceInfo{ 76 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 77 Usage: &types.NodeResource{}, 78 } 79 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 80 cpuPlans = GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 1.2}) 81 assert.Equal(t, 1, len(cpuPlans)) 82 assert.Equal(t, cpuPlans[0].CPUMap, types.CPUMap{"0": 100, "1": 20}) 83 84 // 1.7 -> 2 85 cpuMap = types.CPUMap{ 86 "0": 0, 87 "1": 80, 88 "2": 0, 89 "3": 0, 90 } 91 originCPUMap = types.CPUMap{ 92 "0": 100, 93 "1": 20, 94 "2": 40, 95 "3": 10, 96 } 97 98 resourceInfo = &types.NodeResourceInfo{ 99 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 100 Usage: &types.NodeResource{}, 101 } 102 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 103 cpuPlans = GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 2}) 104 assert.Equal(t, 1, len(cpuPlans)) 105 assert.Equal(t, cpuPlans[0].CPUMap, types.CPUMap{"0": 100, "1": 100}) 106 107 // 1.7 -> 2 without enough pieces 108 cpuMap = types.CPUMap{ 109 "0": 0, 110 "1": 69, 111 "2": 10, 112 } 113 originCPUMap = types.CPUMap{ 114 "0": 100, 115 "1": 30, 116 "2": 40, 117 } 118 119 resourceInfo = &types.NodeResourceInfo{ 120 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 121 Usage: &types.NodeResource{}, 122 } 123 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 124 cpuPlans = GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 2}) 125 assert.Equal(t, 0, len(cpuPlans)) 126 127 // 1.7 -> 2 128 cpuMap = types.CPUMap{ 129 "0": 0, 130 "1": 70, 131 "2": 10, 132 } 133 originCPUMap = types.CPUMap{ 134 "0": 100, 135 "1": 30, 136 "2": 40, 137 } 138 139 resourceInfo = &types.NodeResourceInfo{ 140 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 141 Usage: &types.NodeResource{}, 142 } 143 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 144 cpuPlans = GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 2}) 145 assert.Equal(t, 1, len(cpuPlans)) 146 assert.Equal(t, cpuPlans[0].CPUMap, types.CPUMap{"0": 100, "1": 100}) 147 148 // 1.7 -> 2 149 cpuMap = types.CPUMap{ 150 "0": 100, 151 "1": 60, 152 "2": 0, 153 "3": 100, 154 "4": 100, 155 } 156 originCPUMap = types.CPUMap{ 157 "0": 100, 158 "1": 30, 159 "2": 40, 160 } 161 162 resourceInfo = &types.NodeResourceInfo{ 163 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 164 Usage: &types.NodeResource{}, 165 } 166 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 167 cpuPlans = GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 2}) 168 assert.Equal(t, 2, len(cpuPlans)) 169 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 170 {CPUMap: types.CPUMap{"0": 100, "3": 100}}, 171 {CPUMap: types.CPUMap{"0": 100, "4": 100}}, 172 }) 173 174 // 1.7 -> 2 175 cpuMap = types.CPUMap{ 176 "0": 0, 177 "1": 60, 178 "2": 0, 179 } 180 originCPUMap = types.CPUMap{ 181 "0": 100, 182 "1": 30, 183 "2": 40, 184 } 185 186 resourceInfo = &types.NodeResourceInfo{ 187 Capacity: &types.NodeResource{CPUMap: cpuMap, CPU: float64(len(cpuMap))}, 188 Usage: &types.NodeResource{}, 189 } 190 resourceInfo.Capacity.CPUMap.Add(originCPUMap) 191 cpuPlans = GetCPUPlans(resourceInfo, originCPUMap, 100, -1, &types.WorkloadResourceRequest{CPUBind: true, CPURequest: 2}) 192 assert.Equal(t, 0, len(cpuPlans)) 193 } 194 195 func TestCPUOverSell(t *testing.T) { 196 var cpuMap types.CPUMap 197 var resourceInfo *types.NodeResourceInfo 198 maxShare := -1 199 shareBase := 100 200 201 // oversell 202 cpuMap = types.CPUMap{"0": 300, "1": 300} 203 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 204 CPU: float64(len(cpuMap)), 205 CPUMap: cpuMap, 206 Memory: 12 * units.GiB, 207 }} 208 assert.Nil(t, resourceInfo.Validate()) 209 210 cpuPlans := GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 211 CPUBind: true, 212 CPURequest: 2, 213 MemRequest: 1, 214 }) 215 assert.Equal(t, len(cpuPlans), 3) 216 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 217 {CPUMap: types.CPUMap{"0": 100, "1": 100}}, 218 {CPUMap: types.CPUMap{"0": 100, "1": 100}}, 219 {CPUMap: types.CPUMap{"0": 100, "1": 100}}, 220 }) 221 222 // one core oversell 223 cpuMap = types.CPUMap{"0": 300} 224 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 225 CPU: float64(len(cpuMap)), 226 CPUMap: cpuMap, 227 Memory: 12 * units.GiB, 228 }} 229 assert.Nil(t, resourceInfo.Validate()) 230 231 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 232 CPUBind: true, 233 CPURequest: 0.5, 234 MemRequest: 1, 235 }) 236 assert.Equal(t, len(cpuPlans), 6) 237 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 238 {CPUMap: types.CPUMap{"0": 50}}, 239 {CPUMap: types.CPUMap{"0": 50}}, 240 {CPUMap: types.CPUMap{"0": 50}}, 241 {CPUMap: types.CPUMap{"0": 50}}, 242 {CPUMap: types.CPUMap{"0": 50}}, 243 {CPUMap: types.CPUMap{"0": 50}}, 244 }) 245 246 // balance 247 cpuMap = types.CPUMap{"0": 100, "1": 200, "2": 300} 248 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 249 CPU: float64(len(cpuMap)), 250 CPUMap: cpuMap, 251 Memory: 12 * units.GiB, 252 }} 253 assert.Nil(t, resourceInfo.Validate()) 254 255 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 256 CPUBind: true, 257 CPURequest: 1, 258 MemRequest: 1, 259 }) 260 assert.Equal(t, len(cpuPlans), 6) 261 assert.ElementsMatch(t, cpuPlans[:2], []*types.CPUPlan{ 262 {CPUMap: types.CPUMap{"0": 100}}, 263 {CPUMap: types.CPUMap{"1": 100}}, 264 }) 265 266 // complex 267 cpuMap = types.CPUMap{"0": 50, "1": 100, "2": 300, "3": 70, "4": 200, "5": 30, "6": 230} 268 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 269 CPU: float64(len(cpuMap)), 270 CPUMap: cpuMap, 271 Memory: 12 * units.GiB, 272 }} 273 assert.Nil(t, resourceInfo.Validate()) 274 275 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 276 CPUBind: true, 277 CPURequest: 1.7, 278 MemRequest: 1, 279 }) 280 assert.True(t, len(cpuPlans) >= 2) 281 282 cpuMap = types.CPUMap{"0": 70, "1": 100, "2": 400} 283 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 284 CPU: float64(len(cpuMap)), 285 CPUMap: cpuMap, 286 Memory: 12 * units.GiB, 287 }} 288 assert.Nil(t, resourceInfo.Validate()) 289 290 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 291 CPUBind: true, 292 CPURequest: 1.3, 293 MemRequest: 1, 294 }) 295 assert.Equal(t, len(cpuPlans), 4) 296 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 297 {CPUMap: types.CPUMap{"0": 30, "2": 100}}, 298 {CPUMap: types.CPUMap{"0": 30, "2": 100}}, 299 {CPUMap: types.CPUMap{"1": 30, "2": 100}}, 300 {CPUMap: types.CPUMap{"1": 30, "2": 100}}, 301 }) 302 } 303 304 func applyCPUPlans(t *testing.T, resourceInfo *types.NodeResourceInfo, cpuPlans []*types.CPUPlan) { 305 for _, cpuPlan := range cpuPlans { 306 resourceInfo.Usage.CPUMap.Add(cpuPlan.CPUMap) 307 } 308 assert.Nil(t, resourceInfo.Validate()) 309 } 310 311 func TestCPUOverSellAndStableFragmentCore(t *testing.T) { 312 var cpuMap types.CPUMap 313 var resourceInfo *types.NodeResourceInfo 314 var cpuPlans []*types.CPUPlan 315 maxShare := -1 316 shareBase := 100 317 318 // oversell 319 cpuMap = types.CPUMap{"0": 300, "1": 300} 320 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 321 CPU: float64(len(cpuMap)), 322 CPUMap: cpuMap, 323 Memory: 12 * units.GiB, 324 }} 325 assert.Nil(t, resourceInfo.Validate()) 326 327 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 328 CPUBind: true, 329 CPURequest: 1.7, 330 MemRequest: 1, 331 }) 332 assert.True(t, len(cpuPlans) > 0) 333 334 // stable fragment core 335 cpuMap = types.CPUMap{"0": 230, "1": 200} 336 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 337 CPU: float64(len(cpuMap)), 338 CPUMap: cpuMap, 339 Memory: 12 * units.GiB, 340 }} 341 assert.Nil(t, resourceInfo.Validate()) 342 343 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 344 CPUBind: true, 345 CPURequest: 1.7, 346 MemRequest: 1, 347 }) 348 assert.True(t, len(cpuPlans) > 0) 349 assert.Equal(t, cpuPlans[0].CPUMap, types.CPUMap{"0": 70, "1": 100}) 350 applyCPUPlans(t, resourceInfo, cpuPlans[:1]) 351 352 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 353 CPUBind: true, 354 CPURequest: 1.7, 355 MemRequest: 1, 356 }) 357 assert.True(t, len(cpuPlans) > 0) 358 assert.Equal(t, cpuPlans[0].CPUMap, types.CPUMap{"0": 70, "1": 100}) 359 360 // complex node 361 cpuMap = types.CPUMap{"0": 230, "1": 80, "2": 300, "3": 200} 362 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 363 CPU: float64(len(cpuMap)), 364 CPUMap: cpuMap, 365 Memory: 12 * units.GiB, 366 }} 367 assert.Nil(t, resourceInfo.Validate()) 368 369 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 370 CPUBind: true, 371 CPURequest: 1.7, 372 MemRequest: 1, 373 }) 374 assert.True(t, len(cpuPlans) >= 2) 375 applyCPUPlans(t, resourceInfo, cpuPlans[:2]) 376 assert.Equal(t, resourceInfo.Usage.CPUMap, types.CPUMap{"0": 70, "1": 70, "2": 0, "3": 200}) 377 378 // consume full core 379 cpuMap = types.CPUMap{"0": 70, "1": 50, "2": 100, "3": 100, "4": 100} 380 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 381 CPU: float64(len(cpuMap)), 382 CPUMap: cpuMap, 383 Memory: 12 * units.GiB, 384 }} 385 assert.Nil(t, resourceInfo.Validate()) 386 387 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 388 CPUBind: true, 389 CPURequest: 1.7, 390 MemRequest: 1, 391 }) 392 assert.True(t, len(cpuPlans) >= 2) 393 applyCPUPlans(t, resourceInfo, cpuPlans[:2]) 394 assert.Equal(t, resourceInfo.Usage.CPUMap, types.CPUMap{"0": 70, "1": 0, "2": 70, "3": 100, "4": 100}) 395 396 // consume less fragment core 397 cpuMap = types.CPUMap{"0": 70, "1": 50, "2": 90} 398 resourceInfo = &types.NodeResourceInfo{Capacity: &types.NodeResource{ 399 CPU: float64(len(cpuMap)), 400 CPUMap: cpuMap, 401 Memory: 12 * units.GiB, 402 }} 403 assert.Nil(t, resourceInfo.Validate()) 404 405 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 406 CPUBind: true, 407 CPURequest: 0.5, 408 MemRequest: 1, 409 }) 410 assert.True(t, len(cpuPlans) >= 2) 411 applyCPUPlans(t, resourceInfo, cpuPlans[:2]) 412 assert.Equal(t, resourceInfo.Usage.CPUMap, types.CPUMap{"0": 50, "1": 50, "2": 0}) 413 } 414 415 func TestNUMANodes(t *testing.T) { 416 maxShare := -1 417 shareBase := 100 418 419 // same numa node 420 resourceInfo := &types.NodeResourceInfo{ 421 Capacity: &types.NodeResource{ 422 CPU: 4, 423 CPUMap: types.CPUMap{"0": 100, "1": 100, "2": 100, "3": 100}, 424 Memory: 4 * units.GiB, 425 NUMAMemory: types.NUMAMemory{"0": 2 * units.GiB, "1": 2 * units.GiB}, 426 NUMA: types.NUMA{"0": "0", "1": "0", "2": "1", "3": "1"}, 427 }, 428 Usage: nil, 429 } 430 assert.Nil(t, resourceInfo.Validate()) 431 432 cpuPlans := GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 433 CPUBind: true, 434 CPURequest: 1.3, 435 MemRequest: 1, 436 }) 437 assert.Equal(t, 2, len(cpuPlans)) 438 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 439 {CPUMap: types.CPUMap{"0": 30, "1": 100}, NUMANode: "0"}, 440 {CPUMap: types.CPUMap{"2": 30, "3": 100}, NUMANode: "1"}, 441 }) 442 443 // same numa node + cross numa node 444 resourceInfo = &types.NodeResourceInfo{ 445 Capacity: &types.NodeResource{ 446 CPU: 4, 447 CPUMap: types.CPUMap{"0": 100, "1": 100, "2": 100, "3": 100, "4": 100, "5": 100}, 448 Memory: 6 * units.GiB, 449 NUMAMemory: types.NUMAMemory{"0": 3 * units.GiB, "1": 3 * units.GiB}, 450 NUMA: types.NUMA{"0": "0", "1": "0", "2": "0", "3": "1", "4": "1", "5": "1"}, 451 }, 452 Usage: nil, 453 } 454 assert.Nil(t, resourceInfo.Validate()) 455 456 cpuPlans = GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 457 CPUBind: true, 458 CPURequest: 2, 459 MemRequest: 2 * units.GiB, 460 }) 461 assert.Equal(t, 3, len(cpuPlans)) 462 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 463 {CPUMap: types.CPUMap{"1": 100, "2": 100}, NUMANode: "0"}, 464 {CPUMap: types.CPUMap{"4": 100, "5": 100}, NUMANode: "1"}, 465 {CPUMap: types.CPUMap{"0": 100, "3": 100}, NUMANode: ""}, 466 }) 467 } 468 469 func TestInsufficientMemory(t *testing.T) { 470 maxShare := -1 471 shareBase := 100 472 473 resourceInfo := &types.NodeResourceInfo{ 474 Capacity: &types.NodeResource{ 475 CPU: 4, 476 CPUMap: types.CPUMap{"0": 100, "1": 100, "2": 100, "3": 100}, 477 Memory: 4 * units.GiB, 478 }, 479 Usage: nil, 480 } 481 assert.Nil(t, resourceInfo.Validate()) 482 483 cpuPlans := GetCPUPlans(resourceInfo, nil, shareBase, maxShare, &types.WorkloadResourceRequest{ 484 CPUBind: true, 485 CPURequest: 1.3, 486 MemRequest: 3 * units.GiB, 487 }) 488 assert.Equal(t, 1, len(cpuPlans)) 489 assert.ElementsMatch(t, cpuPlans, []*types.CPUPlan{ 490 {CPUMap: types.CPUMap{"0": 30, "1": 100}}, 491 }) 492 } 493 494 func BenchmarkGetCPUPlans(b *testing.B) { 495 b.StopTimer() 496 resourceInfo := &types.NodeResourceInfo{ 497 Capacity: &types.NodeResource{ 498 CPU: 24, 499 CPUMap: types.CPUMap{}, 500 Memory: 128 * units.GiB, 501 }, 502 } 503 for i := 0; i < 24; i++ { 504 resourceInfo.Capacity.CPUMap[strconv.Itoa(i)] = 100 505 } 506 assert.Nil(b, resourceInfo.Validate()) 507 b.StartTimer() 508 for i := 0; i < b.N; i++ { 509 assert.True(b, len(GetCPUPlans(resourceInfo, nil, 100, -1, &types.WorkloadResourceRequest{ 510 CPUBind: true, 511 CPURequest: 1.3, 512 MemRequest: 1, 513 })) > 0) 514 } 515 }