github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/qrm-plugins/cpu/dynamicpolicy/cpuadvisor/helper.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 cpuadvisor
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  
    24  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    25  	"github.com/kubewharf/katalyst-core/pkg/util/machine"
    26  )
    27  
    28  // FakedContainerName represents a placeholder since pool entry has no container-level
    29  // FakedNUMAID represents a placeholder since pools like shared/reclaimed will not contain a specific numa
    30  const (
    31  	EmptyOwnerPoolName = ""
    32  	FakedContainerName = ""
    33  	FakedNUMAID        = -1
    34  	NameSeparator      = "#"
    35  )
    36  
    37  type BlockCPUSet map[string]machine.CPUSet
    38  
    39  func NewBlockCPUSet() BlockCPUSet {
    40  	return make(BlockCPUSet)
    41  }
    42  
    43  // GetBlocks parses ListAndWatchResponse and returns map[int][]*Block,
    44  // the map is keyed as numa id -> blocks slice (which has been sorted and deduped)
    45  func (lwr *ListAndWatchResponse) GetBlocks() (map[int][]*Block, error) {
    46  	if lwr == nil {
    47  		return nil, fmt.Errorf("got nil ListAndWatchResponse")
    48  	}
    49  
    50  	numaToBlocks := make(map[int]map[string]*Block)
    51  	numaToSortedBlocks := make(map[int][]*Block)
    52  	blocksEntryNames := make(map[string][][]string)
    53  	visBlocksToNUMA := make(map[string]int)
    54  
    55  	for entryName, entry := range lwr.Entries {
    56  		for subEntryName, calculationInfo := range entry.Entries {
    57  			if calculationInfo == nil {
    58  				general.Warningf("[ListAndWatchResponse.GetBlocks] got nil calculationInfo entry: %s, subEntry: %s",
    59  					entryName, subEntryName)
    60  				continue
    61  			}
    62  
    63  			for numaIdInt64, calculationResult := range calculationInfo.CalculationResultsByNumas {
    64  				if calculationResult == nil {
    65  					general.Warningf("[ListAndWatchResponse.GetBlocks] got nil NUMA result entry: %s, subEntry: %s",
    66  						entryName, subEntryName)
    67  					continue
    68  				}
    69  
    70  				numaIdInt, err := general.CovertInt64ToInt(numaIdInt64)
    71  				if err != nil {
    72  					return nil, fmt.Errorf("parse nuam id: %d failed with error: %v entry: %s, subEntry: %s",
    73  						numaIdInt64, err, entryName, subEntryName)
    74  				}
    75  
    76  				if numaToBlocks[numaIdInt] == nil {
    77  					numaToBlocks[numaIdInt] = make(map[string]*Block)
    78  				}
    79  
    80  				for _, block := range calculationResult.Blocks {
    81  					if block == nil {
    82  						general.Warningf("[ListAndWatchResponse.GetBlocks] got nil block result entry: %s, subEntry: %s",
    83  							entryName, subEntryName)
    84  						continue
    85  					}
    86  
    87  					blockId := block.BlockId
    88  					if foundNUMAId, found := visBlocksToNUMA[blockId]; found && numaToBlocks[foundNUMAId][blockId] != nil {
    89  						if foundNUMAId != numaIdInt {
    90  							return nil, fmt.Errorf("found block: %s both in NUMA: %d and NUMA: %d, entry: %s, subEntry: %s",
    91  								blockId, foundNUMAId, numaIdInt, entryName, subEntryName)
    92  						} else if numaToBlocks[foundNUMAId][blockId].Result != block.Result {
    93  							return nil, fmt.Errorf("found block: %s result is different with current block: %s result, entry: %s, subEntry: %s",
    94  								numaToBlocks[foundNUMAId][blockId].String(), block.String(), entryName, subEntryName)
    95  						}
    96  					}
    97  
    98  					numaToBlocks[numaIdInt][blockId] = block
    99  					visBlocksToNUMA[blockId] = numaIdInt
   100  					blocksEntryNames[blockId] = append(blocksEntryNames[blockId], []string{entryName, subEntryName})
   101  				}
   102  			}
   103  		}
   104  	}
   105  
   106  	for numaId, blocksMap := range numaToBlocks {
   107  		blocks := make([]*Block, 0, len(blocksMap))
   108  
   109  		for _, block := range blocksMap {
   110  			blocks = append(blocks, block)
   111  
   112  			if len(blocksEntryNames[block.BlockId]) == 0 {
   113  				return nil, fmt.Errorf("there is no entryName for block: %s", block.BlockId)
   114  			}
   115  		}
   116  
   117  		sort.Slice(blocks, getBlocksLessFunc(blocksEntryNames, blocks))
   118  
   119  		numaToSortedBlocks[numaId] = blocks
   120  	}
   121  
   122  	logNUMAToBlocksSeq(numaToSortedBlocks, blocksEntryNames)
   123  
   124  	return numaToSortedBlocks, nil
   125  }
   126  
   127  // getBlocksLessFunc judges if a block is less than another by entryNames of them
   128  func getBlocksLessFunc(blocksEntryNames map[string][][]string, blocks []*Block) func(i, j int) bool {
   129  	return func(i, j int) bool {
   130  		blockId1 := blocks[i].BlockId
   131  		blockId2 := blocks[j].BlockId
   132  
   133  		entryNames1 := blocksEntryNames[blockId1]
   134  		entryNames2 := blocksEntryNames[blockId2]
   135  
   136  		getNames := func(entryNames [][]string, isPod bool) []string {
   137  			names := make([]string, 0, len(entryNames))
   138  			for _, namesTuple := range entryNames {
   139  				entryName, subEntryName := namesTuple[0], namesTuple[1]
   140  
   141  				if isPod {
   142  					if subEntryName != FakedContainerName {
   143  						names = append(names, entryName)
   144  					}
   145  				} else {
   146  					if subEntryName == FakedContainerName {
   147  						names = append(names, entryName)
   148  					}
   149  				}
   150  			}
   151  			sort.Strings(names)
   152  			return names
   153  		}
   154  
   155  		podNames1, poolNames1, podNames2, poolNames2 := getNames(entryNames1, true),
   156  			getNames(entryNames1, false),
   157  			getNames(entryNames2, true),
   158  			getNames(entryNames2, false)
   159  
   160  		if len(podNames1) > len(podNames2) {
   161  			return true
   162  		} else if len(podNames1) < len(podNames2) {
   163  			return false
   164  		} else {
   165  			if len(podNames1) > 0 {
   166  				return strings.Join(podNames1, NameSeparator) < strings.Join(podNames2, NameSeparator)
   167  			}
   168  
   169  			if len(poolNames1) > len(poolNames2) {
   170  				return true
   171  			} else if len(poolNames1) < len(poolNames2) {
   172  				return false
   173  			} else {
   174  				return strings.Join(poolNames1, NameSeparator) < strings.Join(poolNames2, NameSeparator)
   175  			}
   176  		}
   177  	}
   178  }
   179  
   180  func logNUMAToBlocksSeq(numaToSortedBlocks map[int][]*Block, blocksEntryNames map[string][][]string) {
   181  	for numaId, blocks := range numaToSortedBlocks {
   182  		for i, block := range blocks {
   183  			general.InfoS("logNUMAToBlocksSeq", "numaId", numaId, "seq", i, "blockId", block.BlockId, "entryNames", blocksEntryNames[block.BlockId])
   184  		}
   185  	}
   186  }
   187  
   188  // GeEntryNUMABlocks returns Block lists according to the given [entry, subEntry] pair
   189  func (lwr *ListAndWatchResponse) GeEntryNUMABlocks(entry, subEntry string, numa int64) ([]*Block, bool) {
   190  	if entry, ok := lwr.Entries[entry]; !ok {
   191  		return []*Block{}, false
   192  	} else if info, ok := entry.Entries[subEntry]; !ok {
   193  		return []*Block{}, false
   194  	} else if results, ok := info.CalculationResultsByNumas[numa]; !ok {
   195  		return []*Block{}, false
   196  	} else {
   197  		return results.Blocks, true
   198  	}
   199  }
   200  
   201  // GetCalculationInfo returns CalculationInfo according to the given [entry, subEntry]
   202  func (lwr *ListAndWatchResponse) GetCalculationInfo(entry, subEntry string) (*CalculationInfo, bool) {
   203  	if entry, ok := lwr.Entries[entry]; !ok {
   204  		return nil, false
   205  	} else if info, ok := entry.Entries[subEntry]; !ok {
   206  		return nil, false
   207  	} else {
   208  		return info, true
   209  	}
   210  }
   211  
   212  // FilterCalculationInfo filter out CalculationInfo only for dedicated pod,
   213  // and the returned map and formatted as pod -> container -> CalculationInfo
   214  func (lwr *ListAndWatchResponse) FilterCalculationInfo(pool string) map[string]map[string]*CalculationInfo {
   215  	dedicatedCalculationInfos := make(map[string]map[string]*CalculationInfo)
   216  	for entryName, entry := range lwr.Entries {
   217  		for subEntryName, calculationInfo := range entry.Entries {
   218  			if calculationInfo != nil && calculationInfo.OwnerPoolName == pool {
   219  				if dedicatedCalculationInfos[entryName] == nil {
   220  					dedicatedCalculationInfos[entryName] = make(map[string]*CalculationInfo)
   221  				}
   222  
   223  				dedicatedCalculationInfos[entryName][subEntryName] = calculationInfo
   224  			}
   225  		}
   226  	}
   227  	return dedicatedCalculationInfos
   228  }
   229  
   230  // GetNUMACalculationResult returns numa-level calculation results
   231  func (ce *CalculationEntries) GetNUMACalculationResult(container string, numa int64) (*NumaCalculationResult, bool) {
   232  	info, ok := ce.Entries[container]
   233  	if !ok || info == nil {
   234  		return nil, false
   235  	}
   236  
   237  	if numaInfo, ok := info.CalculationResultsByNumas[numa]; ok {
   238  		return numaInfo, true
   239  	}
   240  	return nil, false
   241  }
   242  
   243  // GetNUMAQuantities returns quantity in each numa for in this CalculationInfo
   244  func (ci *CalculationInfo) GetNUMAQuantities() (map[int]int, error) {
   245  	if ci == nil {
   246  		return nil, fmt.Errorf("got nil ci")
   247  	}
   248  
   249  	numaQuantities := make(map[int]int)
   250  	for numaId, numaResult := range ci.CalculationResultsByNumas {
   251  		if numaResult == nil {
   252  			general.Warningf("[GetTotalQuantity] got nil NUMA result")
   253  			continue
   254  		}
   255  
   256  		var quantityUInt64 uint64 = 0
   257  		for _, block := range numaResult.Blocks {
   258  			if block == nil {
   259  				general.Warningf("[GetTotalQuantity] got nil block")
   260  				continue
   261  			}
   262  			quantityUInt64 += block.Result
   263  		}
   264  
   265  		quantityInt, err := general.CovertUInt64ToInt(quantityUInt64)
   266  		if err != nil {
   267  			return nil, fmt.Errorf("converting quantity: %d to int failed with error: %v",
   268  				quantityUInt64, err)
   269  		}
   270  
   271  		numaIdInt, err := general.CovertInt64ToInt(numaId)
   272  		if err != nil {
   273  			return nil, fmt.Errorf("converting quantity: %d to int failed with error: %v",
   274  				numaId, err)
   275  		}
   276  		numaQuantities[numaIdInt] = quantityInt
   277  	}
   278  	return numaQuantities, nil
   279  }
   280  
   281  // GetTotalQuantity returns total quantity for in this CalculationInfo
   282  func (ci *CalculationInfo) GetTotalQuantity() (int, error) {
   283  	numaQuantities, err := ci.GetNUMAQuantities()
   284  	if err != nil {
   285  		return 0, err
   286  	}
   287  
   288  	totalQuantity := 0
   289  	for _, quantity := range numaQuantities {
   290  		totalQuantity += quantity
   291  	}
   292  	return totalQuantity, nil
   293  }
   294  
   295  // GetCPUSet returns cpuset for this container by union all blocks for it
   296  func (ci *CalculationInfo) GetCPUSet(entry, subEntry string, b BlockCPUSet) (machine.CPUSet, error) {
   297  	cpusets := machine.NewCPUSet()
   298  	for _, results := range ci.CalculationResultsByNumas {
   299  		if results == nil {
   300  			general.Warningf("got nil numa result entry: %s, subEntry: %s", entry, subEntry)
   301  			continue
   302  		}
   303  
   304  		for _, block := range results.Blocks {
   305  			if block == nil {
   306  				general.Warningf("got nil block result entry: %s, subEntry: %s", entry, subEntry)
   307  				continue
   308  			}
   309  
   310  			if cset, found := b[block.BlockId]; !found {
   311  				return machine.CPUSet{}, fmt.Errorf("block %s not found, entry: %s, subEntry: %s", block.BlockId, entry, subEntry)
   312  			} else {
   313  				cpusets = cpusets.Union(cset)
   314  			}
   315  		}
   316  	}
   317  	return cpusets, nil
   318  }