From a32a443a4ae0a9c36cabf4404f6ea0f7dad7ad7c Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 1 Jul 2026 19:37:27 -0400 Subject: [PATCH] perf(tui): precompute maintenance set, eliminate redundant GetAllSites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace O(windows × sites) isMonitorInMaintenance with O(1) map lookup. buildMaintSet runs once per refreshLive/handleTabData, not per call site. groupSparkline/groupUptime now use m.sites (already on model) instead of calling engine.GetAllSites() which copies the full map under a mutex. --- internal/tui/data.go | 1 + internal/tui/sparkline.go | 6 +-- internal/tui/tab_maint.go | 21 +++++++--- internal/tui/tui.go | 1 + internal/tui/update.go | 1 + internal/tui/update_test.go | 83 +++++++++++++++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/internal/tui/data.go b/internal/tui/data.go index 4669c69..8e6249b 100644 --- a/internal/tui/data.go +++ b/internal/tui/data.go @@ -123,6 +123,7 @@ func (m *Model) refreshLive() { ordered = filterSites(ordered, m.filterText) } m.sites = ordered + m.buildMaintSet() m.refreshLogContent() if m.selectedID != 0 { diff --git a/internal/tui/sparkline.go b/internal/tui/sparkline.go index 11a3162..687c10b 100644 --- a/internal/tui/sparkline.go +++ b/internal/tui/sparkline.go @@ -156,9 +156,8 @@ func resolveSparklineIndex(x, sparkWidth, dataLen int) int { } func (m Model) groupSparkline(groupID int, width int, bg lipgloss.TerminalColor) string { - allSites := m.engine.GetAllSites() var childStatuses [][]bool - for _, s := range allSites { + for _, s := range m.sites { if s.ParentID == groupID && !s.Paused && !m.isMonitorInMaintenance(s.ID) { hist, _ := m.engine.GetHistory(s.ID) if len(hist.Statuses) > 0 { @@ -209,9 +208,8 @@ func (m Model) groupSparkline(groupID int, width int, bg lipgloss.TerminalColor) } func (m Model) groupUptime(groupID int) string { - allSites := m.engine.GetAllSites() var allStatuses [][]bool - for _, s := range allSites { + for _, s := range m.sites { if s.ParentID == groupID && !s.Paused && !m.isMonitorInMaintenance(s.ID) { hist, _ := m.engine.GetHistory(s.ID) if len(hist.Statuses) > 0 { diff --git a/internal/tui/tab_maint.go b/internal/tui/tab_maint.go index 24ac44c..c8a7436 100644 --- a/internal/tui/tab_maint.go +++ b/internal/tui/tab_maint.go @@ -21,27 +21,36 @@ type maintFormData struct { } func (m Model) isMonitorInMaintenance(monitorID int) bool { + return m.maintSet[monitorID] +} + +func (m *Model) buildMaintSet() { + set := make(map[int]bool) + now := time.Now() for _, mw := range m.maintenanceWindows { if mw.Type != "maintenance" { continue } - now := time.Now() if mw.StartTime.After(now) { continue } if !mw.EndTime.IsZero() && mw.EndTime.Before(now) { continue } - if mw.MonitorID == 0 || mw.MonitorID == monitorID { - return true + if mw.MonitorID == 0 { + for _, s := range m.sites { + set[s.ID] = true + } + break } + set[mw.MonitorID] = true for _, s := range m.sites { - if s.ID == monitorID && s.ParentID > 0 && mw.MonitorID == s.ParentID { - return true + if s.ParentID == mw.MonitorID { + set[s.ID] = true } } } - return false + m.maintSet = set } func (m *Model) initMaintHuhForm() tea.Cmd { diff --git a/internal/tui/tui.go b/internal/tui/tui.go index e33f03a..0af7ea7 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -207,6 +207,7 @@ type Model struct { lastTabLoad time.Time // last dispatch of loadTabDataCmd (throttle) tabSeq int // seq of the newest issued tab-data load + maintSet map[int]bool bottomPanel bottomPanel detailOpen bool maintCursor int diff --git a/internal/tui/update.go b/internal/tui/update.go index ec4f2de..0d47f04 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -231,6 +231,7 @@ func (m *Model) handleTabData(msg tabDataMsg) (tea.Model, tea.Cmd) { } m.nodes = msg.nodes m.maintenanceWindows = msg.maint + m.buildMaintSet() m.clampCursor() return m, nil } diff --git a/internal/tui/update_test.go b/internal/tui/update_test.go index 97ac363..f8f060d 100644 --- a/internal/tui/update_test.go +++ b/internal/tui/update_test.go @@ -389,3 +389,86 @@ func TestDetailRefreshCmd_OnlyWhileDetailOpen(t *testing.T) { t.Error("refresh Cmd issued for an out-of-range cursor") } } + +func TestBuildMaintSet_GlobalWindow(t *testing.T) { + m := newTestModel(&tuiMockStore{}) + m.sites = []models.Site{ + {SiteConfig: models.SiteConfig{ID: 1, Name: "a"}}, + {SiteConfig: models.SiteConfig{ID: 2, Name: "b"}}, + } + m.maintenanceWindows = []models.MaintenanceWindow{ + {ID: 1, MonitorID: 0, Type: "maintenance", StartTime: time.Now().Add(-time.Hour)}, + } + m.buildMaintSet() + if !m.maintSet[1] || !m.maintSet[2] { + t.Error("global maint window should mark all monitors") + } +} + +func TestBuildMaintSet_TargetedWindow(t *testing.T) { + m := newTestModel(&tuiMockStore{}) + m.sites = []models.Site{ + {SiteConfig: models.SiteConfig{ID: 1, Name: "a"}}, + {SiteConfig: models.SiteConfig{ID: 2, Name: "b"}}, + } + m.maintenanceWindows = []models.MaintenanceWindow{ + {ID: 1, MonitorID: 1, Type: "maintenance", StartTime: time.Now().Add(-time.Hour)}, + } + m.buildMaintSet() + if !m.maintSet[1] { + t.Error("targeted window should mark monitor 1") + } + if m.maintSet[2] { + t.Error("targeted window should NOT mark monitor 2") + } +} + +func TestBuildMaintSet_GroupPropagates(t *testing.T) { + m := newTestModel(&tuiMockStore{}) + m.sites = []models.Site{ + {SiteConfig: models.SiteConfig{ID: 10, Name: "group", Type: "group"}}, + {SiteConfig: models.SiteConfig{ID: 11, Name: "child1", ParentID: 10}}, + {SiteConfig: models.SiteConfig{ID: 12, Name: "child2", ParentID: 10}}, + {SiteConfig: models.SiteConfig{ID: 20, Name: "other"}}, + } + m.maintenanceWindows = []models.MaintenanceWindow{ + {ID: 1, MonitorID: 10, Type: "maintenance", StartTime: time.Now().Add(-time.Hour)}, + } + m.buildMaintSet() + if !m.maintSet[10] || !m.maintSet[11] || !m.maintSet[12] { + t.Error("group maint window should mark group + children") + } + if m.maintSet[20] { + t.Error("unrelated monitor should NOT be marked") + } +} + +func TestBuildMaintSet_ExpiredIgnored(t *testing.T) { + m := newTestModel(&tuiMockStore{}) + m.sites = []models.Site{ + {SiteConfig: models.SiteConfig{ID: 1, Name: "a"}}, + } + m.maintenanceWindows = []models.MaintenanceWindow{ + {ID: 1, MonitorID: 1, Type: "maintenance", + StartTime: time.Now().Add(-2 * time.Hour), + EndTime: time.Now().Add(-1 * time.Hour)}, + } + m.buildMaintSet() + if m.maintSet[1] { + t.Error("expired maint window should not mark monitor") + } +} + +func TestBuildMaintSet_IncidentIgnored(t *testing.T) { + m := newTestModel(&tuiMockStore{}) + m.sites = []models.Site{ + {SiteConfig: models.SiteConfig{ID: 1, Name: "a"}}, + } + m.maintenanceWindows = []models.MaintenanceWindow{ + {ID: 1, MonitorID: 0, Type: "incident", StartTime: time.Now().Add(-time.Hour)}, + } + m.buildMaintSet() + if m.maintSet[1] { + t.Error("incident windows should not mark monitors as in maintenance") + } +} -- 2.52.0