Files
basics/system/disk.go
2025-07-10 05:43:48 +00:00

403 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package system
import (
"os/exec"
"runtime"
"sort"
"strconv"
"strings"
"github.com/shirou/gopsutil/v4/disk"
)
var (
expectDiskFsTypes = []string{
"apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "bcachefs", "btrfs",
"fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone",
}
cpuType string
)
type DiskSingelInfo struct {
TotalStr string
UsageStr string
PercentageStr string
BootPath string
TotalBytes uint64
}
// getDiskInfo 获取硬盘信息
func getDiskInfo() ([]string, []string, []string, string, error) {
var bootPath string
var diskInfos []DiskSingelInfo
var currentDiskInfo *DiskSingelInfo
// macOS 特殊适配
if runtime.GOOS == "darwin" {
cmd := exec.Command("df", "-h")
output, err := cmd.Output()
if err == nil {
lines := strings.Split(string(output), "\n")
for i := 1; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) == "" {
continue
}
fields := strings.Fields(lines[i])
if len(fields) >= 5 {
totalStr := fields[1]
totalBytes := parseSize(totalStr)
percentageStr := fields[4]
if strings.Contains(percentageStr, "%") {
percentageStr = strings.ReplaceAll(percentageStr, "%", "%%")
}
diskInfo := DiskSingelInfo{
TotalStr: fields[1],
UsageStr: fields[2],
PercentageStr: percentageStr,
BootPath: fields[0],
TotalBytes: totalBytes,
}
if len(fields) >= 6 && fields[5] == "/" {
bootPath = fields[0]
currentDiskInfo = &diskInfo
}
if totalBytes >= 200*1024*1024*1024 {
diskInfos = append(diskInfos, diskInfo)
}
}
}
}
} else if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" || runtime.GOOS == "netbsd" {
// BSD系统特殊处理
cmd := exec.Command("df", "-h")
output, err := cmd.Output()
if err == nil {
lines := strings.Split(string(output), "\n")
for i := 1; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) == "" {
continue
}
fields := strings.Fields(lines[i])
if len(fields) >= 5 {
totalStr := fields[1]
totalBytes := parseSize(totalStr)
percentageStr := fields[4]
if percentageStr != "" && strings.Contains(percentageStr, "%") {
percentageStr = strings.ReplaceAll(percentageStr, "%", "%%")
}
diskInfo := DiskSingelInfo{
TotalStr: fields[1],
UsageStr: fields[2],
PercentageStr: percentageStr,
BootPath: fields[0],
TotalBytes: totalBytes,
}
if len(fields) >= 6 && fields[5] == "/" {
bootPath = fields[0]
currentDiskInfo = &diskInfo
}
if totalBytes >= 200*1024*1024*1024 {
diskInfos = append(diskInfos, diskInfo)
}
}
}
}
} else {
// 其他系统使用gopsutil
devices := make(map[string]string)
diskList, _ := disk.Partitions(false)
for _, d := range diskList {
fsType := strings.ToLower(d.Fstype)
if devices[d.Device] == "" && isListContainsStr(expectDiskFsTypes, fsType) && !strings.Contains(d.Mountpoint, "/var/lib/kubelet") {
devices[d.Device] = d.Mountpoint
}
}
for device, mountPath := range devices {
diskUsageOf, err := disk.Usage(mountPath)
if err == nil && diskUsageOf.Total > 0 {
diskTotalGB := float64(diskUsageOf.Total) / (1024 * 1024 * 1024)
diskUsageGB := float64(diskUsageOf.Used) / (1024 * 1024 * 1024)
var diskTotalStr, diskUsageStr string
if diskTotalGB < 1 {
diskTotalStr = strconv.FormatFloat(diskTotalGB*1024, 'f', 2, 64) + " MB"
} else {
diskTotalStr = strconv.FormatFloat(diskTotalGB, 'f', 2, 64) + " GB"
}
if diskUsageGB < 1 {
diskUsageStr = strconv.FormatFloat(diskUsageGB*1024, 'f', 2, 64) + " MB"
} else {
diskUsageStr = strconv.FormatFloat(diskUsageGB, 'f', 2, 64) + " GB"
}
percentageStr := strconv.FormatFloat(float64(diskUsageOf.Used)/float64(diskUsageOf.Total)*100, 'f', 1, 64) + "%%"
diskInfo := DiskSingelInfo{
TotalStr: diskTotalStr,
UsageStr: diskUsageStr,
PercentageStr: percentageStr,
BootPath: device,
TotalBytes: diskUsageOf.Total,
}
if mountPath == "/" || (bootPath == "" && runtime.GOOS == "windows") {
bootPath = device
currentDiskInfo = &diskInfo
}
if diskUsageOf.Total >= 200*1024*1024*1024 {
diskInfos = append(diskInfos, diskInfo)
}
}
}
// 特殊处理 docker、lxc 等虚拟化使用 overlay 挂载的情况
if currentDiskInfo == nil && runtime.GOOS == "linux" {
cmd := exec.Command("df", "-x", "tmpfs", "/")
output, err := cmd.Output()
if err == nil {
lines := strings.Split(string(output), "\n")
if len(lines) >= 2 {
fields := strings.Split(strings.TrimSpace(lines[1]), " ")
var nonEmptyFields []string
for _, field := range fields {
if field != "" {
nonEmptyFields = append(nonEmptyFields, field)
}
}
if len(nonEmptyFields) > 0 && nonEmptyFields[0] != "" {
if strings.Contains(nonEmptyFields[0], "overlay") && len(nonEmptyFields) >= 5 {
tpDiskTotal, err1 := strconv.Atoi(nonEmptyFields[1])
tpDiskUsage, err2 := strconv.Atoi(nonEmptyFields[2])
if err1 == nil && err2 == nil {
totalBytes := uint64(tpDiskTotal) * 1024
diskTotalGB := float64(tpDiskTotal) / (1024 * 1024)
diskUsageGB := float64(tpDiskUsage) / (1024 * 1024)
var diskTotalStr, diskUsageStr string
if diskTotalGB < 1 {
diskTotalStr = strconv.FormatFloat(diskTotalGB*1024, 'f', 2, 64) + " MB"
} else {
diskTotalStr = strconv.FormatFloat(diskTotalGB, 'f', 2, 64) + " GB"
}
if diskUsageGB < 1 {
diskUsageStr = strconv.FormatFloat(diskUsageGB*1024, 'f', 2, 64) + " MB"
} else {
diskUsageStr = strconv.FormatFloat(diskUsageGB, 'f', 2, 64) + " GB"
}
percentageStr := nonEmptyFields[4]
if percentageStr != "" && strings.Contains(percentageStr, "%") {
percentageStr = strings.ReplaceAll(percentageStr, "%", "%%")
}
diskInfo := DiskSingelInfo{
TotalStr: diskTotalStr,
UsageStr: diskUsageStr,
PercentageStr: percentageStr,
BootPath: nonEmptyFields[0],
TotalBytes: totalBytes,
}
bootPath = nonEmptyFields[0]
currentDiskInfo = &diskInfo
if totalBytes >= 200*1024*1024*1024 {
diskInfos = append(diskInfos, diskInfo)
}
}
}
}
}
}
}
}
if currentDiskInfo == nil {
tempDiskTotal, tempDiskUsage := getDiskTotalAndUsed()
if tempDiskTotal > 0 {
diskTotalGB := float64(tempDiskTotal) / (1024 * 1024 * 1024)
diskUsageGB := float64(tempDiskUsage) / (1024 * 1024 * 1024)
var diskTotalStr, diskUsageStr string
if diskTotalGB < 1 {
diskTotalStr = strconv.FormatFloat(diskTotalGB*1024, 'f', 2, 64) + " MB"
} else {
diskTotalStr = strconv.FormatFloat(diskTotalGB, 'f', 2, 64) + " GB"
}
if diskUsageGB < 1 {
diskUsageStr = strconv.FormatFloat(diskUsageGB*1024, 'f', 2, 64) + " MB"
} else {
diskUsageStr = strconv.FormatFloat(diskUsageGB, 'f', 2, 64) + " GB"
}
percentageStr := strconv.FormatFloat(float64(tempDiskUsage)/float64(tempDiskTotal)*100, 'f', 1, 64) + "%%"
diskInfo := DiskSingelInfo{
TotalStr: diskTotalStr,
UsageStr: diskUsageStr,
PercentageStr: percentageStr,
BootPath: "/",
TotalBytes: tempDiskTotal,
}
currentDiskInfo = &diskInfo
if tempDiskTotal >= 200*1024*1024*1024 {
diskInfos = append(diskInfos, diskInfo)
}
}
}
// 应用逻辑:
// 1. 如果完全没有盘大于200GB返回当前磁盘
// 2. 如果有盘大于200GB但当前磁盘不大于200GB返回大于200GB的磁盘+当前磁盘
// 3. 如果当前磁盘和其他磁盘都大于200GB返回所有大于200GB的磁盘
var finalDiskInfos []DiskSingelInfo
if len(diskInfos) == 0 {
// 情况1没有大于200GB的磁盘返回当前磁盘
if currentDiskInfo != nil {
finalDiskInfos = append(finalDiskInfos, *currentDiskInfo)
}
} else {
// 情况2和3有大于200GB的磁盘
finalDiskInfos = diskInfos
// 如果当前磁盘不在大于200GB列表中需要添加
currentInList := false
if currentDiskInfo != nil {
for _, info := range diskInfos {
if info.BootPath == currentDiskInfo.BootPath {
currentInList = true
break
}
}
if !currentInList {
finalDiskInfos = append(finalDiskInfos, *currentDiskInfo)
}
}
}
// 按容量从大到小排序
sort.Slice(finalDiskInfos, func(i, j int) bool {
return finalDiskInfos[i].TotalBytes > finalDiskInfos[j].TotalBytes
})
// 提取切片
var diskTotalStrs, diskUsageStrs, percentageStrs []string
for _, info := range finalDiskInfos {
diskTotalStrs = append(diskTotalStrs, info.TotalStr)
diskUsageStrs = append(diskUsageStrs, info.UsageStr)
percentageStrs = append(percentageStrs, info.PercentageStr)
}
return diskTotalStrs, diskUsageStrs, percentageStrs, bootPath, nil
}
// parseSize 解析尺寸字符串为字节数
func parseSize(sizeStr string) uint64 {
sizeStr = strings.TrimSpace(sizeStr)
if sizeStr == "" {
return 0
}
var multiplier uint64 = 1
sizeStr = strings.ToUpper(sizeStr)
if strings.HasSuffix(sizeStr, "KB") || strings.HasSuffix(sizeStr, "K") {
multiplier = 1024
if strings.HasSuffix(sizeStr, "KB") {
sizeStr = strings.TrimSuffix(sizeStr, "KB")
} else {
sizeStr = strings.TrimSuffix(sizeStr, "K")
}
} else if strings.HasSuffix(sizeStr, "MB") || strings.HasSuffix(sizeStr, "M") {
multiplier = 1024 * 1024
if strings.HasSuffix(sizeStr, "MB") {
sizeStr = strings.TrimSuffix(sizeStr, "MB")
} else {
sizeStr = strings.TrimSuffix(sizeStr, "M")
}
} else if strings.HasSuffix(sizeStr, "GB") || strings.HasSuffix(sizeStr, "G") {
multiplier = 1024 * 1024 * 1024
if strings.HasSuffix(sizeStr, "GB") {
sizeStr = strings.TrimSuffix(sizeStr, "GB")
} else {
sizeStr = strings.TrimSuffix(sizeStr, "G")
}
} else if strings.HasSuffix(sizeStr, "TB") || strings.HasSuffix(sizeStr, "T") {
multiplier = 1024 * 1024 * 1024 * 1024
if strings.HasSuffix(sizeStr, "TB") {
sizeStr = strings.TrimSuffix(sizeStr, "TB")
} else {
sizeStr = strings.TrimSuffix(sizeStr, "T")
}
}
value, err := strconv.ParseFloat(sizeStr, 64)
if err != nil {
return 0
}
return uint64(value * float64(multiplier))
}
func getDiskTotalAndUsed() (total uint64, used uint64) {
// MacOS特殊处理直接用 df -k /
if runtime.GOOS == "darwin" {
cmd := exec.Command("df", "-k", "/")
out, err := cmd.CombinedOutput()
if err == nil {
lines := strings.Split(string(out), "\n")
if len(lines) >= 2 {
fields := strings.Fields(lines[1])
if len(fields) >= 6 {
totalKB, err1 := strconv.ParseUint(fields[1], 10, 64)
usedKB, err2 := strconv.ParseUint(fields[2], 10, 64)
if err1 == nil && err2 == nil {
total = totalKB * 1024
used = usedKB * 1024
return
}
}
}
}
}
devices := make(map[string]string)
// 使用默认过滤规则
diskList, _ := disk.Partitions(false)
for _, d := range diskList {
fsType := strings.ToLower(d.Fstype)
// 不统计 K8s 的虚拟挂载点https://github.com/shirou/gopsutil/issues/1007
if devices[d.Device] == "" && isListContainsStr(expectDiskFsTypes, fsType) && !strings.Contains(d.Mountpoint, "/var/lib/kubelet") {
devices[d.Device] = d.Mountpoint
}
}
for _, mountPath := range devices {
diskUsageOf, err := disk.Usage(mountPath)
if err == nil {
total += diskUsageOf.Total
used += diskUsageOf.Used
}
}
// 回退到根路径的获取方法
if total == 0 && used == 0 {
var cmd *exec.Cmd
// BSD系统使用特定参数
if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" || runtime.GOOS == "netbsd" {
cmd = exec.Command("df", "-k", "/")
} else {
cmd = exec.Command("df")
}
out, err := cmd.CombinedOutput()
if err == nil {
s := strings.Split(string(out), "\n")
for _, c := range s {
info := strings.Fields(c)
if len(info) == 6 {
if info[5] == "/" {
total, _ = strconv.ParseUint(info[1], 0, 64)
used, _ = strconv.ParseUint(info[2], 0, 64)
// 默认获取的是1K块为单位的.
total = total * 1024
used = used * 1024
break
}
} else if len(info) == 5 && (runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" || runtime.GOOS == "netbsd") {
// BSD系统df输出格式可能只有5列
if info[4] == "/" {
total, _ = strconv.ParseUint(info[1], 0, 64)
used, _ = strconv.ParseUint(info[2], 0, 64)
total = total * 1024
used = used * 1024
break
}
}
}
}
}
return
}
func isListContainsStr(list []string, str string) bool {
for i := 0; i < len(list); i++ {
if strings.Contains(str, list[i]) {
return true
}
}
return false
}