diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d93fcd1..e14529b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: run: | git config --global user.name 'github-actions' git config --global user.email 'github-actions@github.com' - TAG="v0.0.12-$(date +'%Y%m%d%H%M%S')" + TAG="v0.0.13-$(date +'%Y%m%d%H%M%S')" git tag $TAG git push origin $TAG echo "TAG=$TAG" >> $GITHUB_ENV diff --git a/README.md b/README.md index 3b3d186..1384b0b 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,7 @@ Include: https://github.com/oneclickvirt/gostun - [x] 适配```MacOS```与```Windows```系统的信息查询 - [x] 部分Windows10系统下打勾打叉编码错误显示,已判断是Win时使用Y/N显示而不是勾叉 - [x] 检测GPU相关信息,参考[ghw](https://github.com/jaypipes/ghw) - -## TODO - -- [ ] 适配MACOS系统的相关信息识别 +- [x] 适配MACOS系统的相关信息识别 ## Usage diff --git a/model/model.go b/model/model.go index 6e5f5a3..64816f2 100644 --- a/model/model.go +++ b/model/model.go @@ -1,6 +1,6 @@ package model -const BasicsVersion = "v0.0.12" +const BasicsVersion = "v0.0.13" var EnableLoger bool diff --git a/system/disk.go b/system/disk.go index a4116cb..f57d1c6 100644 --- a/system/disk.go +++ b/system/disk.go @@ -12,7 +12,27 @@ import ( // getDiskInfo 获取硬盘信息 func getDiskInfo() (string, string, string, string, error) { var diskTotalStr, diskUsageStr, percentageStr, bootPath string - + // macOS 特殊适配 + if runtime.GOOS == "darwin" { + cmd := exec.Command("df", "-h", "/") + output, err := cmd.Output() + if err == nil { + lines := strings.Split(string(output), "\n") + if len(lines) >= 2 { + fields := strings.Fields(lines[1]) + if len(fields) >= 5 { + bootPath = fields[0] + diskTotalStr = fields[1] + diskUsageStr = fields[2] + percentageStr = fields[4] + if strings.Contains(percentageStr, "%") { + percentageStr = strings.ReplaceAll(percentageStr, "%", "%%") + } + return diskTotalStr, diskUsageStr, percentageStr, bootPath, nil + } + } + } + } // BSD系统特殊处理 if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" || runtime.GOOS == "netbsd" { cmd := exec.Command("df", "-h", "/") @@ -26,24 +46,17 @@ func getDiskInfo() (string, string, string, string, error) { diskTotalStr = fields[1] diskUsageStr = fields[2] percentageStr = fields[4] - - // 两个%避免被转义 if percentageStr != "" && strings.Contains(percentageStr, "%") { percentageStr = strings.ReplaceAll(percentageStr, "%", "%%") } - return diskTotalStr, diskUsageStr, percentageStr, bootPath, nil } } } } - - // 保持原有代码逻辑 tempDiskTotal, tempDiskUsage := getDiskTotalAndUsed() diskTotalGB := float64(tempDiskTotal) / (1024 * 1024 * 1024) diskUsageGB := float64(tempDiskUsage) / (1024 * 1024 * 1024) - - // 字节为单位 进行单位转换 if diskTotalGB < 1 { diskTotalStr = strconv.FormatFloat(diskTotalGB*1024, 'f', 2, 64) + " MB" } else { @@ -54,7 +67,6 @@ func getDiskInfo() (string, string, string, string, error) { } else { diskUsageStr = strconv.FormatFloat(diskUsageGB, 'f', 2, 64) + " GB" } - if runtime.GOOS == "windows" { parts, err := disk.Partitions(true) if err != nil { @@ -76,7 +88,6 @@ func getDiskInfo() (string, string, string, string, error) { } } else { // 特殊处理 docker、lxc 等虚拟化使用 overlay 挂载的情况 - // df -x tmpfs / | awk "NR>1" | sed ":a;N;s/\\n//g;ta" | awk '{print $1}' cmd := exec.Command("df", "-x", "tmpfs", "/") output, err := cmd.Output() if err == nil { @@ -115,8 +126,6 @@ func getDiskInfo() (string, string, string, string, error) { } } } - - // 两个%避免被转义 if percentageStr != "" && strings.Contains(percentageStr, "%") { percentageStr = strings.ReplaceAll(percentageStr, "%", "%%") } @@ -124,6 +133,26 @@ func getDiskInfo() (string, string, string, string, error) { } 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) @@ -141,18 +170,15 @@ func getDiskTotalAndUsed() (total uint64, used uint64) { used += diskUsageOf.Used } } - - // Fallback 到根路径的获取方法 + // 回退到根路径的获取方法 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") diff --git a/system/host.go b/system/host.go index 587f228..b5bbdaf 100644 --- a/system/host.go +++ b/system/host.go @@ -168,9 +168,44 @@ func getHostInfo() (string, string, string, string, string, string, string, stri // MAC需要额外获取信息进行判断 if runtime.GOOS == "darwin" { if len(model.MacOSInfo) > 0 { + var modelName, modelIdentifier, chip, vendor string for _, line := range model.MacOSInfo { if strings.Contains(line, "Model Name") { - VmType = strings.TrimSpace(strings.Split(line, ":")[1]) + modelName = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) + } else if strings.Contains(line, "Model Identifier") { + modelIdentifier = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) + } else if strings.Contains(line, "Chip") { + chip = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) + } else if strings.Contains(line, "Manufacturer") || strings.Contains(line, "Vendor") { + vendor = strings.TrimSpace(strings.SplitN(line, ":", 2)[1]) + } + } + allInfo := strings.ToLower(modelName + " " + modelIdentifier + " " + chip + " " + vendor) + virtualKeywords := []string{"vmware", "virtualbox", "parallels", "qemu", "microsoft", "xen"} + isVirtual := false + for _, key := range virtualKeywords { + if strings.Contains(allInfo, key) { + isVirtual = true + break + } + } + physicalWhitelist := []string{"mac mini", "macbook pro", "macbook air", "imac", "mac studio", "mac pro"} + if isVirtual { + VmType = "virtual" + } else { + foundPhysical := false + for _, p := range physicalWhitelist { + if strings.HasPrefix(strings.ToLower(modelName), p) { + foundPhysical = true + break + } + } + if foundPhysical { + VmType = "physical" + } else if modelName == "" { + VmType = "unknown" + } else { + VmType = modelName } } } diff --git a/system/memory.go b/system/memory.go index 739f726..6cb84cd 100644 --- a/system/memory.go +++ b/system/memory.go @@ -1,7 +1,9 @@ package system import ( + "fmt" "os" + "os/exec" "runtime" "strconv" "strings" @@ -19,33 +21,33 @@ func getMemoryInfo() (string, string, string, string, string, string) { memoryTotal := float64(mv.Total) memoryUsage := float64(mv.Total - mv.Available) if memoryTotal < 1024*1024*1024 { - memoryTotalStr = strconv.FormatFloat(memoryTotal/(1024*1024), 'f', 2, 64) + " MB" + memoryTotalStr = fmt.Sprintf("%.2f MB", memoryTotal/(1024*1024)) } else { - memoryTotalStr = strconv.FormatFloat(memoryTotal/(1024*1024*1024), 'f', 2, 64) + " GB" + memoryTotalStr = fmt.Sprintf("%.2f GB", memoryTotal/(1024*1024*1024)) } if memoryUsage < 1024*1024*1024 { - memoryUsageStr = strconv.FormatFloat(memoryUsage/(1024*1024), 'f', 2, 64) + " MB" + memoryUsageStr = fmt.Sprintf("%.2f MB", memoryUsage/(1024*1024)) } else { - memoryUsageStr = strconv.FormatFloat(memoryUsage/(1024*1024*1024), 'f', 2, 64) + " GB" + memoryUsageStr = fmt.Sprintf("%.2f GB", memoryUsage/(1024*1024*1024)) } - if runtime.GOOS != "windows" { + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { swapTotal := float64(mv.SwapTotal) swapUsage := float64(mv.SwapTotal - mv.SwapFree) if swapTotal != 0 { if swapTotal < 1024*1024*1024 { - swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024), 'f', 2, 64) + " MB" + swapTotalStr = fmt.Sprintf("%.2f MB", swapTotal/(1024*1024)) } else { - swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024*1024), 'f', 2, 64) + " GB" + swapTotalStr = fmt.Sprintf("%.2f GB", swapTotal/(1024*1024*1024)) } if swapUsage < 1024*1024*1024 { - swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024), 'f', 2, 64) + " MB" + swapUsageStr = fmt.Sprintf("%.2f MB", swapUsage/(1024*1024)) } else { - swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024*1024), 'f', 2, 64) + " GB" + swapUsageStr = fmt.Sprintf("%.2f GB", swapUsage/(1024*1024*1024)) } } } } - // MAC需要额外获取信息进行判断 + // macOS 特殊处理:内存和 swap if runtime.GOOS == "darwin" { if len(model.MacOSInfo) > 0 { for _, line := range model.MacOSInfo { @@ -54,9 +56,30 @@ func getMemoryInfo() (string, string, string, string, string, string) { } } } + output, err := exec.Command("sysctl", "vm.swapusage").Output() + if err == nil { + // 输出示例: "vm.swapusage: total = 2048.00M used = 1021.25M free = 1026.75M (encrypted)" + fields := strings.Fields(string(output)) + if len(fields) >= 7 { + totalVal, err1 := strconv.ParseFloat(strings.TrimSuffix(fields[3], "M"), 64) + usedVal, err2 := strconv.ParseFloat(strings.TrimSuffix(fields[6], "M"), 64) + if err1 == nil && err2 == nil { + if totalVal >= 1024 { + swapTotalStr = fmt.Sprintf("%.2f GB", totalVal/1024) + } else { + swapTotalStr = fmt.Sprintf("%.2f MB", totalVal) + } + if usedVal >= 1024 { + swapUsageStr = fmt.Sprintf("%.2f GB", usedVal/1024) + } else { + swapUsageStr = fmt.Sprintf("%.2f MB", usedVal) + } + } + } + } } + // Windows 特殊处理 swap(gopsutil 的 VirtualMemory 在 Win 上不准确) if runtime.GOOS == "windows" { - // gopsutil 在 Windows 下不能正确取 swap ms, err := mem.SwapMemory() if err != nil { println("mem.SwapMemory error:", err) @@ -65,26 +88,25 @@ func getMemoryInfo() (string, string, string, string, string, string) { swapUsage := float64(ms.Used) if swapTotal != 0 { if swapTotal < 1024*1024*1024 { - swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024), 'f', 2, 64) + " MB" + swapTotalStr = fmt.Sprintf("%.2f MB", swapTotal/(1024*1024)) } else { - swapTotalStr = strconv.FormatFloat(swapTotal/(1024*1024*1024), 'f', 2, 64) + " GB" + swapTotalStr = fmt.Sprintf("%.2f GB", swapTotal/(1024*1024*1024)) } if swapUsage < 1024*1024*1024 { - swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024), 'f', 2, 64) + " MB" + swapUsageStr = fmt.Sprintf("%.2f MB", swapUsage/(1024*1024)) } else { - swapUsageStr = strconv.FormatFloat(swapUsage/(1024*1024*1024), 'f', 2, 64) + " GB" + swapUsageStr = fmt.Sprintf("%.2f GB", swapUsage/(1024*1024*1024)) } } } } + // virtio_balloon 检测(Linux) virtioBalloon, err := os.ReadFile("/proc/modules") - if err == nil { - if strings.Contains(string(virtioBalloon), "virtio_balloon") { - if runtime.GOOS == "windows" { - virtioBalloonStatus = "[Y] Enabled" - } else { - virtioBalloonStatus = "✔️ Enabled" - } + if err == nil && strings.Contains(string(virtioBalloon), "virtio_balloon") { + if runtime.GOOS == "windows" { + virtioBalloonStatus = "[Y] Enabled" + } else { + virtioBalloonStatus = "✔️ Enabled" } } if virtioBalloonStatus == "" { @@ -94,14 +116,13 @@ func getMemoryInfo() (string, string, string, string, string, string) { virtioBalloonStatus = "❌ Undetected" } } + // KSM 状态检测(Linux) ksmStatus, err := os.ReadFile("/sys/kernel/mm/ksm/run") - if err == nil { - if strings.Contains(string(ksmStatus), "1") { - if runtime.GOOS == "windows" { - KernelSamepageMerging = "[Y] Enabled" - } else { - KernelSamepageMerging = "✔️ Enabled" - } + if err == nil && strings.Contains(string(ksmStatus), "1") { + if runtime.GOOS == "windows" { + KernelSamepageMerging = "[Y] Enabled" + } else { + KernelSamepageMerging = "✔️ Enabled" } } if KernelSamepageMerging == "" { diff --git a/system/system.go b/system/system.go index 41203d5..e262be9 100644 --- a/system/system.go +++ b/system/system.go @@ -78,15 +78,17 @@ func CheckSystemInfo(language string) string { res += " GPU Stats : " + ret.GpuStats + "\n" } } - if runtime.GOOS != "windows" && runtime.GOOS != "macos" { + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { res += " AES-NI : " + ret.CpuAesNi + "\n" } - res += " VM-x/AMD-V/Hyper-V : " + ret.CpuVAH + "\n" + if runtime.GOOS != "darwin" { + res += " VM-x/AMD-V/Hyper-V : " + ret.CpuVAH + "\n" + } res += " RAM : " + ret.MemoryUsage + " / " + ret.MemoryTotal + "\n" - if ret.VirtioBalloon != "" { + if ret.VirtioBalloon != "" && runtime.GOOS != "darwin" && runtime.GOOS != "windows" { res += " Virtio Balloon : " + ret.VirtioBalloon + "\n" } - if ret.KSM != "" { + if ret.KSM != "" && runtime.GOOS != "darwin" && runtime.GOOS != "windows" { res += " KSM : " + ret.KSM + "\n" } if ret.SwapTotal == "" && ret.SwapUsage == "" { @@ -128,15 +130,17 @@ func CheckSystemInfo(language string) string { res += " GPU 状态 : " + ret.GpuStats + "\n" } } - if runtime.GOOS != "windows" && runtime.GOOS != "macos" { + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { res += " AES-NI : " + ret.CpuAesNi + "\n" } - res += " VM-x/AMD-V/Hyper-V : " + ret.CpuVAH + "\n" + if runtime.GOOS != "darwin" { + res += " VM-x/AMD-V/Hyper-V : " + ret.CpuVAH + "\n" + } res += " 内存 : " + ret.MemoryUsage + " / " + ret.MemoryTotal + "\n" - if ret.VirtioBalloon != "" { + if ret.VirtioBalloon != "" && runtime.GOOS != "darwin" && runtime.GOOS != "windows" { res += " 气球驱动 : " + ret.VirtioBalloon + "\n" } - if ret.KSM != "" { + if ret.KSM != "" && runtime.GOOS != "darwin" && runtime.GOOS != "windows" { res += " 内核页合并 : " + ret.KSM + "\n" } if ret.SwapTotal == "" && ret.SwapUsage == "" {