github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/sysadvisor/plugin/qosaware/server/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 server
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"k8s.io/apimachinery/pkg/util/uuid"
    23  
    24  	"github.com/kubewharf/katalyst-core/pkg/agent/qrm-plugins/cpu/dynamicpolicy/cpuadvisor"
    25  	"github.com/kubewharf/katalyst-core/pkg/agent/sysadvisor/types"
    26  )
    27  
    28  // NewPoolCalculationEntries returns CalculationEntries,
    29  // and it will only fill up with OwnerPoolName and leaves numa info empty.
    30  func NewPoolCalculationEntries(poolName string) *cpuadvisor.CalculationEntries {
    31  	ci := &cpuadvisor.CalculationInfo{
    32  		OwnerPoolName:             poolName,
    33  		CalculationResultsByNumas: make(map[int64]*cpuadvisor.NumaCalculationResult),
    34  	}
    35  
    36  	return &cpuadvisor.CalculationEntries{Entries: map[string]*cpuadvisor.CalculationInfo{"": ci}}
    37  }
    38  
    39  // NewBlock constructs a Block structure; generate a new one if blockID is missed
    40  func NewBlock(size uint64, blockID string) *cpuadvisor.Block {
    41  	if blockID == "" {
    42  		blockID = string(uuid.NewUUID())
    43  	}
    44  
    45  	return &cpuadvisor.Block{
    46  		Result:         size,
    47  		OverlapTargets: make([]*cpuadvisor.OverlapTarget, 0),
    48  		BlockId:        blockID,
    49  	}
    50  }
    51  
    52  // internalBlock works as a packed structure for Block;
    53  // aside for Block, it also stores some extra info to speed up efficiency.
    54  type internalBlock struct {
    55  	// Block stores the standard Block info
    56  	// - it is thread-safe since it is referred only by this internalBlock
    57  	Block *cpuadvisor.Block
    58  	// NumaID stores the numa node that this Block belongs to
    59  	NumaID int64
    60  
    61  	// only one of PoolName and ContainerInfo can be set as valid
    62  	// PoolName stores pool that owns this block
    63  	// - it may be nil if this block belongs to pod rather than pool
    64  	PoolName string
    65  	// ContainerInfo stores container that own this block
    66  	// - it may be nil if this block belongs to pool rather than pod
    67  	// - it is thread-safe since it is deep-copied when constructed internalBlock
    68  	ContainerInfo *types.ContainerInfo
    69  
    70  	// NumaCalculationResult stores
    71  	// - it is thread-safe since it is referred only by this internalBlock
    72  	NumaCalculationResult *cpuadvisor.NumaCalculationResult
    73  }
    74  
    75  func NewInnerBlock(block *cpuadvisor.Block, numaID int64, poolName string, containerInfo *types.ContainerInfo,
    76  	numaCalculationResult *cpuadvisor.NumaCalculationResult,
    77  ) *internalBlock {
    78  	ib := &internalBlock{
    79  		Block:                 block,
    80  		NumaID:                numaID,
    81  		PoolName:              poolName,
    82  		NumaCalculationResult: numaCalculationResult,
    83  	}
    84  
    85  	if containerInfo != nil {
    86  		ib.ContainerInfo = containerInfo.Clone()
    87  	}
    88  	return ib
    89  }
    90  
    91  // initialOverlapTarget constructs cpuadvisor.OverlapTarget based on Block info
    92  func (ib *internalBlock) initialOverlapTarget() *cpuadvisor.OverlapTarget {
    93  	target := &cpuadvisor.OverlapTarget{}
    94  
    95  	if ib.ContainerInfo == nil {
    96  		target.OverlapTargetPoolName = ib.PoolName
    97  		target.OverlapType = cpuadvisor.OverlapType_OverlapWithPool
    98  	} else {
    99  		target.OverlapTargetPodUid = ib.ContainerInfo.PodUID
   100  		target.OverlapTargetContainerName = ib.ContainerInfo.ContainerName
   101  		target.OverlapType = cpuadvisor.OverlapType_OverlapWithPod
   102  	}
   103  
   104  	return target
   105  }
   106  
   107  // for multiple Block in one single NUMA node, join tries to
   108  // split the existed Block into more Block to ensure we must
   109  // aggregate different Block according to the size of them.
   110  func (ib *internalBlock) join(blockID string, set blockSet) {
   111  	ibList := set.get(blockID)
   112  	if ibList == nil || len(ibList) == 0 {
   113  		_ = set.add(ib)
   114  		return
   115  	}
   116  
   117  	// support to merge current Block into blockSet iff they belong to the same numa node
   118  	if ib.NumaID != ibList[0].NumaID {
   119  		return
   120  	}
   121  
   122  	thisTarget := ib.initialOverlapTarget()
   123  	if ib.Block.Result == ibList[0].Block.Result {
   124  		// if cpu size equals, it means current Block can share the same;
   125  		// otherwise, a split Block must be generated to ensure size aggregation
   126  
   127  		for _, innerBlock := range ibList {
   128  			innerBlock.Block.OverlapTargets = append(innerBlock.Block.OverlapTargets, thisTarget)
   129  
   130  			target := innerBlock.initialOverlapTarget()
   131  			ib.Block.OverlapTargets = append(ib.Block.OverlapTargets, target)
   132  			ib.Block.BlockId = blockID
   133  		}
   134  		_ = set.add(ib)
   135  	} else if ib.Block.Result > ibList[0].Block.Result {
   136  		// if cpu size for current Block is greater, split current Block into multiple ones
   137  		// i.e. one to match with cpu size for existing blockSet, one to add as a new blockSet
   138  
   139  		newBlock := NewBlock(ibList[0].Block.Result, blockID)
   140  		newInnerBlock := NewInnerBlock(newBlock, ib.NumaID, ib.PoolName, ib.ContainerInfo, ib.NumaCalculationResult)
   141  		newInnerBlock.join(blockID, set)
   142  
   143  		ib.NumaCalculationResult.Blocks = append(ib.NumaCalculationResult.Blocks, newBlock)
   144  		ib.Block.Result = ib.Block.Result - ibList[0].Block.Result
   145  		ib.Block.OverlapTargets = nil
   146  		_ = set.add(ib)
   147  	} else {
   148  		// if cpu size for current Block is smaller, split existing Block in blockSet into multiple ones
   149  		// i.e. one to match with cpu size for current Block, one to add as a new blockSet
   150  
   151  		for _, innerBlock := range ibList {
   152  			newBlock := NewBlock(ibList[0].Block.Result-ib.Block.Result, "")
   153  			newInnerBlock := NewInnerBlock(newBlock, innerBlock.NumaID, innerBlock.PoolName, innerBlock.ContainerInfo, innerBlock.NumaCalculationResult)
   154  			newInnerBlock.join(newBlock.BlockId, set)
   155  
   156  			innerBlock.NumaCalculationResult.Blocks = append(innerBlock.NumaCalculationResult.Blocks, newBlock)
   157  			innerBlock.Block.Result = ib.Block.Result
   158  		}
   159  
   160  		ib.join(ib.Block.BlockId, set)
   161  	}
   162  }
   163  
   164  // blockSet is a global variable to store, and the mapping relation
   165  // block id -> the internalBlock that belongs to this block id
   166  //
   167  // those internalBlock sharing with the same id should satisfy several constraints
   168  // - they belong to the same NUMA Node (though there exists no NUMA concepts here)
   169  // - they share with the same cpuset pool
   170  type blockSet map[string][]*internalBlock
   171  
   172  func NewBlockSet() blockSet {
   173  	return make(map[string][]*internalBlock)
   174  }
   175  
   176  func (bs blockSet) add(ib *internalBlock) error {
   177  	if ib == nil || ib.Block == nil {
   178  		return fmt.Errorf("internal block is invalid")
   179  	}
   180  
   181  	internalBlocks, ok := bs[ib.Block.BlockId]
   182  	if !ok {
   183  		internalBlocks = make([]*internalBlock, 0)
   184  	}
   185  
   186  	internalBlocks = append(internalBlocks, ib)
   187  	bs[ib.Block.BlockId] = internalBlocks
   188  	return nil
   189  }
   190  
   191  func (bs blockSet) get(blockID string) []*internalBlock {
   192  	internalBlocks, ok := bs[blockID]
   193  	if !ok {
   194  		return nil
   195  	}
   196  	return internalBlocks
   197  }
   198  
   199  // getNumaCalculationResult returns numa-level calculation results
   200  func getNumaCalculationResult(calculationEntriesMap map[string]*cpuadvisor.CalculationEntries,
   201  	entryName, containerName string, numa int64,
   202  ) (*cpuadvisor.NumaCalculationResult, bool) {
   203  	entries, ok := calculationEntriesMap[entryName]
   204  	if !ok {
   205  		return nil, false
   206  	}
   207  
   208  	return entries.GetNUMACalculationResult(containerName, numa)
   209  }