package agentd import ( "context" "time" agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1" ) // LoadSource provides the runtime metrics reported in each heartbeat. The // production implementation can read sing-box stats + host CPU; the default only // reports the online peer count (number of provisioned credentials), which never // reveals identities. type LoadSource interface { Load() (onlinePeers int32, upBps, downBps int64, cpuPercent float64) } // defaultLoadSource reports peer count from the SingBox table and zeros for the // rest. Bandwidth/CPU collection is a documented extension point. type defaultLoadSource struct{ sb *SingBox } func (d defaultLoadSource) Load() (int32, int64, int64, float64) { return int32(d.sb.OnlinePeers()), 0, 0, 0 } // runHeartbeat sends a heartbeat every cfg.HeartbeatInterval until ctx is // cancelled or an RPC error occurs. A need_full_resync response returns // errResync so the session re-Registers and overwrites local state. func (a *Agent) runHeartbeat(ctx context.Context, client agentv1.AgentServiceClient) error { ticker := time.NewTicker(a.cfg.HeartbeatInterval) defer ticker.Stop() for { if err := a.sendHeartbeat(ctx, client); err != nil { return err } select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: } } } func (a *Agent) sendHeartbeat(ctx context.Context, client agentv1.AgentServiceClient) error { peers, up, down, cpu := a.load.Load() resp, err := client.Heartbeat(ctx, &agentv1.HeartbeatRequest{ NodeUUID: a.NodeUUID(), ConfigVersion: a.sb.ConfigVersion(), OnlinePeers: peers, BandwidthUpBps: up, BandwidthDownBps: down, CPUPercent: cpu, TimestampUnix: a.clock().Unix(), }) if err != nil { return err } if resp.NeedFullResync { logf("control plane requested full resync") return errResync } return nil }