mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-16 20:26:45 +00:00
* admin: show capacity per storage tier and stop counting remote-tiered bytes as local disk usage A remote-tiered volume reports its cloud object's size, so summing volume sizes inflated the dashboard's used-vs-capacity numbers (the local .dat is gone after volume.tier.move). Split the accounting: DiskUsage now only counts bytes on local disks, with the cloud bytes surfaced separately per server and per remote storage name. The dashboard gains a Storage Tiers table breaking volumes and EC shards down by tier (each local disk type plus each remote storage), using the per-disk-type statfs numbers already in the VolumeList response. The volumes page badges remote-tiered volumes with their storage name, and the EC shards page fills in real per-shard sizes instead of hardcoding 0. * admin: review fixes for the tier capacity display - A disk that predates disk_total_bytes now contributes its logical bytes to the tier's DiskUsed, so a tier mixing old and new volume servers doesn't underreport usage; the usage bar always reflects the displayed Disk Used value (the DataSize fallback in UsagePercent is gone, and the percent math is overflow-safe). - getTopologyViaGRPC defaults a zero VolumeSizeLimitMb to 30000 MB like GetClusterVolumeServers, keeping slot-based capacities consistent. - The dashboard volume-servers column reads Usage / Capacity to match its cell content, and the hdd disk-type default is shared between the volumes-page badge and countUniqueDiskTypes.
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
package dash
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
|
|
)
|
|
|
|
// TierStats aggregates the volumes and EC shards that live on one storage
|
|
// tier: a local disk type ("hdd", "ssd", or a custom tag), or the remote
|
|
// storage a tiered volume was uploaded to. A remote-tiered volume reports
|
|
// the size of its cloud object, so its bytes belong to the remote tier,
|
|
// not to the local disk that holds only its index.
|
|
type TierStats struct {
|
|
Name string `json:"name"`
|
|
IsRemote bool `json:"is_remote"`
|
|
VolumeCount int `json:"volume_count"`
|
|
EcShardCount int `json:"ec_shard_count"`
|
|
DataSize int64 `json:"data_size"`
|
|
DiskUsed int64 `json:"disk_used"`
|
|
DiskCapacity int64 `json:"disk_capacity"`
|
|
MaxVolumes int64 `json:"max_volumes"`
|
|
}
|
|
|
|
// UsagePercent is the tier's local disk usage in percent, clamped to
|
|
// [0, 100]. Remote tiers have no capacity and return 0.
|
|
func (t TierStats) UsagePercent() int {
|
|
if t.IsRemote || t.DiskCapacity <= 0 {
|
|
return 0
|
|
}
|
|
percent := int(float64(t.DiskUsed) / float64(t.DiskCapacity) * 100)
|
|
if percent < 0 {
|
|
return 0
|
|
}
|
|
if percent > 100 {
|
|
return 100
|
|
}
|
|
return percent
|
|
}
|
|
|
|
// tierDiskType maps the empty disk type to its display name.
|
|
func tierDiskType(diskType string) string {
|
|
if diskType == "" {
|
|
return "hdd"
|
|
}
|
|
return diskType
|
|
}
|
|
|
|
// CollectTierStats walks the topology and groups capacity and usage by
|
|
// tier. DiskUsed/DiskCapacity come from the statfs numbers the volume
|
|
// servers report per disk type; a disk that predates disk_total_bytes
|
|
// falls back to the slot-based capacity estimate and to the logical
|
|
// bytes it holds, so mixed-version tiers don't underreport usage.
|
|
// Remote tiers have no local disk, so only VolumeCount and DataSize are
|
|
// meaningful there.
|
|
func CollectTierStats(topo *master_pb.TopologyInfo, volumeSizeLimitMb uint64) []TierStats {
|
|
if topo == nil {
|
|
return nil
|
|
}
|
|
tiers := make(map[string]*TierStats)
|
|
tier := func(name string, isRemote bool) *TierStats {
|
|
key := name
|
|
if isRemote {
|
|
key = "remote\x00" + name
|
|
}
|
|
t := tiers[key]
|
|
if t == nil {
|
|
t = &TierStats{Name: name, IsRemote: isRemote}
|
|
tiers[key] = t
|
|
}
|
|
return t
|
|
}
|
|
|
|
for _, dc := range topo.DataCenterInfos {
|
|
for _, rack := range dc.RackInfos {
|
|
for _, node := range rack.DataNodeInfos {
|
|
for _, diskInfo := range node.DiskInfos {
|
|
local := tier(tierDiskType(diskInfo.Type), false)
|
|
local.MaxVolumes += diskInfo.MaxVolumeCount
|
|
hasStatfs := diskInfo.DiskTotalBytes > 0
|
|
if hasStatfs {
|
|
local.DiskCapacity += int64(diskInfo.DiskTotalBytes)
|
|
if diskInfo.DiskTotalBytes > diskInfo.DiskFreeBytes {
|
|
local.DiskUsed += int64(diskInfo.DiskTotalBytes - diskInfo.DiskFreeBytes)
|
|
}
|
|
} else {
|
|
local.DiskCapacity += diskInfo.MaxVolumeCount * int64(volumeSizeLimitMb) * 1024 * 1024
|
|
}
|
|
|
|
var diskLocalBytes int64
|
|
for _, volInfo := range diskInfo.VolumeInfos {
|
|
if volInfo.RemoteStorageName != "" {
|
|
remote := tier(volInfo.RemoteStorageName, true)
|
|
remote.VolumeCount++
|
|
remote.DataSize += int64(volInfo.Size)
|
|
} else {
|
|
local.VolumeCount++
|
|
local.DataSize += int64(volInfo.Size)
|
|
diskLocalBytes += int64(volInfo.Size)
|
|
}
|
|
}
|
|
|
|
// ShardSizes is local to this node, so summing across
|
|
// nodes gives the tier's physical footprint.
|
|
for _, ecShardInfo := range diskInfo.EcShardInfos {
|
|
local.EcShardCount += erasure_coding.GetShardCount(ecShardInfo)
|
|
ecBytes := erasure_coding.EcShardsTotalSize(ecShardInfo)
|
|
local.DataSize += ecBytes
|
|
diskLocalBytes += ecBytes
|
|
}
|
|
|
|
// Without statfs numbers, approximate this disk's
|
|
// footprint with the logical bytes it holds.
|
|
if !hasStatfs {
|
|
local.DiskUsed += diskLocalBytes
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result := make([]TierStats, 0, len(tiers))
|
|
for _, t := range tiers {
|
|
result = append(result, *t)
|
|
}
|
|
sort.Slice(result, func(i, j int) bool {
|
|
if result[i].IsRemote != result[j].IsRemote {
|
|
return !result[i].IsRemote
|
|
}
|
|
return result[i].Name < result[j].Name
|
|
})
|
|
return result
|
|
}
|