B4: probe task store, panel API, heartbeat ingestion, agent prober

- probe package: probe_ping_task/probe_ping_node tables; task CRUD
  with hub save semantics (id high-water, base-diff assignment,
  64-per-agent crowding refusal, auto_join cap); filing with
  assignment-gated insert; PingHistory weighted-median chart read
  over minute+hour tiers; auto-join on first Record (ensureRows).
- controller: /panel/api/agent/probe-tasks* management endpoints
  (node id space, hub validation chain verbatim); series=ping now
  serves real rows from probe.PingHistory; heartbeat ingests
  probeResults (missing latency skipped, batch capped); config
  response carries probe_tasks (always present, empty = clear).
- agent: per-task probe loops (immediate first probe, ticker,
  900ms resolve/dial budgets, <=3 addresses, resolve overrun = no
  sample), result buffer drained on heartbeat and requeued on
  failure; task set applied before the config-hash early return.
This commit is contained in:
Muse
2026-10-22 21:57:23 +00:00
parent 540c05b0a5
commit e03ceaa41f
11 changed files with 1582 additions and 6 deletions
+21 -3
View File
@@ -120,7 +120,7 @@ func (pc *PublicProbeController) nodes(c *gin.Context) {
type metricsResponse struct {
Metrics []probe.MetricRow `json:"metrics"`
Ping []any `json:"ping"`
Ping []probe.PingRow `json:"ping"`
Probes map[string]string `json:"probes"`
Loss map[string]float64 `json:"loss"`
Step int64 `json:"step"`
@@ -171,12 +171,11 @@ func (pc *PublicProbeController) nodeMetrics(c *gin.Context) {
series := c.Query("series")
wantMetrics := series == "" || series == "metrics"
wantPing := series == "" || series == "ping"
_ = wantPing // ping rows are written by B4; until then the series is empty by construction
span := probe.ComputeSpan(hours, points, time.Now().Unix())
resp := metricsResponse{
Metrics: []probe.MetricRow{},
Ping: []any{},
Ping: []probe.PingRow{},
Probes: map[string]string{},
Loss: map[string]float64{},
Step: span.Step,
@@ -190,6 +189,25 @@ func (pc *PublicProbeController) nodeMetrics(c *gin.Context) {
}
resp.Metrics = rows
}
if wantPing {
// B4: the real latency series over the same window/bucket as
// the metrics above (hub metrics handler series=ping).
pingRows, pingLoss, err := probe.PingHistory(id, span)
if err != nil {
logger.Warningf("probe public: ping %d: %v", id, err)
answerText(c, http.StatusInternalServerError, "服务内部出错")
return
}
names, err := probe.PingTaskNames(id)
if err != nil {
logger.Warningf("probe public: ping names %d: %v", id, err)
answerText(c, http.StatusInternalServerError, "服务内部出错")
return
}
resp.Ping = pingRows
resp.Probes = names
resp.Loss = pingLoss
}
c.JSON(http.StatusOK, resp)
}