package snapshot import ( "context" "sync" "time" "mfd-monitor-agent/internal/status" ) // Collector gathers one subsection of HostStatus into the provided snapshot. type Collector interface { // Name returns the stable collector name used in partial snapshot errors. Name() string // Collect fills its subsection of out and returns an error when that subsection is incomplete. Collect(context.Context, *status.HostStatus) error } // CollectorFunc adapts a function into a Collector for tests and small collectors. type CollectorFunc func(context.Context, *status.HostStatus) error // Name returns a generic collector name for function-backed collectors. func (fn CollectorFunc) Name() string { return "collector" } // Collect calls the wrapped function. func (fn CollectorFunc) Collect(ctx context.Context, out *status.HostStatus) error { return fn(ctx, out) } // Cache stores the most recent HostStatus snapshot behind a concurrency-safe interface. type Cache struct { collectors []Collector version string startedAt time.Time mu sync.RWMutex last status.HostStatus hasLast bool } // NewCache creates an empty snapshot cache using the provided collectors and agent metadata. func NewCache(collectors []Collector, version string, startedAt time.Time) *Cache { return &Cache{collectors: collectors, version: version, startedAt: startedAt} } // Collect runs all collectors once and stores the resulting snapshot even when some collectors fail. func (c *Cache) Collect(ctx context.Context) status.HostStatus { out := status.HostStatus{ Agent: status.AgentInfo{ Version: c.version, StartedAt: c.startedAt, SnapshotAt: time.Now().UTC(), SnapshotStatus: "ok", }, Processes: status.ProcessResult{Status: "ok"}, TCPListeners: status.TCPResult{Status: "ok"}, } for _, collector := range c.collectors { if err := collector.Collect(ctx, &out); err != nil { out.Agent.SnapshotStatus = "partial" out.Errors = append(out.Errors, status.StatusError{ Collector: collector.Name(), Message: err.Error(), }) } } c.mu.Lock() c.last = out c.hasLast = true c.mu.Unlock() return out } // Get returns the latest cached snapshot and whether collection has completed at least once. func (c *Cache) Get() (status.HostStatus, bool) { c.mu.RLock() defer c.mu.RUnlock() return c.last, c.hasLast } // Run collects immediately and then refreshes the cache until ctx is canceled. func (c *Cache) Run(ctx context.Context, interval time.Duration, timeout time.Duration) { c.collectWithTimeout(ctx, timeout) ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: c.collectWithTimeout(ctx, timeout) } } } // collectWithTimeout runs one collection cycle with an overall deadline. func (c *Cache) collectWithTimeout(ctx context.Context, timeout time.Duration) { collectCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() c.Collect(collectCtx) }