From c4ad027df774d84c29c9cc53436071c87138c60d Mon Sep 17 00:00:00 2001 From: Martin Kobetic <mkobetic@gmail.com> Date: Fri, 9 May 2014 18:16:38 +0000 Subject: [PATCH] aggregate bucket stats recursively and add stats to cmd --- bucket.go | 12 ++++++++---- bucket_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ cmd/bolt/main.go | 2 +- cmd/bolt/stats.go | 28 ++++++++++++++++----------- cmd/bolt/stats_test.go | 37 +++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 cmd/bolt/stats_test.go diff --git a/bucket.go b/bucket.go index 41156d7..6f7bbd2 100644 --- a/bucket.go +++ b/bucket.go @@ -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 } diff --git a/bucket_test.go b/bucket_test.go index b87031b..626927a 100644 --- a/bucket_test.go +++ b/bucket_test.go @@ -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() { diff --git a/cmd/bolt/main.go b/cmd/bolt/main.go index 659f1c3..302fe09 100644 --- a/cmd/bolt/main.go +++ b/cmd/bolt/main.go @@ -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) diff --git a/cmd/bolt/stats.go b/cmd/bolt/stats.go index da344d0..6f8505a 100644 --- a/cmd/bolt/stats.go +++ b/cmd/bolt/stats.go @@ -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 { diff --git a/cmd/bolt/stats_test.go b/cmd/bolt/stats_test.go new file mode 100644 index 0000000..9662f09 --- /dev/null +++ b/cmd/bolt/stats_test.go @@ -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) + }) +}