aggregate bucket stats recursively and add stats to cmd

pull/34/head
Martin Kobetic 2014-05-09 18:16:38 +00:00
parent b9899d09ab
commit c4ad027df7
5 changed files with 107 additions and 16 deletions

View File

@ -366,6 +366,9 @@ func (b *Bucket) Stats() BucketStats {
b.forEachPage(func(p *page, depth int) {
if (p.flags & leafPageFlag) != 0 {
s.LeafPageN++
if p.count == 0 {
return
}
s.KeyN += int(p.count)
lastElement := p.leafPageElement(p.count - 1)
used := pageHeaderSize + (leafPageElementSize * int(p.count-1))
@ -373,9 +376,10 @@ func (b *Bucket) Stats() BucketStats {
s.LeafInuse += used
s.LeafOverflowN += int(p.overflow)
// Recurse into sub-buckets
for _, e := range p.leafPageElements() {
if e.flags&bucketLeafFlag != 0 {
// Collect stats from sub-buckets
for i := uint16(0); i < p.count; i++ {
e := p.leafPageElement(i)
if (e.flags & bucketLeafFlag) != 0 {
subStats.Add(b.openBucket(e.value()).Stats())
}
}
@ -666,7 +670,7 @@ func (s *BucketStats) Add(other BucketStats) {
s.BranchOverflowN += other.BranchOverflowN
s.LeafPageN += other.LeafPageN
s.LeafOverflowN += other.LeafOverflowN
s.KeyN += s.KeyN
s.KeyN += other.KeyN
if s.Depth < other.Depth {
s.Depth = other.Depth
}

View File

@ -628,6 +628,50 @@ func TestBucket_Stats_Small(t *testing.T) {
})
}
// Ensure a bucket can calculate stats.
func TestBucket_Stats_Nested(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
b, err := tx.CreateBucket([]byte("foo"))
assert.NoError(t, err)
for i := 0; i < 100; i++ {
b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
}
bar, err := b.CreateBucket([]byte("bar"))
assert.NoError(t, err)
for i := 0; i < 10; i++ {
bar.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
}
baz, err := b.CreateBucket([]byte("baz"))
assert.NoError(t, err)
for i := 0; i < 10; i++ {
baz.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
}
return nil
})
mustCheck(db)
db.View(func(tx *Tx) error {
b := tx.Bucket([]byte("foo"))
stats := b.Stats()
assert.Equal(t, stats.BranchPageN, 0)
assert.Equal(t, stats.BranchOverflowN, 0)
assert.Equal(t, stats.LeafPageN, 3)
assert.Equal(t, stats.LeafOverflowN, 0)
assert.Equal(t, stats.KeyN, 122)
assert.Equal(t, stats.Depth, 2)
if os.Getpagesize() != 4096 {
// Incompatible page size
assert.Equal(t, stats.BranchInuse, 0)
assert.Equal(t, stats.BranchAlloc, 0)
assert.Equal(t, stats.LeafInuse, 38)
assert.Equal(t, stats.LeafAlloc, 4096)
}
return nil
})
})
}
// Ensure a large bucket can calculate stats.
func TestBucket_Stats_Large(t *testing.T) {
if testing.Short() {

View File

@ -93,7 +93,7 @@ func NewApp() *cli.App {
},
{
Name: "stats",
Usage: "Retrieve statistics for a bucket (aggregated recursively)",
Usage: "Aggregate statistics for all buckets matching specified prefix",
Action: func(c *cli.Context) {
path, name := c.Args().Get(0), c.Args().Get(1)
Stats(path, name)

View File

@ -1,13 +1,14 @@
package main
import (
"bytes"
"os"
"github.com/boltdb/bolt"
)
// Keys retrieves a list of keys for a given bucket.
func Stats(path, name string) {
// Collect stats for all top level buckets matching the prefix.
func Stats(path, prefix string) {
if _, err := os.Stat(path); os.IsNotExist(err) {
fatal(err)
return
@ -21,15 +22,18 @@ func Stats(path, name string) {
defer db.Close()
err = db.View(func(tx *bolt.Tx) error {
// Find bucket.
b := tx.Bucket([]byte(name))
if b == nil {
fatalf("bucket not found: %s", name)
var s bolt.BucketStats
var count int
var prefix = []byte(prefix)
tx.ForEach(func(name []byte, b *bolt.Bucket) error {
if bytes.HasPrefix(name, prefix) {
s.Add(b.Stats())
count += 1
}
return nil
}
})
printf("Aggregate statistics for %d buckets\n\n", count)
// Iterate over each key.
s := b.Stats()
println("Page count statistics")
printf("\tNumber of logical branch pages: %d\n", s.BranchPageN)
printf("\tNumber of physical branch overflow pages: %d\n", s.BranchOverflowN)
@ -42,9 +46,11 @@ func Stats(path, name string) {
println("Page size utilization")
printf("\tBytes allocated for physical branch pages: %d\n", s.BranchAlloc)
printf("\tBytes actually used for branch data: %d\n", s.BranchInuse)
percentage := int(float32(s.BranchInuse) * 100.0 / float32(s.BranchAlloc))
printf("\tBytes actually used for branch data: %d (%d%%)\n", s.BranchInuse, percentage)
printf("\tBytes allocated for physical leaf pages: %d\n", s.LeafAlloc)
printf("\tBytes actually used for leaf data: %d\n", s.LeafInuse)
percentage = int(float32(s.LeafInuse) * 100.0 / float32(s.LeafAlloc))
printf("\tBytes actually used for leaf data: %d (%d%%)\n", s.LeafInuse, percentage)
return nil
})
if err != nil {

37
cmd/bolt/stats_test.go Normal file
View File

@ -0,0 +1,37 @@
package main_test
import (
"testing"
"github.com/boltdb/bolt"
. "github.com/boltdb/bolt/cmd/bolt"
"github.com/stretchr/testify/assert"
)
func TestStats(t *testing.T) {
SetTestMode(true)
open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("foo"))
tx.CreateBucket([]byte("bar"))
tx.CreateBucket([]byte("baz"))
return nil
})
db.Close()
output := run("stats", path, "b")
assert.Equal(t, "Aggregate statistics for 2 buckets\n\n"+
"Page count statistics\n"+
"\tNumber of logical branch pages: 0\n"+
"\tNumber of physical branch overflow pages: 0\n"+
"\tNumber of logical leaf pages: 2\n"+
"\tNumber of physical leaf overflow pages: 0\n"+
"Tree statistics\n"+
"\tNumber of keys/value pairs: 0\n"+
"\tNumber of levels in B+tree: 0\n"+
"Page size utilization\n"+
"\tBytes allocated for physical branch pages: 0\n"+
"\tBytes actually used for branch data: 0\n"+
"\tBytes allocated for physical leaf pages: 8192\n"+
"\tBytes actually used for leaf data: 0", output)
})
}