Files
aiops/agent/main.go
T
attacker ac0b8e75c0 feat: add monitoring agent (Go) + agent report API + cloud billing UI
- Go agent: CPU/mem/disk/port collectors, HTTP report, static binary
- Cross-compile: linux/amd64 + linux/arm64 (statically linked)
- One-line install script for all Linux distros
- Backend: POST /api/v1/agent/report (no JWT) + GET /agent/latest,history (JWT)
- CloudBilling.tsx: fixed syntax errors, stats/trends/records/alerts
- K8s menu renamed to Kubernetes管理

Signed-off-by: Logan.Li
2026-06-14 21:27:14 +08:00

137 lines
3.6 KiB
Go

// Package main 是 AIOps Agent 的入口。
// Agent 定时采集 CPU、内存、磁盘及监听端口指标,并通过 HTTP 上报至 AIOps 后端。
//
// 以下变量可在编译时通过 -ldflags "-X main.version=... -X main.buildTime=..." 注入。
//
// 署名 Logan.Li
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"syscall"
"time"
"ezops/agent/collector"
"ezops/agent/model"
"ezops/agent/reporter"
)
// 编译时可注入的版本信息
var (
version = "dev"
buildTime = "unknown"
)
func main() {
server := flag.String("server", "http://localhost:8080", "AIOps 后端上报地址(含 scheme 与端口)")
interval := flag.Int("interval", 10, "采集与上报间隔,单位:秒")
token := flag.String("token", "", "Bearer 鉴权令牌")
hostnameFlag := flag.String("hostname", "", "上报主机名,默认使用系统主机名")
flag.Parse()
// 统一日志格式:带微秒时间戳,便于排查问题
logger := log.New(os.Stdout, "[aiops-agent] ", log.LstdFlags|log.Lmicroseconds)
// 解析主机名:优先使用 -hostname,否则回退到 os.Hostname()
hostname := *hostnameFlag
if hostname == "" {
if h, err := os.Hostname(); err == nil {
hostname = h
} else {
hostname = "unknown"
}
}
// 参数校验
if *server == "" {
logger.Fatal("参数 -server 不能为空")
}
if *interval < 1 {
logger.Fatalf("参数 -interval 必须 >= 1,当前为 %d", *interval)
}
logger.Printf("AIOps Agent 启动: version=%s buildTime=%s", version, buildTime)
logger.Printf("配置: server=%s interval=%ds hostname=%q token=%s",
*server, *interval, hostname, maskToken(*token))
// 初始化各采集器与上报器
cpuCollector := collector.NewCPUCollector()
memCollector := collector.NewMemCollector()
diskCollector := collector.NewDiskCollector()
portCollector := collector.NewPortCollector()
rep := reporter.New(*server, *token, logger)
// 监听 SIGINT / SIGTERM,支持优雅退出
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
ticker := time.NewTicker(time.Duration(*interval) * time.Second)
defer ticker.Stop()
// collectAndReport 执行单次采集并上报;各项采集独立进行,单项失败不影响整体上报
collectAndReport := func() {
report := &model.Report{
Hostname: hostname,
Timestamp: time.Now().UTC().Format(time.RFC3339),
}
if stat, err := cpuCollector.Collect(); err != nil {
logger.Printf("CPU 采集异常: %v", err)
} else {
report.CPU = stat
}
if stat, err := memCollector.Collect(); err != nil {
logger.Printf("内存采集异常: %v", err)
} else {
report.Memory = stat
}
if disks, err := diskCollector.Collect(); err != nil {
logger.Printf("磁盘采集异常: %v", err)
} else {
report.Disks = disks
}
if ports, err := portCollector.Collect(); err != nil {
logger.Printf("端口采集异常: %v", err)
} else {
report.Ports = ports
}
if err := rep.Send(ctx, report); err != nil {
logger.Printf("上报失败: %v", err)
return
}
logger.Printf("上报成功: cpu=%.2f%% mem=%.2f%% disks=%d ports=%d",
report.CPU.UsagePercent, report.Memory.UsagePercent, len(report.Disks), len(report.Ports))
}
// 启动后立即执行一次,再按 ticker 周期执行
collectAndReport()
for {
select {
case <-ticker.C:
collectAndReport()
case <-ctx.Done():
logger.Println("收到退出信号,Agent 正在关闭...")
return
}
}
}
// maskToken 对令牌做脱敏处理,仅用于日志输出
func maskToken(token string) string {
if token == "" {
return "(empty)"
}
if len(token) <= 4 {
return "****"
}
return token[:4] + "****"
}