Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 25 additions & 30 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 (
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -207,18 +201,19 @@ 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)
}
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()
Expand Down
6 changes: 2 additions & 4 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
47 changes: 4 additions & 43 deletions app/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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() {
Expand Down Expand Up @@ -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
}
24 changes: 7 additions & 17 deletions app/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
}
}
107 changes: 107 additions & 0 deletions view/manager.go
Original file line number Diff line number Diff line change
@@ -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()
}
Loading