重构数据采集打包,修改为统一打包发送

This commit is contained in:
dushixiang committed 2026-05-05 17:46:32 +08:00
1 parent 29d8b3e9cf
commit e115d828ab
5 files changed
+169 -283

No files matched your search

+87 -125
View File
@@ -1,18 +1,18 @@
package collector
import (
"errors"
"time"
"github.com/dushixiang/pika/internal/protocol"
"github.com/dushixiang/pika/pkg/agent/config"
)
// WebSocketWriter 定义 WebSocket 写入接口
type WebSocketWriter interface {
WriteJSON(v interface{}) error
}
// ErrNoData 表示采集成功但当前周期无有效数据可上报(如 GPU 不存在、磁盘列表为空)。
// 调用方应静默跳过,不视为错误。
var ErrNoData = errors.New("no data")
// Manager 采集器管理器
// Manager 采集器管理器:纯采集,不做 I/O
type Manager struct {
cpuCollector *CPUCollector
memoryCollector *MemoryCollector
@@ -24,7 +24,6 @@ type Manager struct {
temperatureCollector *TemperatureCollector
gpuCollector *GPUCollector
monitorCollector *MonitorCollector
ddnsCollector *DDNSCollector
}
// NewManager 创建采集器管理器
@@ -40,152 +39,115 @@ func NewManager(cfg *config.Config) *Manager {
temperatureCollector: NewTemperatureCollector(),
gpuCollector: NewGPUCollector(),
monitorCollector: NewMonitorCollector(),
ddnsCollector: nil, // DDNS 采集器需要配置后才能初始化
}
}
// CollectAndSendCPU 采集并发送 CPU 指标
func (m *Manager) CollectAndSendCPU(conn WebSocketWriter) error {
cpuData, err := m.cpuCollector.Collect()
// makeSample 构造一条 MetricSample
func makeSample(t protocol.MetricType, data interface{}) protocol.MetricSample {
return protocol.MetricSample{
Type: t,
Data: data,
Timestamp: time.Now().UnixMilli(),
}
}
// CollectCPU 采集 CPU 指标
func (m *Manager) CollectCPU() (protocol.MetricSample, error) {
data, err := m.cpuCollector.Collect()
if err != nil {
return err
return protocol.MetricSample{}, err
}
return m.sendMetrics(conn, protocol.MetricTypeCPU, cpuData)
return makeSample(protocol.MetricTypeCPU, data), nil
}
// CollectAndSendMemory 采集并发送内存指标
func (m *Manager) CollectAndSendMemory(conn WebSocketWriter) error {
memData, err := m.memoryCollector.Collect()
// CollectMemory 采集内存指标
func (m *Manager) CollectMemory() (protocol.MetricSample, error) {
data, err := m.memoryCollector.Collect()
if err != nil {
return err
return protocol.MetricSample{}, err
}
return m.sendMetrics(conn, protocol.MetricTypeMemory, memData)
return makeSample(protocol.MetricTypeMemory, data), nil
}
// CollectAndSendDisk 采集并发送磁盘指标
func (m *Manager) CollectAndSendDisk(conn WebSocketWriter) error {
diskDataList, err := m.diskCollector.Collect()
if err != nil || len(diskDataList) == 0 {
// 采集失败或无有效数据,不发送空列表
return nil
}
return m.sendMetrics(conn, protocol.MetricTypeDisk, diskDataList)
}
// CollectAndSendDiskIO 采集并发送磁盘 IO 指标
func (m *Manager) CollectAndSendDiskIO(conn WebSocketWriter) error {
diskIODataList, err := m.diskIOCollector.Collect()
// CollectDisk 采集磁盘指标。无有效数据时返回 ErrNoData。
func (m *Manager) CollectDisk() (protocol.MetricSample, error) {
data, err := m.diskCollector.Collect()
if err != nil {
return err
return protocol.MetricSample{}, err
}
return m.sendMetrics(conn, protocol.MetricTypeDiskIO, diskIODataList)
if len(data) == 0 {
return protocol.MetricSample{}, ErrNoData
}
return makeSample(protocol.MetricTypeDisk, data), nil
}
// CollectAndSendNetwork 采集并发送网络指标
func (m *Manager) CollectAndSendNetwork(conn WebSocketWriter) error {
networkDataList, err := m.networkCollector.Collect()
if err != nil || len(networkDataList) == 0 {
// 采集失败或无有效数据,不发送空列表
return nil
}
return m.sendMetrics(conn, protocol.MetricTypeNetwork, networkDataList)
}
// CollectAndSendNetworkConnection 采集并发送网络连接统计
func (m *Manager) CollectAndSendNetworkConnection(conn WebSocketWriter) error {
connectionData, err := m.networkConnectionCollector.Collect()
// CollectDiskIO 采集磁盘 IO 指标
func (m *Manager) CollectDiskIO() (protocol.MetricSample, error) {
data, err := m.diskIOCollector.Collect()
if err != nil {
return err
return protocol.MetricSample{}, err
}
return m.sendMetrics(conn, protocol.MetricTypeNetworkConnection, connectionData)
return makeSample(protocol.MetricTypeDiskIO, data), nil
}
// CollectAndSendHost 采集并发送主机信息
func (m *Manager) CollectAndSendHost(conn WebSocketWriter) error {
hostData, err := m.hostCollector.Collect()
// CollectNetwork 采集网络指标。无有效数据时返回 ErrNoData。
func (m *Manager) CollectNetwork() (protocol.MetricSample, error) {
data, err := m.networkCollector.Collect()
if err != nil {
return err
return protocol.MetricSample{}, err
}
return m.sendMetrics(conn, protocol.MetricTypeHost, hostData)
if len(data) == 0 {
return protocol.MetricSample{}, ErrNoData
}
return makeSample(protocol.MetricTypeNetwork, data), nil
}
// CollectAndSendGPU 采集并发送 GPU 指标
func (m *Manager) CollectAndSendGPU(conn WebSocketWriter) error {
gpuDataList, err := m.gpuCollector.Collect()
if err != nil || len(gpuDataList) == 0 {
// GPU 监控不是必须的,失败或无数据时直接返回
return nil
}
return m.sendMetrics(conn, protocol.MetricTypeGPU, gpuDataList)
}
// CollectAndSendTemperature 采集并发送温度信息
func (m *Manager) CollectAndSendTemperature(conn WebSocketWriter) error {
tempDataList, err := m.temperatureCollector.Collect()
if err != nil || len(tempDataList) == 0 {
// 温度监控不是必须的,失败或无数据时直接返回
return nil
}
return m.sendMetrics(conn, protocol.MetricTypeTemperature, tempDataList)
}
// CollectAndSendMonitor 采集并发送监控数据
func (m *Manager) CollectAndSendMonitor(conn WebSocketWriter, items []protocol.MonitorItem) error {
monitorDataList := m.monitorCollector.Collect(items)
return m.sendMetrics(conn, protocol.MetricTypeMonitor, monitorDataList)
}
// UpdateDDNSConfig 更新 DDNS 配置
func (m *Manager) UpdateDDNSConfig(config *protocol.DDNSConfigData) {
if config == nil || !config.Enabled {
m.ddnsCollector = nil
return
}
if m.ddnsCollector == nil {
m.ddnsCollector = NewDDNSCollector(config)
} else {
m.ddnsCollector.UpdateConfig(config)
}
}
// CollectAndSendDDNSIP 采集并发送 DDNS IP 地址
func (m *Manager) CollectAndSendDDNSIP(conn WebSocketWriter) error {
if m.ddnsCollector == nil {
return nil // DDNS 未启用,静默返回
}
ipData, err := m.ddnsCollector.Collect()
// CollectNetworkConnection 采集网络连接统计
func (m *Manager) CollectNetworkConnection() (protocol.MetricSample, error) {
data, err := m.networkConnectionCollector.Collect()
if err != nil {
return err
return protocol.MetricSample{}, err
}
// 只有当至少有一个 IP 地址时才发送
if ipData.IPv4 == "" && ipData.IPv6 == "" {
return nil
}
return conn.WriteJSON(protocol.OutboundMessage{
Type: protocol.MessageTypeDDNSIPReport,
Data: ipData,
})
return makeSample(protocol.MetricTypeNetworkConnection, data), nil
}
// sendMetrics 发送指标数据
func (m *Manager) sendMetrics(conn WebSocketWriter, metricType protocol.MetricType, data interface{}) error {
return conn.WriteJSON(protocol.OutboundMessage{
Type: protocol.MessageTypeMetrics,
Data: protocol.MetricsPayload{
Type: metricType,
Data: data,
Timestamp: time.Now().UnixMilli(),
},
})
// CollectHost 采集主机信息(含 Load)
func (m *Manager) CollectHost() (protocol.MetricSample, error) {
data, err := m.hostCollector.Collect()
if err != nil {
return protocol.MetricSample{}, err
}
return makeSample(protocol.MetricTypeHost, data), nil
}
// CollectGPU 采集 GPU 指标。无 GPU 时返回 ErrNoData(GPU 不是必备)
func (m *Manager) CollectGPU() (protocol.MetricSample, error) {
data, err := m.gpuCollector.Collect()
if err != nil {
return protocol.MetricSample{}, err
}
if len(data) == 0 {
return protocol.MetricSample{}, ErrNoData
}
return makeSample(protocol.MetricTypeGPU, data), nil
}
// CollectTemperature 采集温度信息。无传感器时返回 ErrNoData(温度不是必备)
func (m *Manager) CollectTemperature() (protocol.MetricSample, error) {
data, err := m.temperatureCollector.Collect()
if err != nil {
return protocol.MetricSample{}, err
}
if len(data) == 0 {
return protocol.MetricSample{}, ErrNoData
}
return makeSample(protocol.MetricTypeTemperature, data), nil
}
// CollectMonitor 采集服务监控数据(HTTP/TCP 探活)
func (m *Manager) CollectMonitor(items []protocol.MonitorItem) protocol.MetricSample {
data := m.monitorCollector.Collect(items)
return makeSample(protocol.MetricTypeMonitor, data)
}
// GetPublicIP 通过 API 获取公网 IP 地址
+62 -127
View File
@@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
@@ -28,10 +29,10 @@ import (
)
const (
agentPingInterval = 10 * time.Second
agentPongWait = 30 * time.Second
agentWriteWait = 5 * time.Second
agentCollectTimeout = 30 * time.Second
agentPingInterval = 10 * time.Second
agentPongWait = 30 * time.Second
agentWriteWait = 5 * time.Second
agentCollectTimeout = 30 * time.Second
)
// safeConn 线程安全的 WebSocket 连接包装器
@@ -162,8 +163,8 @@ func (a *Agent) runOnce(ctx context.Context, onRegistered func()) error {
// 创建自定义的 Dialer
dialer := &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
EnableCompression: true,
}
if a.cfg.Server.InsecureSkipVerify {
@@ -429,10 +430,14 @@ func (a *Agent) handleMonitorConfig(data json.RawMessage) {
slog.Info("收到服务监控配置,立即执行检测", "count", len(payload.Items))
// 立即执行一次监控检测
// 立即执行一次监控检测,结果以单元素 batch 上报
sample := manager.CollectMonitor(payload.Items)
writer := newOutboundWriter(a.getActiveConn(), a.outboundBuffer)
if err := manager.CollectAndSendMonitor(writer, payload.Items); err != nil {
slog.Warn("监控检测失败", "error", err)
if err := writer.WriteJSON(protocol.OutboundMessage{
Type: protocol.MessageTypeMetrics,
Data: protocol.MetricsBatch{Samples: []protocol.MetricSample{sample}},
}); err != nil {
slog.Warn("发送监控结果失败", "error", err)
} else {
slog.Info("服务监控检测完成,已上报或缓存监控项结果", "count", len(payload.Items))
}
@@ -511,26 +516,6 @@ func (a *Agent) metricsLoop(ctx context.Context) {
}
}
type bundleWriter struct {
items []protocol.MetricsPayload
}
func (w *bundleWriter) WriteJSON(v interface{}) error {
msg, ok := v.(protocol.OutboundMessage)
if !ok {
return fmt.Errorf("invalid message type for bundleWriter")
}
if msg.Type == protocol.MessageTypeMetrics {
payload, ok := msg.Data.(protocol.MetricsPayload)
if ok {
w.items = append(w.items, payload)
return nil
}
}
return fmt.Errorf("only metrics messages can be bundled")
}
// collectAndSendAllMetrics 采集并发送所有动态指标
func (a *Agent) collectAndSendAllMetrics(manager *collector.Manager) error {
if manager == nil {
@@ -566,12 +551,15 @@ func newCollectTimer(name string) *collectTimer {
func (t *collectTimer) done() {
d := time.Since(t.start)
if d > 500*time.Millisecond {
if d > 1500*time.Millisecond {
slog.Info("采集耗时", "collector", t.name, "duration", d)
}
}
// doCollectAndSend 执行实际的采集和发送
// collectFn 单个采集器的采集函数签名
type collectFn func() (protocol.MetricSample, error)
// doCollectAndSend 采集所有动态指标,打包成一个 batch 上报
func (a *Agent) doCollectAndSend(manager *collector.Manager) error {
conn := a.getActiveConn()
if conn != nil {
@@ -582,108 +570,55 @@ func (a *Agent) doCollectAndSend(manager *collector.Manager) error {
}
}
writer := newOutboundWriter(conn, a.outboundBuffer)
bw := &bundleWriter{}
// required=true 时采集失败计入错误;required=false 适用于 GPU/温度等可选项
collectors := []struct {
name string
required bool
fn collectFn
}{
{"cpu", true, manager.CollectCPU},
{"memory", true, manager.CollectMemory},
{"disk", true, manager.CollectDisk},
{"disk_io", true, manager.CollectDiskIO},
{"network", true, manager.CollectNetwork},
{"network_connection", true, manager.CollectNetworkConnection},
{"host", true, manager.CollectHost},
{"gpu", false, manager.CollectGPU},
{"temperature", false, manager.CollectTemperature},
}
samples := make([]protocol.MetricSample, 0, len(collectors))
var hasError bool
// CPU 动态指标
func() {
t := newCollectTimer("cpu")
defer t.done()
if err := manager.CollectAndSendCPU(bw); err != nil {
slog.Warn("采集CPU指标失败", "error", err)
hasError = true
}
}()
for _, c := range collectors {
func() {
t := newCollectTimer(c.name)
defer t.done()
sample, err := c.fn()
if err != nil {
if errors.Is(err, collector.ErrNoData) {
return
}
if c.required {
slog.Warn("采集指标失败", "collector", c.name, "error", err)
hasError = true
} else {
slog.Info("采集可选指标失败", "collector", c.name, "error", err)
}
return
}
samples = append(samples, sample)
}()
}
// 内存动态指标
func() {
t := newCollectTimer("memory")
defer t.done()
if err := manager.CollectAndSendMemory(bw); err != nil {
slog.Warn("采集内存指标失败", "error", err)
hasError = true
}
}()
writer := newOutboundWriter(conn, a.outboundBuffer)
// 磁盘指标
func() {
t := newCollectTimer("disk")
defer t.done()
if err := manager.CollectAndSendDisk(bw); err != nil {
slog.Warn("采集磁盘指标失败", "error", err)
hasError = true
}
}()
// 磁盘 IO 指标
func() {
t := newCollectTimer("disk_io")
defer t.done()
if err := manager.CollectAndSendDiskIO(bw); err != nil {
slog.Warn("采集磁盘IO指标失败", "error", err)
hasError = true
}
}()
// 网络指标
func() {
t := newCollectTimer("network")
defer t.done()
if err := manager.CollectAndSendNetwork(bw); err != nil {
slog.Warn("采集网络指标失败", "error", err)
hasError = true
}
}()
// 网络连接统计
func() {
t := newCollectTimer("network_connection")
defer t.done()
if err := manager.CollectAndSendNetworkConnection(bw); err != nil {
slog.Warn("采集网络连接统计失败", "error", err)
hasError = true
}
}()
// 主机信息(包含 Load)
func() {
t := newCollectTimer("host")
defer t.done()
if err := manager.CollectAndSendHost(bw); err != nil {
slog.Warn("采集主机信息失败", "error", err)
hasError = true
}
}()
// GPU 信息(可选)
func() {
t := newCollectTimer("gpu")
defer t.done()
if err := manager.CollectAndSendGPU(bw); err != nil {
slog.Info("采集GPU信息失败", "error", err)
}
}()
// 温度信息(可选)
func() {
t := newCollectTimer("temperature")
defer t.done()
if err := manager.CollectAndSendTemperature(bw); err != nil {
slog.Info("采集温度信息失败", "error", err)
}
}()
// 发送打包后的指标
if len(bw.items) > 0 {
bundle := protocol.BundlePayload{
Items: bw.items,
}
if len(samples) > 0 {
if err := writer.WriteJSON(protocol.OutboundMessage{
Type: protocol.MessageTypeBundle,
Data: bundle,
Type: protocol.MessageTypeMetrics,
Data: protocol.MetricsBatch{Samples: samples},
}); err != nil {
slog.Warn("发送打包指标失败", "error", err)
slog.Warn("发送指标 batch 失败", "error", err)
hasError = true
}
}
+6 -2
View File
@@ -11,7 +11,6 @@ import (
"time"
"github.com/dushixiang/pika/internal/protocol"
"github.com/dushixiang/pika/pkg/agent/collector"
"github.com/dushixiang/pika/pkg/agent/utils"
bolt "go.etcd.io/bbolt"
)
@@ -23,6 +22,11 @@ const (
outboundBufferRetention = 24 * time.Hour
)
// WebSocketWriter 抽象 JSON 写入操作,由 outboundBuffer 和 outboundWriter 共用
type WebSocketWriter interface {
WriteJSON(v interface{}) error
}
type outboundBuffer struct {
path string
mu sync.Mutex
@@ -101,7 +105,7 @@ func (b *outboundBuffer) Append(v interface{}) error {
return nil
}
func (b *outboundBuffer) Flush(writer collector.WebSocketWriter) (int, error) {
func (b *outboundBuffer) Flush(writer WebSocketWriter) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()