/* * SPDX-FileCopyrightText: © Hypermode Inc. * SPDX-License-Identifier: Apache-2.0 */ package badger import ( "encoding/binary" "os" "path/filepath" "sort" "sync" "github.com/dgraph-io/badger/v4/y" "github.com/dgraph-io/ristretto/v2/z" ) // discardStats keeps track of the amount of data that could be discarded for // a given logfile. type discardStats struct { sync.Mutex *z.MmapFile opt Options nextEmptySlot int } const discardFname string = "DISCARD" func InitDiscardStats(opt Options) (*discardStats, error) { fname := filepath.Join(opt.ValueDir, discardFname) // 1MB file can store 65.536 discard entries. Each entry is 16 bytes. mf, err := z.OpenMmapFile(fname, os.O_CREATE|os.O_RDWR, 1<<20) lf := &discardStats{ MmapFile: mf, opt: opt, } if err == z.NewFile { // We don't need to zero out the entire 1MB. lf.zeroOut() } else if err != nil { return nil, y.Wrapf(err, "while opening file: %s\n", discardFname) } for slot := 0; slot < lf.maxSlot(); slot++ { if lf.get(16*slot) == 0 { lf.nextEmptySlot = slot break } } sort.Sort(lf) opt.Infof("Discard stats nextEmptySlot: %d\n", lf.nextEmptySlot) return lf, nil } func (lf *discardStats) Len() int { return lf.nextEmptySlot } func (lf *discardStats) Less(i, j int) bool { return lf.get(16*i) < lf.get(16*j) } func (lf *discardStats) Swap(i, j int) { left := lf.Data[16*i : 16*i+16] right := lf.Data[16*j : 16*j+16] var tmp [16]byte copy(tmp[:], left) copy(left, right) copy(right, tmp[:]) } // offset is not slot. func (lf *discardStats) get(offset int) uint64 { return binary.BigEndian.Uint64(lf.Data[offset : offset+8]) } func (lf *discardStats) set(offset int, val uint64) { binary.BigEndian.PutUint64(lf.Data[offset:offset+8], val) } // zeroOut would zero out the next slot. func (lf *discardStats) zeroOut() { lf.set(lf.nextEmptySlot*16, 0) lf.set(lf.nextEmptySlot*16+8, 0) } func (lf *discardStats) maxSlot() int { return len(lf.Data) / 16 } // Update would update the discard stats for the given file id. If discard is // 0, it would return the current value of discard for the file. If discard is // < 0, it would set the current value of discard to zero for the file. func (lf *discardStats) Update(fidu uint32, discard int64) int64 { fid := uint64(fidu) lf.Lock() defer lf.Unlock() idx := sort.Search(lf.nextEmptySlot, func(slot int) bool { return lf.get(slot*16) >= fid }) if idx < lf.nextEmptySlot && lf.get(idx*16) == fid { off := idx*16 + 8 curDisc := lf.get(off) if discard == 0 { return int64(curDisc) } if discard < 0 { lf.set(off, 0) return 0 } lf.set(off, curDisc+uint64(discard)) return int64(curDisc + uint64(discard)) } if discard <= 0 { // No need to add a new entry. return 0 } // Could not find the fid. Add the entry. idx = lf.nextEmptySlot lf.set(idx*16, fid) lf.set(idx*16+8, uint64(discard)) // Move to next slot. lf.nextEmptySlot++ for lf.nextEmptySlot >= lf.maxSlot() { y.Check(lf.Truncate(2 * int64(len(lf.Data)))) } lf.zeroOut() sort.Sort(lf) return discard } func (lf *discardStats) Iterate(f func(fid, stats uint64)) { for slot := 0; slot < lf.nextEmptySlot; slot++ { idx := 16 * slot f(lf.get(idx), lf.get(idx+8)) } } // MaxDiscard returns the file id with maximum discard bytes. func (lf *discardStats) MaxDiscard() (uint32, int64) { lf.Lock() defer lf.Unlock() var maxFid, maxVal uint64 lf.Iterate(func(fid, val uint64) { if maxVal < val { maxVal = val maxFid = fid } }) return uint32(maxFid), int64(maxVal) }