Files
kopia/cli/command_content_stats.go
Jarek Kowalski daa62de3e4 chore(ci): added checklocks static analyzer (#1838)
From https://github.com/google/gvisor/tree/master/tools/checklocks

This will perform static verification that we're using
`sync.Mutex`, `sync.RWMutex` and `atomic` correctly to guard access
to certain fields.

This was mostly just a matter of adding annotations to indicate which
fields are guarded by which mutex.

In a handful of places the code had to be refactored to allow static
analyzer to do its job better or to not be confused by some
constructs.

In one place this actually uncovered a bug where a function was not
releasing a lock properly in an error case.

The check is part of `make lint` but can also be invoked by
`make check-locks`.
2022-03-19 22:42:59 -07:00

159 lines
4.1 KiB
Go

package cli
import (
"context"
"strconv"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/units"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/compression"
"github.com/kopia/kopia/repo/content"
)
type commandContentStats struct {
raw bool
contentRange contentRangeFlags
out textOutput
}
func (c *commandContentStats) setup(svc appServices, parent commandParent) {
cmd := parent.Command("stats", "Content statistics")
cmd.Flag("raw", "Raw numbers").Short('r').BoolVar(&c.raw)
c.contentRange.setup(cmd)
c.out.setup(svc)
cmd.Action(svc.directRepositoryReadAction(c.run))
}
type contentStatsTotals struct {
originalSize int64
packedSize int64
count int64
}
func (c *commandContentStats) run(ctx context.Context, rep repo.DirectRepository) error {
var (
sizeThreshold uint32 = 10
sizeBuckets []uint32
)
for i := 0; i < 8; i++ {
sizeBuckets = append(sizeBuckets, sizeThreshold)
sizeThreshold *= 10
}
grandTotal, byCompressionTotal, countMap, totalSizeOfContentsUnder, err := c.calculateStats(ctx, rep, sizeBuckets)
if err != nil {
return errors.Wrap(err, "error calculating totals")
}
sizeToString := units.BytesStringBase10
if c.raw {
sizeToString = func(l int64) string {
return strconv.FormatInt(l, 10) // nolint:gomnd
}
}
c.out.printStdout("Count: %v\n", grandTotal.count)
c.out.printStdout("Total Bytes: %v\n", sizeToString(grandTotal.originalSize))
if grandTotal.packedSize < grandTotal.originalSize {
c.out.printStdout(
"Total Packed: %v (compression %v)\n",
sizeToString(grandTotal.packedSize),
formatCompressionPercentage(grandTotal.originalSize, grandTotal.packedSize))
}
if len(byCompressionTotal) > 1 {
c.out.printStdout("By Method:\n")
if bct := byCompressionTotal[content.NoCompression]; bct != nil {
c.out.printStdout(" %-22v count: %v size: %v\n", "(uncompressed)", bct.count, sizeToString(bct.originalSize))
}
for hdrID, bct := range byCompressionTotal {
cname := compression.HeaderIDToName[hdrID]
if cname == "" {
continue
}
c.out.printStdout(" %-22v count: %v size: %v packed: %v compression: %v\n",
cname, bct.count,
sizeToString(bct.originalSize),
sizeToString(bct.packedSize),
formatCompressionPercentage(bct.originalSize, bct.packedSize))
}
}
if grandTotal.count == 0 {
return nil
}
c.out.printStdout("Average: %v\n", sizeToString(grandTotal.originalSize/grandTotal.count))
c.out.printStdout("Histogram:\n\n")
var lastSize uint32
for _, size := range sizeBuckets {
c.out.printStdout("%9v between %v and %v (total %v)\n",
countMap[size]-countMap[lastSize],
sizeToString(int64(lastSize)),
sizeToString(int64(size)),
sizeToString(totalSizeOfContentsUnder[size]-totalSizeOfContentsUnder[lastSize]),
)
lastSize = size
}
return nil
}
func (c *commandContentStats) calculateStats(ctx context.Context, rep repo.DirectRepository, sizeBuckets []uint32) (
grandTotal contentStatsTotals,
byCompressionTotal map[compression.HeaderID]*contentStatsTotals,
countMap map[uint32]int,
totalSizeOfContentsUnder map[uint32]int64,
err error,
) {
byCompressionTotal = make(map[compression.HeaderID]*contentStatsTotals)
totalSizeOfContentsUnder = make(map[uint32]int64)
countMap = make(map[uint32]int)
for _, s := range sizeBuckets {
countMap[s] = 0
}
err = rep.ContentReader().IterateContents(
ctx,
content.IterateOptions{
Range: c.contentRange.contentIDRange(),
},
func(b content.Info) error {
grandTotal.packedSize += int64(b.GetPackedLength())
grandTotal.originalSize += int64(b.GetOriginalLength())
grandTotal.count++
bct := byCompressionTotal[b.GetCompressionHeaderID()]
if bct == nil {
bct = &contentStatsTotals{}
byCompressionTotal[b.GetCompressionHeaderID()] = bct
}
bct.packedSize += int64(b.GetPackedLength())
bct.originalSize += int64(b.GetOriginalLength())
bct.count++
for s := range countMap {
if b.GetPackedLength() < s {
countMap[s]++
totalSizeOfContentsUnder[s] += int64(b.GetPackedLength())
}
}
return nil
})
// nolint:wrapcheck
return grandTotal, byCompressionTotal, countMap, totalSizeOfContentsUnder, err
}