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
+100
View File
@@ -136,3 +136,103 @@ func DeleteAgentData(agentID int64) {
logger.Warningf("probe: delete agent %d data: %v", agentID, err)
}
}
// ---- B4: probe tasks, results and the latency read ----
// PingTasks lists every probe task with its assignments. With no
// store the list is empty.
func PingTasks() ([]PingTask, error) {
st := Default()
if st == nil {
return []PingTask{}, nil
}
return st.PingTasks()
}
// SavePingTask creates or replaces one probe task (see Store).
func SavePingTask(t PingTask, base *[]int64) (int64, error) {
st := Default()
if st == nil {
return 0, nil
}
return st.SavePingTask(t, base)
}
// DeletePingTask removes one probe task and its filed history.
func DeletePingTask(id int64) error {
st := Default()
if st == nil {
return nil
}
return st.DeletePingTask(id)
}
// SetNodePingTasks applies one agent's assignment difference.
func SetNodePingTasks(agentID int64, tasks, base []int64) error {
st := Default()
if st == nil {
return nil
}
return st.SetNodePingTasks(agentID, tasks, base)
}
// ReorderPingTasks rewrites the panel order of the task list.
func ReorderPingTasks(ids []int64) error {
st := Default()
if st == nil {
return nil
}
return st.ReorderPingTasks(ids)
}
// PingTasksFor reads the task list pushed to one agent on its config
// poll. Best-effort like the rest of the ingest side: a store failure
// reads as "no tasks" and is logged, so a broken probe.db never
// breaks config delivery — the agent keeps its current list until a
// poll succeeds.
func PingTasksFor(agentID int64) []PingTaskRef {
st := Default()
if st == nil {
return []PingTaskRef{}
}
refs, err := st.PingTasksFor(agentID)
if err != nil {
logger.Warningf("probe: ping tasks for agent %d: %v", agentID, err)
return []PingTaskRef{}
}
return refs
}
// PingTaskNames reads one agent's probe chart labels.
func PingTaskNames(agentID int64) (map[string]string, error) {
st := Default()
if st == nil {
return map[string]string{}, nil
}
return st.PingTaskNames(agentID)
}
// InsertPings files one agent's probe results at the arrival stamp.
// Best-effort (heartbeat path): failures are logged and swallowed.
func InsertPings(agentID int64, results []PingResult) {
if len(results) == 0 {
return
}
st := Default()
if st == nil {
return
}
if err := st.InsertPings(agentID, results, time.Now().Unix()); err != nil {
logger.Warningf("probe: insert pings agent %d: %v", agentID, err)
}
}
// PingHistory is the B4 read port for one agent's latency window.
// With no store the window is simply empty.
func PingHistory(agentID int64, sp Span) ([]PingRow, map[string]float64, error) {
st := Default()
if st == nil {
return []PingRow{}, map[string]float64{}, nil
}
return st.PingHistory(agentID, sp)
}