re-implement data usage crawler to be more efficient (#9075)
Implementation overview: https://gist.github.com/klauspost/1801c858d5e0df391114436fdad6987bmaster
parent
7fdeb44372
commit
8d98662633
@ -0,0 +1 @@ |
||||
checks = ["all", "-ST1005", "-ST1000", "-SA4000", "-SA9004", "-SA1019", "-SA1008", "-U1000", "-ST1003", "-ST1018"] |
@ -0,0 +1,521 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
||||
* 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 cmd |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"io" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/cespare/xxhash/v2" |
||||
"github.com/minio/minio/cmd/logger" |
||||
"github.com/minio/minio/pkg/color" |
||||
"github.com/minio/minio/pkg/hash" |
||||
"github.com/tinylib/msgp/msgp" |
||||
) |
||||
|
||||
const dataUsageHashLen = 8 |
||||
|
||||
//go:generate msgp -file $GOFILE -unexported
|
||||
|
||||
// dataUsageHash is the hash type used.
|
||||
type dataUsageHash uint64 |
||||
|
||||
// sizeHistogram is a size histogram.
|
||||
type sizeHistogram [dataUsageBucketLen]uint64 |
||||
|
||||
//msgp:tuple dataUsageEntry
|
||||
type dataUsageEntry struct { |
||||
// These fields do no include any children.
|
||||
Size int64 |
||||
Objects uint64 |
||||
ObjSizes sizeHistogram |
||||
|
||||
Children dataUsageHashMap |
||||
} |
||||
|
||||
//msgp:ignore dataUsageEntryInfo
|
||||
type dataUsageEntryInfo struct { |
||||
Name string |
||||
Parent string |
||||
Entry dataUsageEntry |
||||
} |
||||
|
||||
type dataUsageCacheInfo struct { |
||||
// Name of the bucket. Also root element.
|
||||
Name string |
||||
LastUpdate time.Time |
||||
NextCycle uint8 |
||||
} |
||||
|
||||
// merge other data usage entry into this, excluding children.
|
||||
func (e *dataUsageEntry) merge(other dataUsageEntry) { |
||||
e.Objects += other.Objects |
||||
e.Size += other.Size |
||||
for i, v := range other.ObjSizes[:] { |
||||
e.ObjSizes[i] += v |
||||
} |
||||
} |
||||
|
||||
// mod returns true if the hash mod cycles == cycle.
|
||||
func (h dataUsageHash) mod(cycle uint8, cycles uint8) bool { |
||||
return uint8(h)%cycles == cycle%cycles |
||||
} |
||||
|
||||
// addChildString will add a child based on its name.
|
||||
// If it already exists it will not be added again.
|
||||
func (e *dataUsageEntry) addChildString(name string) { |
||||
e.addChild(hashPath(name)) |
||||
} |
||||
|
||||
// addChild will add a child based on its hash.
|
||||
// If it already exists it will not be added again.
|
||||
func (e *dataUsageEntry) addChild(hash dataUsageHash) { |
||||
if _, ok := e.Children[hash]; ok { |
||||
return |
||||
} |
||||
if e.Children == nil { |
||||
e.Children = make(dataUsageHashMap, 1) |
||||
} |
||||
e.Children[hash] = struct{}{} |
||||
} |
||||
|
||||
// find a path in the cache.
|
||||
// Returns nil if not found.
|
||||
func (d *dataUsageCache) find(path string) *dataUsageEntry { |
||||
due, ok := d.Cache[hashPath(path)] |
||||
if !ok { |
||||
return nil |
||||
} |
||||
return &due |
||||
} |
||||
|
||||
// dui converts the flattened version of the path to DataUsageInfo.
|
||||
func (d *dataUsageCache) dui(path string, buckets []BucketInfo) DataUsageInfo { |
||||
e := d.find(path) |
||||
if e == nil { |
||||
return DataUsageInfo{LastUpdate: time.Now()} |
||||
} |
||||
flat := d.flatten(*e) |
||||
return DataUsageInfo{ |
||||
LastUpdate: d.Info.LastUpdate, |
||||
ObjectsCount: flat.Objects, |
||||
ObjectsTotalSize: uint64(flat.Size), |
||||
ObjectsSizesHistogram: flat.ObjSizes.asMap(), |
||||
BucketsCount: uint64(len(e.Children)), |
||||
BucketsSizes: d.pathSizes(buckets), |
||||
} |
||||
} |
||||
|
||||
// replace will add or replace an entry in the cache.
|
||||
// If a parent is specified it will be added to that if not already there.
|
||||
// If the parent does not exist, it will be added.
|
||||
func (d *dataUsageCache) replace(path, parent string, e dataUsageEntry) { |
||||
hash := hashPath(path) |
||||
if d.Cache == nil { |
||||
d.Cache = make(map[dataUsageHash]dataUsageEntry, 100) |
||||
} |
||||
d.Cache[hash] = e |
||||
if parent != "" { |
||||
phash := hashPath(parent) |
||||
p := d.Cache[phash] |
||||
p.addChild(hash) |
||||
d.Cache[phash] = p |
||||
} |
||||
} |
||||
|
||||
// replaceHashed add or replaces an entry to the cache based on its hash.
|
||||
// If a parent is specified it will be added to that if not already there.
|
||||
// If the parent does not exist, it will be added.
|
||||
func (d *dataUsageCache) replaceHashed(hash dataUsageHash, parent *dataUsageHash, e dataUsageEntry) { |
||||
if d.Cache == nil { |
||||
d.Cache = make(map[dataUsageHash]dataUsageEntry, 100) |
||||
} |
||||
d.Cache[hash] = e |
||||
if parent != nil { |
||||
p := d.Cache[*parent] |
||||
p.addChild(hash) |
||||
d.Cache[*parent] = p |
||||
} |
||||
} |
||||
|
||||
// StringAll returns a detailed string representation of all entries in the cache.
|
||||
func (d *dataUsageCache) StringAll() string { |
||||
s := fmt.Sprintf("info:%+v\n", d.Info) |
||||
for k, v := range d.Cache { |
||||
s += fmt.Sprintf("\t%v: %+v\n", k, v) |
||||
} |
||||
return strings.TrimSpace(s) |
||||
} |
||||
|
||||
// String returns a human readable representation of the string.
|
||||
func (h dataUsageHash) String() string { |
||||
return fmt.Sprintf("%x", uint64(h)) |
||||
} |
||||
|
||||
// flatten all children of the root into the root element and return it.
|
||||
func (d *dataUsageCache) flatten(root dataUsageEntry) dataUsageEntry { |
||||
for id := range root.Children { |
||||
e := d.Cache[id] |
||||
if len(e.Children) > 0 { |
||||
e = d.flatten(e) |
||||
} |
||||
root.merge(e) |
||||
} |
||||
root.Children = nil |
||||
return root |
||||
} |
||||
|
||||
// add a size to the histogram.
|
||||
func (h *sizeHistogram) add(size int64) { |
||||
// Fetch the histogram interval corresponding
|
||||
// to the passed object size.
|
||||
for i, interval := range ObjectsHistogramIntervals { |
||||
if size >= interval.start && size <= interval.end { |
||||
h[i]++ |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
// asMap returns the map as a map[string]uint64.
|
||||
func (h *sizeHistogram) asMap() map[string]uint64 { |
||||
res := make(map[string]uint64, 7) |
||||
for i, count := range h { |
||||
res[ObjectsHistogramIntervals[i].name] = count |
||||
} |
||||
return res |
||||
} |
||||
|
||||
// pathSizes returns the path sizes as a map.
|
||||
func (d *dataUsageCache) pathSizes(buckets []BucketInfo) map[string]uint64 { |
||||
var dst = make(map[string]uint64, len(buckets)) |
||||
for _, bucket := range buckets { |
||||
e := d.find(bucket.Name) |
||||
if e == nil { |
||||
if dataUsageDebug { |
||||
logger.Info(color.Green("data-usage:")+" Bucket not found in cache: %v", bucket.Name) |
||||
} |
||||
continue |
||||
} |
||||
flat := d.flatten(*e) |
||||
dst[bucket.Name] = uint64(flat.Size) |
||||
} |
||||
return dst |
||||
} |
||||
|
||||
// sizeRecursive returns the path as a flattened entry.
|
||||
func (d *dataUsageCache) sizeRecursive(path string) *dataUsageEntry { |
||||
root := d.find(path) |
||||
if root == nil || len(root.Children) == 0 { |
||||
return root |
||||
} |
||||
flat := d.flatten(*root) |
||||
return &flat |
||||
} |
||||
|
||||
// dataUsageCache contains a cache of data usage entries.
|
||||
//msgp:ignore dataUsageCache
|
||||
type dataUsageCache struct { |
||||
Info dataUsageCacheInfo |
||||
Cache map[dataUsageHash]dataUsageEntry |
||||
} |
||||
|
||||
// root returns the root of the cache.
|
||||
func (d *dataUsageCache) root() *dataUsageEntry { |
||||
return d.find(d.Info.Name) |
||||
} |
||||
|
||||
// rootHash returns the root of the cache.
|
||||
func (d *dataUsageCache) rootHash() dataUsageHash { |
||||
return hashPath(d.Info.Name) |
||||
} |
||||
|
||||
// clone returns a copy of the cache with no references to the existing.
|
||||
func (d *dataUsageCache) clone() dataUsageCache { |
||||
clone := dataUsageCache{ |
||||
Info: d.Info, |
||||
Cache: make(map[dataUsageHash]dataUsageEntry, len(d.Cache)), |
||||
} |
||||
for k, v := range d.Cache { |
||||
clone.Cache[k] = v |
||||
} |
||||
return clone |
||||
} |
||||
|
||||
// merge root of other into d.
|
||||
// children of root will be flattened before being merged.
|
||||
// Last update time will be set to the last updated.
|
||||
func (d *dataUsageCache) merge(other dataUsageCache) { |
||||
existingRoot := d.root() |
||||
otherRoot := other.root() |
||||
if existingRoot == nil && otherRoot == nil { |
||||
return |
||||
} |
||||
if otherRoot == nil { |
||||
return |
||||
} |
||||
if existingRoot == nil { |
||||
*d = other.clone() |
||||
return |
||||
} |
||||
if other.Info.LastUpdate.After(d.Info.LastUpdate) { |
||||
d.Info.LastUpdate = other.Info.LastUpdate |
||||
} |
||||
existingRoot.merge(*otherRoot) |
||||
eHash := d.rootHash() |
||||
for key := range otherRoot.Children { |
||||
entry := other.Cache[key] |
||||
flat := other.flatten(entry) |
||||
existing := d.Cache[key] |
||||
// If not found, merging simply adds.
|
||||
existing.merge(flat) |
||||
d.replaceHashed(key, &eHash, existing) |
||||
} |
||||
} |
||||
|
||||
// load the cache content with name from minioMetaBackgroundOpsBucket.
|
||||
// Only backend errors are returned as errors.
|
||||
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
||||
func (d *dataUsageCache) load(ctx context.Context, store ObjectLayer, name string) error { |
||||
var buf bytes.Buffer |
||||
err := store.GetObject(ctx, dataUsageBucket, name, 0, -1, &buf, "", ObjectOptions{}) |
||||
if err != nil { |
||||
if !isErrObjectNotFound(err) { |
||||
return toObjectErr(err, dataUsageBucket, name) |
||||
} |
||||
*d = dataUsageCache{} |
||||
return nil |
||||
} |
||||
err = d.deserialize(buf.Bytes()) |
||||
if err != nil { |
||||
*d = dataUsageCache{} |
||||
logger.LogIf(ctx, err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// save the content of the cache to minioMetaBackgroundOpsBucket with the provided name.
|
||||
func (d *dataUsageCache) save(ctx context.Context, store ObjectLayer, name string) error { |
||||
b := d.serialize() |
||||
size := int64(len(b)) |
||||
r, err := hash.NewReader(bytes.NewReader(b), size, "", "", size, false) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
_, err = store.PutObject(ctx, |
||||
dataUsageBucket, |
||||
name, |
||||
NewPutObjReader(r, nil, nil), |
||||
ObjectOptions{}) |
||||
return err |
||||
} |
||||
|
||||
// dataUsageCacheVer indicates the cache version.
|
||||
// Bumping the cache version will drop data from previous versions
|
||||
// and write new data with the new version.
|
||||
const dataUsageCacheVer = 1 |
||||
|
||||
// serialize the contents of the cache.
|
||||
func (d *dataUsageCache) serialize() []byte { |
||||
// Alloc pessimistically
|
||||
// dataUsageCacheVer
|
||||
due := dataUsageEntry{} |
||||
msgLen := 1 |
||||
msgLen += d.Info.Msgsize() |
||||
// len(d.Cache)
|
||||
msgLen += binary.MaxVarintLen64 |
||||
// Hashes (one for key, assume 1 child/node)
|
||||
msgLen += len(d.Cache) * dataUsageHashLen * 2 |
||||
msgLen += len(d.Cache) * due.Msgsize() |
||||
|
||||
// Create destination buffer...
|
||||
dst := make([]byte, 0, msgLen) |
||||
|
||||
var n int |
||||
tmp := make([]byte, 1024) |
||||
// byte: version.
|
||||
dst = append(dst, dataUsageCacheVer) |
||||
// Info...
|
||||
dst, err := d.Info.MarshalMsg(dst) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
n = binary.PutUvarint(tmp, uint64(len(d.Cache))) |
||||
dst = append(dst, tmp[:n]...) |
||||
|
||||
for k, v := range d.Cache { |
||||
// Put key
|
||||
binary.LittleEndian.PutUint64(tmp[:dataUsageHashLen], uint64(k)) |
||||
dst = append(dst, tmp[:8]...) |
||||
tmp, err = v.MarshalMsg(tmp[:0]) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
// key, value pairs.
|
||||
dst = append(dst, tmp...) |
||||
|
||||
} |
||||
return dst |
||||
} |
||||
|
||||
// deserialize the supplied byte slice into the cache.
|
||||
func (d *dataUsageCache) deserialize(b []byte) error { |
||||
if len(b) < 1 { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
switch b[0] { |
||||
case 1: |
||||
default: |
||||
return fmt.Errorf("dataUsageCache: unknown version: %d", int(b[0])) |
||||
} |
||||
b = b[1:] |
||||
|
||||
// Info...
|
||||
b, err := d.Info.UnmarshalMsg(b) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
cacheLen, n := binary.Uvarint(b) |
||||
if n <= 0 { |
||||
return fmt.Errorf("dataUsageCache: reading cachelen, n <= 0 ") |
||||
} |
||||
b = b[n:] |
||||
d.Cache = make(map[dataUsageHash]dataUsageEntry, cacheLen) |
||||
|
||||
for i := 0; i < int(cacheLen); i++ { |
||||
if len(b) <= dataUsageHashLen { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
k := binary.LittleEndian.Uint64(b[:dataUsageHashLen]) |
||||
b = b[dataUsageHashLen:] |
||||
var v dataUsageEntry |
||||
b, err = v.UnmarshalMsg(b) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
d.Cache[dataUsageHash(k)] = v |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Trim this from start+end of hashes.
|
||||
var hashPathCutSet = dataUsageRoot |
||||
|
||||
func init() { |
||||
if dataUsageRoot != string(filepath.Separator) { |
||||
hashPathCutSet = dataUsageRoot + string(filepath.Separator) |
||||
} |
||||
} |
||||
|
||||
// hashPath calculates a hash of the provided string.
|
||||
func hashPath(data string) dataUsageHash { |
||||
if data != dataUsageRoot { |
||||
data = strings.Trim(data, hashPathCutSet) |
||||
} |
||||
data = path.Clean(data) |
||||
return dataUsageHash(xxhash.Sum64String(data)) |
||||
} |
||||
|
||||
//msgp:ignore dataUsageEntryInfo
|
||||
type dataUsageHashMap map[dataUsageHash]struct{} |
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (d dataUsageHashMap) MarshalMsg(b []byte) (o []byte, err error) { |
||||
o = msgp.Require(b, d.Msgsize()) |
||||
|
||||
// Write bin header manually
|
||||
const mbin32 uint8 = 0xc6 |
||||
sz := uint32(len(d)) * dataUsageHashLen |
||||
o = append(o, mbin32, byte(sz>>24), byte(sz>>16), byte(sz>>8), byte(sz)) |
||||
|
||||
var tmp [dataUsageHashLen]byte |
||||
for k := range d { |
||||
binary.LittleEndian.PutUint64(tmp[:], uint64(k)) |
||||
o = append(o, tmp[:]...) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (d dataUsageHashMap) Msgsize() (s int) { |
||||
s = 5 + len(d)*dataUsageHashLen |
||||
return |
||||
} |
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (d *dataUsageHashMap) UnmarshalMsg(bts []byte) (o []byte, err error) { |
||||
var hashes []byte |
||||
hashes, bts, err = msgp.ReadBytesZC(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "dataUsageHashMap") |
||||
return |
||||
} |
||||
|
||||
var dst = make(dataUsageHashMap, len(hashes)/dataUsageHashLen) |
||||
for len(hashes) >= dataUsageHashLen { |
||||
dst[dataUsageHash(binary.LittleEndian.Uint64(hashes[:dataUsageHashLen]))] = struct{}{} |
||||
hashes = hashes[dataUsageHashLen:] |
||||
} |
||||
*d = dst |
||||
o = bts |
||||
return |
||||
} |
||||
|
||||
func (d *dataUsageHashMap) DecodeMsg(dc *msgp.Reader) (err error) { |
||||
var zb0001 uint32 |
||||
zb0001, err = dc.ReadBytesHeader() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
var dst = make(dataUsageHashMap, zb0001) |
||||
var tmp [8]byte |
||||
for i := uint32(0); i < zb0001; i++ { |
||||
_, err = io.ReadFull(dc, tmp[:]) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "dataUsageHashMap") |
||||
return |
||||
} |
||||
dst[dataUsageHash(binary.LittleEndian.Uint64(tmp[:]))] = struct{}{} |
||||
} |
||||
return nil |
||||
} |
||||
func (d dataUsageHashMap) EncodeMsg(en *msgp.Writer) (err error) { |
||||
err = en.WriteBytesHeader(uint32(len(d)) * dataUsageHashLen) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
var tmp [dataUsageHashLen]byte |
||||
for k := range d { |
||||
binary.LittleEndian.PutUint64(tmp[:], uint64(k)) |
||||
_, err = en.Write(tmp[:]) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,447 @@ |
||||
package cmd |
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import ( |
||||
"github.com/tinylib/msgp/msgp" |
||||
) |
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *dataUsageCacheInfo) DecodeMsg(dc *msgp.Reader) (err error) { |
||||
var field []byte |
||||
_ = field |
||||
var zb0001 uint32 |
||||
zb0001, err = dc.ReadMapHeader() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
for zb0001 > 0 { |
||||
zb0001-- |
||||
field, err = dc.ReadMapKeyPtr() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
switch msgp.UnsafeString(field) { |
||||
case "Name": |
||||
z.Name, err = dc.ReadString() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Name") |
||||
return |
||||
} |
||||
case "LastUpdate": |
||||
z.LastUpdate, err = dc.ReadTime() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "LastUpdate") |
||||
return |
||||
} |
||||
case "NextCycle": |
||||
z.NextCycle, err = dc.ReadUint8() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "NextCycle") |
||||
return |
||||
} |
||||
default: |
||||
err = dc.Skip() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z dataUsageCacheInfo) EncodeMsg(en *msgp.Writer) (err error) { |
||||
// map header, size 3
|
||||
// write "Name"
|
||||
err = en.Append(0x83, 0xa4, 0x4e, 0x61, 0x6d, 0x65) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteString(z.Name) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Name") |
||||
return |
||||
} |
||||
// write "LastUpdate"
|
||||
err = en.Append(0xaa, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteTime(z.LastUpdate) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "LastUpdate") |
||||
return |
||||
} |
||||
// write "NextCycle"
|
||||
err = en.Append(0xa9, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x79, 0x63, 0x6c, 0x65) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteUint8(z.NextCycle) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "NextCycle") |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z dataUsageCacheInfo) MarshalMsg(b []byte) (o []byte, err error) { |
||||
o = msgp.Require(b, z.Msgsize()) |
||||
// map header, size 3
|
||||
// string "Name"
|
||||
o = append(o, 0x83, 0xa4, 0x4e, 0x61, 0x6d, 0x65) |
||||
o = msgp.AppendString(o, z.Name) |
||||
// string "LastUpdate"
|
||||
o = append(o, 0xaa, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65) |
||||
o = msgp.AppendTime(o, z.LastUpdate) |
||||
// string "NextCycle"
|
||||
o = append(o, 0xa9, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x79, 0x63, 0x6c, 0x65) |
||||
o = msgp.AppendUint8(o, z.NextCycle) |
||||
return |
||||
} |
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *dataUsageCacheInfo) UnmarshalMsg(bts []byte) (o []byte, err error) { |
||||
var field []byte |
||||
_ = field |
||||
var zb0001 uint32 |
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
for zb0001 > 0 { |
||||
zb0001-- |
||||
field, bts, err = msgp.ReadMapKeyZC(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
switch msgp.UnsafeString(field) { |
||||
case "Name": |
||||
z.Name, bts, err = msgp.ReadStringBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Name") |
||||
return |
||||
} |
||||
case "LastUpdate": |
||||
z.LastUpdate, bts, err = msgp.ReadTimeBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "LastUpdate") |
||||
return |
||||
} |
||||
case "NextCycle": |
||||
z.NextCycle, bts, err = msgp.ReadUint8Bytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "NextCycle") |
||||
return |
||||
} |
||||
default: |
||||
bts, err = msgp.Skip(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
o = bts |
||||
return |
||||
} |
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z dataUsageCacheInfo) Msgsize() (s int) { |
||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 11 + msgp.TimeSize + 10 + msgp.Uint8Size |
||||
return |
||||
} |
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) { |
||||
var zb0001 uint32 |
||||
zb0001, err = dc.ReadArrayHeader() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
if zb0001 != 4 { |
||||
err = msgp.ArrayError{Wanted: 4, Got: zb0001} |
||||
return |
||||
} |
||||
z.Size, err = dc.ReadInt64() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Size") |
||||
return |
||||
} |
||||
z.Objects, err = dc.ReadUint64() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Objects") |
||||
return |
||||
} |
||||
var zb0002 uint32 |
||||
zb0002, err = dc.ReadArrayHeader() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "ObjSizes") |
||||
return |
||||
} |
||||
if zb0002 != uint32(dataUsageBucketLen) { |
||||
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLen), Got: zb0002} |
||||
return |
||||
} |
||||
for za0001 := range z.ObjSizes { |
||||
z.ObjSizes[za0001], err = dc.ReadUint64() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "ObjSizes", za0001) |
||||
return |
||||
} |
||||
} |
||||
err = z.Children.DecodeMsg(dc) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Children") |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) { |
||||
// array header, size 4
|
||||
err = en.Append(0x94) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteInt64(z.Size) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Size") |
||||
return |
||||
} |
||||
err = en.WriteUint64(z.Objects) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Objects") |
||||
return |
||||
} |
||||
err = en.WriteArrayHeader(uint32(dataUsageBucketLen)) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "ObjSizes") |
||||
return |
||||
} |
||||
for za0001 := range z.ObjSizes { |
||||
err = en.WriteUint64(z.ObjSizes[za0001]) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "ObjSizes", za0001) |
||||
return |
||||
} |
||||
} |
||||
err = z.Children.EncodeMsg(en) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Children") |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) { |
||||
o = msgp.Require(b, z.Msgsize()) |
||||
// array header, size 4
|
||||
o = append(o, 0x94) |
||||
o = msgp.AppendInt64(o, z.Size) |
||||
o = msgp.AppendUint64(o, z.Objects) |
||||
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen)) |
||||
for za0001 := range z.ObjSizes { |
||||
o = msgp.AppendUint64(o, z.ObjSizes[za0001]) |
||||
} |
||||
o, err = z.Children.MarshalMsg(o) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Children") |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) { |
||||
var zb0001 uint32 |
||||
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
if zb0001 != 4 { |
||||
err = msgp.ArrayError{Wanted: 4, Got: zb0001} |
||||
return |
||||
} |
||||
z.Size, bts, err = msgp.ReadInt64Bytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Size") |
||||
return |
||||
} |
||||
z.Objects, bts, err = msgp.ReadUint64Bytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Objects") |
||||
return |
||||
} |
||||
var zb0002 uint32 |
||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "ObjSizes") |
||||
return |
||||
} |
||||
if zb0002 != uint32(dataUsageBucketLen) { |
||||
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLen), Got: zb0002} |
||||
return |
||||
} |
||||
for za0001 := range z.ObjSizes { |
||||
z.ObjSizes[za0001], bts, err = msgp.ReadUint64Bytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "ObjSizes", za0001) |
||||
return |
||||
} |
||||
} |
||||
bts, err = z.Children.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Children") |
||||
return |
||||
} |
||||
o = bts |
||||
return |
||||
} |
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *dataUsageEntry) Msgsize() (s int) { |
||||
s = 1 + msgp.Int64Size + msgp.Uint64Size + msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) + z.Children.Msgsize() |
||||
return |
||||
} |
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *dataUsageHash) DecodeMsg(dc *msgp.Reader) (err error) { |
||||
{ |
||||
var zb0001 uint64 |
||||
zb0001, err = dc.ReadUint64() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
(*z) = dataUsageHash(zb0001) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z dataUsageHash) EncodeMsg(en *msgp.Writer) (err error) { |
||||
err = en.WriteUint64(uint64(z)) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z dataUsageHash) MarshalMsg(b []byte) (o []byte, err error) { |
||||
o = msgp.Require(b, z.Msgsize()) |
||||
o = msgp.AppendUint64(o, uint64(z)) |
||||
return |
||||
} |
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *dataUsageHash) UnmarshalMsg(bts []byte) (o []byte, err error) { |
||||
{ |
||||
var zb0001 uint64 |
||||
zb0001, bts, err = msgp.ReadUint64Bytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
(*z) = dataUsageHash(zb0001) |
||||
} |
||||
o = bts |
||||
return |
||||
} |
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z dataUsageHash) Msgsize() (s int) { |
||||
s = msgp.Uint64Size |
||||
return |
||||
} |
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *sizeHistogram) DecodeMsg(dc *msgp.Reader) (err error) { |
||||
var zb0001 uint32 |
||||
zb0001, err = dc.ReadArrayHeader() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
if zb0001 != uint32(dataUsageBucketLen) { |
||||
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLen), Got: zb0001} |
||||
return |
||||
} |
||||
for za0001 := range z { |
||||
z[za0001], err = dc.ReadUint64() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, za0001) |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *sizeHistogram) EncodeMsg(en *msgp.Writer) (err error) { |
||||
err = en.WriteArrayHeader(uint32(dataUsageBucketLen)) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
for za0001 := range z { |
||||
err = en.WriteUint64(z[za0001]) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, za0001) |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *sizeHistogram) MarshalMsg(b []byte) (o []byte, err error) { |
||||
o = msgp.Require(b, z.Msgsize()) |
||||
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen)) |
||||
for za0001 := range z { |
||||
o = msgp.AppendUint64(o, z[za0001]) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *sizeHistogram) UnmarshalMsg(bts []byte) (o []byte, err error) { |
||||
var zb0001 uint32 |
||||
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
if zb0001 != uint32(dataUsageBucketLen) { |
||||
err = msgp.ArrayError{Wanted: uint32(dataUsageBucketLen), Got: zb0001} |
||||
return |
||||
} |
||||
for za0001 := range z { |
||||
z[za0001], bts, err = msgp.ReadUint64Bytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, za0001) |
||||
return |
||||
} |
||||
} |
||||
o = bts |
||||
return |
||||
} |
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *sizeHistogram) Msgsize() (s int) { |
||||
s = msgp.ArrayHeaderSize + (dataUsageBucketLen * (msgp.Uint64Size)) |
||||
return |
||||
} |
@ -0,0 +1,349 @@ |
||||
package cmd |
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/tinylib/msgp/msgp" |
||||
) |
||||
|
||||
func TestMarshalUnmarshaldataUsageCacheInfo(t *testing.T) { |
||||
v := dataUsageCacheInfo{} |
||||
bts, err := v.MarshalMsg(nil) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
left, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) |
||||
} |
||||
|
||||
left, err = msgp.Skip(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkMarshalMsgdataUsageCacheInfo(b *testing.B) { |
||||
v := dataUsageCacheInfo{} |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.MarshalMsg(nil) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkAppendMsgdataUsageCacheInfo(b *testing.B) { |
||||
v := dataUsageCacheInfo{} |
||||
bts := make([]byte, 0, v.Msgsize()) |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkUnmarshaldataUsageCacheInfo(b *testing.B) { |
||||
v := dataUsageCacheInfo{} |
||||
bts, _ := v.MarshalMsg(nil) |
||||
b.ReportAllocs() |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
_, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEncodeDecodedataUsageCacheInfo(t *testing.T) { |
||||
v := dataUsageCacheInfo{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
|
||||
m := v.Msgsize() |
||||
if buf.Len() > m { |
||||
t.Log("WARNING: TestEncodeDecodedataUsageCacheInfo Msgsize() is inaccurate") |
||||
} |
||||
|
||||
vn := dataUsageCacheInfo{} |
||||
err := msgp.Decode(&buf, &vn) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
buf.Reset() |
||||
msgp.Encode(&buf, &v) |
||||
err = msgp.NewReader(&buf).Skip() |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkEncodedataUsageCacheInfo(b *testing.B) { |
||||
v := dataUsageCacheInfo{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
en := msgp.NewWriter(msgp.Nowhere) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.EncodeMsg(en) |
||||
} |
||||
en.Flush() |
||||
} |
||||
|
||||
func BenchmarkDecodedataUsageCacheInfo(b *testing.B) { |
||||
v := dataUsageCacheInfo{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b) |
||||
dc := msgp.NewReader(rd) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
err := v.DecodeMsg(dc) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestMarshalUnmarshaldataUsageEntry(t *testing.T) { |
||||
v := dataUsageEntry{} |
||||
bts, err := v.MarshalMsg(nil) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
left, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) |
||||
} |
||||
|
||||
left, err = msgp.Skip(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkMarshalMsgdataUsageEntry(b *testing.B) { |
||||
v := dataUsageEntry{} |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.MarshalMsg(nil) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkAppendMsgdataUsageEntry(b *testing.B) { |
||||
v := dataUsageEntry{} |
||||
bts := make([]byte, 0, v.Msgsize()) |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkUnmarshaldataUsageEntry(b *testing.B) { |
||||
v := dataUsageEntry{} |
||||
bts, _ := v.MarshalMsg(nil) |
||||
b.ReportAllocs() |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
_, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEncodeDecodedataUsageEntry(t *testing.T) { |
||||
v := dataUsageEntry{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
|
||||
m := v.Msgsize() |
||||
if buf.Len() > m { |
||||
t.Log("WARNING: TestEncodeDecodedataUsageEntry Msgsize() is inaccurate") |
||||
} |
||||
|
||||
vn := dataUsageEntry{} |
||||
err := msgp.Decode(&buf, &vn) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
buf.Reset() |
||||
msgp.Encode(&buf, &v) |
||||
err = msgp.NewReader(&buf).Skip() |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkEncodedataUsageEntry(b *testing.B) { |
||||
v := dataUsageEntry{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
en := msgp.NewWriter(msgp.Nowhere) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.EncodeMsg(en) |
||||
} |
||||
en.Flush() |
||||
} |
||||
|
||||
func BenchmarkDecodedataUsageEntry(b *testing.B) { |
||||
v := dataUsageEntry{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b) |
||||
dc := msgp.NewReader(rd) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
err := v.DecodeMsg(dc) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestMarshalUnmarshalsizeHistogram(t *testing.T) { |
||||
v := sizeHistogram{} |
||||
bts, err := v.MarshalMsg(nil) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
left, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) |
||||
} |
||||
|
||||
left, err = msgp.Skip(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkMarshalMsgsizeHistogram(b *testing.B) { |
||||
v := sizeHistogram{} |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.MarshalMsg(nil) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkAppendMsgsizeHistogram(b *testing.B) { |
||||
v := sizeHistogram{} |
||||
bts := make([]byte, 0, v.Msgsize()) |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkUnmarshalsizeHistogram(b *testing.B) { |
||||
v := sizeHistogram{} |
||||
bts, _ := v.MarshalMsg(nil) |
||||
b.ReportAllocs() |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
_, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEncodeDecodesizeHistogram(t *testing.T) { |
||||
v := sizeHistogram{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
|
||||
m := v.Msgsize() |
||||
if buf.Len() > m { |
||||
t.Log("WARNING: TestEncodeDecodesizeHistogram Msgsize() is inaccurate") |
||||
} |
||||
|
||||
vn := sizeHistogram{} |
||||
err := msgp.Decode(&buf, &vn) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
buf.Reset() |
||||
msgp.Encode(&buf, &v) |
||||
err = msgp.NewReader(&buf).Skip() |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkEncodesizeHistogram(b *testing.B) { |
||||
v := sizeHistogram{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
en := msgp.NewWriter(msgp.Nowhere) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.EncodeMsg(en) |
||||
} |
||||
en.Flush() |
||||
} |
||||
|
||||
func BenchmarkDecodesizeHistogram(b *testing.B) { |
||||
v := sizeHistogram{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b) |
||||
dc := msgp.NewReader(rd) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
err := v.DecodeMsg(dc) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,664 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
||||
* 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 cmd |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
"testing" |
||||
) |
||||
|
||||
type usageTestFile struct { |
||||
name string |
||||
size int |
||||
} |
||||
|
||||
func Test_updateUsage(t *testing.T) { |
||||
base, err := ioutil.TempDir("", "Test_updateUsage") |
||||
if err != nil { |
||||
t.Skip(err) |
||||
} |
||||
defer os.RemoveAll(base) |
||||
var files = []usageTestFile{ |
||||
{name: "rootfile", size: 10000}, |
||||
{name: "rootfile2", size: 10000}, |
||||
{name: "dir1/d1file", size: 2000}, |
||||
{name: "dir2/d2file", size: 300}, |
||||
{name: "dir1/dira/dafile", size: 100000}, |
||||
{name: "dir1/dira/dbfile", size: 200000}, |
||||
{name: "dir1/dira/dirasub/dcfile", size: 1000000}, |
||||
{name: "dir1/dira/dirasub/sublevel3/dccccfile", size: 10}, |
||||
} |
||||
createUsageTestFiles(t, base, files) |
||||
|
||||
getSize := func(item Item) (i int64, err error) { |
||||
if item.Typ&os.ModeDir == 0 { |
||||
s, err := os.Stat(item.Path) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return s.Size(), nil |
||||
} |
||||
return 0, nil |
||||
} |
||||
got, err := updateUsage(context.Background(), base, dataUsageCache{}, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Test dirs
|
||||
var want = []struct { |
||||
path string |
||||
isNil bool |
||||
size, objs int |
||||
flatten bool |
||||
oSizes sizeHistogram |
||||
}{ |
||||
{ |
||||
path: "/", |
||||
size: 1322310, |
||||
flatten: true, |
||||
objs: 8, |
||||
oSizes: sizeHistogram{0: 2, 1: 6}, |
||||
}, |
||||
{ |
||||
path: "/", |
||||
size: 20000, |
||||
objs: 2, |
||||
oSizes: sizeHistogram{1: 2}, |
||||
}, |
||||
{ |
||||
path: "/dir1", |
||||
size: 2000, |
||||
objs: 1, |
||||
oSizes: sizeHistogram{1: 1}, |
||||
}, |
||||
{ |
||||
path: "/dir1/dira", |
||||
flatten: true, |
||||
size: 1300010, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 1, 1: 3}, |
||||
}, |
||||
{ |
||||
path: "/dir1/dira/", |
||||
flatten: true, |
||||
size: 1300010, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 1, 1: 3}, |
||||
}, |
||||
{ |
||||
path: "/dir1/dira", |
||||
size: 300000, |
||||
objs: 2, |
||||
oSizes: sizeHistogram{0: 0, 1: 2}, |
||||
}, |
||||
{ |
||||
path: "/dir1/dira/", |
||||
size: 300000, |
||||
objs: 2, |
||||
oSizes: sizeHistogram{0: 0, 1: 2}, |
||||
}, |
||||
{ |
||||
path: "/nonexistying", |
||||
isNil: true, |
||||
}, |
||||
} |
||||
|
||||
for _, w := range want { |
||||
t.Run(w.path, func(t *testing.T) { |
||||
e := got.find(w.path) |
||||
if w.isNil { |
||||
if e != nil { |
||||
t.Error("want nil, got", e) |
||||
} |
||||
return |
||||
} |
||||
if e == nil { |
||||
t.Fatal("got nil result") |
||||
} |
||||
if w.flatten { |
||||
*e = got.flatten(*e) |
||||
} |
||||
if e.Size != int64(w.size) { |
||||
t.Error("got size", e.Size, "want", w.size) |
||||
} |
||||
if e.Objects != uint64(w.objs) { |
||||
t.Error("got objects", e.Objects, "want", w.objs) |
||||
} |
||||
if e.ObjSizes != w.oSizes { |
||||
t.Error("got histogram", e.ObjSizes, "want", w.oSizes) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
files = []usageTestFile{ |
||||
{ |
||||
name: "newfolder/afile", |
||||
size: 4, |
||||
}, |
||||
{ |
||||
name: "newfolder/anotherone", |
||||
size: 1, |
||||
}, |
||||
{ |
||||
name: "newfolder/anemptyone", |
||||
size: 0, |
||||
}, |
||||
{ |
||||
name: "dir1/fileindir1", |
||||
size: 20000, |
||||
}, |
||||
{ |
||||
name: "dir1/dirc/fileindirc", |
||||
size: 20000, |
||||
}, |
||||
{ |
||||
name: "rootfile3", |
||||
size: 1000, |
||||
}, |
||||
} |
||||
createUsageTestFiles(t, base, files) |
||||
got, err = updateUsage(context.Background(), base, got, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
want = []struct { |
||||
path string |
||||
isNil bool |
||||
size, objs int |
||||
flatten bool |
||||
oSizes sizeHistogram |
||||
}{ |
||||
{ |
||||
path: "/", |
||||
size: 1363315, |
||||
flatten: true, |
||||
objs: 14, |
||||
oSizes: sizeHistogram{0: 6, 1: 8}, |
||||
}, |
||||
{ |
||||
path: "/", |
||||
size: 21000, |
||||
objs: 3, |
||||
oSizes: sizeHistogram{0: 1, 1: 2}, |
||||
}, |
||||
{ |
||||
path: "/newfolder", |
||||
size: 5, |
||||
objs: 3, |
||||
oSizes: sizeHistogram{0: 3}, |
||||
}, |
||||
{ |
||||
path: "/dir1/dira", |
||||
size: 1300010, |
||||
flatten: true, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 1, 1: 3}, |
||||
}, |
||||
{ |
||||
path: "/nonexistying", |
||||
isNil: true, |
||||
}, |
||||
} |
||||
|
||||
for _, w := range want { |
||||
t.Run(w.path, func(t *testing.T) { |
||||
e := got.find(w.path) |
||||
if w.isNil { |
||||
if e != nil { |
||||
t.Error("want nil, got", e) |
||||
} |
||||
return |
||||
} |
||||
if e == nil { |
||||
t.Fatal("got nil result") |
||||
} |
||||
if w.flatten { |
||||
*e = got.flatten(*e) |
||||
} |
||||
if e.Size != int64(w.size) { |
||||
t.Error("got size", e.Size, "want", w.size) |
||||
} |
||||
if e.Objects != uint64(w.objs) { |
||||
t.Error("got objects", e.Objects, "want", w.objs) |
||||
} |
||||
if e.ObjSizes != w.oSizes { |
||||
t.Error("got histogram", e.ObjSizes, "want", w.oSizes) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
files = []usageTestFile{ |
||||
{ |
||||
name: "dir1/dira/dirasub/fileindira2", |
||||
size: 200, |
||||
}, |
||||
} |
||||
|
||||
createUsageTestFiles(t, base, files) |
||||
err = os.RemoveAll(filepath.Join(base, "dir1/dira/dirasub/dcfile")) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
// Changed dir must be picked up in this many cycles.
|
||||
for i := 0; i < dataUsageUpdateDirCycles; i++ { |
||||
got, err = updateUsage(context.Background(), base, got, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
want = []struct { |
||||
path string |
||||
isNil bool |
||||
size, objs int |
||||
flatten bool |
||||
oSizes sizeHistogram |
||||
}{ |
||||
{ |
||||
path: "/", |
||||
size: 363515, |
||||
flatten: true, |
||||
objs: 14, |
||||
oSizes: sizeHistogram{0: 7, 1: 7}, |
||||
}, |
||||
{ |
||||
path: "/dir1/dira", |
||||
size: 300210, |
||||
objs: 4, |
||||
flatten: true, |
||||
oSizes: sizeHistogram{0: 2, 1: 2}, |
||||
}, |
||||
} |
||||
|
||||
for _, w := range want { |
||||
t.Run(w.path, func(t *testing.T) { |
||||
e := got.find(w.path) |
||||
if w.isNil { |
||||
if e != nil { |
||||
t.Error("want nil, got", e) |
||||
} |
||||
return |
||||
} |
||||
if e == nil { |
||||
t.Fatal("got nil result") |
||||
} |
||||
if w.flatten { |
||||
*e = got.flatten(*e) |
||||
} |
||||
if e.Size != int64(w.size) { |
||||
t.Error("got size", e.Size, "want", w.size) |
||||
} |
||||
if e.Objects != uint64(w.objs) { |
||||
t.Error("got objects", e.Objects, "want", w.objs) |
||||
} |
||||
if e.ObjSizes != w.oSizes { |
||||
t.Error("got histogram", e.ObjSizes, "want", w.oSizes) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
t.Log(got.StringAll()) |
||||
|
||||
t.Logf("Root, flat: %+v", got.flatten(*got.root())) |
||||
t.Logf("Root: %+v", *got.root()) |
||||
t.Logf("/dir1/dira: %+v", *got.find("/dir1/dira")) |
||||
|
||||
} |
||||
|
||||
func Test_updateUsagePrefix(t *testing.T) { |
||||
base, err := ioutil.TempDir("", "Test_updateUsagePrefix") |
||||
if err != nil { |
||||
t.Skip(err) |
||||
} |
||||
base = filepath.Join(base, "bucket") |
||||
defer os.RemoveAll(base) |
||||
var files = []usageTestFile{ |
||||
{name: "bucket/rootfile", size: 10000}, |
||||
{name: "bucket/rootfile2", size: 10000}, |
||||
{name: "bucket/dir1/d1file", size: 2000}, |
||||
{name: "bucket/dir2/d2file", size: 300}, |
||||
{name: "bucket/dir1/dira/dafile", size: 100000}, |
||||
{name: "bucket/dir1/dira/dbfile", size: 200000}, |
||||
{name: "bucket/dir1/dira/dirasub/dcfile", size: 1000000}, |
||||
{name: "bucket/dir1/dira/dirasub/sublevel3/dccccfile", size: 10}, |
||||
} |
||||
createUsageTestFiles(t, base, files) |
||||
|
||||
getSize := func(item Item) (i int64, err error) { |
||||
if item.Typ&os.ModeDir == 0 { |
||||
s, err := os.Stat(item.Path) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return s.Size(), nil |
||||
} |
||||
return 0, nil |
||||
} |
||||
got, err := updateUsage(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Test dirs
|
||||
var want = []struct { |
||||
path string |
||||
isNil bool |
||||
size, objs int |
||||
oSizes sizeHistogram |
||||
}{ |
||||
{ |
||||
path: "flat", |
||||
size: 1322310, |
||||
objs: 8, |
||||
oSizes: sizeHistogram{0: 2, 1: 6}, |
||||
}, |
||||
{ |
||||
path: "bucket/", |
||||
size: 20000, |
||||
objs: 2, |
||||
oSizes: sizeHistogram{1: 2}, |
||||
}, |
||||
{ |
||||
path: "bucket/dir1", |
||||
size: 2000, |
||||
objs: 1, |
||||
oSizes: sizeHistogram{1: 1}, |
||||
}, |
||||
{ |
||||
path: "bucket/dir1/dira", |
||||
size: 1300010, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 1, 1: 3}, |
||||
}, |
||||
{ |
||||
path: "bucket/dir1/dira/", |
||||
size: 1300010, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 1, 1: 3}, |
||||
}, |
||||
{ |
||||
path: "bucket/nonexistying", |
||||
isNil: true, |
||||
}, |
||||
} |
||||
|
||||
for _, w := range want { |
||||
t.Run(w.path, func(t *testing.T) { |
||||
e := got.find(w.path) |
||||
if w.path == "flat" { |
||||
f := got.flatten(*got.root()) |
||||
e = &f |
||||
} |
||||
if w.isNil { |
||||
if e != nil { |
||||
t.Error("want nil, got", e) |
||||
} |
||||
return |
||||
} |
||||
if e == nil { |
||||
t.Fatal("got nil result") |
||||
} |
||||
if e.Size != int64(w.size) { |
||||
t.Error("got size", e.Size, "want", w.size) |
||||
} |
||||
if e.Objects != uint64(w.objs) { |
||||
t.Error("got objects", e.Objects, "want", w.objs) |
||||
} |
||||
if e.ObjSizes != w.oSizes { |
||||
t.Error("got histogram", e.ObjSizes, "want", w.oSizes) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
files = []usageTestFile{ |
||||
{ |
||||
name: "bucket/newfolder/afile", |
||||
size: 4, |
||||
}, |
||||
{ |
||||
name: "bucket/newfolder/anotherone", |
||||
size: 1, |
||||
}, |
||||
{ |
||||
name: "bucket/newfolder/anemptyone", |
||||
size: 0, |
||||
}, |
||||
{ |
||||
name: "bucket/dir1/fileindir1", |
||||
size: 20000, |
||||
}, |
||||
{ |
||||
name: "bucket/dir1/dirc/fileindirc", |
||||
size: 20000, |
||||
}, |
||||
{ |
||||
name: "bucket/rootfile3", |
||||
size: 1000, |
||||
}, |
||||
} |
||||
createUsageTestFiles(t, base, files) |
||||
got, err = updateUsage(context.Background(), base, got, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
want = []struct { |
||||
path string |
||||
isNil bool |
||||
size, objs int |
||||
oSizes sizeHistogram |
||||
}{ |
||||
{ |
||||
path: "flat", |
||||
size: 1363315, |
||||
objs: 14, |
||||
oSizes: sizeHistogram{0: 6, 1: 8}, |
||||
}, |
||||
{ |
||||
path: "bucket/", |
||||
size: 21000, |
||||
objs: 3, |
||||
oSizes: sizeHistogram{0: 1, 1: 2}, |
||||
}, |
||||
{ |
||||
path: "bucket/newfolder", |
||||
size: 5, |
||||
objs: 3, |
||||
oSizes: sizeHistogram{0: 3}, |
||||
}, |
||||
{ |
||||
path: "bucket/dir1/dira", |
||||
size: 1300010, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 1, 1: 3}, |
||||
}, |
||||
{ |
||||
path: "bucket/nonexistying", |
||||
isNil: true, |
||||
}, |
||||
} |
||||
|
||||
for _, w := range want { |
||||
t.Run(w.path, func(t *testing.T) { |
||||
e := got.find(w.path) |
||||
if w.path == "flat" { |
||||
f := got.flatten(*got.root()) |
||||
e = &f |
||||
} |
||||
if w.isNil { |
||||
if e != nil { |
||||
t.Error("want nil, got", e) |
||||
} |
||||
return |
||||
} |
||||
if e == nil { |
||||
t.Fatal("got nil result") |
||||
} |
||||
if e.Size != int64(w.size) { |
||||
t.Error("got size", e.Size, "want", w.size) |
||||
} |
||||
if e.Objects != uint64(w.objs) { |
||||
t.Error("got objects", e.Objects, "want", w.objs) |
||||
} |
||||
if e.ObjSizes != w.oSizes { |
||||
t.Error("got histogram", e.ObjSizes, "want", w.oSizes) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
files = []usageTestFile{ |
||||
{ |
||||
name: "bucket/dir1/dira/dirasub/fileindira2", |
||||
size: 200, |
||||
}, |
||||
} |
||||
|
||||
createUsageTestFiles(t, base, files) |
||||
err = os.RemoveAll(filepath.Join(base, "bucket/dir1/dira/dirasub/dcfile")) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
// Changed dir must be picked up in this many cycles.
|
||||
for i := 0; i < dataUsageUpdateDirCycles; i++ { |
||||
got, err = updateUsage(context.Background(), base, got, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
want = []struct { |
||||
path string |
||||
isNil bool |
||||
size, objs int |
||||
oSizes sizeHistogram |
||||
}{ |
||||
{ |
||||
path: "flat", |
||||
size: 363515, |
||||
objs: 14, |
||||
oSizes: sizeHistogram{0: 7, 1: 7}, |
||||
}, |
||||
{ |
||||
path: "bucket/dir1/dira", |
||||
size: 300210, |
||||
objs: 4, |
||||
oSizes: sizeHistogram{0: 2, 1: 2}, |
||||
}, |
||||
} |
||||
|
||||
for _, w := range want { |
||||
t.Run(w.path, func(t *testing.T) { |
||||
e := got.find(w.path) |
||||
if w.path == "flat" { |
||||
f := got.flatten(*got.root()) |
||||
e = &f |
||||
} |
||||
if w.isNil { |
||||
if e != nil { |
||||
t.Error("want nil, got", e) |
||||
} |
||||
return |
||||
} |
||||
if e == nil { |
||||
t.Fatal("got nil result") |
||||
} |
||||
if e.Size != int64(w.size) { |
||||
t.Error("got size", e.Size, "want", w.size) |
||||
} |
||||
if e.Objects != uint64(w.objs) { |
||||
t.Error("got objects", e.Objects, "want", w.objs) |
||||
} |
||||
if e.ObjSizes != w.oSizes { |
||||
t.Error("got histogram", e.ObjSizes, "want", w.oSizes) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
t.Log(got.StringAll()) |
||||
|
||||
t.Logf("Root, flat: %+v", got.flatten(*got.root())) |
||||
t.Logf("Root: %+v", *got.root()) |
||||
t.Logf("bucket/dir1/dira: %+v", *got.find("bucket/dir1/dira")) |
||||
} |
||||
|
||||
func createUsageTestFiles(t *testing.T, base string, files []usageTestFile) { |
||||
for _, f := range files { |
||||
err := os.MkdirAll(filepath.Dir(filepath.Join(base, f.name)), os.ModePerm) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
err = ioutil.WriteFile(filepath.Join(base, f.name), make([]byte, f.size), os.ModePerm) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func Test_dataUsageCacheSerialize(t *testing.T) { |
||||
base, err := ioutil.TempDir("", "Test_dataUsageCacheSerialize") |
||||
if err != nil { |
||||
t.Skip(err) |
||||
} |
||||
defer os.RemoveAll(base) |
||||
var files = []usageTestFile{ |
||||
{name: "rootfile", size: 10000}, |
||||
{name: "rootfile2", size: 10000}, |
||||
{name: "dir1/d1file", size: 2000}, |
||||
{name: "dir2/d2file", size: 300}, |
||||
{name: "dir1/dira/dafile", size: 100000}, |
||||
{name: "dir1/dira/dbfile", size: 200000}, |
||||
{name: "dir1/dira/dirasub/dcfile", size: 1000000}, |
||||
{name: "dir1/dira/dirasub/sublevel3/dccccfile", size: 10}, |
||||
} |
||||
createUsageTestFiles(t, base, files) |
||||
|
||||
getSize := func(item Item) (i int64, err error) { |
||||
if item.Typ&os.ModeDir == 0 { |
||||
s, err := os.Stat(item.Path) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return s.Size(), nil |
||||
} |
||||
return 0, nil |
||||
} |
||||
want, err := updateUsage(context.Background(), base, dataUsageCache{}, func() {}, getSize) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
b := want.serialize() |
||||
t.Log("serialize -> ", len(b), "bytes") |
||||
|
||||
var got dataUsageCache |
||||
err = got.deserialize(b) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if got.Info.LastUpdate.IsZero() { |
||||
t.Error("lastupdate not set") |
||||
} |
||||
|
||||
if fmt.Sprint(want) == fmt.Sprint(got) { |
||||
t.Fatalf("deserialize mismatch\nwant: %+v\ngot: %+v", want, got) |
||||
} |
||||
} |
Loading…
Reference in new issue