Rename state to app and furhter cleanups
authorSimon J Mudd <sjmudd@pobox.com>
Fri, 6 Feb 2015 17:12:51 +0000 (18:12 +0100)
committerSimon J Mudd <sjmudd@pobox.com>
Fri, 6 Feb 2015 17:12:51 +0000 (18:12 +0100)
app/app.go [new file with mode: 0644]
connector/connector.go [new file with mode: 0644]
i_s/processlist/public.go
key_value_cache/key_value_cache.go
lib/common.go
main.go
p_s/setup_instruments/setup_instruments.go
screen/screen.go
state/state.go [deleted file]
version/version.go

diff --git a/app/app.go b/app/app.go
new file mode 100644 (file)
index 0000000..1d44bab
--- /dev/null
@@ -0,0 +1,624 @@
+// lib - library routines for pstop.
+//
+// this file contains the library routines related to the stored state in pstop.
+package app
+
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "log"
+       "os"
+       "os/signal"
+       "regexp"
+       "strings"
+       "syscall"
+       "time"
+
+       "github.com/nsf/termbox-go"
+
+       "github.com/sjmudd/pstop/i_s/processlist"
+       "github.com/sjmudd/pstop/lib"
+       essgben "github.com/sjmudd/pstop/p_s/events_stages_summary_global_by_event_name"
+       ewsgben "github.com/sjmudd/pstop/p_s/events_waits_summary_global_by_event_name"
+       fsbi "github.com/sjmudd/pstop/p_s/file_summary_by_instance"
+       "github.com/sjmudd/pstop/p_s/ps_table"
+       "github.com/sjmudd/pstop/p_s/setup_instruments"
+       tiwsbt "github.com/sjmudd/pstop/p_s/table_io_waits_summary_by_table"
+       tlwsbt "github.com/sjmudd/pstop/p_s/table_lock_waits_summary_by_table"
+       "github.com/sjmudd/pstop/screen"
+       "github.com/sjmudd/pstop/version"
+       "github.com/sjmudd/pstop/wait_info"
+)
+
+// what information to show
+type Show int
+
+const (
+       showLatency = iota
+       showOps     = iota
+       showIO      = iota
+       showLocks   = iota
+       showUsers   = iota
+       showMutex   = iota
+       showStages  = iota
+)
+
+var (
+        re_valid_version = regexp.MustCompile(`^(5\.[67]\.|10\.[01])`)
+)
+
+type App struct {
+       finished            bool
+       datadir             string
+       dbh                 *sql.DB
+       help                bool
+       hostname            string
+       fsbi                ps_table.Tabler // ufsbi.File_summary_by_instance
+       tiwsbt              tiwsbt.Object
+       tlwsbt              ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
+       ewsgben             ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
+       essgben             ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
+       users               processlist.Object
+       screen              screen.TermboxScreen
+       show                Show
+       mysql_version       string
+       want_relative_stats bool
+       wait_info.WaitInfo  // embedded
+       setup_instruments   setup_instruments.SetupInstruments
+}
+
+func (app *App) Setup(dbh *sql.DB) {
+       app.dbh = dbh
+
+       if err := app.validate_mysql_version(); err != nil {
+                log.Fatal(err)
+        }
+
+       app.finished = false
+
+       app.screen.Initialise()
+
+       app.setup_instruments = setup_instruments.NewSetupInstruments(dbh)
+       app.setup_instruments.EnableMonitoring()
+
+       _, variables := lib.SelectAllGlobalVariablesByVariableName(app.dbh)
+       // setup to their initial types/values
+       app.fsbi = fsbi.NewFileSummaryByInstance(variables)
+       app.tlwsbt = new(tlwsbt.Object)
+       app.ewsgben = new(ewsgben.Object)
+       app.essgben = new(essgben.Object)
+
+       app.want_relative_stats = true // we show info from the point we start collecting data
+       app.fsbi.SetWantRelativeStats(app.want_relative_stats)
+       app.fsbi.SetNow()
+       app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
+       app.tlwsbt.SetNow()
+       app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
+       app.tiwsbt.SetNow()
+       app.users.SetWantRelativeStats(app.want_relative_stats) // ignored
+       app.users.SetNow()                                        // ignored
+       app.essgben.SetWantRelativeStats(app.want_relative_stats)
+       app.essgben.SetNow()
+       app.ewsgben.SetWantRelativeStats(app.want_relative_stats) // ignored
+       app.ewsgben.SetNow()                                        // ignored
+
+       app.ResetDBStatistics()
+
+       app.SetHelp(false)
+       app.show = showLatency
+       app.tiwsbt.SetWantsLatency(true)
+
+       // get short name (to save space)
+       _, hostname := lib.SelectGlobalVariableByVariableName(app.dbh, "HOSTNAME")
+       if index := strings.Index(hostname, "."); index >= 0 {
+               hostname = hostname[0:index]
+       }
+       _, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
+       _, datadir := lib.SelectGlobalVariableByVariableName(app.dbh, "DATADIR")
+       app.SetHostname(hostname)
+       app.SetMySQLVersion(mysql_version)
+       app.SetDatadir(datadir)
+}
+
+// have we finished ?
+func (app App) Finished() bool {
+       return app.finished
+}
+
+// indicate we have finished
+func (app *App) SetFinished() {
+       app.finished = true
+}
+
+// do a fresh collection of data and then update the initial values based on that.
+func (app *App) ResetDBStatistics() {
+       app.CollectAll()
+       app.SyncReferenceValues()
+}
+
+func (app *App) SyncReferenceValues() {
+       start := time.Now()
+       app.fsbi.SyncReferenceValues()
+       app.tlwsbt.SyncReferenceValues()
+       app.tiwsbt.SyncReferenceValues()
+       app.essgben.SyncReferenceValues()
+       lib.Logger.Println("app.SyncReferenceValues() took", time.Duration(time.Since(start)).String())
+}
+
+// collect all initial values on startup / reset
+func (app *App) CollectAll() {
+       app.fsbi.Collect(app.dbh)
+       app.tlwsbt.Collect(app.dbh)
+       app.tiwsbt.Collect(app.dbh)
+}
+
+// Only collect the data we are looking at.
+func (app *App) Collect() {
+       start := time.Now()
+
+       switch app.show {
+       case showLatency, showOps:
+               app.tiwsbt.Collect(app.dbh)
+       case showIO:
+               app.fsbi.Collect(app.dbh)
+       case showLocks:
+               app.tlwsbt.Collect(app.dbh)
+       case showUsers:
+               app.users.Collect(app.dbh)
+       case showMutex:
+               app.ewsgben.Collect(app.dbh)
+       case showStages:
+               app.essgben.Collect(app.dbh)
+       }
+       lib.Logger.Println("app.Collect() took", time.Duration(time.Since(start)).String())
+}
+
+func (app App) MySQLVersion() string {
+       return app.mysql_version
+}
+
+func (app App) Datadir() string {
+       return app.datadir
+}
+
+func (app *App) SetHelp(newHelp bool) {
+       app.help = newHelp
+
+       app.screen.Clear()
+       app.screen.Flush()
+}
+
+func (app *App) SetDatadir(datadir string) {
+       app.datadir = datadir
+}
+
+func (app *App) SetMySQLVersion(mysql_version string) {
+       app.mysql_version = mysql_version
+}
+
+func (app *App) SetHostname(hostname string) {
+       app.hostname = hostname
+}
+
+func (app App) Help() bool {
+       return app.help
+}
+
+// apps go: showLatency -> showOps -> showIO -> showLocks -> showUsers -> showMutex -> showStages
+
+// display the output according to the mode we are in
+func (app *App) Display() {
+       if app.help {
+               app.screen.DisplayHelp()
+       } else {
+               app.displayHeading()
+               switch app.show {
+               case showLatency, showOps:
+                       app.displayOpsOrLatency()
+               case showIO:
+                       app.displayIO()
+               case showLocks:
+                       app.displayLocks()
+               case showUsers:
+                       app.displayUsers()
+               case showMutex:
+                       app.displayMutex()
+               case showStages:
+                       app.displayStages()
+               }
+       }
+}
+
+// fix_latency_setting() ensures the SetWantsLatency() value is
+// correct. This needs to be done more cleanly.
+func (app *App) fix_latency_setting() {
+       if app.show == showLatency {
+               app.tiwsbt.SetWantsLatency(true)
+       }
+       if app.show == showOps {
+               app.tiwsbt.SetWantsLatency(false)
+       }
+}
+
+// change to the previous display mode
+func (app *App) DisplayPrevious() {
+       if app.show == showLatency {
+               app.show = showStages
+       } else {
+               app.show--
+       }
+       app.fix_latency_setting()
+       app.screen.Clear()
+       app.screen.Flush()
+}
+
+// change to the next display mode
+func (app *App) DisplayNext() {
+       if app.show == showStages {
+               app.show = showLatency
+       } else {
+               app.show++
+       }
+       app.fix_latency_setting()
+       app.screen.Clear()
+       app.screen.Flush()
+}
+
+func (app App) displayHeading() {
+       app.displayLine0()
+       app.displayDescription()
+}
+
+func (app App) displayLine0() {
+       _, uptime := lib.SelectGlobalStatusByVariableName(app.dbh, "UPTIME")
+       top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + app.hostname + " / " + app.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
+       if app.want_relative_stats {
+               now := time.Now()
+
+               var initial time.Time
+
+               switch app.show {
+               case showLatency, showOps:
+                       initial = app.tiwsbt.Last()
+               case showIO:
+                       initial = app.fsbi.Last()
+               case showLocks:
+                       initial = app.tlwsbt.Last()
+               case showUsers:
+                       initial = app.users.Last()
+               case showStages:
+                       initial = app.essgben.Last()
+               case showMutex:
+                       initial = app.ewsgben.Last()
+               default:
+                       // should not get here !
+               }
+
+               d := now.Sub(initial)
+
+               top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
+       } else {
+               top_line = top_line + " [ABS]             "
+       }
+       app.screen.PrintAt(0, 0, top_line)
+}
+
+func (app App) displayDescription() {
+       description := "UNKNOWN"
+
+       switch app.show {
+       case showLatency, showOps:
+               description = app.tiwsbt.Description()
+       case showIO:
+               description = app.fsbi.Description()
+       case showLocks:
+               description = app.tlwsbt.Description()
+       case showUsers:
+               description = app.users.Description()
+       case showMutex:
+               description = app.ewsgben.Description()
+       case showStages:
+               description = app.essgben.Description()
+       }
+
+       app.screen.PrintAt(0, 1, description)
+}
+
+func (app *App) displayOpsOrLatency() {
+       app.screen.BoldPrintAt(0, 2, app.tiwsbt.Headings())
+
+       max_rows := app.screen.Height() - 3
+       row_content := app.tiwsbt.RowContent(max_rows)
+
+       // print out rows
+       for k := range row_content {
+               y := 3 + k
+               app.screen.PrintAt(0, y, row_content[k])
+       }
+       // print out empty rows
+       for k := len(row_content); k < (app.screen.Height() - 3); k++ {
+               y := 3 + k
+               if y < app.screen.Height()-1 {
+                       app.screen.PrintAt(0, y, app.tiwsbt.EmptyRowContent())
+               }
+       }
+
+       // print out the totals at the bottom
+       app.screen.BoldPrintAt(0, app.screen.Height()-1, app.tiwsbt.TotalRowContent())
+}
+
+// show actual I/O latency values
+func (app App) displayIO() {
+       app.screen.BoldPrintAt(0, 2, app.fsbi.Headings())
+
+       // print out the data
+       max_rows := app.screen.Height() - 3
+       row_content := app.fsbi.RowContent(max_rows)
+
+       // print out rows
+       for k := range row_content {
+               y := 3 + k
+               app.screen.PrintAt(0, y, row_content[k])
+       }
+       // print out empty rows
+       for k := len(row_content); k < (app.screen.Height() - 3); k++ {
+               y := 3 + k
+               if y < app.screen.Height()-1 {
+                       app.screen.PrintAt(0, y, app.fsbi.EmptyRowContent())
+               }
+       }
+
+       // print out the totals at the bottom
+       app.screen.BoldPrintAt(0, app.screen.Height()-1, app.fsbi.TotalRowContent())
+}
+
+func (app *App) displayLocks() {
+       app.screen.BoldPrintAt(0, 2, app.tlwsbt.Headings())
+
+       // print out the data
+       max_rows := app.screen.Height() - 3
+       row_content := app.tlwsbt.RowContent(max_rows)
+
+       // print out rows
+       for k := range row_content {
+               y := 3 + k
+               app.screen.PrintAt(0, y, row_content[k])
+       }
+       // print out empty rows
+       for k := len(row_content); k < (app.screen.Height() - 3); k++ {
+               y := 3 + k
+               if y < app.screen.Height()-1 {
+                       app.screen.PrintAt(0, y, app.tlwsbt.EmptyRowContent())
+               }
+       }
+
+       // print out the totals at the bottom
+       app.screen.BoldPrintAt(0, app.screen.Height()-1, app.tlwsbt.TotalRowContent())
+}
+
+func (app *App) displayUsers() {
+       app.screen.BoldPrintAt(0, 2, app.users.Headings())
+
+       // print out the data
+       max_rows := app.screen.Height() - 3
+       row_content := app.users.RowContent(max_rows)
+
+       // print out rows
+       for k := range row_content {
+               y := 3 + k
+               app.screen.PrintAt(0, y, row_content[k])
+       }
+       // print out empty rows
+       for k := len(row_content); k < (app.screen.Height() - 3); k++ {
+               y := 3 + k
+               if y < app.screen.Height()-1 {
+                       app.screen.PrintAt(0, y, app.users.EmptyRowContent())
+               }
+       }
+
+       // print out the totals at the bottom
+       app.screen.BoldPrintAt(0, app.screen.Height()-1, app.users.TotalRowContent())
+}
+
+func (app *App) displayMutex() {
+       app.screen.BoldPrintAt(0, 2, app.ewsgben.Headings())
+
+       // print out the data
+       max_rows := app.screen.Height() - 3
+       row_content := app.ewsgben.RowContent(max_rows)
+
+       // print out rows
+       for k := range row_content {
+               y := 3 + k
+               app.screen.PrintAt(0, y, row_content[k])
+       }
+       // print out empty rows
+       for k := len(row_content); k < (app.screen.Height() - 3); k++ {
+               y := 3 + k
+               if y < app.screen.Height()-1 {
+                       app.screen.PrintAt(0, y, app.ewsgben.EmptyRowContent())
+               }
+       }
+
+       // print out the totals at the bottom
+       app.screen.BoldPrintAt(0, app.screen.Height()-1, app.ewsgben.TotalRowContent())
+}
+
+func (app *App) displayStages() {
+       app.screen.BoldPrintAt(0, 2, app.essgben.Headings())
+
+       // print out the data
+       max_rows := app.screen.Height() - 3
+       row_content := app.essgben.RowContent(max_rows)
+
+       // print out rows
+       for k := range row_content {
+               y := 3 + k
+               app.screen.PrintAt(0, y, row_content[k])
+       }
+       // print out empty rows
+       for k := len(row_content); k < (app.screen.Height() - 3); k++ {
+               y := 3 + k
+               if y < app.screen.Height()-1 {
+                       app.screen.PrintAt(0, y, app.essgben.EmptyRowContent())
+               }
+       }
+
+       // print out the totals at the bottom
+       app.screen.BoldPrintAt(0, app.screen.Height()-1, app.essgben.TotalRowContent())
+}
+
+// do we want to show all p_s data?
+func (app App) WantRelativeStats() bool {
+       return app.want_relative_stats
+}
+
+// set if we want data from when we started/reset stats.
+func (app *App) SetWantRelativeStats(want_relative_stats bool) {
+       app.want_relative_stats = want_relative_stats
+
+       app.fsbi.SetWantRelativeStats(want_relative_stats)
+       app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
+       app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
+       app.ewsgben.SetWantRelativeStats(app.want_relative_stats)
+       app.essgben.SetWantRelativeStats(app.want_relative_stats)
+}
+
+// if there's a better way of doing this do it better ...
+func now_hhmmss() string {
+       t := time.Now()
+       return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
+}
+
+// record the latest screen size
+func (app *App) ScreenSetSize(width, height int) {
+       app.screen.SetSize(width, height)
+}
+
+// clean up screen and disconnect database
+func (app *App) Cleanup() {
+       app.screen.Close()
+       if app.dbh != nil {
+               app.setup_instruments.RestoreConfiguration()
+               _ = app.dbh.Close()
+       }
+}
+
+// make chan for termbox events and run a poller to send events to the channel
+// - return the channel
+func new_tb_chan() chan termbox.Event {
+       termboxChan := make(chan termbox.Event)
+       go func() {
+               for {
+                       termboxChan <- termbox.PollEvent()
+               }
+       }()
+       return termboxChan
+}
+
+// get into a run loop
+func (app *App) Run() {
+       done := make(chan struct{})
+       defer close(done)
+
+       sigChan := make(chan os.Signal, 1)
+       signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+       var wi wait_info.WaitInfo
+       wi.SetWaitInterval(time.Second)
+
+       termboxChan := new_tb_chan()
+
+       for !app.Finished() {
+               select {
+               case <-done:
+                       fmt.Println("exiting")
+                       app.SetFinished()
+               case sig := <-sigChan:
+                       fmt.Println("Caught a signal", sig)
+                       done <- struct{}{}
+               case <-wi.WaitNextPeriod():
+                       app.Collect()
+                       wi.CollectedNow()
+                       app.Display()
+               case event := <-termboxChan:
+                       // switch on event type
+                       switch event.Type {
+                       case termbox.EventKey: // actions depend on key
+                               switch event.Key {
+                               case termbox.KeyCtrlZ, termbox.KeyCtrlC, termbox.KeyEsc:
+                                       app.SetFinished()
+                               case termbox.KeyArrowLeft: // left arrow change to previous display mode
+                                       app.DisplayPrevious()
+                                       app.Display()
+                               case termbox.KeyTab, termbox.KeyArrowRight: // tab or right arrow - change to next display mode
+                                       app.DisplayNext()
+                                       app.Display()
+                               }
+                               switch event.Ch {
+                               case '-': // decrease the interval if > 1
+                                       if wi.WaitInterval() > time.Second {
+                                               wi.SetWaitInterval(wi.WaitInterval() - time.Second)
+                                       }
+                               case '+': // increase interval by creating a new ticker
+                                       wi.SetWaitInterval(wi.WaitInterval() + time.Second)
+                               case 'h', '?': // help
+                                       app.SetHelp(!app.Help())
+                               case 'q': // quit
+                                       app.SetFinished()
+                               case 't': // toggle between absolute/relative statistics
+                                       app.SetWantRelativeStats(!app.WantRelativeStats())
+                                       app.Display()
+                               case 'z': // reset the statistics to now by taking a query of current values
+                                       app.ResetDBStatistics()
+                                       app.Display()
+                               }
+                       case termbox.EventResize: // set sizes
+                               app.ScreenSetSize(event.Width, event.Height)
+                               app.Display()
+                       case termbox.EventError: // quit
+                               log.Fatalf("Quitting because of termbox error: \n%s\n", event.Err)
+                       }
+               }
+       }
+}
+
+// pstop requires MySQL 5.6+ or MariaDB 10.0+. Check the version
+// rather than giving an error message if the requires P_S tables can't
+// be found.
+func (app *App) validate_mysql_version() error {
+        var tables = [...]string{
+                "performance_schema.events_waits_summary_global_by_event_name",
+                "performance_schema.file_summary_by_instance",
+                "performance_schema.table_io_waits_summary_by_table",
+                "performance_schema.table_lock_waits_summary_by_table",
+        }
+
+        lib.Logger.Println("validate_mysql_version()")
+
+        lib.Logger.Println("- Getting MySQL version")
+        err, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
+        if err != nil {
+                return err
+        }
+        lib.Logger.Println("- mysql_version: '" + mysql_version + "'")
+
+        if !re_valid_version.MatchString(mysql_version) {
+                return errors.New(lib.MyName() + " does not work with MySQL version " + mysql_version)
+        }
+        lib.Logger.Println("OK: MySQL version is valid, continuing")
+
+        lib.Logger.Println("Checking access to required tables:")
+        for i := range tables {
+                if err := lib.CheckTableAccess(app.dbh, tables[i]); err == nil {
+                        lib.Logger.Println("OK: " + tables[i] + " found")
+                } else {
+                        return err
+                }
+        }
+        lib.Logger.Println("OK: all table checks passed")
+
+        return nil
+}
+
diff --git a/connector/connector.go b/connector/connector.go
new file mode 100644 (file)
index 0000000..53077e8
--- /dev/null
@@ -0,0 +1,104 @@
+// Use Connector to specify how to connect to MySQL.
+// Then get a sql.*DB from it which is returned to the app..
+package connector
+
+import (
+       "database/sql"
+       "log"
+
+       _ "github.com/go-sql-driver/mysql"
+
+       "github.com/sjmudd/mysql_defaults_file"
+       "github.com/sjmudd/pstop/lib"
+)
+
+const (
+       sql_driver = "mysql"
+       db         = "performance_schema"
+
+       DEFAULTS_FILE = iota
+       COMPONENTS    = iota
+)
+
+// connector struct
+type Connector struct {
+       connectBy int
+       components map[string] string
+       defaults_file string
+       dbh *sql.DB
+}
+
+// return the database handle
+func (c Connector) Handle() *sql.DB {
+       return c.dbh
+}
+
+// return the defaults file
+func (c Connector) DefaultsFile() string {
+       return c.defaults_file
+}
+
+// set the defaults file
+func (c *Connector) SetDefaultsFile( defaults_file string ) {
+       c.defaults_file = defaults_file
+}
+
+// set the components
+func (c *Connector) SetComponents(components map[string]string) {
+       c.components = components
+}
+
+// things to do after connecting
+func (c *Connector) postConnectStuff() {
+       if err := c.dbh.Ping(); err != nil {
+               log.Fatal(err)
+       }
+
+       // deliberately limit the pool size to 5 to avoid "problems" if any queries hang.
+       c.dbh.SetMaxOpenConns(5) // hard-coded value!
+}
+
+// determine how we want to connect
+func (c *Connector) SetConnectBy( connectHow int ) {
+       c.connectBy = connectHow
+}
+
+// make the database connection
+func (c *Connector) Connect() {
+       var err error
+
+       switch {
+       case c.connectBy == DEFAULTS_FILE:
+               lib.Logger.Println("connect_by_defaults_file() connecting to database")
+
+               c.dbh, err = mysql_defaults_file.OpenUsingDefaultsFile(sql_driver, c.defaults_file, db)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       case c.connectBy == COMPONENTS:
+               lib.Logger.Println("connect_by_components() connecting to database")
+
+               new_dsn := mysql_defaults_file.BuildDSN(c.components, db)
+               c.dbh, err = sql.Open(sql_driver, new_dsn)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       default:
+               log.Fatal("Connector.Connect() c.connectBy not DEFAULTS_FILE/COMPONENTS")
+       }
+       c.postConnectStuff()
+}
+
+// Connect to MySQL using various component parts needed to make the dsn.
+func (c *Connector) ConnectByComponents(components map[string]string) {
+       c.SetComponents(components)
+       c.SetConnectBy(COMPONENTS)
+       c.Connect()
+}
+
+// Connect to the database with the given defaults-file, or ~/.my.cnf if not provided.
+func (c *Connector) ConnectByDefaultsFile(defaults_file string) {
+       c.SetDefaultsFile(defaults_file)
+       c.SetConnectBy(DEFAULTS_FILE)
+       c.Connect()
+}
index dcd0a25..4a75c1d 100644 (file)
@@ -20,9 +20,9 @@ type map_string_int map[string]int
 type Object struct {
        p_s.RelativeStats
        p_s.InitialTime
-       current table_rows // processlist
-       results pl_by_user_rows  // results by user
-       totals  pl_by_user_row   // totals of results
+       current table_rows      // processlist
+       results pl_by_user_rows // results by user
+       totals  pl_by_user_row  // totals of results
 }
 
 // Collect() collects data from the db, updating initial
index 9b689a2..9292de5 100644 (file)
@@ -20,7 +20,7 @@ type KeyValueCache struct {
 func NewKeyValueCache() KeyValueCache {
        lib.Logger.Println("KeyValueCache()")
 
-       return KeyValueCache {}
+       return KeyValueCache{}
 }
 
 // Given a lookup key return the value if found.
index 3cee791..d93f776 100644 (file)
@@ -138,13 +138,13 @@ func FormatAmount(amount uint64) string {
 }
 
 // like Amount but tigher in space
-func FormatCounter( counter int, width int ) string {
+func FormatCounter(counter int, width int) string {
        if counter == 0 {
                pattern := "%" + fmt.Sprintf("%d", width) + "s"
-               return fmt.Sprintf( pattern, " " )
+               return fmt.Sprintf(pattern, " ")
        } else {
                pattern := "%" + fmt.Sprintf("%d", width) + "d"
-               return fmt.Sprintf( pattern, counter )
+               return fmt.Sprintf(pattern, counter)
        }
 }
 
diff --git a/main.go b/main.go
index facfa65..2d218f9 100644 (file)
--- a/main.go
+++ b/main.go
@@ -3,26 +3,18 @@
 package main
 
 import (
-       "database/sql"
-       "errors"
        "flag"
        "fmt"
        "log"
        "os"
-       "os/signal"
-       "regexp"
        "runtime/pprof"
-       "syscall"
-       "time"
 
        _ "github.com/go-sql-driver/mysql"
-       "github.com/nsf/termbox-go"
 
-       "github.com/sjmudd/mysql_defaults_file"
+       "github.com/sjmudd/pstop/app"
+       "github.com/sjmudd/pstop/connector"
        "github.com/sjmudd/pstop/lib"
-       "github.com/sjmudd/pstop/state"
        "github.com/sjmudd/pstop/version"
-       "github.com/sjmudd/pstop/wait_info"
 )
 
 const (
@@ -35,66 +27,14 @@ var (
        flag_defaults_file = flag.String("defaults-file", "", "Provide a defaults-file to use to connect to MySQL")
        flag_help          = flag.Bool("help", false, "Provide some help for "+lib.MyName())
        flag_host          = flag.String("host", "", "Provide the hostname of the MySQL to connect to")
-       flag_port          = flag.Int("port", 0 , "Provide the port number of the MySQL to connect to (default: 3306)") /* deliberately 0 here, defaults to 3306 elsewhere */
+       flag_port          = flag.Int("port", 0, "Provide the port number of the MySQL to connect to (default: 3306)") /* deliberately 0 here, defaults to 3306 elsewhere */
        flag_socket        = flag.String("socket", "", "Provide the path to the local MySQL server to connect to")
        flag_password      = flag.String("password", "", "Provide the password when connecting to the MySQL server")
        flag_user          = flag.String("user", "", "Provide the username to connect with to MySQL (default: $USER)")
        flag_version       = flag.Bool("version", false, "Show the version of "+lib.MyName())
        cpuprofile         = flag.String("cpuprofile", "", "write cpu profile to file")
-
-       re_valid_version = regexp.MustCompile(`^(5\.[67]\.|10\.[01])`)
 )
 
-// Connect to the database with the given defaults-file, or ~/.my.cnf if not provided.
-func connect_by_defaults_file( defaults_file string ) *sql.DB {
-       var err error
-       var dbh *sql.DB
-       lib.Logger.Println("connect_by_defaults_file() connecting to database")
-
-       dbh, err = mysql_defaults_file.OpenUsingDefaultsFile(sql_driver, defaults_file, "performance_schema")
-       if err != nil {
-               log.Fatal(err)
-       }
-       if err = dbh.Ping(); err != nil {
-               log.Fatal(err)
-       }
-
-       // deliberately limit the pool size to 5 to avoid "problems" if any queries hang.
-       dbh.SetMaxOpenConns(5) // hard-coded value!
-
-       return dbh
-}
-
-// connect to MySQL using various component parts needed to make the dsn
-func connect_by_components( components map[string]string ) *sql.DB {
-       var err error
-       var dbh *sql.DB
-       lib.Logger.Println("connect_by_components() connecting to database")
-
-       new_dsn := mysql_defaults_file.BuildDSN(components, "performance_schema")
-       dbh, err = sql.Open(sql_driver, new_dsn)
-       if err != nil {
-               log.Fatal(err)
-       }
-       if err = dbh.Ping(); err != nil {
-               log.Fatal(err)
-       }
-
-       return dbh
-}
-
-// make chan for termbox events and run a poller to send events to the channel
-// - return the channel
-func new_tb_chan() chan termbox.Event {
-       termboxChan := make(chan termbox.Event)
-       go func() {
-               for {
-                       termboxChan <- termbox.PollEvent()
-               }
-       }()
-       return termboxChan
-}
-
 func usage() {
        fmt.Println(lib.MyName() + " - " + lib.Copyright())
        fmt.Println("")
@@ -104,7 +44,7 @@ func usage() {
        fmt.Println("Usage: " + lib.MyName() + " <options>")
        fmt.Println("")
        fmt.Println("Options:")
-       fmt.Println("--defaults-file=/path/to/defaults.file   Connect to MySQL using given defaults-file" )
+       fmt.Println("--defaults-file=/path/to/defaults.file   Connect to MySQL using given defaults-file")
        fmt.Println("--help                                   Show this help message")
        fmt.Println("--version                                Show the version")
        fmt.Println("--host=<hostname>                        MySQL host to connect to")
@@ -114,45 +54,6 @@ func usage() {
        fmt.Println("--password=<password>                    Password to use when connecting")
 }
 
-// pstop requires MySQL 5.6+ or MariaDB 10.0+. Check the version
-// rather than giving an error message if the requires P_S tables can't
-// be found.
-func validate_mysql_version(dbh *sql.DB) error {
-       var tables = [...]string{
-               "performance_schema.events_waits_summary_global_by_event_name",
-               "performance_schema.file_summary_by_instance",
-               "performance_schema.table_io_waits_summary_by_table",
-               "performance_schema.table_lock_waits_summary_by_table",
-       }
-
-       lib.Logger.Println("validate_mysql_version()")
-
-       lib.Logger.Println("- Getting MySQL version")
-       err, mysql_version := lib.SelectGlobalVariableByVariableName(dbh, "VERSION")
-       if err != nil {
-               return err
-       }
-       lib.Logger.Println("- mysql_version: '" + mysql_version + "'")
-
-       if !re_valid_version.MatchString(mysql_version) {
-               err := errors.New(lib.MyName() + " does not work with MySQL version " + mysql_version)
-               return err
-       }
-       lib.Logger.Println("OK: MySQL version is valid, continuing")
-
-       lib.Logger.Println("Checking access to required tables:")
-       for i := range tables {
-               if err := lib.CheckTableAccess(dbh, tables[i]); err == nil {
-                       lib.Logger.Println("OK: " + tables[i] + " found")
-               } else {
-                       return err
-               }
-       }
-       lib.Logger.Println("OK: all table checks passed")
-
-       return nil
-}
-
 func main() {
        var defaults_file string = ""
        flag.Parse()
@@ -181,13 +82,13 @@ func main() {
 
        lib.Logger.Println("Starting " + lib.MyName())
 
-       var dbh *sql.DB
+       var connector connector.Connector
 
        if *flag_host != "" || *flag_socket != "" {
                lib.Logger.Println("--host= or --socket= defined")
                var components = make(map[string]string)
                if *flag_host != "" && *flag_socket != "" {
-                       fmt.Println(lib.MyName() + ": Do not specify --host and --socket together" )
+                       fmt.Println(lib.MyName() + ": Do not specify --host and --socket together")
                        os.Exit(1)
                }
                if *flag_host != "" {
@@ -197,7 +98,7 @@ func main() {
                        if *flag_socket == "" {
                                components["port"] = fmt.Sprintf("%d", *flag_port)
                        } else {
-                               fmt.Println(lib.MyName() + ": Do not specify --socket and --port together" )
+                               fmt.Println(lib.MyName() + ": Do not specify --socket and --port together")
                                os.Exit(1)
                        }
                }
@@ -210,85 +111,21 @@ func main() {
                if *flag_password != "" {
                        components["password"] = *flag_password
                }
-               dbh = connect_by_components( components )
+               connector.ConnectByComponents(components)
        } else {
-                if flag_defaults_file != nil && *flag_defaults_file != "" {
+               if flag_defaults_file != nil && *flag_defaults_file != "" {
                        lib.Logger.Println("--defaults-file defined")
                        defaults_file = *flag_defaults_file
                } else {
                        lib.Logger.Println("connecting by implicit defaults file")
                }
-               dbh = connect_by_defaults_file( defaults_file )
+               connector.ConnectByDefaultsFile(defaults_file)
        }
 
-       if err := validate_mysql_version(dbh); err != nil {
-               log.Fatal(err)
-       }
-
-       var state state.State
-       var wi wait_info.WaitInfo
-       wi.SetWaitInterval(time.Second)
-
-       sigChan := make(chan os.Signal, 1)
-       done := make(chan struct{})
-       defer close(done)
-       termboxChan := new_tb_chan()
+       var app app.App
 
-       signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-
-       state.Setup(dbh)
-       for !state.Finished() {
-               select {
-               case <-done:
-                       fmt.Println("exiting")
-                       state.SetFinished()
-               case sig := <-sigChan:
-                       fmt.Println("Caught a signal", sig)
-                       done <- struct{}{}
-               case <-wi.WaitNextPeriod():
-                       state.Collect()
-                       wi.CollectedNow()
-                       state.Display()
-               case event := <-termboxChan:
-                       // switch on event type
-                       switch event.Type {
-                       case termbox.EventKey: // actions depend on key
-                               switch event.Key {
-                               case termbox.KeyCtrlZ, termbox.KeyCtrlC, termbox.KeyEsc:
-                                       state.SetFinished()
-                               case termbox.KeyArrowLeft: // left arrow change to previous display mode
-                                       state.DisplayPrevious()
-                                       state.Display()
-                               case termbox.KeyTab, termbox.KeyArrowRight: // tab or right arrow - change to next display mode
-                                       state.DisplayNext()
-                                       state.Display()
-                               }
-                               switch event.Ch {
-                               case '-': // decrease the interval if > 1
-                                       if wi.WaitInterval() > time.Second {
-                                               wi.SetWaitInterval(wi.WaitInterval() - time.Second)
-                                       }
-                               case '+': // increase interval by creating a new ticker
-                                       wi.SetWaitInterval(wi.WaitInterval() + time.Second)
-                               case 'h', '?': // help
-                                       state.SetHelp(!state.Help())
-                               case 'q': // quit
-                                       state.SetFinished()
-                               case 't': // toggle between absolute/relative statistics
-                                       state.SetWantRelativeStats(!state.WantRelativeStats())
-                                       state.Display()
-                               case 'z': // reset the statistics to now by taking a query of current values
-                                       state.ResetDBStatistics()
-                                       state.Display()
-                               }
-                       case termbox.EventResize: // set sizes
-                               state.ScreenSetSize(event.Width, event.Height)
-                               state.Display()
-                       case termbox.EventError: // quit
-                               log.Fatalf("Quitting because of termbox error: \n%s\n", event.Err)
-                       }
-               }
-       }
-       state.Cleanup()
+       app.Setup(connector.Handle())
+       app.Run()
+       app.Cleanup()
        lib.Logger.Println("Terminating " + lib.MyName())
 }
index 8a93493..3475be4 100644 (file)
@@ -37,7 +37,7 @@ type SetupInstruments struct {
 // Return a newly initialised SetupInstruments structure with a handle to the database.
 // Better to return a pointer ?
 func NewSetupInstruments(dbh *sql.DB) SetupInstruments {
-       return SetupInstruments{ dbh: dbh }
+       return SetupInstruments{dbh: dbh}
 }
 
 // enable mutex and stage monitoring
@@ -144,7 +144,7 @@ func (si *SetupInstruments) Configure(select_sql string, collecting, updating st
                count = 0
                for i := range si.rows {
                        lib.Logger.Println("- changing row:", si.rows[i].NAME)
-                       lib.Logger.Println("stmt.Exec", "YES", "YES", si.rows[i].NAME )
+                       lib.Logger.Println("stmt.Exec", "YES", "YES", si.rows[i].NAME)
                        if res, err := stmt.Exec("YES", "YES", si.rows[i].NAME); err == nil {
                                lib.Logger.Println("update succeeded")
                                si.update_succeeded = true
@@ -166,7 +166,7 @@ func (si *SetupInstruments) Configure(select_sql string, collecting, updating st
                }
                stmt.Close()
        }
-       lib.Logger.Println( "Configure() returns update_tried", si.update_tried, ", update_succeeded", si.update_succeeded)
+       lib.Logger.Println("Configure() returns update_tried", si.update_tried, ", update_succeeded", si.update_succeeded)
 }
 
 // restore setup_instruments rows to their previous settings
@@ -182,14 +182,14 @@ func (si *SetupInstruments) RestoreConfiguration() {
 
        // update the rows which need to be set - do multiple updates but I don't care
        update_sql := "UPDATE setup_instruments SET enabled = ?, TIMED = ? WHERE NAME = ?"
-       lib.Logger.Println("dbh.Prepare(",update_sql,")")
+       lib.Logger.Println("dbh.Prepare(", update_sql, ")")
        stmt, err := si.dbh.Prepare(update_sql)
        if err != nil {
                log.Fatal(err)
        }
        count := 0
        for i := range si.rows {
-               lib.Logger.Println("stmt.Exec(",si.rows[i].ENABLED, si.rows[i].TIMED, si.rows[i].NAME,")")
+               lib.Logger.Println("stmt.Exec(", si.rows[i].ENABLED, si.rows[i].TIMED, si.rows[i].NAME, ")")
                if _, err := stmt.Exec(si.rows[i].ENABLED, si.rows[i].TIMED, si.rows[i].NAME); err != nil {
                        log.Fatal(err)
                }
index f428da8..4484e45 100644 (file)
@@ -83,7 +83,7 @@ func (s *TermboxScreen) BoldPrintAt(x int, y int, text string) {
        offset := 0
        for c := range text {
                if (x + offset) < s.width {
-                       termbox.SetCell(x+offset, y, rune(text[c]), s.fg | termbox.AttrBold, s.bg)
+                       termbox.SetCell(x+offset, y, rune(text[c]), s.fg|termbox.AttrBold, s.bg)
                        offset++
                }
        }
diff --git a/state/state.go b/state/state.go
deleted file mode 100644 (file)
index de0d4ff..0000000
+++ /dev/null
@@ -1,490 +0,0 @@
-// lib - library routines for pstop.
-//
-// this file contains the library routines related to the stored state in pstop.
-package state
-
-import (
-       "database/sql"
-       "fmt"
-       "strings"
-       "time"
-
-       "github.com/sjmudd/pstop/i_s/processlist"
-       "github.com/sjmudd/pstop/lib"
-       ewsgben "github.com/sjmudd/pstop/p_s/events_waits_summary_global_by_event_name"
-       essgben "github.com/sjmudd/pstop/p_s/events_stages_summary_global_by_event_name"
-       fsbi "github.com/sjmudd/pstop/p_s/file_summary_by_instance"
-       "github.com/sjmudd/pstop/p_s/ps_table"
-       "github.com/sjmudd/pstop/p_s/setup_instruments"
-       tiwsbt "github.com/sjmudd/pstop/p_s/table_io_waits_summary_by_table"
-       tlwsbt "github.com/sjmudd/pstop/p_s/table_lock_waits_summary_by_table"
-       "github.com/sjmudd/pstop/screen"
-       "github.com/sjmudd/pstop/version"
-       "github.com/sjmudd/pstop/wait_info"
-)
-
-// what information to show
-type Show int
-
-const (
-       showLatency = iota
-       showOps     = iota
-       showIO      = iota
-       showLocks   = iota
-       showUsers   = iota
-       showMutex   = iota
-       showStages  = iota
-)
-
-type State struct {
-       finished            bool
-       datadir             string
-       dbh                 *sql.DB
-       help                bool
-       hostname            string
-       fsbi                ps_table.Tabler // ufsbi.File_summary_by_instance
-       tiwsbt              tiwsbt.Object
-       tlwsbt              ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
-       ewsgben             ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
-       essgben             ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
-       users               processlist.Object
-       screen              screen.TermboxScreen
-       show                Show
-       mysql_version       string
-       want_relative_stats bool
-       wait_info.WaitInfo  // embedded
-       setup_instruments   setup_instruments.SetupInstruments
-}
-
-func (state *State) Setup(dbh *sql.DB) {
-       state.dbh = dbh
-       state.finished = false
-
-       state.screen.Initialise()
-
-       state.setup_instruments = setup_instruments.NewSetupInstruments(dbh)
-       state.setup_instruments.EnableMonitoring()
-
-       _, variables := lib.SelectAllGlobalVariablesByVariableName(state.dbh)
-       // setup to their initial types/values
-       state.fsbi = fsbi.NewFileSummaryByInstance(variables)
-       state.tlwsbt = new(tlwsbt.Object)
-       state.ewsgben = new(ewsgben.Object)
-       state.essgben = new(essgben.Object)
-
-       state.want_relative_stats = true // we show info from the point we start collecting data
-       state.fsbi.SetWantRelativeStats(state.want_relative_stats)
-       state.fsbi.SetNow()
-       state.tlwsbt.SetWantRelativeStats(state.want_relative_stats)
-       state.tlwsbt.SetNow()
-       state.tiwsbt.SetWantRelativeStats(state.want_relative_stats)
-       state.tiwsbt.SetNow()
-       state.users.SetWantRelativeStats(state.want_relative_stats)   // ignored
-       state.users.SetNow()                                          // ignored
-       state.essgben.SetWantRelativeStats(state.want_relative_stats)
-       state.essgben.SetNow()
-       state.ewsgben.SetWantRelativeStats(state.want_relative_stats) // ignored
-       state.ewsgben.SetNow()                                        // ignored
-
-       state.ResetDBStatistics()
-
-       state.SetHelp(false)
-       state.show = showLatency
-       state.tiwsbt.SetWantsLatency(true)
-
-       // get short name (to save space)
-       _, hostname := lib.SelectGlobalVariableByVariableName(state.dbh, "HOSTNAME")
-       if index := strings.Index(hostname, "."); index >= 0 {
-               hostname = hostname[0:index]
-       }
-       _, mysql_version := lib.SelectGlobalVariableByVariableName(state.dbh, "VERSION")
-       _, datadir := lib.SelectGlobalVariableByVariableName(state.dbh, "DATADIR")
-       state.SetHostname(hostname)
-       state.SetMySQLVersion(mysql_version)
-       state.SetDatadir(datadir)
-}
-
-// have we finished ?
-func (state State) Finished() bool {
-       return state.finished
-}
-
-// indicate we have finished
-func (state *State) SetFinished() {
-       state.finished = true
-}
-
-// do a fresh collection of data and then update the initial values based on that.
-func (state *State) ResetDBStatistics() {
-       state.CollectAll()
-       state.SyncReferenceValues()
-}
-
-func (state *State) SyncReferenceValues() {
-       start := time.Now()
-       state.fsbi.SyncReferenceValues()
-       state.tlwsbt.SyncReferenceValues()
-       state.tiwsbt.SyncReferenceValues()
-       state.essgben.SyncReferenceValues()
-       lib.Logger.Println("state.SyncReferenceValues() took", time.Duration(time.Since(start)).String())
-}
-
-// collect all initial values on startup / reset
-func (state *State) CollectAll() {
-       state.fsbi.Collect(state.dbh)
-       state.tlwsbt.Collect(state.dbh)
-       state.tiwsbt.Collect(state.dbh)
-}
-
-// Only collect the data we are looking at.
-func (state *State) Collect() {
-       start := time.Now()
-
-       switch state.show {
-       case showLatency, showOps:
-               state.tiwsbt.Collect(state.dbh)
-       case showIO:
-               state.fsbi.Collect(state.dbh)
-       case showLocks:
-               state.tlwsbt.Collect(state.dbh)
-       case showUsers:
-               state.users.Collect(state.dbh)
-       case showMutex:
-               state.ewsgben.Collect(state.dbh)
-       case showStages:
-               state.essgben.Collect(state.dbh)
-       }
-       lib.Logger.Println("state.Collect() took", time.Duration(time.Since(start)).String())
-}
-
-func (state State) MySQLVersion() string {
-       return state.mysql_version
-}
-
-func (state State) Datadir() string {
-       return state.datadir
-}
-
-func (state *State) SetHelp(newHelp bool) {
-       state.help = newHelp
-
-       state.screen.Clear()
-       state.screen.Flush()
-}
-
-func (state *State) SetDatadir(datadir string) {
-       state.datadir = datadir
-}
-
-func (state *State) SetMySQLVersion(mysql_version string) {
-       state.mysql_version = mysql_version
-}
-
-func (state *State) SetHostname(hostname string) {
-       state.hostname = hostname
-}
-
-func (state State) Help() bool {
-       return state.help
-}
-
-// states go: showLatency -> showOps -> showIO -> showLocks -> showUsers -> showMutex -> showStages
-
-// display the output according to the mode we are in
-func (state *State) Display() {
-       if state.help {
-               state.screen.DisplayHelp()
-       } else {
-               state.displayHeading()
-               switch state.show {
-               case showLatency, showOps:
-                       state.displayOpsOrLatency()
-               case showIO:
-                       state.displayIO()
-               case showLocks:
-                       state.displayLocks()
-               case showUsers:
-                       state.displayUsers()
-               case showMutex:
-                       state.displayMutex()
-               case showStages:
-                       state.displayStages()
-               }
-       }
-}
-
-// fix_latency_setting() ensures the SetWantsLatency() value is
-// correct. This needs to be done more cleanly.
-func (state *State) fix_latency_setting() {
-       if state.show == showLatency {
-               state.tiwsbt.SetWantsLatency(true)
-       }
-       if state.show == showOps {
-               state.tiwsbt.SetWantsLatency(false)
-       }
-}
-
-// change to the previous display mode
-func (state *State) DisplayPrevious() {
-       if state.show == showLatency {
-               state.show = showStages
-       } else {
-               state.show--
-       }
-       state.fix_latency_setting()
-       state.screen.Clear()
-       state.screen.Flush()
-}
-
-// change to the next display mode
-func (state *State) DisplayNext() {
-       if state.show == showStages {
-               state.show = showLatency
-       } else {
-               state.show++
-       }
-       state.fix_latency_setting()
-       state.screen.Clear()
-       state.screen.Flush()
-}
-
-func (state State) displayHeading() {
-       state.displayLine0()
-       state.displayDescription()
-}
-
-func (state State) displayLine0() {
-       _, uptime := lib.SelectGlobalStatusByVariableName(state.dbh, "UPTIME")
-       top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + state.hostname + " / " + state.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
-       if state.want_relative_stats {
-               now := time.Now()
-
-               var initial time.Time
-
-               switch state.show {
-               case showLatency, showOps:
-                       initial = state.tiwsbt.Last()
-               case showIO:
-                       initial = state.fsbi.Last()
-               case showLocks:
-                       initial = state.tlwsbt.Last()
-               case showUsers:
-                       initial = state.users.Last()
-               case showStages:
-                       initial = state.essgben.Last()
-               case showMutex:
-                       initial = state.ewsgben.Last()
-               default:
-                       // should not get here !
-               }
-
-               d := now.Sub(initial)
-
-               top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
-       } else {
-               top_line = top_line + " [ABS]             "
-       }
-       state.screen.PrintAt(0, 0, top_line)
-}
-
-func (state State) displayDescription() {
-       description := "UNKNOWN"
-
-       switch state.show {
-       case showLatency, showOps:
-               description = state.tiwsbt.Description()
-       case showIO:
-               description = state.fsbi.Description()
-       case showLocks:
-               description = state.tlwsbt.Description()
-       case showUsers:
-               description = state.users.Description()
-       case showMutex:
-               description = state.ewsgben.Description()
-       case showStages:
-               description = state.essgben.Description()
-       }
-
-       state.screen.PrintAt(0, 1, description)
-}
-
-func (state *State) displayOpsOrLatency() {
-       state.screen.BoldPrintAt(0, 2, state.tiwsbt.Headings())
-
-       max_rows := state.screen.Height() - 3
-       row_content := state.tiwsbt.RowContent(max_rows)
-
-       // print out rows
-       for k := range row_content {
-               y := 3 + k
-               state.screen.PrintAt(0, y, row_content[k])
-       }
-       // print out empty rows
-       for k := len(row_content); k < (state.screen.Height() - 3); k++ {
-               y := 3 + k
-               if y < state.screen.Height()-1 {
-                       state.screen.PrintAt(0, y, state.tiwsbt.EmptyRowContent())
-               }
-       }
-
-       // print out the totals at the bottom
-       state.screen.BoldPrintAt(0, state.screen.Height()-1, state.tiwsbt.TotalRowContent())
-}
-
-// show actual I/O latency values
-func (state State) displayIO() {
-       state.screen.BoldPrintAt(0, 2, state.fsbi.Headings())
-
-       // print out the data
-       max_rows := state.screen.Height() - 3
-       row_content := state.fsbi.RowContent(max_rows)
-
-       // print out rows
-       for k := range row_content {
-               y := 3 + k
-               state.screen.PrintAt(0, y, row_content[k])
-       }
-       // print out empty rows
-       for k := len(row_content); k < (state.screen.Height() - 3); k++ {
-               y := 3 + k
-               if y < state.screen.Height()-1 {
-                       state.screen.PrintAt(0, y, state.fsbi.EmptyRowContent())
-               }
-       }
-
-       // print out the totals at the bottom
-       state.screen.BoldPrintAt(0, state.screen.Height()-1, state.fsbi.TotalRowContent())
-}
-
-func (state *State) displayLocks() {
-       state.screen.BoldPrintAt(0, 2, state.tlwsbt.Headings())
-
-       // print out the data
-       max_rows := state.screen.Height() - 3
-       row_content := state.tlwsbt.RowContent(max_rows)
-
-       // print out rows
-       for k := range row_content {
-               y := 3 + k
-               state.screen.PrintAt(0, y, row_content[k])
-       }
-       // print out empty rows
-       for k := len(row_content); k < (state.screen.Height() - 3); k++ {
-               y := 3 + k
-               if y < state.screen.Height()-1 {
-                       state.screen.PrintAt(0, y, state.tlwsbt.EmptyRowContent())
-               }
-       }
-
-       // print out the totals at the bottom
-       state.screen.BoldPrintAt(0, state.screen.Height()-1, state.tlwsbt.TotalRowContent())
-}
-
-func (state *State) displayUsers() {
-       state.screen.BoldPrintAt(0, 2, state.users.Headings())
-
-       // print out the data
-       max_rows := state.screen.Height() - 3
-       row_content := state.users.RowContent(max_rows)
-
-       // print out rows
-       for k := range row_content {
-               y := 3 + k
-               state.screen.PrintAt(0, y, row_content[k])
-       }
-       // print out empty rows
-       for k := len(row_content); k < (state.screen.Height() - 3); k++ {
-               y := 3 + k
-               if y < state.screen.Height()-1 {
-                       state.screen.PrintAt(0, y, state.users.EmptyRowContent())
-               }
-       }
-
-       // print out the totals at the bottom
-       state.screen.BoldPrintAt(0, state.screen.Height()-1, state.users.TotalRowContent())
-}
-
-func (state *State) displayMutex() {
-       state.screen.BoldPrintAt(0, 2, state.ewsgben.Headings())
-
-       // print out the data
-       max_rows := state.screen.Height() - 3
-       row_content := state.ewsgben.RowContent(max_rows)
-
-       // print out rows
-       for k := range row_content {
-               y := 3 + k
-               state.screen.PrintAt(0, y, row_content[k])
-       }
-       // print out empty rows
-       for k := len(row_content); k < (state.screen.Height() - 3); k++ {
-               y := 3 + k
-               if y < state.screen.Height()-1 {
-                       state.screen.PrintAt(0, y, state.ewsgben.EmptyRowContent())
-               }
-       }
-
-       // print out the totals at the bottom
-       state.screen.BoldPrintAt(0, state.screen.Height()-1, state.ewsgben.TotalRowContent())
-}
-
-func (state *State) displayStages() {
-       state.screen.BoldPrintAt(0, 2, state.essgben.Headings())
-
-       // print out the data
-       max_rows := state.screen.Height() - 3
-       row_content := state.essgben.RowContent(max_rows)
-
-       // print out rows
-       for k := range row_content {
-               y := 3 + k
-               state.screen.PrintAt(0, y, row_content[k])
-       }
-       // print out empty rows
-       for k := len(row_content); k < (state.screen.Height() - 3); k++ {
-               y := 3 + k
-               if y < state.screen.Height()-1 {
-                       state.screen.PrintAt(0, y, state.essgben.EmptyRowContent())
-               }
-       }
-
-       // print out the totals at the bottom
-       state.screen.BoldPrintAt(0, state.screen.Height()-1, state.essgben.TotalRowContent())
-}
-
-
-// do we want to show all p_s data?
-func (state State) WantRelativeStats() bool {
-       return state.want_relative_stats
-}
-
-// set if we want data from when we started/reset stats.
-func (state *State) SetWantRelativeStats(want_relative_stats bool) {
-       state.want_relative_stats = want_relative_stats
-
-       state.fsbi.SetWantRelativeStats(want_relative_stats)
-       state.tlwsbt.SetWantRelativeStats(state.want_relative_stats)
-       state.tiwsbt.SetWantRelativeStats(state.want_relative_stats)
-       state.ewsgben.SetWantRelativeStats(state.want_relative_stats)
-       state.essgben.SetWantRelativeStats(state.want_relative_stats)
-}
-
-// if there's a better way of doing this do it better ...
-func now_hhmmss() string {
-       t := time.Now()
-       return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
-}
-
-// record the latest screen size
-func (state *State) ScreenSetSize(width, height int) {
-       state.screen.SetSize(width, height)
-}
-
-// clean up screen and disconnect database
-func (state *State) Cleanup() {
-       state.screen.Close()
-       if state.dbh != nil {
-               state.setup_instruments.RestoreConfiguration()
-               _ = state.dbh.Close()
-       }
-}
index cf77d28..16782b6 100644 (file)
@@ -2,7 +2,7 @@
 package version
 
 const (
-       version = "0.3.2"
+       version = "0.3.3"
 )
 
 // return the current application version