增加日志进程等信息采集
This commit is contained in:
BIN
agent/agent
BIN
agent/agent
Binary file not shown.
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"server_url": "http://localhost:8080/api",
|
"server_url": "http://10.35.10.12:8080/api",
|
||||||
"id": "yunc",
|
"id": "agent-fnos1",
|
||||||
"name": "cloud",
|
"name": "fnos1",
|
||||||
"device_id": "yunc",
|
"device_id": "agent-fnos1",
|
||||||
"token": "f1dee2c8ffbdd4974af84b92a254892b",
|
"token": "eea3ffc9b3bb6b2a9f2e5bf228a2c7db",
|
||||||
"interval": "10s",
|
"interval": "10s",
|
||||||
"debug": true,
|
"debug": true,
|
||||||
"api_port": 8081
|
"api_port": 8081
|
||||||
|
|||||||
23
agent/config.json
Normal file
23
agent/config.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"port": 8080
|
||||||
|
},
|
||||||
|
"influxdb": {
|
||||||
|
"url": "http://localhost:8086",
|
||||||
|
"token": "",
|
||||||
|
"org": "monitor",
|
||||||
|
"bucket": "monitor",
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
|
"db": {
|
||||||
|
"type": "mysql",
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 3306,
|
||||||
|
"username": "root",
|
||||||
|
"password": "",
|
||||||
|
"database": "monitor",
|
||||||
|
"ssl_mode": "disable",
|
||||||
|
"charset": "utf8mb4"
|
||||||
|
}
|
||||||
|
}
|
||||||
415
agent/main.go
415
agent/main.go
@@ -5,8 +5,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
stdnet "net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,6 +17,7 @@ import (
|
|||||||
"github.com/shirou/gopsutil/disk"
|
"github.com/shirou/gopsutil/disk"
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/mem"
|
||||||
"github.com/shirou/gopsutil/net"
|
"github.com/shirou/gopsutil/net"
|
||||||
|
"github.com/shirou/gopsutil/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config Agent配置
|
// Config Agent配置
|
||||||
@@ -43,13 +46,56 @@ type DiskMetrics struct {
|
|||||||
Total uint64 `json:"total"` // 总容量 (bytes)
|
Total uint64 `json:"total"` // 总容量 (bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessMetrics 进程监控指标
|
||||||
|
type ProcessMetrics struct {
|
||||||
|
Name string `json:"name"` // 进程名
|
||||||
|
Username string `json:"username"` // 用户名
|
||||||
|
PID int32 `json:"pid"` // 进程ID
|
||||||
|
CPU float64 `json:"cpu"` // CPU使用率
|
||||||
|
Memory float64 `json:"memory"` // 内存使用率
|
||||||
|
Path string `json:"path"` // 路径
|
||||||
|
Cmdline string `json:"cmdline"` // 命令行
|
||||||
|
Ports []int `json:"ports"` // 占用端口
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskDetailMetrics 磁盘详细信息
|
||||||
|
type DiskDetailMetrics struct {
|
||||||
|
DeviceID string `json:"device_id"` // 设备ID
|
||||||
|
Status string `json:"status"` // 设备状态
|
||||||
|
Type string `json:"type"` // 设备类型
|
||||||
|
SizeGB float64 `json:"size_gb"` // 设备大小(GB)
|
||||||
|
Model string `json:"model"` // 设备型号
|
||||||
|
InterfaceType string `json:"interface_type"` // 接口类型
|
||||||
|
Description string `json:"description"` // 设备描述
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogEntry 系统日志条目
|
||||||
|
type LogEntry struct {
|
||||||
|
Sequence int `json:"sequence"` // 日志序号
|
||||||
|
Source string `json:"source"` // 来源
|
||||||
|
Time time.Time `json:"time"` // 发生时间
|
||||||
|
Message string `json:"message"` // 内容
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics 监控指标
|
// Metrics 监控指标
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
CPU float64 `json:"cpu"`
|
CPU float64 `json:"cpu"`
|
||||||
CPUHz float64 `json:"cpu_hz"` // CPU频率 (MHz)
|
CPUHz float64 `json:"cpu_hz"` // CPU频率 (MHz)
|
||||||
Memory float64 `json:"memory"`
|
Memory float64 `json:"memory"`
|
||||||
Disk map[string]DiskMetrics `json:"disk"`
|
Disk map[string]DiskMetrics `json:"disk"`
|
||||||
Network map[string]NetworkInterfaceMetrics `json:"network"`
|
DiskDetails []DiskDetailMetrics `json:"disk_details"` // 磁盘详细信息
|
||||||
|
Network map[string]NetworkInterfaceMetrics `json:"network"`
|
||||||
|
Processes []ProcessMetrics `json:"processes"` // 进程信息
|
||||||
|
Logs []LogEntry `json:"logs"` // 系统日志
|
||||||
|
RxTotal uint64 `json:"rx_total"` // 所有网卡累计接收字节数总和
|
||||||
|
TxTotal uint64 `json:"tx_total"` // 所有网卡累计发送字节数总和
|
||||||
|
RxRate uint64 `json:"rx_rate"` // 所有网卡实时接收速率总和 (bytes/s)
|
||||||
|
TxRate uint64 `json:"tx_rate"` // 所有网卡实时发送速率总和 (bytes/s)
|
||||||
|
// 设备信息字段
|
||||||
|
DeviceID string `json:"device_id"` // 设备ID
|
||||||
|
AgentID string `json:"agent_id"` // Agent唯一标识
|
||||||
|
Name string `json:"name"` // 设备名称
|
||||||
|
IP string `json:"ip"` // 设备IP地址
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局配置
|
// 全局配置
|
||||||
@@ -78,6 +124,60 @@ func init() {
|
|||||||
metricsBuffer = make([]*Metrics, 0)
|
metricsBuffer = make([]*Metrics, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getLocalIP 获取本机IP地址
|
||||||
|
func getLocalIP() string {
|
||||||
|
// 获取所有网络接口
|
||||||
|
interfaces, err := stdnet.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to get network interfaces: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历网络接口查找非回环、UP状态的IP
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
// 跳过回环接口和非UP状态的接口
|
||||||
|
if iface.Flags&stdnet.FlagLoopback != 0 || iface.Flags&stdnet.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取接口的IP地址
|
||||||
|
addresses, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to get addresses for interface %s: %v", iface.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历地址并返回IPv4地址
|
||||||
|
for _, addr := range addresses {
|
||||||
|
var ip stdnet.IP
|
||||||
|
switch v := addr.(type) {
|
||||||
|
case *stdnet.IPNet:
|
||||||
|
ip = v.IP
|
||||||
|
case *stdnet.IPAddr:
|
||||||
|
ip = v.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过IPv6地址和回环地址
|
||||||
|
if ip == nil || ip.IsLoopback() || ip.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找不到合适的IP,尝试另一种方法
|
||||||
|
conn, err := stdnet.Dial("udp", "8.8.8.8:80")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to dial UDP: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
localAddr := conn.LocalAddr().(*stdnet.UDPAddr)
|
||||||
|
return localAddr.IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
// 默认配置
|
// 默认配置
|
||||||
@@ -353,25 +453,27 @@ func collectDisk() (map[string]DiskMetrics, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 采集网络流量
|
// 采集网络流量
|
||||||
func collectNetwork() (map[string]NetworkInterfaceMetrics, error) {
|
func collectNetwork() (map[string]NetworkInterfaceMetrics, uint64, uint64, uint64, uint64, error) {
|
||||||
// 获取所有网卡的统计数据
|
// 获取所有网卡的统计数据
|
||||||
ioCounters, err := net.IOCounters(true)
|
ioCounters, err := net.IOCounters(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// 当获取网卡数据失败时,返回空map和0值
|
||||||
|
return make(map[string]NetworkInterfaceMetrics), 0, 0, 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ioCounters) == 0 {
|
// 初始化返回值
|
||||||
return make(map[string]NetworkInterfaceMetrics), nil
|
networkMetrics := make(map[string]NetworkInterfaceMetrics)
|
||||||
}
|
var totalRxBytes, totalTxBytes, totalRxRate, totalTxRate uint64
|
||||||
|
|
||||||
// 获取当前时间
|
// 获取当前时间
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
||||||
// 初始化返回值
|
|
||||||
networkMetrics := make(map[string]NetworkInterfaceMetrics)
|
|
||||||
|
|
||||||
// 遍历所有网卡
|
// 遍历所有网卡
|
||||||
for _, counter := range ioCounters {
|
for _, counter := range ioCounters {
|
||||||
|
// 跳过空名称的网卡
|
||||||
|
if counter.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// 获取当前网卡的累计流量
|
// 获取当前网卡的累计流量
|
||||||
currentBytesSent := counter.BytesSent
|
currentBytesSent := counter.BytesSent
|
||||||
currentBytesReceived := counter.BytesRecv
|
currentBytesReceived := counter.BytesRecv
|
||||||
@@ -409,23 +511,265 @@ func collectNetwork() (map[string]NetworkInterfaceMetrics, error) {
|
|||||||
TxBytes: currentBytesSent,
|
TxBytes: currentBytesSent,
|
||||||
RxBytes: currentBytesReceived,
|
RxBytes: currentBytesReceived,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 累加总流量
|
||||||
|
totalRxBytes += currentBytesReceived
|
||||||
|
totalTxBytes += currentBytesSent
|
||||||
|
totalRxRate += bytesReceivedRate
|
||||||
|
totalTxRate += bytesSentRate
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新上一次采集时间
|
// 更新上一次采集时间
|
||||||
lastCollectTime = currentTime
|
lastCollectTime = currentTime
|
||||||
|
|
||||||
// 返回所有网卡的速率和累计流量
|
// 返回所有网卡的速率和累计流量,以及总和
|
||||||
return networkMetrics, nil
|
return networkMetrics, totalRxBytes, totalTxBytes, totalRxRate, totalTxRate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 采集所有监控指标
|
// 采集所有监控指标
|
||||||
|
// 采集进程信息,返回CPU使用率较高的前N个进程
|
||||||
|
func collectProcessMetrics() ([]ProcessMetrics, error) {
|
||||||
|
// 只采集CPU使用率较高的前20个进程,避免性能问题
|
||||||
|
const maxProcesses = 20
|
||||||
|
|
||||||
|
// 获取所有进程ID
|
||||||
|
pids, err := process.Pids()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get process IDs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建进程信息切片
|
||||||
|
processes := make([]ProcessMetrics, 0, maxProcesses)
|
||||||
|
|
||||||
|
// 用于并发采集进程信息
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
errCh := make(chan error, len(pids))
|
||||||
|
|
||||||
|
// 限制并发数量
|
||||||
|
concurrencyLimit := 10
|
||||||
|
semaphore := make(chan struct{}, concurrencyLimit)
|
||||||
|
|
||||||
|
for _, pid := range pids {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
// 控制并发数量
|
||||||
|
semaphore <- struct{}{}
|
||||||
|
|
||||||
|
go func(pid int32) {
|
||||||
|
defer wg.Done()
|
||||||
|
defer func() { <-semaphore }()
|
||||||
|
|
||||||
|
// 获取进程信息
|
||||||
|
p, err := process.NewProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- nil // 忽略无法访问的进程
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取进程名
|
||||||
|
name, err := p.Name()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户名
|
||||||
|
username := ""
|
||||||
|
if uids, err := p.Uids(); err == nil && len(uids) > 0 {
|
||||||
|
// 简单实现,实际需要映射UID到用户名
|
||||||
|
username = strconv.Itoa(int(uids[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取CPU使用率
|
||||||
|
cpuPercent, err := p.CPUPercent()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取内存使用率
|
||||||
|
memInfo, err := p.MemoryInfo()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取系统总内存
|
||||||
|
vmStat, err := mem.VirtualMemory()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算内存使用率百分比
|
||||||
|
memPercent := float64(memInfo.RSS) / float64(vmStat.Total) * 100
|
||||||
|
|
||||||
|
// 获取进程路径
|
||||||
|
path, err := p.Exe()
|
||||||
|
if err != nil {
|
||||||
|
path = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取命令行
|
||||||
|
cmdline, err := p.Cmdline()
|
||||||
|
if err != nil {
|
||||||
|
cmdline = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取占用端口
|
||||||
|
ports := []int{}
|
||||||
|
if connections, err := p.Connections(); err == nil {
|
||||||
|
for _, conn := range connections {
|
||||||
|
// 只添加监听或已建立连接的端口
|
||||||
|
if conn.Status == "LISTEN" || conn.Status == "ESTABLISHED" {
|
||||||
|
ports = append(ports, int(conn.Laddr.Port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建进程信息
|
||||||
|
procMetric := ProcessMetrics{
|
||||||
|
Name: name,
|
||||||
|
Username: username,
|
||||||
|
PID: pid,
|
||||||
|
CPU: cpuPercent,
|
||||||
|
Memory: memPercent,
|
||||||
|
Path: path,
|
||||||
|
Cmdline: cmdline,
|
||||||
|
Ports: ports,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到切片
|
||||||
|
mu.Lock()
|
||||||
|
processes = append(processes, procMetric)
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
errCh <- nil
|
||||||
|
}(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有goroutine完成
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
|
||||||
|
// 检查是否有错误
|
||||||
|
for err := range errCh {
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: failed to collect process info: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据CPU使用率排序,取前N个
|
||||||
|
sort.Slice(processes, func(i, j int) bool {
|
||||||
|
return processes[i].CPU > processes[j].CPU
|
||||||
|
})
|
||||||
|
|
||||||
|
// 限制返回的进程数量
|
||||||
|
if len(processes) > maxProcesses {
|
||||||
|
processes = processes[:maxProcesses]
|
||||||
|
}
|
||||||
|
|
||||||
|
return processes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 采集磁盘详细信息
|
||||||
|
func collectDiskDetails() ([]DiskDetailMetrics, error) {
|
||||||
|
// 获取所有挂载点信息
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get disk partitions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建磁盘详细信息切片
|
||||||
|
diskDetails := make([]DiskDetailMetrics, 0, len(partitions))
|
||||||
|
|
||||||
|
for _, partition := range partitions {
|
||||||
|
// 获取磁盘使用情况
|
||||||
|
usage, err := disk.Usage(partition.Mountpoint)
|
||||||
|
if err != nil {
|
||||||
|
continue // 忽略无法访问的分区
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单实现,获取设备ID
|
||||||
|
deviceID := partition.Device
|
||||||
|
if len(deviceID) > 0 && deviceID[0] == '/' {
|
||||||
|
deviceID = deviceID[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备状态
|
||||||
|
status := "online"
|
||||||
|
|
||||||
|
// 设备类型
|
||||||
|
diskType := "unknown"
|
||||||
|
if partition.Fstype != "" {
|
||||||
|
diskType = partition.Fstype
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备大小(GB)
|
||||||
|
sizeGB := float64(usage.Total) / (1024 * 1024 * 1024)
|
||||||
|
|
||||||
|
// 设备型号 - 简化实现,实际需要更复杂的逻辑
|
||||||
|
model := partition.Device
|
||||||
|
|
||||||
|
// 接口类型 - 简化实现
|
||||||
|
interfaceType := "unknown"
|
||||||
|
if len(partition.Device) > 0 {
|
||||||
|
if partition.Device[:3] == "sda" || partition.Device[:3] == "sdb" {
|
||||||
|
interfaceType = "SATA"
|
||||||
|
} else if partition.Device[:3] == "nvme" {
|
||||||
|
interfaceType = "NVMe"
|
||||||
|
} else if partition.Device[:3] == "mmc" {
|
||||||
|
interfaceType = "MMC"
|
||||||
|
} else if partition.Device[:3] == "vda" || partition.Device[:3] == "vdb" {
|
||||||
|
interfaceType = "Virtual"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备描述
|
||||||
|
description := fmt.Sprintf("%s (%s)", partition.Device, partition.Fstype)
|
||||||
|
|
||||||
|
// 创建磁盘详细信息
|
||||||
|
diskDetail := DiskDetailMetrics{
|
||||||
|
DeviceID: deviceID,
|
||||||
|
Status: status,
|
||||||
|
Type: diskType,
|
||||||
|
SizeGB: sizeGB,
|
||||||
|
Model: model,
|
||||||
|
InterfaceType: interfaceType,
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
|
||||||
|
diskDetails = append(diskDetails, diskDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return diskDetails, nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectMetrics() (*Metrics, error) {
|
func collectMetrics() (*Metrics, error) {
|
||||||
metrics := &Metrics{}
|
metrics := &Metrics{}
|
||||||
|
|
||||||
|
// 初始化Network字段为非nil,避免空指针问题
|
||||||
|
metrics.Network = make(map[string]NetworkInterfaceMetrics)
|
||||||
|
|
||||||
|
// 设置设备信息
|
||||||
|
deviceID := config.DeviceID
|
||||||
|
if deviceID == "" {
|
||||||
|
deviceID = config.ID
|
||||||
|
}
|
||||||
|
metrics.DeviceID = deviceID
|
||||||
|
metrics.AgentID = config.ID
|
||||||
|
metrics.Name = config.Name
|
||||||
|
// 尝试获取本机IP地址
|
||||||
|
metrics.IP = getLocalIP()
|
||||||
|
|
||||||
// 采集CPU使用率和频率
|
// 采集CPU使用率和频率
|
||||||
cpuUsage, cpuHz, err := collectCPU()
|
cpuUsage, cpuHz, err := collectCPU()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to collect CPU metrics: %w", err)
|
// CPU采集失败时使用0值
|
||||||
|
log.Printf("Failed to collect CPU metrics: %v, using 0 values", err)
|
||||||
|
cpuUsage = 0
|
||||||
|
cpuHz = 0
|
||||||
}
|
}
|
||||||
metrics.CPU = cpuUsage
|
metrics.CPU = cpuUsage
|
||||||
metrics.CPUHz = cpuHz
|
metrics.CPUHz = cpuHz
|
||||||
@@ -433,24 +777,53 @@ func collectMetrics() (*Metrics, error) {
|
|||||||
// 采集内存使用率
|
// 采集内存使用率
|
||||||
memoryUsage, err := collectMemory()
|
memoryUsage, err := collectMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to collect memory metrics: %w", err)
|
// 内存采集失败时使用0值
|
||||||
|
log.Printf("Failed to collect memory metrics: %v, using 0 value", err)
|
||||||
|
memoryUsage = 0
|
||||||
}
|
}
|
||||||
metrics.Memory = memoryUsage
|
metrics.Memory = memoryUsage
|
||||||
|
|
||||||
// 采集磁盘使用率和总容量
|
// 采集磁盘使用率和总容量
|
||||||
diskMetricsMap, err := collectDisk()
|
diskMetricsMap, err := collectDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to collect disk metrics: %w", err)
|
// 磁盘采集失败时使用空map
|
||||||
|
log.Printf("Failed to collect disk metrics: %v, using empty map", err)
|
||||||
|
diskMetricsMap = make(map[string]DiskMetrics)
|
||||||
}
|
}
|
||||||
metrics.Disk = diskMetricsMap
|
metrics.Disk = diskMetricsMap
|
||||||
|
|
||||||
// 采集网络流量
|
// 采集磁盘详细信息
|
||||||
networkMetrics, err := collectNetwork()
|
diskDetails, err := collectDiskDetails()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to collect network metrics: %w", err)
|
// 磁盘详细信息采集失败时使用空切片
|
||||||
|
log.Printf("Failed to collect disk details: %v, using empty slice", err)
|
||||||
|
diskDetails = make([]DiskDetailMetrics, 0)
|
||||||
|
}
|
||||||
|
metrics.DiskDetails = diskDetails
|
||||||
|
|
||||||
|
// 采集进程信息
|
||||||
|
processes, err := collectProcessMetrics()
|
||||||
|
if err != nil {
|
||||||
|
// 进程信息采集失败时使用空切片
|
||||||
|
log.Printf("Failed to collect process metrics: %v, using empty slice", err)
|
||||||
|
processes = make([]ProcessMetrics, 0)
|
||||||
|
}
|
||||||
|
metrics.Processes = processes
|
||||||
|
|
||||||
|
// 采集网络流量
|
||||||
|
networkMetrics, rxTotal, txTotal, rxRate, txRate, err := collectNetwork()
|
||||||
|
if err != nil {
|
||||||
|
// 网络采集失败时使用0值(实际上collectNetwork已经处理了错误情况)
|
||||||
|
log.Printf("Failed to collect network metrics: %v, using 0 values", err)
|
||||||
|
networkMetrics = make(map[string]NetworkInterfaceMetrics)
|
||||||
|
rxTotal, txTotal, rxRate, txRate = 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
// 直接使用采集到的网卡流量
|
// 直接使用采集到的网卡流量
|
||||||
metrics.Network = networkMetrics
|
metrics.Network = networkMetrics
|
||||||
|
metrics.RxTotal = rxTotal
|
||||||
|
metrics.TxTotal = txTotal
|
||||||
|
metrics.RxRate = rxRate
|
||||||
|
metrics.TxRate = txRate
|
||||||
|
|
||||||
return metrics, nil
|
return metrics, nil
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
backend/backend
Executable file
BIN
backend/backend
Executable file
Binary file not shown.
@@ -3,7 +3,7 @@
|
|||||||
"port": 8080
|
"port": 8080
|
||||||
},
|
},
|
||||||
"influxdb": {
|
"influxdb": {
|
||||||
"url": "http://10.35.10.130:8066",
|
"url": "http://10.35.10.70:8066",
|
||||||
"token": "aVI5qMGz6e8d4pfyhVZNYfS5we7C8Bb-5bi-V7LpL3K6CmQyudauigoxDFv1UFo2Hvda7swKEqTe8eP6xy4jBw==",
|
"token": "aVI5qMGz6e8d4pfyhVZNYfS5we7C8Bb-5bi-V7LpL3K6CmQyudauigoxDFv1UFo2Hvda7swKEqTe8eP6xy4jBw==",
|
||||||
"org": "AMAZEHOME",
|
"org": "AMAZEHOME",
|
||||||
"bucket": "AMAZEHOME",
|
"bucket": "AMAZEHOME",
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ func RegisterRoutes(r *gin.Engine) {
|
|||||||
metrics.GET("/memory", GetMemoryMetrics)
|
metrics.GET("/memory", GetMemoryMetrics)
|
||||||
metrics.GET("/disk", GetDiskMetrics)
|
metrics.GET("/disk", GetDiskMetrics)
|
||||||
metrics.GET("/network", GetNetworkMetrics)
|
metrics.GET("/network", GetNetworkMetrics)
|
||||||
|
metrics.GET("/processes", GetProcessMetrics) // 添加进程信息查询端点
|
||||||
|
metrics.GET("/disk_details", GetDiskDetails) // 添加磁盘详细信息查询端点
|
||||||
// 添加POST端点,接收Agent发送的指标数据
|
// 添加POST端点,接收Agent发送的指标数据
|
||||||
metrics.POST("/", HandleMetricsPost)
|
metrics.POST("/", HandleMetricsPost)
|
||||||
}
|
}
|
||||||
@@ -73,6 +75,29 @@ type DiskMetrics struct {
|
|||||||
Total uint64 `json:"total"` // 总容量 (bytes)
|
Total uint64 `json:"total"` // 总容量 (bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessMetrics 进程监控指标
|
||||||
|
type ProcessMetrics struct {
|
||||||
|
Name string `json:"name"` // 进程名
|
||||||
|
Username string `json:"username"` // 用户名
|
||||||
|
PID int32 `json:"pid"` // 进程ID
|
||||||
|
CPU float64 `json:"cpu"` // CPU使用率
|
||||||
|
Memory float64 `json:"memory"` // 内存使用率
|
||||||
|
Path string `json:"path"` // 路径
|
||||||
|
Cmdline string `json:"cmdline"` // 命令行
|
||||||
|
Ports []int `json:"ports"` // 占用端口
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskDetailMetrics 磁盘详细信息
|
||||||
|
type DiskDetailMetrics struct {
|
||||||
|
DeviceID string `json:"device_id"` // 设备ID
|
||||||
|
Status string `json:"status"` // 设备状态
|
||||||
|
Type string `json:"type"` // 设备类型
|
||||||
|
SizeGB float64 `json:"size_gb"` // 设备大小(GB)
|
||||||
|
Model string `json:"model"` // 设备型号
|
||||||
|
InterfaceType string `json:"interface_type"` // 接口类型
|
||||||
|
Description string `json:"description"` // 设备描述
|
||||||
|
}
|
||||||
|
|
||||||
// NetworkInterfaceMetrics 网卡监控指标
|
// NetworkInterfaceMetrics 网卡监控指标
|
||||||
type NetworkInterfaceMetrics struct {
|
type NetworkInterfaceMetrics struct {
|
||||||
BytesSent uint64 `json:"bytes_sent"` // 发送速率 (bytes/s)
|
BytesSent uint64 `json:"bytes_sent"` // 发送速率 (bytes/s)
|
||||||
@@ -83,11 +108,17 @@ type NetworkInterfaceMetrics struct {
|
|||||||
|
|
||||||
// MetricsRequest 指标请求结构
|
// MetricsRequest 指标请求结构
|
||||||
type MetricsRequest struct {
|
type MetricsRequest struct {
|
||||||
CPU float64 `json:"cpu"`
|
CPU float64 `json:"cpu"`
|
||||||
CPUHz float64 `json:"cpu_hz"` // CPU频率 (MHz)
|
CPUHz float64 `json:"cpu_hz"` // CPU频率 (MHz)
|
||||||
Memory float64 `json:"memory"`
|
Memory float64 `json:"memory"`
|
||||||
Disk map[string]DiskMetrics `json:"disk"`
|
Disk map[string]DiskMetrics `json:"disk"`
|
||||||
Network map[string]NetworkInterfaceMetrics `json:"network"`
|
DiskDetails []DiskDetailMetrics `json:"disk_details"` // 磁盘详细信息
|
||||||
|
Network map[string]NetworkInterfaceMetrics `json:"network"`
|
||||||
|
Processes []ProcessMetrics `json:"processes"` // 进程信息
|
||||||
|
RxTotal uint64 `json:"rx_total"` // 所有网卡累计接收字节数总和
|
||||||
|
TxTotal uint64 `json:"tx_total"` // 所有网卡累计发送字节数总和
|
||||||
|
RxRate uint64 `json:"rx_rate"` // 所有网卡实时接收速率总和 (bytes/s)
|
||||||
|
TxRate uint64 `json:"tx_rate"` // 所有网卡实时发送速率总和 (bytes/s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleMetricsPost 处理Agent发送的指标数据
|
// HandleMetricsPost 处理Agent发送的指标数据
|
||||||
@@ -162,24 +193,28 @@ func HandleMetricsPost(c *gin.Context) {
|
|||||||
metricsList = append(metricsList, singleMetric)
|
metricsList = append(metricsList, singleMetric)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建单独的上下文用于InfluxDB写入,避免HTTP请求结束时上下文被取消
|
||||||
|
writeCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
// 处理所有指标
|
// 处理所有指标
|
||||||
for i, req := range metricsList {
|
for i, req := range metricsList {
|
||||||
// 写入CPU使用率指标
|
// 写入CPU使用率指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "cpu", req.CPU, baseTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "cpu", req.CPU, baseTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write CPU metrics: %v", err)
|
log.Printf("Warning: Failed to write CPU metrics: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入CPU频率指标(如果有)
|
// 写入CPU频率指标(如果有)
|
||||||
if req.CPUHz > 0 {
|
if req.CPUHz > 0 {
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "cpu_hz", req.CPUHz, baseTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "cpu_hz", req.CPUHz, baseTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write CPU Hz metrics: %v", err)
|
log.Printf("Warning: Failed to write CPU Hz metrics: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入内存指标
|
// 写入内存指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "memory", req.Memory, baseTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "memory", req.Memory, baseTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write memory metrics: %v", err)
|
log.Printf("Warning: Failed to write memory metrics: %v", err)
|
||||||
}
|
}
|
||||||
@@ -196,7 +231,7 @@ func HandleMetricsPost(c *gin.Context) {
|
|||||||
tags["mountpoint"] = mountpoint
|
tags["mountpoint"] = mountpoint
|
||||||
|
|
||||||
// 写入磁盘使用率指标
|
// 写入磁盘使用率指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "disk", diskMetrics.UsedPercent, tags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "disk", diskMetrics.UsedPercent, tags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write disk metrics for mountpoint %s: %v", mountpoint, err)
|
log.Printf("Warning: Failed to write disk metrics for mountpoint %s: %v", mountpoint, err)
|
||||||
}
|
}
|
||||||
@@ -206,6 +241,10 @@ func HandleMetricsPost(c *gin.Context) {
|
|||||||
var totalBytesSent, totalBytesReceived uint64
|
var totalBytesSent, totalBytesReceived uint64
|
||||||
var totalTxBytes, totalRxBytes uint64 // 累计总流量
|
var totalTxBytes, totalRxBytes uint64 // 累计总流量
|
||||||
for interfaceName, networkMetrics := range req.Network {
|
for interfaceName, networkMetrics := range req.Network {
|
||||||
|
// 跳过空名称的网卡
|
||||||
|
if interfaceName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// 为每个网卡创建标签,包含基础标签和网卡名称
|
// 为每个网卡创建标签,包含基础标签和网卡名称
|
||||||
interfaceTags := make(map[string]string)
|
interfaceTags := make(map[string]string)
|
||||||
// 复制基础标签
|
// 复制基础标签
|
||||||
@@ -216,25 +255,25 @@ func HandleMetricsPost(c *gin.Context) {
|
|||||||
interfaceTags["interface"] = interfaceName
|
interfaceTags["interface"] = interfaceName
|
||||||
|
|
||||||
// 写入网络发送速率指标
|
// 写入网络发送速率指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "network_sent", float64(networkMetrics.BytesSent), interfaceTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "network_sent", float64(networkMetrics.BytesSent), interfaceTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write network sent metrics for interface %s: %v", interfaceName, err)
|
log.Printf("Warning: Failed to write network sent metrics for interface %s: %v", interfaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入网络接收速率指标
|
// 写入网络接收速率指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "network_received", float64(networkMetrics.BytesReceived), interfaceTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "network_received", float64(networkMetrics.BytesReceived), interfaceTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write network received metrics for interface %s: %v", interfaceName, err)
|
log.Printf("Warning: Failed to write network received metrics for interface %s: %v", interfaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入累计发送字节数指标
|
// 写入累计发送字节数指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "network_tx_bytes", float64(networkMetrics.TxBytes), interfaceTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "network_tx_bytes", float64(networkMetrics.TxBytes), interfaceTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write network tx_bytes metrics for interface %s: %v", interfaceName, err)
|
log.Printf("Warning: Failed to write network tx_bytes metrics for interface %s: %v", interfaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入累计接收字节数指标
|
// 写入累计接收字节数指标
|
||||||
if err := globalStorage.WriteMetric(c.Request.Context(), deviceID, "network_rx_bytes", float64(networkMetrics.RxBytes), interfaceTags); err != nil {
|
if err := globalStorage.WriteMetric(writeCtx, deviceID, "network_rx_bytes", float64(networkMetrics.RxBytes), interfaceTags); err != nil {
|
||||||
// 只记录警告,不影响后续指标处理
|
// 只记录警告,不影响后续指标处理
|
||||||
log.Printf("Warning: Failed to write network rx_bytes metrics for interface %s: %v", interfaceName, err)
|
log.Printf("Warning: Failed to write network rx_bytes metrics for interface %s: %v", interfaceName, err)
|
||||||
}
|
}
|
||||||
@@ -248,6 +287,22 @@ func HandleMetricsPost(c *gin.Context) {
|
|||||||
totalRxBytes += networkMetrics.RxBytes
|
totalRxBytes += networkMetrics.RxBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 写入进程信息
|
||||||
|
for _, proc := range req.Processes {
|
||||||
|
if err := globalStorage.WriteProcessMetric(writeCtx, deviceID, proc.Name, proc.Username, proc.PID, proc.CPU, proc.Memory, proc.Path, proc.Cmdline, proc.Ports, baseTags); err != nil {
|
||||||
|
// 只记录警告,不影响后续指标处理
|
||||||
|
log.Printf("Warning: Failed to write process metrics for PID %d: %v", proc.PID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入磁盘详细信息
|
||||||
|
for _, diskDetail := range req.DiskDetails {
|
||||||
|
if err := globalStorage.WriteDiskDetailMetric(writeCtx, deviceID, diskDetail.DeviceID, diskDetail.Status, diskDetail.Type, diskDetail.SizeGB, diskDetail.Model, diskDetail.InterfaceType, diskDetail.Description, baseTags); err != nil {
|
||||||
|
// 只记录警告,不影响后续指标处理
|
||||||
|
log.Printf("Warning: Failed to write disk details for device %s: %v", diskDetail.DeviceID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 广播指标更新消息,只广播最后一个指标
|
// 广播指标更新消息,只广播最后一个指标
|
||||||
if i == len(metricsList)-1 {
|
if i == len(metricsList)-1 {
|
||||||
// 准备广播的磁盘使用率数据(兼容旧格式)
|
// 准备广播的磁盘使用率数据(兼容旧格式)
|
||||||
@@ -412,7 +467,7 @@ func GetCPUMetrics(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理数据,传递interval、startTime和endTime参数
|
// 处理数据,传递interval、startTime和endTime参数
|
||||||
processedData := ProcessMetrics(points, aggregation, interval, startTime, endTime)
|
processedData := ProcessMetricData(points, aggregation, interval, startTime, endTime)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": processedData,
|
"data": processedData,
|
||||||
@@ -440,7 +495,7 @@ func GetMemoryMetrics(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理数据,传递interval、startTime和endTime参数
|
// 处理数据,传递interval、startTime和endTime参数
|
||||||
processedData := ProcessMetrics(points, aggregation, interval, startTime, endTime)
|
processedData := ProcessMetricData(points, aggregation, interval, startTime, endTime)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"data": processedData,
|
"data": processedData,
|
||||||
@@ -481,7 +536,7 @@ func GetDiskMetrics(c *gin.Context) {
|
|||||||
// 处理数据,为每个挂载点创建独立的数据集
|
// 处理数据,为每个挂载点创建独立的数据集
|
||||||
result := make(map[string][]MetricData)
|
result := make(map[string][]MetricData)
|
||||||
for mountpoint, mountpointPoints := range mountpointData {
|
for mountpoint, mountpointPoints := range mountpointData {
|
||||||
processedData := ProcessMetrics(mountpointPoints, aggregation, interval, startTime, endTime)
|
processedData := ProcessMetricData(mountpointPoints, aggregation, interval, startTime, endTime)
|
||||||
result[mountpoint] = processedData
|
result[mountpoint] = processedData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,10 +554,14 @@ func GetNetworkMetrics(c *gin.Context) {
|
|||||||
aggregation := c.DefaultQuery("aggregation", "average")
|
aggregation := c.DefaultQuery("aggregation", "average")
|
||||||
interval := c.DefaultQuery("interval", "10s") // 添加interval参数,默认10秒
|
interval := c.DefaultQuery("interval", "10s") // 添加interval参数,默认10秒
|
||||||
|
|
||||||
// 查询发送和接收的网络指标
|
// 查询发送和接收的网络速率指标
|
||||||
sentPoints, err1 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_sent", startTime, endTime)
|
sentPoints, err1 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_sent", startTime, endTime)
|
||||||
receivedPoints, err2 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_received", startTime, endTime)
|
receivedPoints, err2 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_received", startTime, endTime)
|
||||||
|
|
||||||
|
// 查询发送和接收的累积总流量指标
|
||||||
|
txBytesPoints, err3 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_total_tx_bytes", startTime, endTime)
|
||||||
|
rxBytesPoints, err4 := globalStorage.QueryMetrics(context.Background(), deviceID, "network_total_rx_bytes", startTime, endTime)
|
||||||
|
|
||||||
// 处理错误
|
// 处理错误
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
log.Printf("Warning: Failed to query network sent metrics: %v", err1)
|
log.Printf("Warning: Failed to query network sent metrics: %v", err1)
|
||||||
@@ -512,12 +571,24 @@ func GetNetworkMetrics(c *gin.Context) {
|
|||||||
log.Printf("Warning: Failed to query network received metrics: %v", err2)
|
log.Printf("Warning: Failed to query network received metrics: %v", err2)
|
||||||
receivedPoints = []storage.MetricPoint{}
|
receivedPoints = []storage.MetricPoint{}
|
||||||
}
|
}
|
||||||
|
if err3 != nil {
|
||||||
|
log.Printf("Warning: Failed to query network_total_tx_bytes metrics: %v", err3)
|
||||||
|
txBytesPoints = []storage.MetricPoint{}
|
||||||
|
}
|
||||||
|
if err4 != nil {
|
||||||
|
log.Printf("Warning: Failed to query network_total_rx_bytes metrics: %v", err4)
|
||||||
|
rxBytesPoints = []storage.MetricPoint{}
|
||||||
|
}
|
||||||
|
|
||||||
// 按网卡名称分组发送和接收的指标
|
// 按网卡名称分组发送和接收的速率指标
|
||||||
sentByInterface := make(map[string][]storage.MetricPoint)
|
sentByInterface := make(map[string][]storage.MetricPoint)
|
||||||
receivedByInterface := make(map[string][]storage.MetricPoint)
|
receivedByInterface := make(map[string][]storage.MetricPoint)
|
||||||
|
|
||||||
// 分组发送的网络指标
|
// 按网卡名称分组发送和接收的累积总流量指标
|
||||||
|
txBytesByInterface := make(map[string][]storage.MetricPoint)
|
||||||
|
rxBytesByInterface := make(map[string][]storage.MetricPoint)
|
||||||
|
|
||||||
|
// 分组发送的网络速率指标
|
||||||
for _, point := range sentPoints {
|
for _, point := range sentPoints {
|
||||||
// 获取网卡名称,默认使用"all"表示所有网卡
|
// 获取网卡名称,默认使用"all"表示所有网卡
|
||||||
interfaceName := point.Tags["interface"]
|
interfaceName := point.Tags["interface"]
|
||||||
@@ -527,7 +598,7 @@ func GetNetworkMetrics(c *gin.Context) {
|
|||||||
sentByInterface[interfaceName] = append(sentByInterface[interfaceName], point)
|
sentByInterface[interfaceName] = append(sentByInterface[interfaceName], point)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分组接收的网络指标
|
// 分组接收的网络速率指标
|
||||||
for _, point := range receivedPoints {
|
for _, point := range receivedPoints {
|
||||||
// 获取网卡名称,默认使用"all"表示所有网卡
|
// 获取网卡名称,默认使用"all"表示所有网卡
|
||||||
interfaceName := point.Tags["interface"]
|
interfaceName := point.Tags["interface"]
|
||||||
@@ -537,6 +608,26 @@ func GetNetworkMetrics(c *gin.Context) {
|
|||||||
receivedByInterface[interfaceName] = append(receivedByInterface[interfaceName], point)
|
receivedByInterface[interfaceName] = append(receivedByInterface[interfaceName], point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分组发送的累积总流量指标
|
||||||
|
for _, point := range txBytesPoints {
|
||||||
|
// 获取网卡名称,默认使用"all"表示所有网卡
|
||||||
|
interfaceName := point.Tags["interface"]
|
||||||
|
if interfaceName == "" {
|
||||||
|
interfaceName = "all"
|
||||||
|
}
|
||||||
|
txBytesByInterface[interfaceName] = append(txBytesByInterface[interfaceName], point)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分组接收的累积总流量指标
|
||||||
|
for _, point := range rxBytesPoints {
|
||||||
|
// 获取网卡名称,默认使用"all"表示所有网卡
|
||||||
|
interfaceName := point.Tags["interface"]
|
||||||
|
if interfaceName == "" {
|
||||||
|
interfaceName = "all"
|
||||||
|
}
|
||||||
|
rxBytesByInterface[interfaceName] = append(rxBytesByInterface[interfaceName], point)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理数据,为每个网卡创建独立的数据集
|
// 处理数据,为每个网卡创建独立的数据集
|
||||||
result := make(map[string]map[string][]MetricData)
|
result := make(map[string]map[string][]MetricData)
|
||||||
|
|
||||||
@@ -548,21 +639,37 @@ func GetNetworkMetrics(c *gin.Context) {
|
|||||||
for iface := range receivedByInterface {
|
for iface := range receivedByInterface {
|
||||||
allInterfaces[iface] = true
|
allInterfaces[iface] = true
|
||||||
}
|
}
|
||||||
|
for iface := range txBytesByInterface {
|
||||||
|
allInterfaces[iface] = true
|
||||||
|
}
|
||||||
|
for iface := range rxBytesByInterface {
|
||||||
|
allInterfaces[iface] = true
|
||||||
|
}
|
||||||
|
|
||||||
// 为每个网卡处理数据
|
// 为每个网卡处理数据
|
||||||
for iface := range allInterfaces {
|
for iface := range allInterfaces {
|
||||||
// 获取该网卡的发送和接收指标
|
// 获取该网卡的速率指标
|
||||||
ifaceSentPoints := sentByInterface[iface]
|
ifaceSentPoints := sentByInterface[iface]
|
||||||
ifaceReceivedPoints := receivedByInterface[iface]
|
ifaceReceivedPoints := receivedByInterface[iface]
|
||||||
|
|
||||||
// 处理数据
|
// 获取该网卡的累积总流量指标
|
||||||
processedSentData := ProcessMetrics(ifaceSentPoints, aggregation, interval, startTime, endTime)
|
ifaceTxBytesPoints := txBytesByInterface[iface]
|
||||||
processedReceivedData := ProcessMetrics(ifaceReceivedPoints, aggregation, interval, startTime, endTime)
|
ifaceRxBytesPoints := rxBytesByInterface[iface]
|
||||||
|
|
||||||
|
// 处理速率数据
|
||||||
|
processedSentData := ProcessMetricData(ifaceSentPoints, aggregation, interval, startTime, endTime)
|
||||||
|
processedReceivedData := ProcessMetricData(ifaceReceivedPoints, aggregation, interval, startTime, endTime)
|
||||||
|
|
||||||
|
// 处理累积总流量数据
|
||||||
|
processedTxBytesData := ProcessMetricData(ifaceTxBytesPoints, aggregation, interval, startTime, endTime)
|
||||||
|
processedRxBytesData := ProcessMetricData(ifaceRxBytesPoints, aggregation, interval, startTime, endTime)
|
||||||
|
|
||||||
// 保存结果
|
// 保存结果
|
||||||
result[iface] = map[string][]MetricData{
|
result[iface] = map[string][]MetricData{
|
||||||
"sent": processedSentData,
|
"sent": processedSentData, // 发送速率数据
|
||||||
"received": processedReceivedData,
|
"received": processedReceivedData, // 接收速率数据
|
||||||
|
"tx_bytes": processedTxBytesData, // 发送累积总流量数据
|
||||||
|
"rx_bytes": processedRxBytesData, // 接收累积总流量数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,3 +913,49 @@ func GetAllDevices(c *gin.Context) {
|
|||||||
"devices": devices,
|
"devices": devices,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProcessMetrics 获取进程指标
|
||||||
|
func GetProcessMetrics(c *gin.Context) {
|
||||||
|
// 获取查询参数
|
||||||
|
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
||||||
|
startTime := c.DefaultQuery("start_time", "-1h")
|
||||||
|
endTime := c.DefaultQuery("end_time", "now()")
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
processes, err := globalStorage.QueryProcessMetrics(context.Background(), deviceID, startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
// 只记录警告,返回空数据
|
||||||
|
log.Printf("Warning: Failed to query process metrics: %v", err)
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": []map[string]interface{}{},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": processes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiskDetails 获取磁盘详细信息
|
||||||
|
func GetDiskDetails(c *gin.Context) {
|
||||||
|
// 获取查询参数
|
||||||
|
deviceID := c.Query("device_id") // 不使用默认值,空值表示查询所有设备
|
||||||
|
startTime := c.DefaultQuery("start_time", "-1h")
|
||||||
|
endTime := c.DefaultQuery("end_time", "now()")
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
diskDetails, err := globalStorage.QueryDiskDetails(context.Background(), deviceID, startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
// 只记录警告,返回空数据
|
||||||
|
log.Printf("Warning: Failed to query disk details: %v", err)
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": []map[string]interface{}{},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": diskDetails,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ func FormatTimeByInterval(t time.Time, intervalSeconds int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessMetrics 处理监控数据,支持动态时间区间
|
// ProcessMetricData 处理监控数据,支持动态时间区间
|
||||||
func ProcessMetrics(points []storage.MetricPoint, aggregation string, intervalStr string, startTime, endTime string) []MetricData {
|
func ProcessMetricData(points []storage.MetricPoint, aggregation string, intervalStr string, startTime, endTime string) []MetricData {
|
||||||
// 解析时间区间
|
// 解析时间区间
|
||||||
intervalSeconds, err := ParseInterval(intervalStr)
|
intervalSeconds, err := ParseInterval(intervalStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,6 +12,28 @@ import (
|
|||||||
"github.com/monitor/backend/config"
|
"github.com/monitor/backend/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// formatTags 将标签映射格式化为InfluxDB行协议格式
|
||||||
|
func formatTags(tags map[string]string) string {
|
||||||
|
var tagList []string
|
||||||
|
for k, v := range tags {
|
||||||
|
// 跳过空值的标签,避免InfluxDB解析错误
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tagList = append(tagList, fmt.Sprintf("%s=%s", k, escapeTagValue(v)))
|
||||||
|
}
|
||||||
|
return strings.Join(tagList, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeTagValue 转义标签值中的特殊字符
|
||||||
|
func escapeTagValue(value string) string {
|
||||||
|
// 替换逗号、空格和等号为转义后的形式
|
||||||
|
escaped := strings.ReplaceAll(value, ",", "\\,")
|
||||||
|
escaped = strings.ReplaceAll(escaped, " ", "\\ ")
|
||||||
|
escaped = strings.ReplaceAll(escaped, "=", "\\=")
|
||||||
|
return escaped
|
||||||
|
}
|
||||||
|
|
||||||
// MetricPoint 自定义监控指标点
|
// MetricPoint 自定义监控指标点
|
||||||
type MetricPoint struct {
|
type MetricPoint struct {
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
@@ -39,8 +63,10 @@ func NewStorage(cfg *config.Config) *Storage {
|
|||||||
client = influxdb2.NewClient(cfg.InfluxDB.URL, "")
|
client = influxdb2.NewClient(cfg.InfluxDB.URL, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 配置InfluxDB客户端选项
|
||||||
|
options := client.Options()
|
||||||
// 禁用InfluxDB客户端的调试日志
|
// 禁用InfluxDB客户端的调试日志
|
||||||
client.Options().SetLogLevel(0)
|
options.SetLogLevel(0)
|
||||||
|
|
||||||
return &Storage{
|
return &Storage{
|
||||||
client: client,
|
client: client,
|
||||||
@@ -54,10 +80,70 @@ func (s *Storage) Close() {
|
|||||||
s.client.Close()
|
s.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMetric 写入监控指标
|
// 写入数据到InfluxDB,带重试机制
|
||||||
func (s *Storage) WriteMetric(ctx context.Context, deviceID, metricType string, value float64, tags map[string]string) error {
|
func (s *Storage) writeData(ctx context.Context, measurement string, tags map[string]string, fields map[string]interface{}, deviceID, metricType string) error {
|
||||||
writeAPI := s.client.WriteAPIBlocking(s.org, s.bucket)
|
// 重试配置 - 减少重试次数和延迟,确保在超时时间内完成
|
||||||
|
maxRetries := 2
|
||||||
|
baseDelay := 200 * time.Millisecond
|
||||||
|
|
||||||
|
for i := 0; i <= maxRetries; i++ {
|
||||||
|
// 如果上下文已取消,直接返回
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据点
|
||||||
|
writeAPI := s.client.WriteAPIBlocking(s.org, s.bucket)
|
||||||
|
// 构建行协议字符串
|
||||||
|
var fieldList []string
|
||||||
|
for k, v := range fields {
|
||||||
|
var fieldStr string
|
||||||
|
// 根据字段类型格式化
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
fieldStr = fmt.Sprintf("%s=%q", k, v)
|
||||||
|
case float64, int, int32, int64:
|
||||||
|
fieldStr = fmt.Sprintf("%s=%v", k, v)
|
||||||
|
case bool:
|
||||||
|
fieldStr = fmt.Sprintf("%s=%t", k, v)
|
||||||
|
default:
|
||||||
|
// 转换为字符串
|
||||||
|
fieldStr = fmt.Sprintf("%s=%q", k, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
fieldList = append(fieldList, fieldStr)
|
||||||
|
}
|
||||||
|
line := fmt.Sprintf("%s,%s %s %d", measurement, formatTags(tags), strings.Join(fieldList, ","), time.Now().UnixNano())
|
||||||
|
err := writeAPI.WriteRecord(ctx, line)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// 写入成功,直接返回
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是最后一次重试,返回错误
|
||||||
|
if i == maxRetries {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算重试延迟(指数退避)
|
||||||
|
delay := baseDelay*time.Duration(1<<i) + time.Duration(rand.Intn(50))*time.Millisecond
|
||||||
|
log.Printf("Warning: InfluxDB write failed for device %s, metric %s, retrying in %v... (Attempt %d/%d)\nError: %v", deviceID, metricType, delay, i+1, maxRetries, err)
|
||||||
|
|
||||||
|
// 等待重试
|
||||||
|
select {
|
||||||
|
case <-time.After(delay):
|
||||||
|
// 继续重试
|
||||||
|
case <-ctx.Done():
|
||||||
|
// 上下文取消,返回错误
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMetric 写入监控指标,带重试机制
|
||||||
|
func (s *Storage) WriteMetric(ctx context.Context, deviceID, metricType string, value float64, tags map[string]string) error {
|
||||||
// 创建标签映射,合并原有标签和新标签
|
// 创建标签映射,合并原有标签和新标签
|
||||||
allTags := make(map[string]string)
|
allTags := make(map[string]string)
|
||||||
// 复制原有标签
|
// 复制原有标签
|
||||||
@@ -69,18 +155,77 @@ func (s *Storage) WriteMetric(ctx context.Context, deviceID, metricType string,
|
|||||||
// 添加指标类型标签
|
// 添加指标类型标签
|
||||||
allTags["type"] = metricType
|
allTags["type"] = metricType
|
||||||
|
|
||||||
// 创建数据点
|
// 创建字段映射
|
||||||
point := influxdb2.NewPoint(
|
fields := map[string]interface{}{
|
||||||
"metrics",
|
"value": value,
|
||||||
allTags,
|
}
|
||||||
map[string]interface{}{
|
|
||||||
"value": value,
|
|
||||||
},
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 写入数据点
|
// 使用新的writeData方法
|
||||||
return writeAPI.WritePoint(ctx, point)
|
return s.writeData(ctx, "metrics", allTags, fields, deviceID, metricType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteProcessMetric 写入进程指标
|
||||||
|
func (s *Storage) WriteProcessMetric(ctx context.Context, deviceID string, processName, username string, pid int32, cpu, memory float64, path, cmdline string, ports []int, tags map[string]string) error {
|
||||||
|
// 创建标签映射,合并原有标签和新标签
|
||||||
|
allTags := make(map[string]string)
|
||||||
|
// 复制原有标签
|
||||||
|
for k, v := range tags {
|
||||||
|
allTags[k] = v
|
||||||
|
}
|
||||||
|
// 添加设备ID标签
|
||||||
|
allTags["device_id"] = deviceID
|
||||||
|
// 添加进程相关标签
|
||||||
|
allTags["process_name"] = processName
|
||||||
|
allTags["username"] = username
|
||||||
|
allTags["pid"] = fmt.Sprintf("%d", pid)
|
||||||
|
|
||||||
|
// 处理端口标签,只取前5个端口
|
||||||
|
portsStr := make([]string, 0, len(ports))
|
||||||
|
for i, port := range ports {
|
||||||
|
if i >= 5 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
portsStr = append(portsStr, fmt.Sprintf("%d", port))
|
||||||
|
}
|
||||||
|
allTags["ports"] = strings.Join(portsStr, ",")
|
||||||
|
|
||||||
|
// 创建字段映射
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"cpu_usage": cpu,
|
||||||
|
"memory_usage": memory,
|
||||||
|
"path": path,
|
||||||
|
"cmdline": cmdline,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用新的writeData方法
|
||||||
|
return s.writeData(ctx, "processes", allTags, fields, deviceID, "process")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteDiskDetailMetric 写入磁盘详细信息
|
||||||
|
func (s *Storage) WriteDiskDetailMetric(ctx context.Context, deviceID, diskDeviceID, status, diskType string, sizeGB float64, model, interfaceType, description string, tags map[string]string) error {
|
||||||
|
// 创建标签映射,合并原有标签和新标签
|
||||||
|
allTags := make(map[string]string)
|
||||||
|
// 复制原有标签
|
||||||
|
for k, v := range tags {
|
||||||
|
allTags[k] = v
|
||||||
|
}
|
||||||
|
// 添加设备ID标签
|
||||||
|
allTags["device_id"] = deviceID
|
||||||
|
// 添加磁盘相关标签
|
||||||
|
allTags["disk_id"] = diskDeviceID
|
||||||
|
allTags["status"] = status
|
||||||
|
allTags["type"] = diskType
|
||||||
|
allTags["model"] = model
|
||||||
|
allTags["interface_type"] = interfaceType
|
||||||
|
|
||||||
|
// 创建字段映射
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"size_gb": sizeGB,
|
||||||
|
"description": description,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用新的writeData方法
|
||||||
|
return s.writeData(ctx, "disk_details", allTags, fields, deviceID, "disk_detail")
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryMetrics 查询监控指标
|
// QueryMetrics 查询监控指标
|
||||||
@@ -315,3 +460,132 @@ func (s *Storage) QueryDeviceStatus(ctx context.Context, deviceID string) (strin
|
|||||||
|
|
||||||
return agentName, status, nil
|
return agentName, status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryProcessMetrics 查询进程指标
|
||||||
|
func (s *Storage) QueryProcessMetrics(ctx context.Context, deviceID string, startTime, endTime string) ([]map[string]interface{}, error) {
|
||||||
|
queryAPI := s.client.QueryAPI(s.org)
|
||||||
|
|
||||||
|
// 构建查询语句
|
||||||
|
query := `from(bucket: "` + s.bucket + `")
|
||||||
|
|> range(start: ` + startTime + `, stop: ` + endTime + `)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "processes")`
|
||||||
|
|
||||||
|
// 如果指定了设备ID,添加设备ID过滤
|
||||||
|
if deviceID != "" {
|
||||||
|
query += `
|
||||||
|
|> filter(fn: (r) => r["device_id"] == "` + deviceID + `")`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最新的进程数据
|
||||||
|
query += `
|
||||||
|
|> last()`
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
queryResult, err := queryAPI.Query(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer queryResult.Close()
|
||||||
|
|
||||||
|
// 存储进程数据
|
||||||
|
processes := make([]map[string]interface{}, 0)
|
||||||
|
|
||||||
|
// 处理查询结果
|
||||||
|
for queryResult.Next() {
|
||||||
|
if queryResult.TableChanged() {
|
||||||
|
// 表结构变化,跳过
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取记录
|
||||||
|
record := queryResult.Record()
|
||||||
|
|
||||||
|
// 构建进程数据
|
||||||
|
processData := map[string]interface{}{
|
||||||
|
"time": record.Time(),
|
||||||
|
"device_id": record.ValueByKey("device_id"),
|
||||||
|
"process_name": record.ValueByKey("process_name"),
|
||||||
|
"username": record.ValueByKey("username"),
|
||||||
|
"pid": record.ValueByKey("pid"),
|
||||||
|
"cpu_usage": record.ValueByKey("cpu_usage"),
|
||||||
|
"memory_usage": record.ValueByKey("memory_usage"),
|
||||||
|
"path": record.ValueByKey("path"),
|
||||||
|
"cmdline": record.ValueByKey("cmdline"),
|
||||||
|
"ports": record.ValueByKey("ports"),
|
||||||
|
"agent_name": record.ValueByKey("agent_name"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到进程列表
|
||||||
|
processes = append(processes, processData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryResult.Err() != nil {
|
||||||
|
return nil, queryResult.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return processes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryDiskDetails 查询磁盘详细信息
|
||||||
|
func (s *Storage) QueryDiskDetails(ctx context.Context, deviceID string, startTime, endTime string) ([]map[string]interface{}, error) {
|
||||||
|
queryAPI := s.client.QueryAPI(s.org)
|
||||||
|
|
||||||
|
// 构建查询语句
|
||||||
|
query := `from(bucket: "` + s.bucket + `")
|
||||||
|
|> range(start: ` + startTime + `, stop: ` + endTime + `)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "disk_details")`
|
||||||
|
|
||||||
|
// 如果指定了设备ID,添加设备ID过滤
|
||||||
|
if deviceID != "" {
|
||||||
|
query += `
|
||||||
|
|> filter(fn: (r) => r["device_id"] == "` + deviceID + `")`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最新的磁盘详细信息
|
||||||
|
query += `
|
||||||
|
|> last()`
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
queryResult, err := queryAPI.Query(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer queryResult.Close()
|
||||||
|
|
||||||
|
// 存储磁盘详细信息
|
||||||
|
diskDetails := make([]map[string]interface{}, 0)
|
||||||
|
|
||||||
|
// 处理查询结果
|
||||||
|
for queryResult.Next() {
|
||||||
|
if queryResult.TableChanged() {
|
||||||
|
// 表结构变化,跳过
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取记录
|
||||||
|
record := queryResult.Record()
|
||||||
|
|
||||||
|
// 构建磁盘详细信息
|
||||||
|
diskData := map[string]interface{}{
|
||||||
|
"time": record.Time(),
|
||||||
|
"device_id": record.ValueByKey("device_id"),
|
||||||
|
"disk_id": record.ValueByKey("disk_id"),
|
||||||
|
"status": record.ValueByKey("status"),
|
||||||
|
"type": record.ValueByKey("type"),
|
||||||
|
"size_gb": record.ValueByKey("size_gb"),
|
||||||
|
"model": record.ValueByKey("model"),
|
||||||
|
"interface_type": record.ValueByKey("interface_type"),
|
||||||
|
"description": record.ValueByKey("description"),
|
||||||
|
"agent_name": record.ValueByKey("agent_name"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到磁盘详细信息列表
|
||||||
|
diskDetails = append(diskDetails, diskData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryResult.Err() != nil {
|
||||||
|
return nil, queryResult.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return diskDetails, nil
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -254,19 +254,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 网络 状态卡片 -->
|
<!-- 网络流量 状态卡片 -->
|
||||||
<div class="bg-white rounded-xl shadow-md p-6 card-hover border border-gray-100">
|
<div class="bg-white rounded-xl shadow-md p-6 card-hover border border-gray-100">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-500 font-medium mb-1">网络流量</p>
|
<p class="text-sm text-gray-500 font-medium mb-1">网络流量</p>
|
||||||
<h3 id="networkValue" class="text-3xl font-bold text-gray-900 metric-value">0.0</h3>
|
<h3 id="networkValue" class="text-3xl font-bold text-gray-900 metric-value">0.00 ↓</h3>
|
||||||
<p id="networkDetails" class="text-xs text-gray-500 mt-1">接收: 0 MB/s | 发送: 0 MB/s<br>总量: 接收 0 MB | 发送 0 MB</p>
|
<p id="networkDetails" class="text-xs text-gray-500 mt-1">接收速率 0.00 MB | 发送速率 0.00 MB<br>接收总量 0.00 MB | 发送总量 0.00 MB</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-purple-100 p-3 rounded-full">
|
<div class="bg-purple-100 p-3 rounded-full">
|
||||||
<i class="fa fa-exchange-alt text-purple-600 text-xl"></i>
|
<i class="fa fa-network-wired text-purple-600 text-xl"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图表选项卡导航 -->
|
<!-- 图表选项卡导航 -->
|
||||||
@@ -289,12 +290,16 @@
|
|||||||
</button>
|
</button>
|
||||||
<!-- 网络 选项卡 -->
|
<!-- 网络 选项卡 -->
|
||||||
<button class="chart-tab px-4 py-2 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:bg-gray-50 transition-colors" data-tab="network">
|
<button class="chart-tab px-4 py-2 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:bg-gray-50 transition-colors" data-tab="network">
|
||||||
网络
|
流量
|
||||||
</button>
|
</button>
|
||||||
<!-- 网速 选项卡 -->
|
<!-- 网速 选项卡 -->
|
||||||
<button class="chart-tab px-4 py-2 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:bg-gray-50 transition-colors" data-tab="speed">
|
<button class="chart-tab px-4 py-2 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:bg-gray-50 transition-colors" data-tab="speed">
|
||||||
网速
|
网速
|
||||||
</button>
|
</button>
|
||||||
|
<!-- 系统日志 选项卡 -->
|
||||||
|
<button class="chart-tab px-4 py-2 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:bg-gray-50 transition-colors" data-tab="logs">
|
||||||
|
系统日志
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- 网卡选择和刷新按钮 -->
|
<!-- 网卡选择和刷新按钮 -->
|
||||||
<div id="interfaceSelectorContainer" class="flex items-center gap-1 ml-4 hidden">
|
<div id="interfaceSelectorContainer" class="flex items-center gap-1 ml-4 hidden">
|
||||||
@@ -315,6 +320,12 @@
|
|||||||
<label for="autoRefreshToggle" class="toggle-label block overflow-hidden h-5 rounded-full bg-gray-300 cursor-pointer"></label>
|
<label for="autoRefreshToggle" class="toggle-label block overflow-hidden h-5 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 刷新状态指示器 -->
|
||||||
|
<div class="flex items-center gap-1 ml-4">
|
||||||
|
<div id="refreshStatusIndicator" class="w-2 h-2 bg-red-500 rounded-full animate-pulse"></div>
|
||||||
|
<span id="lastRefreshTime" class="text-xs text-gray-500">上次刷新: 刚刚</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 自定义时间选择 -->
|
<!-- 自定义时间选择 -->
|
||||||
@@ -333,43 +344,161 @@
|
|||||||
<!-- CPU 图表 -->
|
<!-- CPU 图表 -->
|
||||||
<div id="cpuChartContainer" class="chart-container h-80">
|
<div id="cpuChartContainer" class="chart-container h-80">
|
||||||
<canvas id="cpuChart"></canvas>
|
<canvas id="cpuChart"></canvas>
|
||||||
|
<!-- 缩放控件 -->
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<div class="text-sm text-gray-500">当前显示范围: <span id="cpuCurrentTimeRangeDisplay">过去1小时</span></div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="cpuResetZoomBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-refresh"></i> 重置
|
||||||
|
</button>
|
||||||
|
<button id="cpuZoomOutBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-minus"></i> 缩小
|
||||||
|
</button>
|
||||||
|
<button id="cpuZoomInBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-plus"></i> 放大
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进程信息展示 -->
|
||||||
|
<div id="processInfoContainer" class="mt-8">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">进程信息</h3>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table id="processTable" class="min-w-full bg-white rounded-lg overflow-hidden shadow-md">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">进程名</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户名</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">进程ID</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CPU (%)</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">内存 (%)</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">路径</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">命令行</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">占用端口</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="processTableBody" class="bg-white divide-y divide-gray-200">
|
||||||
|
<!-- 进程信息将通过JavaScript动态添加 -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- 进程信息分页容器 -->
|
||||||
|
<div id="processPaginationContainer" class="mt-4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内存 图表 -->
|
<!-- 内存 图表 -->
|
||||||
<div id="memoryChartContainer" class="chart-container h-80 hidden">
|
<div id="memoryChartContainer" class="chart-container h-80 hidden">
|
||||||
<canvas id="memoryChart"></canvas>
|
<canvas id="memoryChart"></canvas>
|
||||||
|
<!-- 缩放控件 -->
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<div class="text-sm text-gray-500">当前显示范围: <span id="currentTimeRangeDisplay">过去1小时</span></div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="memoryResetZoomBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-refresh"></i> 重置
|
||||||
|
</button>
|
||||||
|
<button id="memoryZoomOutBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-minus"></i> 缩小
|
||||||
|
</button>
|
||||||
|
<button id="memoryZoomInBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-plus"></i> 放大
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 磁盘 图表 -->
|
<!-- 磁盘 图表 -->
|
||||||
<div id="diskChartContainer" class="chart-container h-80 hidden">
|
<div id="diskChartContainer" class="chart-container h-80 hidden">
|
||||||
<canvas id="diskChart"></canvas>
|
<canvas id="diskChart"></canvas>
|
||||||
|
<!-- 缩放控件 -->
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<div class="text-sm text-gray-500">当前显示范围: <span id="diskCurrentTimeRangeDisplay">过去1小时</span></div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="diskResetZoomBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-refresh"></i> 重置
|
||||||
|
</button>
|
||||||
|
<button id="diskZoomOutBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-minus"></i> 缩小
|
||||||
|
</button>
|
||||||
|
<button id="diskZoomInBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-plus"></i> 放大
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 磁盘详细信息展示 -->
|
||||||
|
<div id="diskDetailsContainer" class="mt-8 hidden">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">磁盘详细信息</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<!-- 磁盘详细信息将通过JavaScript动态添加 -->
|
||||||
|
<div id="diskDetailsContent"></div>
|
||||||
|
<!-- 磁盘信息分页容器 -->
|
||||||
|
<div id="diskPaginationContainer" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 网络 图表 -->
|
<!-- 网络 图表 -->
|
||||||
<div id="networkChartContainer" class="chart-container h-80 hidden">
|
<div id="networkChartContainer" class="chart-container h-80 hidden">
|
||||||
<canvas id="networkChart"></canvas>
|
<canvas id="networkChart"></canvas>
|
||||||
|
<!-- 缩放控件 -->
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<div class="text-sm text-gray-500">当前显示范围: <span id="networkCurrentTimeRangeDisplay">过去1小时</span></div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="networkResetZoomBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-refresh"></i> 重置
|
||||||
|
</button>
|
||||||
|
<button id="networkZoomOutBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-minus"></i> 缩小
|
||||||
|
</button>
|
||||||
|
<button id="networkZoomInBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
|
<i class="fa fa-search-plus"></i> 放大
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 网速 图表 -->
|
<!-- 网速 图表 -->
|
||||||
<div id="speedChartContainer" class="chart-container h-80 hidden">
|
<div id="speedChartContainer" class="chart-container h-80 hidden">
|
||||||
<canvas id="speedChart"></canvas>
|
<canvas id="speedChart"></canvas>
|
||||||
</div>
|
<!-- 缩放控件 -->
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
<!-- 缩放控件 -->
|
<div class="text-sm text-gray-500">当前显示范围: <span id="speedCurrentTimeRangeDisplay">过去1小时</span></div>
|
||||||
<div class="mt-6 flex items-center justify-between">
|
<div class="flex items-center gap-2">
|
||||||
<div class="text-sm text-gray-500">当前显示范围: <span id="currentTimeRangeDisplay">过去24小时</span></div>
|
<button id="speedResetZoomBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
<div class="flex items-center gap-2">
|
<i class="fa fa-refresh"></i> 重置
|
||||||
<button id="resetZoomBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
</button>
|
||||||
<i class="fa fa-refresh"></i> 重置
|
<button id="speedZoomOutBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
</button>
|
<i class="fa fa-search-minus"></i> 缩小
|
||||||
<button id="zoomOutBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
</button>
|
||||||
<i class="fa fa-search-minus"></i> 缩小
|
<button id="speedZoomInBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
||||||
</button>
|
<i class="fa fa-search-plus"></i> 放大
|
||||||
<button id="zoomInBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded-lg transition-colors">
|
</button>
|
||||||
<i class="fa fa-search-plus"></i> 放大
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 系统日志 图表 -->
|
||||||
|
<div id="logChartContainer" class="chart-container h-80 hidden">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">系统日志</h3>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table id="logTable" class="min-w-full bg-white rounded-lg overflow-hidden shadow-md">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">序号</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">来源</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">发生时间</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">内容</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="logTableBody" class="bg-white divide-y divide-gray-200">
|
||||||
|
<!-- 日志信息将通过JavaScript动态添加 -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 缩放控件已移动到各个图表容器内 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
98
backend/static/network_data_analysis.md
Normal file
98
backend/static/network_data_analysis.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# 网络流量数据来源分析
|
||||||
|
|
||||||
|
## 数据来源
|
||||||
|
|
||||||
|
### 1. API调用
|
||||||
|
- **函数**:`loadMetrics()`
|
||||||
|
- **调用方式**:`fetchMetric('network')`
|
||||||
|
- **API路径**:`${API_BASE_URL}/metrics/network`
|
||||||
|
- **参数**:
|
||||||
|
- `start_time`: 起始时间
|
||||||
|
- `end_time`: 结束时间
|
||||||
|
- `aggregation`: 聚合方式(默认为average)
|
||||||
|
- `interval`: 时间区间(固定为10m)
|
||||||
|
|
||||||
|
### 2. WebSocket实时更新
|
||||||
|
- **函数**:`handleMetricsUpdate(message)`
|
||||||
|
- **消息类型**:`metrics_update`
|
||||||
|
- **数据结构**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "metrics_update",
|
||||||
|
"device_id": "device_id",
|
||||||
|
"metrics": {
|
||||||
|
"network": {...}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据处理流程
|
||||||
|
|
||||||
|
### 1. 数据格式化
|
||||||
|
- **函数**:`formatNetworkDataForCards(networkData)`
|
||||||
|
- **作用**:将API或WebSocket返回的原始网络数据格式化为状态卡片可用的格式
|
||||||
|
- **处理逻辑**:
|
||||||
|
- 初始化返回数据结构,包含发送速率、接收速率、发送总量、接收总量
|
||||||
|
- 根据数据类型(数组或对象)进行不同处理
|
||||||
|
- **遍历所有网卡**,累加所有网卡的流量数据
|
||||||
|
- 支持多种数据格式,包括数组格式、WebSocket消息格式、按网卡分组的数据格式
|
||||||
|
- 支持旧格式的总量数据(bytes_sent_total, bytes_received_total)
|
||||||
|
|
||||||
|
### 2. 数据更新
|
||||||
|
- **函数**:`_updateStatusCards(metrics)`
|
||||||
|
- **作用**:更新状态卡片的显示内容
|
||||||
|
- **网络流量卡片更新逻辑**:
|
||||||
|
- 解析网络数据,获取发送速率、接收速率、发送总量、接收总量
|
||||||
|
- 计算接收速率/发送速率的比值,根据比值显示箭头
|
||||||
|
- 使用`formatBytes`函数格式化速率和总量,自动处理MB和GB的转换
|
||||||
|
- 更新DOM元素,显示比值、速率和总量
|
||||||
|
|
||||||
|
## 显示内容
|
||||||
|
|
||||||
|
### 1. 大数字显示
|
||||||
|
- **内容**:接收速率/发送速率的比值
|
||||||
|
- **逻辑**:
|
||||||
|
- 如果接收速率和发送速率都为0,显示无穷符号(∞)
|
||||||
|
- 如果发送速率为0,接收速率不为0,显示无穷符号(∞)和↓
|
||||||
|
- 如果接收速率为0,发送速率不为0,显示0和↑
|
||||||
|
- 正常情况:计算比值,显示比值和箭头(比值>1显示↓,否则显示↑)
|
||||||
|
|
||||||
|
### 2. 速率显示
|
||||||
|
- **格式**:接收速率 MB/s | 发送速率 MB/s
|
||||||
|
- **处理**:使用`formatBytes`函数格式化,自动处理MB/s和GB/s的转换
|
||||||
|
|
||||||
|
### 3. 总量显示
|
||||||
|
- **格式**:接收总量 MB | 发送总量 MB
|
||||||
|
- **处理**:使用`formatBytes`函数格式化,自动处理MB和GB的转换
|
||||||
|
|
||||||
|
## 正确性验证
|
||||||
|
|
||||||
|
### 1. 网卡总流量
|
||||||
|
- `formatNetworkDataForCards`函数会遍历所有网卡,累加所有网卡的流量数据
|
||||||
|
- 这样显示的是所有网卡的总流量信息,符合用户需求
|
||||||
|
|
||||||
|
### 2. 数据类型转换
|
||||||
|
- `formatBytes`函数会根据数据大小自动转换为合适的单位(MB或GB)
|
||||||
|
- 速率显示带有正确的单位(MB/s或GB/s)
|
||||||
|
- 总量显示带有正确的单位(MB或GB)
|
||||||
|
|
||||||
|
### 3. 边界情况处理
|
||||||
|
- 处理了接收速率和发送速率都为0的情况
|
||||||
|
- 处理了发送速率为0,接收速率不为0的情况
|
||||||
|
- 处理了接收速率为0,发送速率不为0的情况
|
||||||
|
|
||||||
|
## 代码优化
|
||||||
|
|
||||||
|
### 1. 已完成的优化
|
||||||
|
- 使用`formatBytes`函数格式化流量数据,自动处理MB和GB的转换
|
||||||
|
- 确保显示的是所有网卡的总流量信息
|
||||||
|
- 处理了各种边界情况
|
||||||
|
|
||||||
|
### 2. 优化效果
|
||||||
|
- 显示内容更准确,带有正确的单位
|
||||||
|
- 自动适应数据大小,提高可读性
|
||||||
|
- 处理了各种边界情况,避免显示错误
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
网络流量卡片的数据来源是可靠的,通过API调用或WebSocket实时更新获取原始数据,经过`formatNetworkDataForCards`函数格式化处理后,显示的是所有网卡的总流量信息。使用`formatBytes`函数格式化后,显示内容带有正确的单位,并且会根据数据大小自动转换为合适的单位(MB或GB),提高了数据的可读性。
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"server_url": "http://localhost:8080/api",
|
|
||||||
"id": "test-agent",
|
|
||||||
"name": "Test Agent",
|
|
||||||
"device_id": "test-device",
|
|
||||||
"token": "bb30bfaee01bf7b541bbefe422f72645",
|
|
||||||
"interval": "5s",
|
|
||||||
"debug": true,
|
|
||||||
"api_port": 8081
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 测试发送单个指标对象
|
|
||||||
echo "测试发送单个指标对象:"
|
|
||||||
curl -v -X POST -H "Content-Type: application/json" -H "X-Device-ID: test-device" -H "X-Agent-Name: test-agent" -H "X-Device-Token: test-token" -d '{"cpu": 50.5, "memory": 30.2, "disk": {":/": 45.6}, "network": {"eth0": {"bytes_sent": 1000, "bytes_received": 2000}}}' http://localhost:8080/api/metrics/
|
|
||||||
|
|
||||||
echo "\n---\n"
|
|
||||||
|
|
||||||
# 测试发送指标数组
|
|
||||||
echo "测试发送指标数组:"
|
|
||||||
curl -v -X POST -H "Content-Type: application/json" -H "X-Device-ID: test-device" -H "X-Agent-Name: test-agent" -H "X-Device-Token: test-token" -d '[{"cpu": 50.5, "memory": 30.2, "disk": {":/": 45.6}, "network": {"eth0": {"bytes_sent": 1000, "bytes_received": 2000}}}, {"cpu": 60.5, "memory": 40.2, "disk": {":/": 55.6}, "network": {"eth0": {"bytes_sent": 2000, "bytes_received": 3000}}}]' http://localhost:8080/api/metrics/
|
|
||||||
Reference in New Issue
Block a user