From ef3b393fd4a4481e27f9dd84ca80724e0e8eb651 Mon Sep 17 00:00:00 2001 From: Andrew Durbin Date: Fri, 19 Apr 2024 12:21:13 -0600 Subject: [PATCH] Check allocated vs provisioned file size in metrics. Thin allocated/sparse files will report full virtual/max size in ls but allocated in du. Comparable stat output is in %s and %b respectively. Populate DiskMetric.UsedBytes with file allocated bytes to avoid rolling over negative when eve node is populated with persistent volume instances with little data allocated. For example an empty system with a single unattached 1GB persistent volume instance defined would see the following in publishMetrics() of handlemetrics.go: persistUsage = ~1 MB persistAppUsage = 1 GB runtimeStorageOverhead = (persistUsage-persistAppUsage) = ~17592186042378 MB ls vs du: ls -l /persist/clear/volumes/9599c42f-42b0-4807-96a7-2d01cb424e8f\#0.raw -rw-r--r-- 1 root root 1073741824 Apr 19 17:31 /persist/clear/volumes/... du -h /persist/clear/volumes/9599c42f-42b0-4807-96a7-2d01cb424e8f\#0.raw 4.0K /persist/clear/volumes/9599c42f-42b0-4807-96a7-2d01cb424e8f#0.raw stat virt vs. allocated: stat -c '%s' /persist/clear/volumes/9599c42f-42b0-4807-96a7-2d01cb424e8f\#0.raw 1073741824 stat -c '%b' /persist/clear/volumes/9599c42f-42b0-4807-96a7-2d01cb424e8f\#0.raw 8 Signed-off-by: Andrew Durbin (cherry picked from commit 8be1999c60dc5c257a73a7143b096572d3fa89ec) --- pkg/pillar/diskmetrics/usage.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/pkg/pillar/diskmetrics/usage.go b/pkg/pillar/diskmetrics/usage.go index 3b813545c6..12d4b9e993 100644 --- a/pkg/pillar/diskmetrics/usage.go +++ b/pkg/pillar/diskmetrics/usage.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "strings" + "syscall" "github.com/containerd/containerd/mount" "github.com/lf-edge/eve/pkg/pillar/base" @@ -18,6 +19,17 @@ import ( "github.com/shirou/gopsutil/disk" ) +// StatAllocatedBytes returns the allocated size of a file in bytes +// This value will be less than fileInfo.Size() for thinly allocated files +func StatAllocatedBytes(path string) (uint64, error) { + var stat syscall.Stat_t + err := syscall.Stat(path, &stat) + if err != nil { + return uint64(0), err + } + return uint64(stat.Blocks * int64(stat.Blksize)), nil +} + // SizeFromDir performs a du -s equivalent operation. // Didn't use ioutil.ReadDir and filepath.Walk because they sort (quick_sort) all files per directory // which is an unnecessary costly operation. @@ -57,8 +69,21 @@ func SizeFromDir(log *base.LogObject, dirname string) (uint64, error) { log.Tracef("Dir %s size %d\n", filename, size) totalUsed += size } else { - log.Tracef("File %s Size %d\n", filename, location.Size()) - totalUsed += uint64(location.Size()) + // FileInfo.Size() returns the provisioned size + // Sparse files will have a smaller allocated size than provisioned + // Use full syscall.Stat_t to get the allocated size + allocatedBytes, err := StatAllocatedBytes(filename) + if err != nil { + log.Errorf("StatAllocatedBytes: %s failed %s treating as fully allocated\n", filename, err) + allocatedBytes = uint64(location.Size()) + } + // Fully Allocated: don't use allocated bytes + // stat math of %b*%B as it will over-account space + if allocatedBytes >= uint64(location.Size()) { + allocatedBytes = uint64(location.Size()) + } + log.Tracef("File %s Size %d\n", filename, allocatedBytes) + totalUsed += allocatedBytes } } }