diff --git a/app/app.go b/app/app.go index 9fc3271..bbbbda4 100644 --- a/app/app.go +++ b/app/app.go @@ -15,6 +15,7 @@ import ( "github.com/sjmudd/ps-top/global" "github.com/sjmudd/ps-top/log" "github.com/sjmudd/ps-top/model/filter" + "github.com/sjmudd/ps-top/pstable" "github.com/sjmudd/ps-top/setupinstruments" "github.com/sjmudd/ps-top/utils" "github.com/sjmudd/ps-top/view" @@ -35,11 +36,11 @@ type App struct { db *sql.DB // connection to MySQL display *display.Display // display displays the information to the screen finished bool // has the app finished? - help bool // show help (during runtime) collector *DBCollector // owns all tablers and collection logic signalHandler *SignalHandler // handles signals waiter *wait.Waiter // for handling waits between collecting metrics setupInstruments *setupinstruments.SetupInstruments // for setting up and restoring performance_schema configuration. + viewManager *view.Manager // manages view state and display } var ( @@ -89,7 +90,6 @@ func NewApp( app.config = config.NewConfig(status, variables, settings.Filter, true) app.display = display.NewDisplay(app.config) app.finished = false - app.help = false app.display.Clear() app.setupInstruments = setupinstruments.NewSetupInstruments(app.db) @@ -105,13 +105,27 @@ func NewApp( // Create signal handler app.signalHandler = NewSignalHandler() - // Setup view (using collector's currentView field) + // Setup view system using ViewManager var viewErr error - app.collector.currentView, viewErr = view.SetupAndValidate(settings.ViewName, app.db) // if empty will use the default + v, viewErr := view.SetupAndValidate(settings.ViewName, app.db) // if empty will use the default if viewErr != nil { return nil, fmt.Errorf("app.NewApp: %w", viewErr) } - app.collector.UpdateCurrentTabler() + + // Build tabler mapping for ViewManager + tablers := map[view.Code]pstable.Tabler{ + view.ViewLatency: app.collector.tableIoLatency, + view.ViewOps: app.collector.tableIoOps, + view.ViewIO: app.collector.fileInfoLatency, + view.ViewLocks: app.collector.tableLockLatency, + view.ViewUsers: app.collector.userLatency, + view.ViewMutex: app.collector.mutexLatency, + view.ViewStages: app.collector.stagesLatency, + view.ViewMemory: app.collector.memoryUsage, + } + + // Create ViewManager, passing collector as the TablerUpdater + app.viewManager = view.NewManager(v, tablers, app.display, app.collector) // Initial collection and reset to establish baseline log.Println("app.NewApp: Initial collection and reset") @@ -134,27 +148,7 @@ func (app *App) Collect() { // Display shows the output appropriate to the corresponding view and device func (app *App) Display() { - if app.help { - app.display.Display(display.Help) - } else { - app.display.Display(app.collector.CurrentTabler()) - } -} - -// change to the previous display mode -func (app *App) displayPrevious() { - app.collector.currentView.SetPrev() - app.collector.UpdateCurrentTabler() - app.display.Clear() - app.Display() -} - -// change to the next display mode -func (app *App) displayNext() { - app.collector.currentView.SetNext() - app.collector.UpdateCurrentTabler() - app.display.Clear() - app.Display() + app.viewManager.Display() } // Cleanup prepares the application prior to shutting down @@ -207,9 +201,9 @@ func (app *App) handleInputEvent(inputEvent event.Event) bool { case event.EventFinished: app.finished = true case event.EventViewNext: - app.displayNext() + app.viewManager.DisplayNext() case event.EventViewPrev: - app.displayPrevious() + app.viewManager.DisplayPrev() case event.EventDecreasePollTime: if app.waiter.WaitInterval() > time.Second { app.waiter.SetWaitInterval(app.waiter.WaitInterval() - time.Second) @@ -217,8 +211,9 @@ func (app *App) handleInputEvent(inputEvent event.Event) bool { case event.EventIncreasePollTime: app.waiter.SetWaitInterval(app.waiter.WaitInterval() + time.Second) case event.EventHelp: - app.help = !app.help - app.display.Clear() + app.viewManager.ToggleHelp() + app.viewManager.ClearDisplay() + app.Display() case event.EventToggleWantRelative: app.config.SetWantRelativeStats(!app.config.WantRelativeStats()) app.Display() diff --git a/app/app_test.go b/app/app_test.go index 0d50425..e34a94c 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -8,10 +8,8 @@ import ( "github.com/sjmudd/ps-top/wait" ) -// Note: UpdateCurrentTabler depends on view.SetupAndValidate state which -// initialises global view tables; testing it would require initialising a -// fake DB or stubbing view internals. The following tests focus on -// input-event handling which is pure logic and easy to unit-test. +// Note: The view management logic is now encapsulated in view.Manager. +// These tests focus on input-event handling which is pure logic and easy to unit-test. func TestHandleInputEvent_IncreaseDecreasePollTime(t *testing.T) { a := &App{} diff --git a/app/collector.go b/app/collector.go index 61d1d77..4746476 100644 --- a/app/collector.go +++ b/app/collector.go @@ -8,7 +8,6 @@ import ( "github.com/sjmudd/ps-top/presenter/tableiolatency" "github.com/sjmudd/ps-top/presenter/tableioops" "github.com/sjmudd/ps-top/pstable" - "github.com/sjmudd/ps-top/view" ) // DBCollector owns all the Tabler instances and coordinates data collection. @@ -25,7 +24,6 @@ type DBCollector struct { memoryUsage pstable.Tabler userLatency pstable.Tabler currentTabler pstable.Tabler - currentView view.View } // NewDBCollector creates and initializes all tablers. @@ -50,29 +48,6 @@ func NewDBCollector(cfg *config.Config, db *sql.DB) *DBCollector { return dc } -// UpdateCurrentTabler sets currentTabler based on currentView. -// Extracted from App.UpdateCurrentTabler. -func (dc *DBCollector) UpdateCurrentTabler() { - switch dc.currentView.Get() { - case view.ViewLatency: - dc.currentTabler = dc.tableIoLatency - case view.ViewOps: - dc.currentTabler = dc.tableIoOps - case view.ViewIO: - dc.currentTabler = dc.fileInfoLatency - case view.ViewLocks: - dc.currentTabler = dc.tableLockLatency - case view.ViewUsers: - dc.currentTabler = dc.userLatency - case view.ViewMutex: - dc.currentTabler = dc.mutexLatency - case view.ViewStages: - dc.currentTabler = dc.stagesLatency - case view.ViewMemory: - dc.currentTabler = dc.memoryUsage - } -} - // Collect collects data for the current tabler. // Extracted from App.Collect. func (dc *DBCollector) Collect() { @@ -103,27 +78,13 @@ func (dc *DBCollector) ResetAll() { dc.memoryUsage.ResetStatistics() } -// SetView updates the current view and refreshes currentTabler. -func (dc *DBCollector) SetView(v view.View) { - dc.currentView = v - dc.UpdateCurrentTabler() -} - // CurrentTabler returns the currently selected tabler (for display). func (dc *DBCollector) CurrentTabler() pstable.Tabler { return dc.currentTabler } -// CurrentView returns the current view code. -func (dc *DBCollector) CurrentView() view.View { - return dc.currentView -} - -// SetViewByName sets the view by name (convenience wrapper). -func (dc *DBCollector) SetViewByName(name string) error { - if err := dc.currentView.SetByName(name); err != nil { - return err - } - dc.UpdateCurrentTabler() - return nil +// SetCurrentTabler updates the currently selected tabler. +// Used by ViewManager when the view changes. +func (dc *DBCollector) SetCurrentTabler(t pstable.Tabler) { + dc.currentTabler = t } diff --git a/app/collector_test.go b/app/collector_test.go index 30a1d13..7728e9c 100644 --- a/app/collector_test.go +++ b/app/collector_test.go @@ -3,8 +3,6 @@ package app import ( "testing" "time" - - "github.com/sjmudd/ps-top/view" ) // mockTabler is a minimal implementation of pstable.Tabler for testing. @@ -141,28 +139,20 @@ func TestDBCollector_ResetAll(t *testing.T) { } } -// TestDBCollector_Accessors tests CurrentTabler and CurrentView getters. +// TestDBCollector_Accessors tests CurrentTabler getter and SetCurrentTabler setter. func TestDBCollector_Accessors(t *testing.T) { mt := &mockTabler{} - fv := view.View{} // zero value dc := &DBCollector{ currentTabler: mt, - currentView: fv, } if dc.CurrentTabler() != mt { t.Error("CurrentTabler did not return correct tabler") } - if dc.CurrentView() != fv { - t.Error("CurrentView did not return correct view") - } -} -// TestDBCollector_SetView tests that SetView updates currentView and calls UpdateCurrentTabler. -func TestDBCollector_SetView(t *testing.T) { - t.Skip("Requires view.View with valid manager; tested in integration") -} - -// TestDBCollector_SetViewByName tests SetViewByName; integration scope. -func TestDBCollector_SetViewByName(t *testing.T) { - t.Skip("Requires view.View with valid manager; tested in integration") + // Test SetCurrentTabler + mt2 := &mockTabler{name: "second"} + dc.SetCurrentTabler(mt2) + if dc.CurrentTabler() != mt2 { + t.Error("SetCurrentTabler did not update currentTabler") + } } diff --git a/view/manager.go b/view/manager.go new file mode 100644 index 0000000..d21998c --- /dev/null +++ b/view/manager.go @@ -0,0 +1,107 @@ +package view + +import ( + "github.com/sjmudd/ps-top/display" + "github.com/sjmudd/ps-top/pstable" +) + +// TablerUpdater knows how to update the current tabler. +// Implemented by DBCollector to receive tabler updates when the view changes. +type TablerUpdater interface { + SetCurrentTabler(pstable.Tabler) +} + +// Manager manages view state, tabler selection, and display. +// It owns the current view, maps views to tablers, and handles rendering. +type Manager struct { + view View + tablers map[Code]pstable.Tabler + display *display.Display + help bool + updater TablerUpdater +} + +// NewManager creates a Manager with the given initial view, tabler mapping, and display. +// The updater is notified when the current tabler changes (including at initialization). +func NewManager(v View, tablers map[Code]pstable.Tabler, d *display.Display, updater TablerUpdater) *Manager { + m := &Manager{ + view: v, + tablers: tablers, + display: d, + updater: updater, + } + // Initialize the current tabler to match the initial view + m.updater.SetCurrentTabler(m.CurrentTabler()) + return m +} + +// CurrentTabler returns the Tabler for the current view. +func (m *Manager) CurrentTabler() pstable.Tabler { + return m.tablers[m.view.Get()] +} + +// SetNext changes to the next view and updates the current tabler. +func (m *Manager) SetNext() { + m.view.SetNext() + m.updater.SetCurrentTabler(m.CurrentTabler()) +} + +// SetPrev changes to the previous view and updates the current tabler. +func (m *Manager) SetPrev() { + m.view.SetPrev() + m.updater.SetCurrentTabler(m.CurrentTabler()) +} + +// Set changes to the specified view code and updates the current tabler. +func (m *Manager) Set(code Code) { + m.view.Set(code) + m.updater.SetCurrentTabler(m.CurrentTabler()) +} + +// SetByName changes to the view with the given name and updates the current tabler. +// Returns an error if the name is not found or not selectable. +func (m *Manager) SetByName(name string) error { + if err := m.view.SetByName(name); err != nil { + return err + } + m.updater.SetCurrentTabler(m.CurrentTabler()) + return nil +} + +// ToggleHelp toggles the help display flag. +func (m *Manager) ToggleHelp() { + m.help = !m.help +} + +// Help returns whether help is currently shown. +func (m *Manager) Help() bool { + return m.help +} + +// Display renders the current view (either help or the current tabler) to the screen. +func (m *Manager) Display() { + if m.help { + m.display.Display(display.Help) + } else { + m.display.Display(m.CurrentTabler()) + } +} + +// ClearDisplay clears the screen. +func (m *Manager) ClearDisplay() { + m.display.Clear() +} + +// DisplayNext advances to the next view, clears the screen, and displays the new view. +func (m *Manager) DisplayNext() { + m.SetNext() + m.display.Clear() + m.Display() +} + +// DisplayPrev goes to the previous view, clears the screen, and displays the new view. +func (m *Manager) DisplayPrev() { + m.SetPrev() + m.display.Clear() + m.Display() +}