💥 feat(agent)!: 添加主机监控代理首版
* 实现周期采集、快照缓存、本地 HTTP API 和主动上报 * 补齐 Linux 和 Windows 主机、网络、资源、进程和 TCP 采集 * 添加服务生命周期管理、配置加载、构建脚本和测试覆盖 BREAKING CHANGE: network.ips 拆分为 network.ipv4s 和 network.ipv6s。
This commit is contained in:
99
internal/reporter/reporter.go
Normal file
99
internal/reporter/reporter.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"mfd-monitor-agent/internal/status"
|
||||
)
|
||||
|
||||
const defaultRetryDelay = 2 * time.Second
|
||||
|
||||
// Provider supplies the cached HostStatus used for outbound reports.
|
||||
type Provider interface {
|
||||
// Get returns the latest snapshot and whether a snapshot is ready.
|
||||
Get() (status.HostStatus, bool)
|
||||
}
|
||||
|
||||
// Client posts HostStatus snapshots to a configured center endpoint.
|
||||
type Client struct {
|
||||
endpoint string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a reporting client with the provided endpoint and request timeout.
|
||||
func NewClient(endpoint string, timeout time.Duration) *Client {
|
||||
return &Client{
|
||||
endpoint: endpoint,
|
||||
client: &http.Client{Timeout: timeout},
|
||||
}
|
||||
}
|
||||
|
||||
// Report posts one HostStatus snapshot as JSON and returns non-2xx responses as errors.
|
||||
func (c *Client) Report(snapshot status.HostStatus) error {
|
||||
body, err := json.Marshal(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, c.endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("report returned HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run periodically posts the latest cached snapshot until ctx is canceled.
|
||||
func Run(ctx context.Context, provider Provider, client *Client, interval time.Duration, logger *slog.Logger) {
|
||||
runWithRetryDelay(ctx, provider, client, interval, defaultRetryDelay, logger)
|
||||
}
|
||||
|
||||
// runWithRetryDelay runs the reporting loop and retries one failed report after retryDelay.
|
||||
func runWithRetryDelay(ctx context.Context, provider Provider, client *Client, interval time.Duration, retryDelay time.Duration, logger *slog.Logger) {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
snapshot, ok := provider.Get()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err := reportWithRetry(ctx, client, snapshot, retryDelay); err != nil {
|
||||
logger.Warn("status report failed", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reportWithRetry posts a snapshot and performs one cancellation-aware retry on failure.
|
||||
func reportWithRetry(ctx context.Context, client *Client, snapshot status.HostStatus, retryDelay time.Duration) error {
|
||||
err := client.Report(snapshot)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(retryDelay):
|
||||
}
|
||||
if retryErr := client.Report(snapshot); retryErr != nil {
|
||||
return retryErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user