mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
345 lines
8.5 KiB
Go
345 lines
8.5 KiB
Go
// Copyright 2020 Pilosa Corp.
|
|
//
|
|
// 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 pilosa
|
|
|
|
// util.go: a place for generic, reusable utilities.
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/molecula/featurebase/v2/roaring"
|
|
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// LeftShifted16MaxContainerKey is 0xffffffffffff0000. It is similar
|
|
// to the roaring.maxContainerKey 0x0000ffffffffffff, but
|
|
// shifted 16 bits to the left so its domain is the full [0, 2^64) bit space.
|
|
// It is used to match the semantics of the roaring.OffsetRange() API.
|
|
// This is the maximum endx value for Tx.OffsetRange(), because the lowbits,
|
|
// as in the roaring.OffsetRange(), are not allowed to be set.
|
|
// It is used in Tx.RoaringBitamp() to obtain the full contents of a fragment
|
|
// from a call from tx.OffsetRange() by requesting [0, LeftShifted16MaxContainerKey)
|
|
// with an offset of 0.
|
|
const LeftShifted16MaxContainerKey = uint64(0xffffffffffff0000) // or math.MaxUint64 - (1<<16 - 1), or 18446744073709486080
|
|
|
|
// NilInside checks if the provided iface is nil or
|
|
// contains a nil pointer, slice, array, map, or channel.
|
|
func NilInside(iface interface{}) bool {
|
|
if iface == nil {
|
|
return true
|
|
}
|
|
switch reflect.TypeOf(iface).Kind() {
|
|
case reflect.Ptr, reflect.Slice, reflect.Array, reflect.Map, reflect.Chan:
|
|
return reflect.ValueOf(iface).IsNil()
|
|
}
|
|
return false
|
|
}
|
|
|
|
//////////////////////////////////
|
|
// helper utility functions
|
|
|
|
func highbits(v uint64) uint64 { return v >> 16 }
|
|
func lowbits(v uint64) uint16 { return uint16(v & 0xFFFF) }
|
|
|
|
func toArray16(a []byte) []uint16 {
|
|
return (*[4096]uint16)(unsafe.Pointer(&a[0]))[: len(a)/2 : len(a)/2]
|
|
}
|
|
func toArray64(a []byte) []uint64 {
|
|
return (*[1024]uint64)(unsafe.Pointer(&a[0]))[:1024:1024]
|
|
}
|
|
func toInterval16(a []byte) []roaring.Interval16 {
|
|
return (*[2048]roaring.Interval16)(unsafe.Pointer(&a[0]))[: len(a)/4 : len(a)/4]
|
|
}
|
|
|
|
func sliceToMap(slc []uint64) (m map[uint64]bool) {
|
|
m = make(map[uint64]bool)
|
|
for _, v := range slc {
|
|
m[v] = true
|
|
}
|
|
return
|
|
}
|
|
|
|
// return A - B
|
|
func mapDiff(mapA, mapB map[uint64]bool) (r []int) {
|
|
for a := range mapA {
|
|
_, ok := mapB[a]
|
|
if !ok {
|
|
r = append(r, int(a))
|
|
}
|
|
}
|
|
sort.Ints(r)
|
|
return
|
|
}
|
|
|
|
func asInts(a []uint64) (r []int) {
|
|
r = make([]int, len(a))
|
|
for i, v := range a {
|
|
r[i] = int(v)
|
|
}
|
|
return
|
|
}
|
|
|
|
func containerAsString(ckey uint64, rc *roaring.Container) (r string) {
|
|
rbm := roaring.NewBitmap()
|
|
rbm.Containers.Put(ckey, rc)
|
|
return BitmapAsString(rbm)
|
|
}
|
|
|
|
var _ = containerAsString // happy linter
|
|
|
|
func roaringBitmapDiff(a, b *roaring.Bitmap) error {
|
|
nA := a.Count()
|
|
nB := b.Count()
|
|
|
|
slcA := a.Slice()
|
|
slcB := b.Slice()
|
|
|
|
mapA := sliceToMap(slcA)
|
|
mapB := sliceToMap(slcB)
|
|
|
|
AminusB := mapDiff(mapA, mapB)
|
|
BminusA := mapDiff(mapB, mapA)
|
|
|
|
sort.Ints(AminusB)
|
|
sort.Ints(BminusA)
|
|
|
|
res := fmt.Sprintf("nA = %v; nB = %v;\n", nA, nB)
|
|
ndiff := 0
|
|
if nA != nB {
|
|
ndiff++
|
|
}
|
|
|
|
if len(AminusB) > 0 {
|
|
res += fmt.Sprintf("==> AminusB = (len %v) '%#v'; ", len(AminusB), AminusB)
|
|
ndiff++
|
|
}
|
|
if len(BminusA) > 0 {
|
|
res += fmt.Sprintf("\n==> BminusA = (len %v) '%#v'; ", len(BminusA), BminusA)
|
|
ndiff++
|
|
}
|
|
if ndiff == 0 {
|
|
return nil
|
|
}
|
|
res += fmt.Sprintf("\n ==> A = '%#v'\n ==> B = '%#v'", asInts(slcA), asInts(slcB))
|
|
return errors.New(res)
|
|
}
|
|
|
|
func dirAsString(path string) (r string) {
|
|
r = fmt.Sprintf("dump of directory '%v':\n", path)
|
|
files, err := ioutil.ReadDir(path)
|
|
PanicOn(err)
|
|
for _, f := range files {
|
|
r += f.Name() + "\n"
|
|
}
|
|
return r
|
|
}
|
|
|
|
var _ = dirAsString // happy linter
|
|
|
|
var _ = zeroKeyContainerAsString // happy linter
|
|
|
|
// for debugging
|
|
func zeroKeyContainerAsString(ct *roaring.Container) (r string) {
|
|
cts := roaring.NewSliceContainers()
|
|
cts.Put(0, ct)
|
|
rbm := &roaring.Bitmap{Containers: cts}
|
|
r = fmt.Sprintf("[%v]:", containerTypeNames[roaring.ContainerType(ct)]) + BitmapAsString(rbm)
|
|
return
|
|
}
|
|
|
|
var containerTypeNames = map[byte]string{
|
|
roaring.ContainerArray: "array",
|
|
roaring.ContainerBitmap: "bitmap",
|
|
roaring.ContainerRun: "run",
|
|
}
|
|
|
|
func BitmapAsString(rbm *roaring.Bitmap) (r string) {
|
|
r = "c("
|
|
slc := rbm.Slice()
|
|
width := 0
|
|
s := ""
|
|
for _, v := range slc {
|
|
if width == 0 {
|
|
s = fmt.Sprintf("%v", v)
|
|
} else {
|
|
s = fmt.Sprintf(", %v", v)
|
|
}
|
|
width += len(s)
|
|
r += s
|
|
if width > 70 {
|
|
r += ",\n"
|
|
width = 0
|
|
}
|
|
}
|
|
if width == 0 && len(r) > 2 {
|
|
r = r[:len(r)-2]
|
|
}
|
|
return r + ")"
|
|
}
|
|
|
|
// fromArray16 converts to an 8KB page
|
|
func fromArray16(a []uint16) []byte {
|
|
if len(a) == 0 {
|
|
return []byte{}
|
|
}
|
|
if len(a) > 4096 {
|
|
PanicOn(fmt.Sprintf("cannot put more than 4096 integers into an array container: %v too big", len(a)))
|
|
}
|
|
return (*[8192]byte)(unsafe.Pointer(&a[0]))[: len(a)*2 : len(a)*2]
|
|
}
|
|
|
|
// fromArray64 converts to an 8KB page
|
|
func fromArray64(a []uint64) []byte {
|
|
if len(a) == 0 {
|
|
return []byte{}
|
|
}
|
|
return (*[8192]byte)(unsafe.Pointer(&a[0]))[:8192:8192]
|
|
}
|
|
|
|
// fromInterval16 converts to 8KB page
|
|
func fromInterval16(a []roaring.Interval16) []byte {
|
|
if len(a) == 0 {
|
|
return []byte{}
|
|
}
|
|
if len(a) > 2048 {
|
|
PanicOn(fmt.Sprintf("cannot put more than 2048 roaring.Interval16 into a container: %v too big", len(a)))
|
|
}
|
|
return (*[8192]byte)(unsafe.Pointer(&a[0]))[: len(a)*4 : len(a)*4]
|
|
}
|
|
|
|
// DiskUse reports the total bytes uses by all files under root
|
|
// that match requiredSuffix. requiredSuffix can be empty string.
|
|
// Space used by directories is not counted.
|
|
func DiskUse(root string, requiredSuffix string) (tot int, err error) {
|
|
if !DirExists(root) {
|
|
return -1, fmt.Errorf("listFilesUnderDir error: root directory '%v' not found", root)
|
|
}
|
|
|
|
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if info == nil {
|
|
PanicOn(fmt.Sprintf("info was nil for path = '%v'", path))
|
|
}
|
|
if info.IsDir() {
|
|
// skip the size of directories themselves, only summing files.
|
|
} else {
|
|
sz := info.Size()
|
|
if requiredSuffix == "" || strings.HasSuffix(path, requiredSuffix) {
|
|
tot += int(sz)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
return
|
|
}
|
|
|
|
// rootDir must exist. Return the size in bytes of the largest sub-directory
|
|
// that has the required suffix. The largestSize is from DiskUse() called
|
|
// on the sub-dir. DiskUse only counts file size, nothing for directory inodes.
|
|
func SubdirLargestDirWithSuffix(rootDir, requiredDirSuffix string) (exists bool, largestSize int, err error) {
|
|
if !DirExists(rootDir) {
|
|
return false, -1, fmt.Errorf("SubdirExistsWithSuffix error: root directory '%v' not found", rootDir)
|
|
}
|
|
|
|
err = filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
|
if info == nil {
|
|
PanicOn(fmt.Sprintf("info was nil for path = '%v'", path))
|
|
}
|
|
|
|
if info.IsDir() && strings.HasSuffix(path, requiredDirSuffix) {
|
|
exists = true
|
|
size, err := DiskUse(path, "")
|
|
if err != nil {
|
|
// disk error? report it
|
|
return err
|
|
}
|
|
if size > largestSize {
|
|
largestSize = size
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return exists, -1, err
|
|
}
|
|
return
|
|
}
|
|
|
|
// called by Holder.hasRoaringData()
|
|
func roaringFragmentHasData(path string, index, field, view string, shard uint64) (hasData bool, err error) {
|
|
|
|
var info roaring.BitmapInfo
|
|
_ = info
|
|
var f *os.File
|
|
f, err = os.Open(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var fi os.FileInfo
|
|
fi, err = f.Stat()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Memory map the file.
|
|
data, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "mmapping")
|
|
return
|
|
}
|
|
defer func() {
|
|
err = syscall.Munmap(data)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "roaringFragmentHasData: munmap failed")
|
|
}
|
|
err = f.Close()
|
|
if err != nil {
|
|
err = errors.Wrap(err, "roaringFragmentHasData f.Close() in defer")
|
|
}
|
|
}()
|
|
|
|
// Attach the mmap file to the bitmap.
|
|
var rbm *roaring.Bitmap
|
|
rbm, _, err = roaring.InspectBinary(data, true, &info)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "inspecting")
|
|
return
|
|
}
|
|
|
|
if info.ContainerCount > 0 {
|
|
return true, nil
|
|
}
|
|
if info.Ops > 0 {
|
|
return true, nil
|
|
}
|
|
|
|
citer, found := rbm.Containers.Iterator(0)
|
|
_ = found
|
|
|
|
for citer.Next() {
|
|
return true, nil
|
|
}
|
|
|
|
return
|
|
}
|