feat: 增强虚拟化检测功能,支持Windows和Linux环境

This commit is contained in:
Akizon77
2025-08-16 15:36:09 +00:00
parent af730aafb2
commit aa461f2189
4 changed files with 128 additions and 6 deletions

1
go.mod
View File

@@ -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

2
go.sum
View File

@@ -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=

View File

@@ -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 ""
}

View File

@@ -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)
}