From aa461f21895ad8f76a84a21515ee9800a78316de Mon Sep 17 00:00:00 2001 From: Akizon77 Date: Sat, 16 Aug 2025 15:36:09 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E5=8C=96=E6=A3=80=E6=B5=8B=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81Windows=E5=92=8CLinux=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 2 + monitoring/unit/virtualization.go | 127 +++++++++++++++++++++++-- monitoring/unit/virtualization_test.go | 4 + 4 files changed, 128 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 76647a8..96ae14f 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/creack/pty v1.1.24 github.com/gorilla/websocket v1.5.3 + github.com/klauspost/cpuid/v2 v2.3.0 github.com/prometheus-community/pro-bing v0.7.0 github.com/rhysd/go-github-selfupdate v1.2.3 github.com/shirou/gopsutil/v4 v4.25.6 diff --git a/go.sum b/go.sum index 7d63fa7..4966574 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7V github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= diff --git a/monitoring/unit/virtualization.go b/monitoring/unit/virtualization.go index 8d60353..cacf1a8 100644 --- a/monitoring/unit/virtualization.go +++ b/monitoring/unit/virtualization.go @@ -1,19 +1,134 @@ package monitoring import ( + "fmt" + "os" "os/exec" "runtime" "strings" + + cpuid "github.com/klauspost/cpuid/v2" ) func Virtualized() string { + // Windows: use CPUID to detect hypervisor presence and vendor. if runtime.GOOS == "windows" { - return "Unknown" + return detectByCPUID() } - out, err := exec.Command("systemd-detect-virt").Output() - if err != nil { - return "Unknown" + + // Linux/others: prefer systemd-detect-virt if available; fallback to CPUID. + if out, err := exec.Command("systemd-detect-virt").Output(); err == nil { + virt := strings.TrimSpace(string(out)) + if virt != "" { + return virt + } } - virt := strings.TrimSpace(string(out)) - return virt + + // Non-systemd environments (e.g., Alpine containers): try container heuristics. + if ct := detectContainer(); ct != "" { + return ct + } + + // Fallback (any OS): CPUID hypervisor bit and vendor mapping. + return detectByCPUID() +} + +// detectByCPUID uses cpuid to check if running under a hypervisor and maps vendor to a common name. +func detectByCPUID() string { + if !cpuid.CPU.VM() { + // Align with systemd-detect-virt for bare metal. + return "none" + } + vendor := strings.ToLower(cpuid.CPU.HypervisorVendorString) + switch { + case vendor == "kvm" || strings.Contains(vendor, "kvm"): + return "kvm" + case vendor == "microsoft" || strings.Contains(vendor, "hyper-v") || strings.Contains(vendor, "msvm") || strings.Contains(vendor, "mshyperv"): + return "microsoft" // systemd uses "microsoft" for Hyper-V/WSL + case strings.Contains(vendor, "vmware"): + return "vmware" + case strings.Contains(vendor, "xen"): + return "xen" + case strings.Contains(vendor, "bhyve"): + return "bhyve" + case strings.Contains(vendor, "qemu"): + return "qemu" + case strings.Contains(vendor, "parallels"): + return "parallels" + case strings.Contains(vendor, "oracle") || strings.Contains(vendor, "virtualbox") || strings.Contains(vendor, "vbox"): + return "oracle" // systemd reports "oracle" for VirtualBox + case strings.Contains(vendor, "acrn"): + return "acrn" + default: + if vendor != "" { + return fmt.Sprintf("hypervisor:%s", vendor) + } + return "virtualized" + } +} + +// detectContainer attempts to detect common Linux container environments when systemd isn't available. +// Returns a systemd-detect-virt-like string such as "docker", "podman", "lxc", "container" or empty if not detected. +func detectContainer() string { + // Quick file markers used by Docker/Podman/CRI-O + if fileExists("/.dockerenv") { + return "docker" + } + if fileExists("/run/.containerenv") { + // podman/cri-o often drop this file + // Try to refine using cgroup content. + if s := parseCgroupForContainer(); s != "" { + return s + } + return "container" + } + + // Check cgroup info for container keywords. + if s := parseCgroupForContainer(); s != "" { + return s + } + + // Check mounts for overlay/containers hints. + if data, err := os.ReadFile("/proc/self/mountinfo"); err == nil { + lower := strings.ToLower(string(data)) + switch { + case strings.Contains(lower, "/docker/"): + return "docker" + case strings.Contains(lower, "/containers/overlay-containers/") || strings.Contains(lower, "/lib/containers/"): + return "podman" + case strings.Contains(lower, "/kubelet/"): + return "kubernetes" + } + } + + return "" +} + +func fileExists(p string) bool { + if st, err := os.Stat(p); err == nil && !st.IsDir() { + return true + } + return false +} + +func parseCgroupForContainer() string { + // cgroup v1 & v2 paths can contain docker, kubepods, containerd, crio, lxc, podman + if data, err := os.ReadFile("/proc/self/cgroup"); err == nil { + lower := strings.ToLower(string(data)) + switch { + case strings.Contains(lower, "docker"): + return "docker" + case strings.Contains(lower, "containerd"): + return "container" + case strings.Contains(lower, "kubepods") || strings.Contains(lower, "kubelet"): + return "kubernetes" + case strings.Contains(lower, "crio"): + return "container" + case strings.Contains(lower, "lxc"): + return "lxc" + case strings.Contains(lower, "podman"): + return "podman" + } + } + return "" } diff --git a/monitoring/unit/virtualization_test.go b/monitoring/unit/virtualization_test.go index f555460..820828f 100644 --- a/monitoring/unit/virtualization_test.go +++ b/monitoring/unit/virtualization_test.go @@ -6,5 +6,9 @@ import ( func TestVirtualized(t *testing.T) { virt := Virtualized() + cpuid_result := detectByCPUID() + container_result := detectContainer() t.Logf("Virtualization type: %s", virt) + t.Logf("CPUID result: %s", cpuid_result) + t.Logf("Container result: %s", container_result) }