package loadstorage import ( "context" "sort" "sync" loadcontroller "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/container/announcement/load/controller" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" ) type usedSpaceEstimations struct { announcement container.SizeEstimation sizes []uint64 } type storageKey struct { epoch uint64 cid string } // Storage represents in-memory storage of // container.SizeEstimation values. // // The write operation has the usual behavior - to save // the next number of used container space for a specific epoch. // All values related to one key (epoch, container ID) are stored // as a list. // // Storage also provides an iterator interface, into the handler // of which the final score is passed, built on all values saved // at the time of the call. Currently the only possible estimation // formula is used - the average between 10th and 90th percentile. // // For correct operation, Storage must be created // using the constructor (New) based on the required parameters // and optional components. After successful creation, // Storage is immediately ready to work through API. type Storage struct { mtx sync.RWMutex mItems map[storageKey]*usedSpaceEstimations } // Prm groups the required parameters of the Storage's constructor. // // The component is not parameterizable at the moment. type Prm struct{} // New creates a new instance of the Storage. // // The created Storage does not require additional // initialization and is completely ready for work. func New(_ Prm) *Storage { return &Storage{ mItems: make(map[storageKey]*usedSpaceEstimations), } } // Put appends the next value of the occupied container space for the epoch // to the list of already saved values. // // Always returns nil error. func (s *Storage) Put(a container.SizeEstimation) error { s.mtx.Lock() { key := storageKey{ epoch: a.Epoch(), cid: a.Container().EncodeToString(), } estimations, ok := s.mItems[key] if !ok { estimations = &usedSpaceEstimations{ announcement: a, sizes: make([]uint64, 0, 1), } s.mItems[key] = estimations } estimations.sizes = append(estimations.sizes, a.Value()) } s.mtx.Unlock() return nil } func (s *Storage) Close(context.Context) error { return nil } // Iterate goes through all the lists with the key (container ID, epoch), // calculates the final grade for all values, and passes it to the handler. // // Final grade is the average between 10th and 90th percentiles. func (s *Storage) Iterate(f loadcontroller.UsedSpaceFilter, h loadcontroller.UsedSpaceHandler) (err error) { s.mtx.RLock() { for _, v := range s.mItems { if f(v.announcement) { // calculate estimation based on 90th percentile v.announcement.SetValue(finalEstimation(v.sizes)) if err = h(v.announcement); err != nil { break } } } } s.mtx.RUnlock() return } func finalEstimation(vals []uint64) uint64 { sort.Slice(vals, func(i, j int) bool { return vals[i] < vals[j] }) const ( lowerRank = 10 upperRank = 90 ) if len(vals) >= lowerRank { lowerInd := percentile(lowerRank, vals) upperInd := percentile(upperRank, vals) vals = vals[lowerInd:upperInd] } sum := uint64(0) for i := range vals { sum += vals[i] } return sum / uint64(len(vals)) } func percentile(rank int, vals []uint64) int { p := len(vals) * rank / 100 return p }