blob: 855026adef894858ecc6c97e951f957c4b7bf7db [file] [log] [blame]
/*
* Copyright 2020 Dgraph Labs, Inc. and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package badger
import (
"encoding/binary"
"os"
"path/filepath"
"sort"
"sync"
"github.com/dgraph-io/badger/v3/y"
"github.com/dgraph-io/ristretto/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)
// 1GB file can store 67M 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 1GB.
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, uint64(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 int64(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)
}