Files
3x-ui-agent/internal/web/service/probe/probe.go
T

139 lines
3.8 KiB
Go

package probe
import (
"strconv"
"strings"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/logger"
)
// RetentionDays reads the history retention window (days) from the
// panel settings (key retention_days, as in hub). Default 7 — the
// window the user settled on — clamped to [1, 365] (hub's cap).
func RetentionDays() int64 {
var row model.Setting
err := database.GetDB().Where("key = ?", "retention_days").First(&row).Error
if err != nil {
return DetailDays
}
n, perr := strconv.ParseInt(strings.TrimSpace(row.Value), 10, 64)
if perr != nil {
return DetailDays
}
if n < 1 {
return 1
}
if n > 365 {
return 365
}
return n
}
// Record files one heartbeat's probe sample into the default store.
// prevLastSeenMs is the agent row's last-seen stamp from before this
// heartbeat (milliseconds, as stored on agent_nodes). Best-effort: any
// failure is logged and swallowed so a heartbeat never fails over
// history.
func Record(agentID int64, smp Sample, prevLastSeenMs int64) {
st := Default()
if st == nil {
return
}
if err := st.Record(agentID, smp, time.Now(), prevLastSeenMs/1000); err != nil {
logger.Warningf("probe: record agent %d: %v", agentID, err)
}
}
// Maintain runs one housekeeping pass against the default store:
// roll_up first, then prune — prune runs even when roll_up fails, as
// it keeps minute rows until their hour is folded and skipping it
// would leave the database growing (hub housekeeping discipline).
func Maintain() {
st := Default()
if st == nil {
return
}
keep := RetentionDays()
if folded, err := st.RollUp(time.Now().Unix(), keep); err != nil {
logger.Warningf("probe: roll_up failed: %v", err)
} else if folded > 1 {
logger.Infof("probe: folded %d hours of history into the hourly tier", folded)
}
if _, err := st.Prune(keep); err != nil {
logger.Warningf("probe: prune failed: %v", err)
}
}
// BookHeld books every node's held, not-yet-booked reading on the
// default store; called as the master shuts down.
func BookHeld() {
if st := Default(); st != nil {
st.BookHeld()
}
}
// TrafficSnapshot is the B3 read port: every agent's gated ledger.
func TrafficSnapshot() (map[int64]Traffic, error) {
st := Default()
if st == nil {
return map[int64]Traffic{}, nil
}
return st.AllTraffic()
}
// MetaSnapshot is the B3 read port for display metadata: every agent's
// probe_meta row, keyed by agent id. Agents without a row are absent;
// callers substitute DefaultMeta.
func MetaSnapshot() (map[int64]Meta, error) {
st := Default()
if st == nil {
return map[int64]Meta{}, nil
}
return st.AllMeta()
}
// MetaFor reads one agent's metadata row from the default store.
// ok is false when the agent never reported (no row ensured yet).
func MetaFor(agentID int64) (Meta, bool, error) {
st := Default()
if st == nil {
return Meta{}, false, nil
}
return st.Meta(agentID)
}
// SaveMeta persists one agent's metadata row (B3 edit surface).
func SaveMeta(m Meta) error {
st := Default()
if st == nil {
return nil
}
return st.UpsertMeta(m)
}
// MetricHistory is the B3 read port for one agent's history window.
// With no store the window is simply empty.
func MetricHistory(agentID int64, sp Span) ([]MetricRow, error) {
st := Default()
if st == nil {
return []MetricRow{}, nil
}
return st.MetricHistory(agentID, sp)
}
// DeleteAgentData drops a removed agent's probe rows (B3 cleanup point
// called from agent deletion). Best-effort: failures are logged and
// swallowed, as a stale probe row must never block a panel operation.
func DeleteAgentData(agentID int64) {
st := Default()
if st == nil {
return
}
if err := st.DeleteAgentData(agentID); err != nil {
logger.Warningf("probe: delete agent %d data: %v", agentID, err)
}
}