github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/resource/plugins/cpumem/node.go (about) 1 package cpumem 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "math" 8 "strconv" 9 10 "github.com/cockroachdb/errors" 11 "github.com/mitchellh/mapstructure" 12 enginetypes "github.com/projecteru2/core/engine/types" 13 "github.com/projecteru2/core/log" 14 "github.com/projecteru2/core/resource/plugins/cpumem/schedule" 15 cpumemtypes "github.com/projecteru2/core/resource/plugins/cpumem/types" 16 plugintypes "github.com/projecteru2/core/resource/plugins/types" 17 coretypes "github.com/projecteru2/core/types" 18 "github.com/projecteru2/core/utils" 19 "github.com/sanity-io/litter" 20 ) 21 22 // AddNode . 23 func (p Plugin) AddNode(ctx context.Context, nodename string, resource plugintypes.NodeResourceRequest, info *enginetypes.Info) (*plugintypes.AddNodeResponse, error) { 24 // try to get the node resource 25 var err error 26 if _, err = p.doGetNodeResourceInfo(ctx, nodename); err == nil { 27 return nil, coretypes.ErrNodeExists 28 } 29 30 if !errors.Is(err, coretypes.ErrInvaildCount) { 31 log.WithFunc("resource.cpumem.AddNode").WithField("node", nodename).Error(ctx, err, "failed to get resource info of node") 32 return nil, err 33 } 34 35 req := &cpumemtypes.NodeResourceRequest{} 36 if err := req.Parse(p.config, resource); err != nil { 37 return nil, err 38 } 39 40 if info != nil { //nolint 41 // extract NodeResource from Resources 42 var nodeRes cpumemtypes.NodeResource 43 if b, ok := info.Resources[p.Name()]; ok { 44 if err := json.Unmarshal(b, &nodeRes); err != nil { 45 return nil, err 46 } 47 // NodeResource has higher priority 48 info.NCPU = int(nodeRes.CPU) 49 info.MemTotal = nodeRes.Memory 50 } 51 if len(req.CPUMap) == 0 { 52 req.CPUMap = cpumemtypes.CPUMap{} 53 for i := 0; i < info.NCPU; i++ { 54 req.CPUMap[strconv.Itoa(i)] = p.config.Scheduler.ShareBase 55 } 56 req.NUMA = nodeRes.NUMA 57 } 58 59 if req.Memory == 0 { 60 req.Memory = info.MemTotal * rate / 10 // use 80% of real memory 61 req.NUMAMemory = cpumemtypes.NUMAMemory{} 62 for k, v := range nodeRes.NUMAMemory { 63 req.NUMAMemory[k] = v * rate / 10 64 } 65 } 66 } 67 68 nodeResourceInfo := &cpumemtypes.NodeResourceInfo{ 69 Capacity: &cpumemtypes.NodeResource{ 70 CPU: float64(len(req.CPUMap)), 71 CPUMap: req.CPUMap, 72 Memory: req.Memory, 73 NUMAMemory: req.NUMAMemory, 74 NUMA: req.NUMA, 75 }, 76 } 77 78 // if NUMA is set but NUMAMemory is not set 79 // then divide memory equally according to the number of numa nodes 80 if len(req.NUMA) > 0 && (req.NUMAMemory == nil || len(req.NUMAMemory) == 0) { 81 averageMemory := req.Memory / int64(len(req.NUMA)) 82 nodeResourceInfo.Capacity.NUMAMemory = cpumemtypes.NUMAMemory{} 83 for _, ID := range req.NUMA { 84 nodeResourceInfo.Capacity.NUMAMemory[ID] = averageMemory 85 } 86 } 87 88 if err = p.doSetNodeResourceInfo(ctx, nodename, nodeResourceInfo); err != nil { 89 return nil, err 90 } 91 92 resp := &plugintypes.AddNodeResponse{} 93 return resp, mapstructure.Decode(map[string]any{ 94 "capacity": nodeResourceInfo.Capacity, 95 "usage": nodeResourceInfo.Usage, 96 }, resp) 97 } 98 99 // RemoveNode . 100 func (p Plugin) RemoveNode(ctx context.Context, nodename string) (*plugintypes.RemoveNodeResponse, error) { 101 var err error 102 if _, err = p.store.Delete(ctx, fmt.Sprintf(nodeResourceInfoKey, nodename)); err != nil { 103 log.WithFunc("resource.cpumem.RemoveNode").WithField("node", nodename).Error(ctx, err, "faield to delete node") 104 } 105 return &plugintypes.RemoveNodeResponse{}, err 106 } 107 108 // GetNodesDeployCapacity returns available nodes and total capacity 109 func (p Plugin) GetNodesDeployCapacity(ctx context.Context, nodenames []string, resource plugintypes.WorkloadResourceRequest) (*plugintypes.GetNodesDeployCapacityResponse, error) { 110 logger := log.WithFunc("resource.cpumem.GetNodesDeployCapacity") 111 req := &cpumemtypes.WorkloadResourceRequest{} 112 if err := req.Parse(resource); err != nil { 113 return nil, err 114 } 115 116 if err := req.Validate(); err != nil { 117 logger.Errorf(ctx, err, "invalid resource opts %+v", req) 118 return nil, err 119 } 120 121 nodesDeployCapacityMap := map[string]*plugintypes.NodeDeployCapacity{} 122 total := 0 123 124 nodesResourceInfos, err := p.doGetNodesResourceInfo(ctx, nodenames) 125 if err != nil { 126 return nil, err 127 } 128 129 for nodename, nodeResourceInfo := range nodesResourceInfos { 130 nodeDeployCapacity := p.doGetNodeDeployCapacity(nodeResourceInfo, req) 131 if nodeDeployCapacity.Capacity > 0 { 132 nodesDeployCapacityMap[nodename] = nodeDeployCapacity 133 if total == math.MaxInt || nodeDeployCapacity.Capacity == math.MaxInt { 134 total = math.MaxInt 135 } else { 136 total += nodeDeployCapacity.Capacity 137 } 138 } 139 } 140 141 resp := &plugintypes.GetNodesDeployCapacityResponse{} 142 return resp, mapstructure.Decode(map[string]any{ 143 "nodes_deploy_capacity_map": nodesDeployCapacityMap, 144 "total": total, 145 }, resp) 146 } 147 148 // SetNodeResourceCapacity sets the amount of total resource info 149 func (p Plugin) SetNodeResourceCapacity(ctx context.Context, nodename string, resource plugintypes.NodeResource, resourceRequest plugintypes.NodeResourceRequest, delta bool, incr bool) (*plugintypes.SetNodeResourceCapacityResponse, error) { 150 logger := log.WithFunc("resource.cpumem.SetNodeResourceCapacity").WithField("node", "nodename") 151 req, nodeResource, _, nodeResourceInfo, err := p.parseNodeResourceInfos(ctx, nodename, resource, resourceRequest, nil) 152 if err != nil { 153 return nil, err 154 } 155 origin := nodeResourceInfo.Capacity 156 before := origin.DeepCopy() 157 158 if !delta && req != nil { 159 req.LoadFromOrigin(origin, resourceRequest) 160 } 161 nodeResourceInfo.Capacity = p.calculateNodeResource(req, nodeResource, origin, nil, delta, incr) 162 163 // add new cpu 164 for cpu := range nodeResourceInfo.Capacity.CPUMap { 165 if _, ok := nodeResourceInfo.Usage.CPUMap[cpu]; !ok { 166 nodeResourceInfo.Usage.CPUMap[cpu] = 0 167 } 168 } 169 // delete cpus with no pieces 170 nodeResourceInfo.RemoveEmptyCores() 171 172 if err := p.doSetNodeResourceInfo(ctx, nodename, nodeResourceInfo); err != nil { 173 logger.Errorf(ctx, err, "node resource info %+v", litter.Sdump(nodeResourceInfo)) 174 return nil, err 175 } 176 177 resp := &plugintypes.SetNodeResourceCapacityResponse{} 178 return resp, mapstructure.Decode(map[string]any{ 179 "before": before, 180 "after": nodeResourceInfo.Capacity, 181 }, resp) 182 } 183 184 // GetNodeResourceInfo . 185 func (p Plugin) GetNodeResourceInfo(ctx context.Context, nodename string, workloadsResource []plugintypes.WorkloadResource) (*plugintypes.GetNodeResourceInfoResponse, error) { 186 nodeResourceInfo, _, diffs, err := p.getNodeResourceInfo(ctx, nodename, workloadsResource) 187 if err != nil { 188 return nil, err 189 } 190 191 resp := &plugintypes.GetNodeResourceInfoResponse{} 192 return resp, mapstructure.Decode(map[string]any{ 193 "capacity": nodeResourceInfo.Capacity, 194 "usage": nodeResourceInfo.Usage, 195 "diffs": diffs, 196 }, resp) 197 } 198 199 // SetNodeResourceInfo . 200 func (p Plugin) SetNodeResourceInfo(ctx context.Context, nodename string, capacity plugintypes.NodeResource, usage plugintypes.NodeResource) (*plugintypes.SetNodeResourceInfoResponse, error) { 201 capacityResource := &cpumemtypes.NodeResource{} 202 usageResource := &cpumemtypes.NodeResource{} 203 if err := capacityResource.Parse(capacity); err != nil { 204 return nil, err 205 } 206 if err := usageResource.Parse(usage); err != nil { 207 return nil, err 208 } 209 resourceInfo := &cpumemtypes.NodeResourceInfo{ 210 Capacity: capacityResource, 211 Usage: usageResource, 212 } 213 214 return &plugintypes.SetNodeResourceInfoResponse{}, p.doSetNodeResourceInfo(ctx, nodename, resourceInfo) 215 } 216 217 // SetNodeResourceUsage . 218 func (p Plugin) SetNodeResourceUsage(ctx context.Context, nodename string, resource plugintypes.NodeResource, resourceRequest plugintypes.NodeResourceRequest, workloadsResource []plugintypes.WorkloadResource, delta bool, incr bool) (*plugintypes.SetNodeResourceUsageResponse, error) { 219 logger := log.WithFunc("resource.cpumem.SetNodeResourceUsage").WithField("node", "nodename") 220 req, nodeResource, wrksResource, nodeResourceInfo, err := p.parseNodeResourceInfos(ctx, nodename, resource, resourceRequest, workloadsResource) 221 if err != nil { 222 return nil, err 223 } 224 origin := nodeResourceInfo.Usage 225 before := origin.DeepCopy() 226 227 nodeResourceInfo.Usage = p.calculateNodeResource(req, nodeResource, origin, wrksResource, delta, incr) 228 229 if err := p.doSetNodeResourceInfo(ctx, nodename, nodeResourceInfo); err != nil { 230 logger.Errorf(ctx, err, "node resource info %+v", litter.Sdump(nodeResourceInfo)) 231 return nil, err 232 } 233 234 resp := &plugintypes.SetNodeResourceUsageResponse{} 235 return resp, mapstructure.Decode(map[string]any{ 236 "before": before, 237 "after": nodeResourceInfo.Usage, 238 }, resp) 239 } 240 241 // GetMostIdleNode . 242 func (p Plugin) GetMostIdleNode(ctx context.Context, nodenames []string) (*plugintypes.GetMostIdleNodeResponse, error) { 243 var mostIdleNode string 244 var minIdle = math.MaxFloat64 245 246 nodesResourceInfo, err := p.doGetNodesResourceInfo(ctx, nodenames) 247 if err != nil { 248 return nil, err 249 } 250 251 for nodename, nodeResourceInfo := range nodesResourceInfo { 252 idle := float64(nodeResourceInfo.Usage.CPUMap.TotalPieces()) / float64(nodeResourceInfo.Capacity.CPUMap.TotalPieces()) 253 idle += float64(nodeResourceInfo.Usage.Memory) / float64(nodeResourceInfo.Capacity.Memory) 254 255 if idle < minIdle { 256 mostIdleNode = nodename 257 minIdle = idle 258 } 259 } 260 261 resp := &plugintypes.GetMostIdleNodeResponse{} 262 return resp, mapstructure.Decode(map[string]any{ 263 "nodename": mostIdleNode, 264 "priority": priority, 265 }, resp) 266 } 267 268 // FixNodeResource . 269 func (p Plugin) FixNodeResource(ctx context.Context, nodename string, workloadsResource []plugintypes.WorkloadResource) (*plugintypes.GetNodeResourceInfoResponse, error) { 270 nodeResourceInfo, actuallyWorkloadsUsage, diffs, err := p.getNodeResourceInfo(ctx, nodename, workloadsResource) 271 if err != nil { 272 return nil, err 273 } 274 275 if len(diffs) != 0 { 276 nodeResourceInfo.Usage = &cpumemtypes.NodeResource{ 277 CPU: actuallyWorkloadsUsage.CPURequest, 278 CPUMap: actuallyWorkloadsUsage.CPUMap, 279 Memory: actuallyWorkloadsUsage.MemoryRequest, 280 NUMAMemory: actuallyWorkloadsUsage.NUMAMemory, 281 } 282 if err = p.doSetNodeResourceInfo(ctx, nodename, nodeResourceInfo); err != nil { 283 log.WithFunc("resource.cpumem.FixNodeResource").Error(ctx, err) 284 diffs = append(diffs, err.Error()) 285 } 286 } 287 288 resp := &plugintypes.GetNodeResourceInfoResponse{} 289 return resp, mapstructure.Decode(map[string]any{ 290 "capacity": nodeResourceInfo.Capacity, 291 "usage": nodeResourceInfo.Usage, 292 "diffs": diffs, 293 }, resp) 294 } 295 296 func (p Plugin) getNodeResourceInfo(ctx context.Context, nodename string, workloadsResource []plugintypes.WorkloadResource) (*cpumemtypes.NodeResourceInfo, *cpumemtypes.WorkloadResource, []string, error) { 297 logger := log.WithFunc("resource.cpumem.getNodeResourceInfo").WithField("node", nodename) 298 nodeResourceInfo, err := p.doGetNodeResourceInfo(ctx, nodename) 299 if err != nil { 300 logger.Error(ctx, err) 301 return nil, nil, nil, err 302 } 303 304 actuallyWorkloadsUsage := &cpumemtypes.WorkloadResource{CPUMap: cpumemtypes.CPUMap{}, NUMAMemory: cpumemtypes.NUMAMemory{}} 305 for _, workloadResource := range workloadsResource { 306 workloadUsage := &cpumemtypes.WorkloadResource{} 307 if err := workloadUsage.Parse(workloadResource); err != nil { 308 logger.Error(ctx, err) 309 return nil, nil, nil, err 310 } 311 actuallyWorkloadsUsage.Add(workloadUsage) 312 } 313 314 diffs := []string{} 315 316 actuallyWorkloadsUsage.CPURequest = utils.Round(actuallyWorkloadsUsage.CPURequest) 317 totalCPUUsage := utils.Round(nodeResourceInfo.Usage.CPU) 318 if actuallyWorkloadsUsage.CPURequest != totalCPUUsage { 319 diffs = append(diffs, fmt.Sprintf("node.CPUUsed != sum(workload.CPURequest): %.2f != %.2f", totalCPUUsage, actuallyWorkloadsUsage.CPURequest)) 320 } 321 322 for cpu := range nodeResourceInfo.Capacity.CPUMap { 323 if actuallyWorkloadsUsage.CPUMap[cpu] != nodeResourceInfo.Usage.CPUMap[cpu] { 324 diffs = append(diffs, fmt.Sprintf("node.CPUMap[%+v] != sum(workload.CPUMap[%+v]): %+v != %+v", cpu, cpu, nodeResourceInfo.Usage.CPUMap[cpu], actuallyWorkloadsUsage.CPUMap[cpu])) 325 } 326 } 327 328 for numaNodeID := range nodeResourceInfo.Capacity.NUMAMemory { 329 if actuallyWorkloadsUsage.NUMAMemory[numaNodeID] != nodeResourceInfo.Usage.NUMAMemory[numaNodeID] { 330 diffs = append(diffs, fmt.Sprintf("node.NUMAMemory[%+v] != sum(workload.NUMAMemory[%+v]: %+v != %+v)", numaNodeID, numaNodeID, nodeResourceInfo.Usage.NUMAMemory[numaNodeID], actuallyWorkloadsUsage.NUMAMemory[numaNodeID])) 331 } 332 } 333 334 if nodeResourceInfo.Usage.Memory != actuallyWorkloadsUsage.MemoryRequest { 335 diffs = append(diffs, fmt.Sprintf("node.MemoryUsed != sum(workload.MemoryRequest): %d != %d", nodeResourceInfo.Usage.Memory, actuallyWorkloadsUsage.MemoryRequest)) 336 } 337 338 return nodeResourceInfo, actuallyWorkloadsUsage, diffs, nil 339 } 340 341 func (p Plugin) doGetNodeResourceInfo(ctx context.Context, nodename string) (*cpumemtypes.NodeResourceInfo, error) { 342 resp, err := p.doGetNodesResourceInfo(ctx, []string{nodename}) 343 if err != nil { 344 return nil, err 345 } 346 return resp[nodename], err 347 } 348 349 func (p Plugin) doGetNodesResourceInfo(ctx context.Context, nodenames []string) (map[string]*cpumemtypes.NodeResourceInfo, error) { 350 keys := []string{} 351 for _, nodename := range nodenames { 352 keys = append(keys, fmt.Sprintf(nodeResourceInfoKey, nodename)) 353 } 354 resps, err := p.store.GetMulti(ctx, keys) 355 if err != nil { 356 return nil, err 357 } 358 359 result := map[string]*cpumemtypes.NodeResourceInfo{} 360 361 for _, resp := range resps { 362 r := &cpumemtypes.NodeResourceInfo{} 363 if err := json.Unmarshal(resp.Value, r); err != nil { 364 return nil, err 365 } 366 result[utils.Tail(string(resp.Key))] = r 367 } 368 return result, nil 369 } 370 371 func (p Plugin) doSetNodeResourceInfo(ctx context.Context, nodename string, resourceInfo *cpumemtypes.NodeResourceInfo) error { 372 if err := resourceInfo.Validate(); err != nil { 373 return err 374 } 375 376 data, err := json.Marshal(resourceInfo) 377 if err != nil { 378 return err 379 } 380 381 _, err = p.store.Put(ctx, fmt.Sprintf(nodeResourceInfoKey, nodename), string(data)) 382 return err 383 } 384 385 func (p Plugin) doGetNodeDeployCapacity(nodeResourceInfo *cpumemtypes.NodeResourceInfo, req *cpumemtypes.WorkloadResourceRequest) *plugintypes.NodeDeployCapacity { 386 availableResource := nodeResourceInfo.GetAvailableResource() 387 388 capacityInfo := &plugintypes.NodeDeployCapacity{ 389 Weight: 1, // TODO why 1? 390 } 391 // if cpu-bind is not required, then returns capacity by memory 392 if !req.CPUBind { 393 // check if cpu is enough 394 if req.CPURequest > float64(len(nodeResourceInfo.Capacity.CPUMap)) { 395 return capacityInfo 396 } 397 398 // calculate by memory request 399 if req.MemRequest == 0 { 400 capacityInfo.Capacity = math.MaxInt 401 capacityInfo.Rate = 0 402 } else { 403 capacityInfo.Capacity = int(availableResource.Memory / req.MemRequest) 404 capacityInfo.Rate = utils.AdvancedDivide(float64(req.MemRequest), float64(nodeResourceInfo.Capacity.Memory)) 405 } 406 capacityInfo.Usage = utils.AdvancedDivide(float64(nodeResourceInfo.Usage.Memory), float64(nodeResourceInfo.Capacity.Memory)) 407 return capacityInfo 408 } 409 410 // if cpu-bind is required, then returns capacity by cpu scheduling 411 cpuPlans := schedule.GetCPUPlans(nodeResourceInfo, nil, p.config.Scheduler.ShareBase, p.config.Scheduler.MaxShare, req) 412 capacityInfo.Capacity = len(cpuPlans) 413 capacityInfo.Usage = utils.AdvancedDivide(nodeResourceInfo.Usage.CPU, nodeResourceInfo.Capacity.CPU) 414 capacityInfo.Rate = utils.AdvancedDivide(req.CPURequest, nodeResourceInfo.Capacity.CPU) 415 capacityInfo.Weight = 100 // cpu-bind above all 416 return capacityInfo 417 } 418 419 // calculateNodeResource priority: node resource request > node resource > workload resource args list 420 func (p Plugin) calculateNodeResource(req *cpumemtypes.NodeResourceRequest, nodeResource *cpumemtypes.NodeResource, origin *cpumemtypes.NodeResource, workloadsResource []*cpumemtypes.WorkloadResource, delta bool, incr bool) *cpumemtypes.NodeResource { 421 var resp *cpumemtypes.NodeResource 422 if origin == nil || !delta { // no delta means node resource rewrite with whole new data 423 resp = (&cpumemtypes.NodeResource{}).DeepCopy() // init nil pointer! 424 // 这个接口最诡异的在于,如果 delta 为 false,意味着是全量写入 425 // 但这时候 incr 为 false 的话 426 // 实际上是 set 进了负值,所以这里 incr 应该强制为 true 427 incr = true 428 } else { 429 resp = origin.DeepCopy() 430 } 431 432 if req != nil { 433 nodeResource = &cpumemtypes.NodeResource{ 434 CPU: float64(len(req.CPUMap)), 435 CPUMap: req.CPUMap, 436 Memory: req.Memory, 437 NUMAMemory: req.NUMAMemory, 438 NUMA: req.NUMA, 439 } 440 } 441 442 if nodeResource != nil { 443 if incr { 444 resp.Add(nodeResource) 445 } else { 446 resp.Sub(nodeResource) 447 } 448 return resp 449 } 450 451 for _, workloadResource := range workloadsResource { 452 nodeResource = &cpumemtypes.NodeResource{ 453 CPU: workloadResource.CPURequest, 454 CPUMap: workloadResource.CPUMap, 455 NUMAMemory: workloadResource.NUMAMemory, 456 Memory: workloadResource.MemoryRequest, 457 } 458 if incr { 459 resp.Add(nodeResource) 460 } else { 461 resp.Sub(nodeResource) 462 } 463 } 464 return resp 465 } 466 467 func (p Plugin) parseNodeResourceInfos( 468 ctx context.Context, nodename string, 469 resource plugintypes.NodeResource, 470 resourceRequest plugintypes.NodeResourceRequest, 471 workloadsResource []plugintypes.WorkloadResource, 472 ) ( 473 *cpumemtypes.NodeResourceRequest, 474 *cpumemtypes.NodeResource, 475 []*cpumemtypes.WorkloadResource, 476 *cpumemtypes.NodeResourceInfo, 477 error, 478 ) { 479 var req *cpumemtypes.NodeResourceRequest 480 var nodeResource *cpumemtypes.NodeResource 481 wrksResource := []*cpumemtypes.WorkloadResource{} 482 483 if resourceRequest != nil { 484 req = &cpumemtypes.NodeResourceRequest{} 485 if err := req.Parse(p.config, resourceRequest); err != nil { 486 return nil, nil, nil, nil, err 487 } 488 } 489 490 if resource != nil { 491 nodeResource = &cpumemtypes.NodeResource{} 492 if err := nodeResource.Parse(resource); err != nil { 493 return nil, nil, nil, nil, err 494 } 495 } 496 497 for _, workloadResource := range workloadsResource { 498 wrkResource := &cpumemtypes.WorkloadResource{} 499 if err := wrkResource.Parse(workloadResource); err != nil { 500 return nil, nil, nil, nil, err 501 } 502 wrksResource = append(wrksResource, wrkResource) 503 } 504 505 nodeResourceInfo, err := p.doGetNodeResourceInfo(ctx, nodename) 506 if err != nil { 507 return nil, nil, nil, nil, err 508 } 509 return req, nodeResource, wrksResource, nodeResourceInfo, nil 510 }