Files
seaweedfs/weed/mount/weedfs_stats.go
T
Chris Lu a02c0024e5 master: cap the reported capacity at what the disks hold (#10960)
* master: cap the reported capacity at what the disks hold

Statistics reported max volume count times the volume size limit, which is
how many volumes the cluster is allowed to place, not how much space it has.
A cluster given far more slots than its disks can fill reported a capacity it
could never reach -- 65536 slots at 30GB read as 1.9PB on a 460GB disk -- and
the number never moved, since writing data changes neither the slot count nor
the size limit.

The volume servers already report each filesystem's total and free bytes in
their heartbeats, so bound the answer by what they say is left.

* mount: keep the last known sizes when filer statistics fails

A failed Statistics call returned before df's answer was filled in, so a
mount whose filer or master was briefly unreachable reported an empty
filesystem rather than the sizes it already had.

* master: drop the disk ceiling when a volume server does not report

A cluster part way through an upgrade has volume servers that predate the disk
bytes in the heartbeat. Summing only the ones that answered left the quiet
server's free space out of the total, and the server holding the room is
exactly the one that could make the cluster read as full.

Answer with the disks only when every one of them reported.
2026-08-26 00:12:56 -07:00

131 lines
4.1 KiB
Go

package mount
import (
"context"
"fmt"
"math"
"time"
"github.com/seaweedfs/go-fuse/v2/fuse"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
)
const blockSize = 512
type statsCache struct {
filer_pb.StatisticsResponse
lastChecked int64 // unix time in seconds
}
// diskSizes reports the sizes df and the quota work from. By default that is
// the space the cluster gives up to the data, counting every replica and every
// EC shard. Under -df.logical it is the data itself, with the free space
// converted to how much more of it the mount's replication setting allows.
func (wfs *WFS) diskSizes() (totalSize, usedSize uint64) {
// a filer older than the logical sizes sends zeros, so keep reporting the
// raw ones rather than an empty filesystem
if wfs.option.LogicalDiskUsage && wfs.stats.LogicalTotalSize > 0 {
return wfs.stats.LogicalTotalSize, wfs.stats.LogicalUsedSize
}
return wfs.stats.TotalSize, wfs.stats.UsedSize
}
func (wfs *WFS) StatFs(cancel <-chan struct{}, in *fuse.InHeader, out *fuse.StatfsOut) (code fuse.Status) {
// glog.V(4).Infof("reading fs stats")
if wfs.stats.lastChecked < time.Now().Unix()-20 {
err := wfs.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
request := &filer_pb.StatisticsRequest{
Collection: wfs.option.Collection,
Replication: wfs.option.Replication,
Ttl: fmt.Sprintf("%ds", wfs.option.TtlSec),
DiskType: string(wfs.option.DiskType),
}
glog.V(4).Infof("reading filer stats: %+v", request)
resp, err := client.Statistics(context.Background(), request)
if err != nil {
glog.V(0).Infof("reading filer stats %v: %v", request, err)
return err
}
glog.V(4).Infof("read filer stats: %+v", resp)
wfs.stats.TotalSize = resp.TotalSize
wfs.stats.UsedSize = resp.UsedSize
wfs.stats.LogicalTotalSize = resp.LogicalTotalSize
wfs.stats.LogicalUsedSize = resp.LogicalUsedSize
wfs.stats.FileCount = resp.FileCount
wfs.stats.lastChecked = time.Now().Unix()
return nil
})
if err != nil {
// the last known sizes beat the empty filesystem a bare return reports
glog.V(0).Infof("filer Statistics: %v", err)
}
}
totalDiskSize, usedDiskSize := wfs.diskSizes()
actualFileCount := wfs.stats.FileCount
if wfs.option.Quota > 0 && totalDiskSize > uint64(wfs.option.Quota) {
totalDiskSize = uint64(wfs.option.Quota)
if usedDiskSize > totalDiskSize {
totalDiskSize = usedDiskSize
}
}
// http://man.he.net/man2/statfs
/*
struct statfs {
__fsword_t f_type; // Type of filesystem (see below)
__fsword_t f_bsize; // Optimal transfer block size
fsblkcnt_t f_blocks; // Total data blocks in filesystem
fsblkcnt_t f_bfree; // Free blocks in filesystem
fsblkcnt_t f_bavail; // Free blocks available to
unprivileged user
fsfilcnt_t f_files; // Total file nodes in filesystem
fsfilcnt_t f_ffree; // Free file nodes in filesystem
fsid_t f_fsid; // Filesystem ID
__fsword_t f_namelen; // Maximum length of filenames
__fsword_t f_frsize; // Fragment size (since Linux 2.6)
__fsword_t f_flags; // Mount flags of filesystem
(since Linux 2.6.36)
__fsword_t f_spare[xxx];
// Padding bytes reserved for future use
};
*/
// Compute the total number of available blocks
out.Blocks = totalDiskSize / blockSize
if out.Blocks <= 0 {
out.Blocks = 1
}
// Compute the number of used blocks
numBlocks := usedDiskSize / blockSize
remainingBlocks := int64(out.Blocks) - int64(numBlocks)
if remainingBlocks < 0 {
remainingBlocks = 0
}
// Report the number of free and available blocks for the block size
out.Bfree = uint64(remainingBlocks)
out.Bavail = uint64(remainingBlocks)
out.Bsize = uint32(blockSize)
// Report the total number of possible files in the file system (and those free)
out.Files = math.MaxInt64
out.Ffree = math.MaxInt64 - actualFileCount
// Report the maximum length of a name and the minimum fragment size
out.NameLen = 255
out.Frsize = uint32(blockSize)
return fuse.OK
}