Files
hyung-hwan a284a64816 monitoring page styling
caching of monitoring summary data on th server side
2026-07-02 22:06:25 +09:00

78 lines
2.2 KiB
Go

package monitor
import "context"
import "sync"
import "time"
import "codit/internal/db"
const summaryRefreshInterval time.Duration = 30 * time.Second
const summaryTTL time.Duration = 90 * time.Second
// MonitorSummary holds the expensive derived per-monitor field: uptime over the
// 24h/30d windows, keyed by public id as [24h, 30d]. (Recent-beats bars were
// dropped from the list payload; the detail view fetches full checks instead.)
type MonitorSummary struct {
Uptime map[string][2]float64
}
// SummaryCache serves the derived monitor summary on a slower cadence than the
// list is polled: a background loop refreshes it every summaryRefreshInterval
// (proactive), and a read that finds it older than summaryTTL triggers a
// single-flight refresh (lazy safety net). Fresh per-request fields (state,
// latency) are NOT here — only the costly aggregates.
type SummaryCache struct {
store *db.Store
ttl time.Duration
mu sync.RWMutex
snap MonitorSummary
computedAt time.Time
refreshMu sync.Mutex
}
func NewSummaryCache(store *db.Store, ttl time.Duration) *SummaryCache {
return &SummaryCache{store: store, ttl: ttl}
}
// Refresh recomputes the summary from the store and replaces the snapshot.
func (c *SummaryCache) Refresh(ctx context.Context) error {
var now int64
var uptime map[string][2]float64
var err error
now = time.Now().UTC().Unix()
uptime, err = c.store.MonitorUptimeAll(now)
if err != nil { return err }
c.mu.Lock()
c.snap = MonitorSummary{Uptime: uptime}
c.computedAt = time.Now()
c.mu.Unlock()
return nil
}
// Snapshot returns the cached summary, refreshing synchronously (single-flight)
// when the cache is empty or older than the TTL. If another goroutine is already
// refreshing, the current (possibly slightly stale) snapshot is returned.
func (c *SummaryCache) Snapshot(ctx context.Context) MonitorSummary {
var snap MonitorSummary
var age time.Duration
c.mu.RLock()
snap = c.snap
age = time.Since(c.computedAt)
c.mu.RUnlock()
if snap.Uptime != nil && age <= c.ttl {
return snap
}
if c.refreshMu.TryLock() {
_ = c.Refresh(ctx)
c.refreshMu.Unlock()
c.mu.RLock()
snap = c.snap
c.mu.RUnlock()
}
return snap
}