1 // lib - library routines for pstop.
3 // this file contains the library routines related to the stored state in pstop.
18 "github.com/nsf/termbox-go"
20 "github.com/sjmudd/pstop/i_s/processlist"
21 "github.com/sjmudd/pstop/lib"
22 essgben "github.com/sjmudd/pstop/p_s/events_stages_summary_global_by_event_name"
23 ewsgben "github.com/sjmudd/pstop/p_s/events_waits_summary_global_by_event_name"
24 fsbi "github.com/sjmudd/pstop/p_s/file_summary_by_instance"
25 "github.com/sjmudd/pstop/p_s/ps_table"
26 "github.com/sjmudd/pstop/p_s/setup_instruments"
27 tiwsbt "github.com/sjmudd/pstop/p_s/table_io_waits_summary_by_table"
28 tlwsbt "github.com/sjmudd/pstop/p_s/table_lock_waits_summary_by_table"
29 "github.com/sjmudd/pstop/screen"
30 "github.com/sjmudd/pstop/version"
31 "github.com/sjmudd/pstop/wait_info"
34 // what information to show
48 re_valid_version = regexp.MustCompile(`^(5\.[67]\.|10\.[01])`)
53 sigChan chan os.Signal
60 fsbi ps_table.Tabler // ufsbi.File_summary_by_instance
62 tlwsbt ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
63 ewsgben ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
64 essgben ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
65 users processlist.Object
66 screen screen.TermboxScreen
69 want_relative_stats bool
70 wait_info.WaitInfo // embedded
71 setup_instruments setup_instruments.SetupInstruments
74 func (app *App) Setup(dbh *sql.DB) {
77 if err := app.validate_mysql_version(); err != nil {
83 app.screen.Initialise()
85 app.setup_instruments = setup_instruments.NewSetupInstruments(dbh)
86 app.setup_instruments.EnableMonitoring()
88 _, variables := lib.SelectAllGlobalVariablesByVariableName(app.dbh)
89 // setup to their initial types/values
90 app.fsbi = fsbi.NewFileSummaryByInstance(variables)
91 app.tlwsbt = new(tlwsbt.Object)
92 app.ewsgben = new(ewsgben.Object)
93 app.essgben = new(essgben.Object)
95 app.want_relative_stats = true // we show info from the point we start collecting data
96 app.fsbi.SetWantRelativeStats(app.want_relative_stats)
98 app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
100 app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
102 app.users.SetWantRelativeStats(app.want_relative_stats) // ignored
103 app.users.SetNow() // ignored
104 app.essgben.SetWantRelativeStats(app.want_relative_stats)
106 app.ewsgben.SetWantRelativeStats(app.want_relative_stats) // ignored
107 app.ewsgben.SetNow() // ignored
109 app.ResetDBStatistics()
112 app.show = showLatency
113 app.tiwsbt.SetWantsLatency(true)
115 // get short name (to save space)
116 _, hostname := lib.SelectGlobalVariableByVariableName(app.dbh, "HOSTNAME")
117 if index := strings.Index(hostname, "."); index >= 0 {
118 hostname = hostname[0:index]
120 _, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
121 _, datadir := lib.SelectGlobalVariableByVariableName(app.dbh, "DATADIR")
122 app.SetHostname(hostname)
123 app.SetMySQLVersion(mysql_version)
124 app.SetDatadir(datadir)
127 // have we finished ?
128 func (app App) Finished() bool {
132 // indicate we have finished
133 func (app *App) SetFinished() {
137 // do a fresh collection of data and then update the initial values based on that.
138 func (app *App) ResetDBStatistics() {
140 app.SyncReferenceValues()
143 func (app *App) SyncReferenceValues() {
145 app.fsbi.SyncReferenceValues()
146 app.tlwsbt.SyncReferenceValues()
147 app.tiwsbt.SyncReferenceValues()
148 app.essgben.SyncReferenceValues()
149 lib.Logger.Println("app.SyncReferenceValues() took", time.Duration(time.Since(start)).String())
152 // collect all initial values on startup / reset
153 func (app *App) CollectAll() {
154 app.fsbi.Collect(app.dbh)
155 app.tlwsbt.Collect(app.dbh)
156 app.tiwsbt.Collect(app.dbh)
159 // Only collect the data we are looking at.
160 func (app *App) Collect() {
164 case showLatency, showOps:
165 app.tiwsbt.Collect(app.dbh)
167 app.fsbi.Collect(app.dbh)
169 app.tlwsbt.Collect(app.dbh)
171 app.users.Collect(app.dbh)
173 app.ewsgben.Collect(app.dbh)
175 app.essgben.Collect(app.dbh)
177 app.wi.CollectedNow()
178 lib.Logger.Println("app.Collect() took", time.Duration(time.Since(start)).String())
181 func (app App) MySQLVersion() string {
182 return app.mysql_version
185 func (app App) Datadir() string {
189 func (app *App) SetHelp(newHelp bool) {
196 func (app *App) SetDatadir(datadir string) {
197 app.datadir = datadir
200 func (app *App) SetMySQLVersion(mysql_version string) {
201 app.mysql_version = mysql_version
204 func (app *App) SetHostname(hostname string) {
205 app.hostname = hostname
208 func (app App) Help() bool {
212 // apps go: showLatency -> showOps -> showIO -> showLocks -> showUsers -> showMutex -> showStages
214 // display the output according to the mode we are in
215 func (app *App) Display() {
217 app.screen.DisplayHelp()
221 case showLatency, showOps:
222 app.displayOpsOrLatency()
237 // fix_latency_setting() ensures the SetWantsLatency() value is
238 // correct. This needs to be done more cleanly.
239 func (app *App) fix_latency_setting() {
240 if app.show == showLatency {
241 app.tiwsbt.SetWantsLatency(true)
243 if app.show == showOps {
244 app.tiwsbt.SetWantsLatency(false)
248 // change to the previous display mode
249 func (app *App) DisplayPrevious() {
250 if app.show == showLatency {
251 app.show = showStages
255 app.fix_latency_setting()
260 // change to the next display mode
261 func (app *App) DisplayNext() {
262 if app.show == showStages {
263 app.show = showLatency
267 app.fix_latency_setting()
272 func (app App) displayHeading() {
274 app.displayDescription()
277 func (app App) displayLine0() {
278 _, uptime := lib.SelectGlobalStatusByVariableName(app.dbh, "UPTIME")
279 top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + app.hostname + " / " + app.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
280 if app.want_relative_stats {
283 var initial time.Time
286 case showLatency, showOps:
287 initial = app.tiwsbt.Last()
289 initial = app.fsbi.Last()
291 initial = app.tlwsbt.Last()
293 initial = app.users.Last()
295 initial = app.essgben.Last()
297 initial = app.ewsgben.Last()
299 // should not get here !
302 d := now.Sub(initial)
304 top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
306 top_line = top_line + " [ABS] "
308 app.screen.PrintAt(0, 0, top_line)
311 func (app App) displayDescription() {
312 description := "UNKNOWN"
315 case showLatency, showOps:
316 description = app.tiwsbt.Description()
318 description = app.fsbi.Description()
320 description = app.tlwsbt.Description()
322 description = app.users.Description()
324 description = app.ewsgben.Description()
326 description = app.essgben.Description()
329 app.screen.PrintAt(0, 1, description)
332 func (app *App) displayOpsOrLatency() {
333 app.screen.BoldPrintAt(0, 2, app.tiwsbt.Headings())
335 max_rows := app.screen.Height() - 3
336 row_content := app.tiwsbt.RowContent(max_rows)
339 for k := range row_content {
341 app.screen.PrintAt(0, y, row_content[k])
343 // print out empty rows
344 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
346 if y < app.screen.Height()-1 {
347 app.screen.PrintAt(0, y, app.tiwsbt.EmptyRowContent())
351 // print out the totals at the bottom
352 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.tiwsbt.TotalRowContent())
355 // show actual I/O latency values
356 func (app App) displayIO() {
357 app.screen.BoldPrintAt(0, 2, app.fsbi.Headings())
359 // print out the data
360 max_rows := app.screen.Height() - 3
361 row_content := app.fsbi.RowContent(max_rows)
364 for k := range row_content {
366 app.screen.PrintAt(0, y, row_content[k])
368 // print out empty rows
369 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
371 if y < app.screen.Height()-1 {
372 app.screen.PrintAt(0, y, app.fsbi.EmptyRowContent())
376 // print out the totals at the bottom
377 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.fsbi.TotalRowContent())
380 func (app *App) displayLocks() {
381 app.screen.BoldPrintAt(0, 2, app.tlwsbt.Headings())
383 // print out the data
384 max_rows := app.screen.Height() - 3
385 row_content := app.tlwsbt.RowContent(max_rows)
388 for k := range row_content {
390 app.screen.PrintAt(0, y, row_content[k])
392 // print out empty rows
393 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
395 if y < app.screen.Height()-1 {
396 app.screen.PrintAt(0, y, app.tlwsbt.EmptyRowContent())
400 // print out the totals at the bottom
401 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.tlwsbt.TotalRowContent())
404 func (app *App) displayUsers() {
405 app.screen.BoldPrintAt(0, 2, app.users.Headings())
407 // print out the data
408 max_rows := app.screen.Height() - 3
409 row_content := app.users.RowContent(max_rows)
412 for k := range row_content {
414 app.screen.PrintAt(0, y, row_content[k])
416 // print out empty rows
417 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
419 if y < app.screen.Height()-1 {
420 app.screen.PrintAt(0, y, app.users.EmptyRowContent())
424 // print out the totals at the bottom
425 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.users.TotalRowContent())
428 func (app *App) displayMutex() {
429 app.screen.BoldPrintAt(0, 2, app.ewsgben.Headings())
431 // print out the data
432 max_rows := app.screen.Height() - 3
433 row_content := app.ewsgben.RowContent(max_rows)
436 for k := range row_content {
438 app.screen.PrintAt(0, y, row_content[k])
440 // print out empty rows
441 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
443 if y < app.screen.Height()-1 {
444 app.screen.PrintAt(0, y, app.ewsgben.EmptyRowContent())
448 // print out the totals at the bottom
449 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.ewsgben.TotalRowContent())
452 func (app *App) displayStages() {
453 app.screen.BoldPrintAt(0, 2, app.essgben.Headings())
455 // print out the data
456 max_rows := app.screen.Height() - 3
457 row_content := app.essgben.RowContent(max_rows)
460 for k := range row_content {
462 app.screen.PrintAt(0, y, row_content[k])
464 // print out empty rows
465 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
467 if y < app.screen.Height()-1 {
468 app.screen.PrintAt(0, y, app.essgben.EmptyRowContent())
472 // print out the totals at the bottom
473 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.essgben.TotalRowContent())
476 // do we want to show all p_s data?
477 func (app App) WantRelativeStats() bool {
478 return app.want_relative_stats
481 // set if we want data from when we started/reset stats.
482 func (app *App) SetWantRelativeStats(want_relative_stats bool) {
483 app.want_relative_stats = want_relative_stats
485 app.fsbi.SetWantRelativeStats(want_relative_stats)
486 app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
487 app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
488 app.ewsgben.SetWantRelativeStats(app.want_relative_stats)
489 app.essgben.SetWantRelativeStats(app.want_relative_stats)
492 // if there's a better way of doing this do it better ...
493 func now_hhmmss() string {
495 return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
498 // record the latest screen size
499 func (app *App) ScreenSetSize(width, height int) {
500 app.screen.SetSize(width, height)
503 // clean up screen and disconnect database
504 func (app *App) Cleanup() {
507 app.setup_instruments.RestoreConfiguration()
512 // make chan for termbox events and run a poller to send events to the channel
513 // - return the channel
514 func new_tb_chan() chan termbox.Event {
515 termboxChan := make(chan termbox.Event)
518 termboxChan <- termbox.PollEvent()
524 // get into a run loop
525 func (app *App) Run() {
526 app.done = make(chan struct{})
527 defer close(app.done)
529 app.sigChan = make(chan os.Signal, 1)
530 signal.Notify(app.sigChan, syscall.SIGINT, syscall.SIGTERM)
532 app.wi.SetWaitInterval(time.Second)
534 termboxChan := new_tb_chan()
536 for !app.Finished() {
539 fmt.Println("app.done(): exiting")
541 case sig := <-app.sigChan:
542 fmt.Println("Caught a signal", sig)
543 app.done <- struct{}{}
544 case <-app.wi.WaitNextPeriod():
547 case event := <-termboxChan:
548 // switch on event type
550 case termbox.EventKey: // actions depend on key
552 case termbox.KeyCtrlZ, termbox.KeyCtrlC, termbox.KeyEsc:
554 case termbox.KeyArrowLeft: // left arrow change to previous display mode
555 app.DisplayPrevious()
557 case termbox.KeyTab, termbox.KeyArrowRight: // tab or right arrow - change to next display mode
562 case '-': // decrease the interval if > 1
563 if app.wi.WaitInterval() > time.Second {
564 app.wi.SetWaitInterval(app.wi.WaitInterval() - time.Second)
566 case '+': // increase interval by creating a new ticker
567 app.wi.SetWaitInterval(app.wi.WaitInterval() + time.Second)
568 case 'h', '?': // help
569 app.SetHelp(!app.Help())
572 case 't': // toggle between absolute/relative statistics
573 app.SetWantRelativeStats(!app.WantRelativeStats())
575 case 'z': // reset the statistics to now by taking a query of current values
576 app.ResetDBStatistics()
579 case termbox.EventResize: // set sizes
580 app.ScreenSetSize(event.Width, event.Height)
582 case termbox.EventError: // quit
583 log.Fatalf("Quitting because of termbox error: \n%s\n", event.Err)
589 // pstop requires MySQL 5.6+ or MariaDB 10.0+. Check the version
590 // rather than giving an error message if the requires P_S tables can't
592 func (app *App) validate_mysql_version() error {
593 var tables = [...]string{
594 "performance_schema.events_waits_summary_global_by_event_name",
595 "performance_schema.file_summary_by_instance",
596 "performance_schema.table_io_waits_summary_by_table",
597 "performance_schema.table_lock_waits_summary_by_table",
600 lib.Logger.Println("validate_mysql_version()")
602 lib.Logger.Println("- Getting MySQL version")
603 err, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
607 lib.Logger.Println("- mysql_version: '" + mysql_version + "'")
609 if !re_valid_version.MatchString(mysql_version) {
610 return errors.New(lib.MyName() + " does not work with MySQL version " + mysql_version)
612 lib.Logger.Println("OK: MySQL version is valid, continuing")
614 lib.Logger.Println("Checking access to required tables:")
615 for i := range tables {
616 if err := lib.CheckTableAccess(app.dbh, tables[i]); err == nil {
617 lib.Logger.Println("OK: " + tables[i] + " found")
622 lib.Logger.Println("OK: all table checks passed")