Files
aiops/agent/collector/network.go
T
attacker 8240a88a84 feat(k8s): M14 Namespace 管理看板(资源配额 + 命名空间 CRUD)
- 后端: ListNamespacesDetailed/CreateNamespace/DeleteNamespace/ListResourceQuotas/ListLimitRanges
- 后端: GetNamespacesDetail handler (6 API calls aggregate, not N×3)
- 后端: CreateNamespaceHandler + DeleteNamespaceHandler (system NS protection)
- 前端: NamespaceManagementPage.tsx (~280行) — 表格+概览卡片+创建/删除弹框
- 路由: GET/POST/DELETE /:clusterCode/namespaces + GET /namespace/:ns/detail
- 验证: 9/9 通过 (urllib), TS 零错误, Vite 构建成功
2026-06-24 23:46:52 +08:00

202 lines
5.0 KiB
Go

// Package collector 提供服务器各项系统指标的采集能力。
// 本文件实现网络连接数、网卡流量等网络指标采集。
//
// 署名 Hermes (extended from Logan.Li pattern)
package collector
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
"ezops/agent/model"
)
// netSample 保存上一次的网络采样数据,用于计算速率
type netSample struct {
time time.Time
rxBytes uint64
txBytes uint64
rxErrors uint64
txErrors uint64
}
// NetworkCollector 网络指标采集器
type NetworkCollector struct {
mu sync.Mutex
prev *netSample
}
// NewNetworkCollector 创建网络采集器
func NewNetworkCollector() *NetworkCollector {
return &NetworkCollector{}
}
// Collect 采集网络指标(并发连接数、TIME_WAIT、网卡 rx/tx B/s、错误包)
func (c *NetworkCollector) Collect() (model.NetworkStat, error) {
stat := model.NetworkStat{}
// 1. 采集 TCP 连接统计
if tcpStat, err := readSockStat(); err == nil {
stat.TCPEstablished = tcpStat.established
stat.TCPTimeWait = tcpStat.timeWait
stat.TCPTotal = tcpStat.total
}
stat.TCPCloseWait = countCloseWait()
// 2. 采集网卡流量 + 错误包(取所有非 lo 网卡合计)
curRx, curTx, curRxErr, curTxErr, nicName, err := readNetDevTotal()
if err != nil {
return stat, fmt.Errorf("读取网卡流量失败: %w", err)
}
stat.NicName = nicName
c.mu.Lock()
defer c.mu.Unlock()
now := time.Now()
cur := &netSample{time: now, rxBytes: curRx, txBytes: curTx, rxErrors: curRxErr, txErrors: curTxErr}
if c.prev != nil && now.After(c.prev.time) {
elapsed := now.Sub(c.prev.time).Seconds()
if elapsed > 0 && cur.rxBytes >= c.prev.rxBytes && cur.txBytes >= c.prev.txBytes {
stat.NetRxBPS = float64(cur.rxBytes-c.prev.rxBytes) / elapsed
stat.NetTxBPS = float64(cur.txBytes-c.prev.txBytes) / elapsed
}
}
// 错误包:返回当前累计值(供前端判断是否激增)
stat.NetRxErrors = int64(cur.rxErrors)
stat.NetTxErrors = int64(cur.txErrors)
c.prev = cur
return stat, nil
}
// tcpSockStat 解析 /proc/net/sockstat 或 /proc/net/tcp 的结果
type tcpSockStat struct {
established int
timeWait int
total int
}
// readSockStat 从 /proc/net/sockstat 读取 TCP 连接统计
func readSockStat() (tcpSockStat, error) {
var result tcpSockStat
f, err := os.Open("/proc/net/sockstat")
if err != nil {
// Fallback: try /proc/net/sockstat6
return result, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "TCP:") {
continue
}
// 格式: "TCP: inuse 123 orphan 0 tw 45 alloc 200 mem 30"
fields := strings.Fields(line)
for i, f := range fields {
switch f {
case "inuse":
if i+1 < len(fields) {
result.established, _ = strconv.Atoi(fields[i+1])
}
case "tw":
if i+1 < len(fields) {
result.timeWait, _ = strconv.Atoi(fields[i+1])
}
case "alloc":
if i+1 < len(fields) {
result.total, _ = strconv.Atoi(fields[i+1])
}
}
}
result.total = result.established // total = established count (inuse)
break
}
return result, scanner.Err()
}
// readNetDevTotal 读取 /proc/net/dev,取第一个非 lo 且活跃的物理网卡总流量 + 错误包
func readNetDevTotal() (rxBytes, txBytes, rxErrors, txErrors uint64, nicName string, err error) {
f, err := os.Open("/proc/net/dev")
if err != nil {
return 0, 0, 0, 0, "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
// Skip header lines (first 2 lines)
lineNum := 0
for scanner.Scan() {
lineNum++
if lineNum <= 2 {
continue
}
line := strings.TrimSpace(scanner.Text())
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
name := strings.TrimSpace(parts[0])
// Skip loopback and virtual interfaces
if name == "lo" || strings.HasPrefix(name, "veth") || strings.HasPrefix(name, "docker") ||
strings.HasPrefix(name, "br-") || strings.HasPrefix(name, "virbr") {
continue
}
fields := strings.Fields(parts[1])
if len(fields) < 11 {
continue
}
// fields[0]=rx_bytes, fields[2]=rx_errors, fields[8]=tx_bytes, fields[10]=tx_errors
rb, _ := strconv.ParseUint(fields[0], 10, 64)
tb, _ := strconv.ParseUint(fields[8], 10, 64)
re, _ := strconv.ParseUint(fields[2], 10, 64)
te, _ := strconv.ParseUint(fields[10], 10, 64)
if rb > 0 || tb > 0 {
nicName = name
rxBytes += rb
txBytes += tb
rxErrors += re
txErrors += te
}
}
if nicName == "" {
return 0, 0, 0, 0, "eth0", nil
}
return rxBytes, txBytes, rxErrors, txErrors, nicName, scanner.Err()
}
// countCloseWait 从 /proc/net/tcp 统计 CLOSE_WAIT 连接数
func countCloseWait() int {
count := 0
for _, path := range []string{"/proc/net/tcp", "/proc/net/tcp6"} {
data, err := os.ReadFile(path)
if err != nil {
continue
}
for _, line := range strings.Split(string(data), "\n") {
fields := strings.Fields(line)
if len(fields) >= 4 && fields[3] == "08" {
count++
}
}
}
return count
}