- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
146 lines
3.5 KiB
Go
146 lines
3.5 KiB
Go
//go:build !windows
|
|
|
|
// Package collector 提供服务器各项系统指标的采集能力。
|
|
// 本文件实现对所有已挂载的真实文件系统执行 statfs,采集磁盘使用情况。
|
|
// 使用 golang.org/x/sys/unix 的 Statfs,兼容 Linux 与 macOS。
|
|
//
|
|
// 署名 Logan.Li
|
|
package collector
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"ezops/agent/model"
|
|
)
|
|
|
|
// DiskCollector 磁盘使用率采集器
|
|
type DiskCollector struct{}
|
|
|
|
// NewDiskCollector 创建磁盘采集器实例
|
|
func NewDiskCollector() *DiskCollector {
|
|
return &DiskCollector{}
|
|
}
|
|
|
|
// mount 描述一个挂载点信息
|
|
type mount struct {
|
|
device string
|
|
mountpoint string
|
|
fstype string
|
|
}
|
|
|
|
// realFilesystems 允许采集的"真实"文件系统类型白名单,
|
|
// 用于排除 tmpfs/proc/sysfs 等伪文件系统,避免产生噪音数据。
|
|
var realFilesystems = map[string]bool{
|
|
"ext2": true, "ext3": true, "ext4": true,
|
|
"xfs": true, "btrfs": true, "zfs": true,
|
|
"apfs": true, "hfs": true, "exfat": true,
|
|
"ntfs": true, "vfat": true, "fat32": true,
|
|
"f2fs": true, "jfs": true, "reiserfs": true,
|
|
"ufs": true,
|
|
}
|
|
|
|
// Collect 采集所有真实文件系统挂载点的磁盘使用情况
|
|
func (d *DiskCollector) Collect() ([]model.DiskStat, error) {
|
|
mounts, err := d.getMounts()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取挂载点失败: %w", err)
|
|
}
|
|
|
|
disks := make([]model.DiskStat, 0, len(mounts))
|
|
seen := make(map[string]bool) // 按挂载点去重
|
|
|
|
for _, m := range mounts {
|
|
if !realFilesystems[m.fstype] {
|
|
continue
|
|
}
|
|
if seen[m.mountpoint] {
|
|
continue
|
|
}
|
|
seen[m.mountpoint] = true
|
|
|
|
var fs unix.Statfs_t
|
|
if err := unix.Statfs(m.mountpoint, &fs); err != nil {
|
|
// 单个挂载点采集失败不影响其余挂载点
|
|
continue
|
|
}
|
|
|
|
bsize := uint64(fs.Bsize)
|
|
total := fs.Blocks * bsize
|
|
free := fs.Bfree * bsize
|
|
if total == 0 {
|
|
continue
|
|
}
|
|
used := total - free
|
|
|
|
// inode 使用率
|
|
var inodePct float64
|
|
if fs.Files > 0 {
|
|
inodePct = float64(fs.Files-fs.Ffree) / float64(fs.Files) * 100.0
|
|
if inodePct < 0 {
|
|
inodePct = 0
|
|
}
|
|
}
|
|
|
|
disks = append(disks, model.DiskStat{
|
|
Mount: m.mountpoint,
|
|
Device: m.device,
|
|
TotalGB: bytesToGB(total),
|
|
UsedGB: bytesToGB(used),
|
|
UsagePercent: float64(used) / float64(total) * 100.0,
|
|
InodeUsagePct: inodePct,
|
|
})
|
|
}
|
|
return disks, nil
|
|
}
|
|
|
|
// getMounts 获取挂载点列表。
|
|
// - Linux: 解析 /proc/mounts
|
|
// - macOS(开发环境): 返回根分区
|
|
func (d *DiskCollector) getMounts() ([]mount, error) {
|
|
if runtime.GOOS != "linux" {
|
|
// 开发环境(macOS)下提供根分区以便本地调试
|
|
return []mount{{device: "/dev/disk1", mountpoint: "/", fstype: "apfs"}}, nil
|
|
}
|
|
|
|
f, err := os.Open("/proc/mounts")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var mounts []mount
|
|
scanner := bufio.NewScanner(f)
|
|
// 挂载点路径可能较长,扩大缓冲区
|
|
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
|
for scanner.Scan() {
|
|
// 形如: /dev/sda1 / ext4 rw,relatime 0 0
|
|
fields := strings.Fields(scanner.Text())
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
mounts = append(mounts, mount{
|
|
device: fields[0],
|
|
mountpoint: unescapeMount(fields[1]),
|
|
fstype: fields[2],
|
|
})
|
|
}
|
|
return mounts, scanner.Err()
|
|
}
|
|
|
|
// unescapeMount 还原 /proc/mounts 中被转义的挂载点路径(如 \040 -> 空格)
|
|
func unescapeMount(s string) string {
|
|
return strings.ReplaceAll(s, "\\040", " ")
|
|
}
|
|
|
|
// bytesToGB 字节数转 GB
|
|
func bytesToGB(b uint64) float64 {
|
|
const gb = 1024.0 * 1024.0 * 1024.0
|
|
return float64(b) / gb
|
|
}
|