aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bucket.go12
-rw-r--r--bucket_test.go44
-rw-r--r--cmd/bolt/main.go2
-rw-r--r--cmd/bolt/stats.go28
-rw-r--r--cmd/bolt/stats_test.go37
5 files changed, 107 insertions, 16 deletions
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)
+ })
+}