You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
2.6 KiB
125 lines
2.6 KiB
package main
|
|
|
|
import (
|
|
"container/heap"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"cloud.google.com/go/storage"
|
|
"google.golang.org/api/iterator"
|
|
)
|
|
|
|
var (
|
|
b = flag.String("bucket", "", "name of the bucket to use")
|
|
)
|
|
|
|
func main() {
|
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
|
|
defer cancel()
|
|
|
|
flag.Parse()
|
|
|
|
client, err := storage.NewClient(ctx)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
bucket := client.Bucket(*b)
|
|
|
|
it := bucket.Objects(ctx, nil)
|
|
var totalSize int64
|
|
var minSize int64 = math.MaxInt64
|
|
var maxSize int64
|
|
sizes := &Int64Heap{}
|
|
var oldest time.Time
|
|
var youngest time.Time
|
|
heap.Init(sizes)
|
|
for {
|
|
objAttrs, err := it.Next()
|
|
if err == iterator.Done {
|
|
break
|
|
}
|
|
if err != nil {
|
|
log.Fatalf("Failed to iterate objects: %v", err)
|
|
}
|
|
|
|
// Add the size of each object to the total size
|
|
sizes.Push(objAttrs.Size)
|
|
totalSize += objAttrs.Size
|
|
minSize = min(minSize, objAttrs.Size)
|
|
maxSize = max(maxSize, objAttrs.Size)
|
|
if oldest.IsZero() || objAttrs.Created.Before(oldest) {
|
|
oldest = objAttrs.Created
|
|
}
|
|
if youngest.IsZero() || objAttrs.Created.After(youngest) {
|
|
youngest = objAttrs.Created
|
|
}
|
|
}
|
|
|
|
fmt.Printf("bucket: %s\n", *b)
|
|
fmt.Printf("total size: %s\n", toHumanBytes(totalSize))
|
|
fmt.Printf("min: %s\n", toHumanBytes(minSize))
|
|
fmt.Printf("max: %s\n", toHumanBytes(maxSize))
|
|
fmt.Printf("median: %s\n", toHumanBytes(sizes.At(sizes.Len()/2)))
|
|
fmt.Printf("mean: %s\n", toHumanBytes(totalSize/int64(sizes.Len())))
|
|
}
|
|
|
|
func min(a, b int64) int64 {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func max(a, b int64) int64 {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// An Int64Heap is a min-heap of ints.
|
|
type Int64Heap []int64
|
|
|
|
func (h Int64Heap) Len() int { return len(h) }
|
|
func (h Int64Heap) Less(i, j int) bool { return h[i] < h[j] }
|
|
func (h Int64Heap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
|
func (h Int64Heap) At(i int) int64 { return h[i] }
|
|
|
|
func (h *Int64Heap) Push(x any) {
|
|
// Push and Pop use pointer receivers because they modify the slice's length,
|
|
// not just its contents.
|
|
*h = append(*h, x.(int64))
|
|
}
|
|
|
|
func (h *Int64Heap) Pop() any {
|
|
old := *h
|
|
n := len(old)
|
|
x := old[n-1]
|
|
*h = old[0 : n-1]
|
|
return x
|
|
}
|
|
|
|
var prefixes = []string{"b", "Kib", "Mib", "Gib", "Tib", "Pib"}
|
|
|
|
func toHumanBytes(i int64) string {
|
|
f := float64(i)
|
|
if f < 1024.0 {
|
|
return fmt.Sprintf("%4.2fb", f)
|
|
}
|
|
|
|
for i := 1; i < len(prefixes); i++ {
|
|
f /= 1024
|
|
if f < 1024.0 {
|
|
return fmt.Sprintf("%4.2f%s", f, prefixes[i])
|
|
}
|
|
}
|
|
return fmt.Sprintf("%4.2f%s", f, prefixes[len(prefixes)-1])
|
|
}
|