1 // app - pstop application package
3 // This file contains the library routines related to running the app.
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
59 fsbi ps_table.Tabler // ufsbi.File_summary_by_instance
61 tlwsbt ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
62 ewsgben ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
63 essgben ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
64 users processlist.Object
65 screen screen.TermboxScreen
68 want_relative_stats bool
69 wait_info.WaitInfo // embedded
70 setup_instruments setup_instruments.SetupInstruments
73 func (app *App) Setup(dbh *sql.DB) {
76 if err := app.validate_mysql_version(); err != nil {
81 app.screen.Initialise()
82 app.setup_instruments = setup_instruments.NewSetupInstruments(dbh)
83 app.setup_instruments.EnableMonitoring()
85 _, variables := lib.SelectAllGlobalVariablesByVariableName(app.dbh)
86 // setup to their initial types/values
87 app.fsbi = fsbi.NewFileSummaryByInstance(variables)
88 app.tlwsbt = new(tlwsbt.Object)
89 app.ewsgben = new(ewsgben.Object)
90 app.essgben = new(essgben.Object)
92 app.want_relative_stats = true // we show info from the point we start collecting data
93 app.fsbi.SetWantRelativeStats(app.want_relative_stats)
95 app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
97 app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
99 app.users.SetWantRelativeStats(app.want_relative_stats) // ignored
100 app.users.SetNow() // ignored
101 app.essgben.SetWantRelativeStats(app.want_relative_stats)
103 app.ewsgben.SetWantRelativeStats(app.want_relative_stats) // ignored
104 app.ewsgben.SetNow() // ignored
106 app.ResetDBStatistics()
109 app.show = showLatency
110 app.tiwsbt.SetWantsLatency(true)
112 // get short name (to save space)
113 _, hostname := lib.SelectGlobalVariableByVariableName(app.dbh, "HOSTNAME")
114 if index := strings.Index(hostname, "."); index >= 0 {
115 hostname = hostname[0:index]
117 _, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
118 app.SetHostname(hostname)
119 app.SetMySQLVersion(mysql_version)
122 // have we finished ?
123 func (app App) Finished() bool {
127 // indicate we have finished
128 func (app *App) SetFinished() {
132 // do a fresh collection of data and then update the initial values based on that.
133 func (app *App) ResetDBStatistics() {
135 app.SyncReferenceValues()
138 func (app *App) SyncReferenceValues() {
140 app.fsbi.SyncReferenceValues()
141 app.tlwsbt.SyncReferenceValues()
142 app.tiwsbt.SyncReferenceValues()
143 app.essgben.SyncReferenceValues()
144 lib.Logger.Println("app.SyncReferenceValues() took", time.Duration(time.Since(start)).String())
147 // collect all initial values on startup / reset
148 func (app *App) CollectAll() {
149 app.fsbi.Collect(app.dbh)
150 app.tlwsbt.Collect(app.dbh)
151 app.tiwsbt.Collect(app.dbh)
154 // Only collect the data we are looking at.
155 func (app *App) Collect() {
159 case showLatency, showOps:
160 app.tiwsbt.Collect(app.dbh)
162 app.fsbi.Collect(app.dbh)
164 app.tlwsbt.Collect(app.dbh)
166 app.users.Collect(app.dbh)
168 app.ewsgben.Collect(app.dbh)
170 app.essgben.Collect(app.dbh)
172 app.wi.CollectedNow()
173 lib.Logger.Println("app.Collect() took", time.Duration(time.Since(start)).String())
176 func (app App) MySQLVersion() string {
177 return app.mysql_version
180 func (app *App) SetHelp(newHelp bool) {
187 func (app *App) SetMySQLVersion(mysql_version string) {
188 app.mysql_version = mysql_version
191 func (app *App) SetHostname(hostname string) {
192 app.hostname = hostname
195 func (app App) Help() bool {
199 // apps go: showLatency -> showOps -> showIO -> showLocks -> showUsers -> showMutex -> showStages
201 // display the output according to the mode we are in
202 func (app *App) Display() {
204 app.screen.DisplayHelp()
208 case showLatency, showOps:
209 app.displayOpsOrLatency()
224 // fix_latency_setting() ensures the SetWantsLatency() value is
225 // correct. This needs to be done more cleanly.
226 func (app *App) fix_latency_setting() {
227 if app.show == showLatency {
228 app.tiwsbt.SetWantsLatency(true)
230 if app.show == showOps {
231 app.tiwsbt.SetWantsLatency(false)
235 // change to the previous display mode
236 func (app *App) DisplayPrevious() {
237 if app.show == showLatency {
238 app.show = showStages
242 app.fix_latency_setting()
247 // change to the next display mode
248 func (app *App) DisplayNext() {
249 if app.show == showStages {
250 app.show = showLatency
254 app.fix_latency_setting()
259 func (app App) displayHeading() {
261 app.displayDescription()
264 func (app App) displayLine0() {
265 _, uptime := lib.SelectGlobalStatusByVariableName(app.dbh, "UPTIME")
266 top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + app.hostname + " / " + app.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
267 if app.want_relative_stats {
270 var initial time.Time
273 case showLatency, showOps:
274 initial = app.tiwsbt.Last()
276 initial = app.fsbi.Last()
278 initial = app.tlwsbt.Last()
280 initial = app.users.Last()
282 initial = app.essgben.Last()
284 initial = app.ewsgben.Last()
286 // should not get here !
289 d := now.Sub(initial)
291 top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
293 top_line = top_line + " [ABS] "
295 app.screen.PrintAt(0, 0, top_line)
298 func (app App) displayDescription() {
299 description := "UNKNOWN"
302 case showLatency, showOps:
303 description = app.tiwsbt.Description()
305 description = app.fsbi.Description()
307 description = app.tlwsbt.Description()
309 description = app.users.Description()
311 description = app.ewsgben.Description()
313 description = app.essgben.Description()
316 app.screen.PrintAt(0, 1, description)
319 func (app *App) displayOpsOrLatency() {
320 app.screen.BoldPrintAt(0, 2, app.tiwsbt.Headings())
322 max_rows := app.screen.Height() - 3
323 last_row := app.screen.Height() - 1
324 row_content := app.tiwsbt.RowContent(max_rows)
327 for k := range row_content {
329 app.screen.PrintAt(0, y, row_content[k])
330 app.screen.ClearLine(len(row_content[k]), y)
332 // print out empty rows
333 for k := len(row_content); k < max_rows; k++ {
335 if y < max_rows - 1 {
336 app.screen.PrintAt(0, y, app.tiwsbt.EmptyRowContent())
340 // print out the totals at the bottom
341 total := app.tiwsbt.TotalRowContent()
342 app.screen.BoldPrintAt(0, last_row, total)
343 app.screen.ClearLine(len(total), last_row)
346 // show actual I/O latency values
347 func (app App) displayIO() {
348 app.screen.BoldPrintAt(0, 2, app.fsbi.Headings())
350 // print out the data
351 max_rows := app.screen.Height() - 4
352 last_row := app.screen.Height() - 1
353 row_content := app.fsbi.RowContent(max_rows)
356 for k := range row_content {
358 app.screen.PrintAt(0, y, row_content[k])
359 app.screen.ClearLine(len(row_content[k]), y)
361 // print out empty rows
362 for k := len(row_content); k < max_rows; k++ {
365 app.screen.PrintAt(0, y, app.fsbi.EmptyRowContent())
369 // print out the totals at the bottom
370 total := app.fsbi.TotalRowContent()
371 app.screen.BoldPrintAt(0, last_row, total)
372 app.screen.ClearLine(len(total), last_row)
375 func (app *App) displayLocks() {
376 app.screen.BoldPrintAt(0, 2, app.tlwsbt.Headings())
378 // print out the data
379 max_rows := app.screen.Height() - 4
380 last_row := app.screen.Height() - 1
381 row_content := app.tlwsbt.RowContent(max_rows)
384 for k := range row_content {
386 app.screen.PrintAt(0, y, row_content[k])
387 app.screen.ClearLine(len(row_content[k]), y)
389 // print out empty rows
390 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
393 app.screen.PrintAt(0, y, app.tlwsbt.EmptyRowContent())
397 // print out the totals at the bottom
398 total := app.tlwsbt.TotalRowContent()
399 app.screen.BoldPrintAt(0, last_row, total)
400 app.screen.ClearLine(len(total), last_row)
403 func (app *App) displayUsers() {
404 app.screen.BoldPrintAt(0, 2, app.users.Headings())
406 // print out the data
407 max_rows := app.screen.Height() - 4
408 last_row := app.screen.Height() - 1
409 row_content := app.users.RowContent(max_rows)
412 for k := range row_content {
414 app.screen.PrintAt(0, y, row_content[k])
415 app.screen.ClearLine(len(row_content[k]), y)
417 // print out empty rows
418 for k := len(row_content); k < max_rows; k++ {
421 app.screen.PrintAt(0, y, app.users.EmptyRowContent())
425 // print out the totals at the bottom
426 total := app.users.TotalRowContent()
427 app.screen.BoldPrintAt(0, last_row, total)
428 app.screen.ClearLine(len(total), last_row)
431 func (app *App) displayMutex() {
432 app.screen.BoldPrintAt(0, 2, app.ewsgben.Headings())
434 // print out the data
435 max_rows := app.screen.Height() - 4
436 last_row := app.screen.Height() - 1
437 row_content := app.ewsgben.RowContent(max_rows)
440 for k := range row_content {
442 app.screen.PrintAt(0, y, row_content[k])
443 app.screen.ClearLine(len(row_content[k]), y)
445 // print out empty rows
446 for k := len(row_content); k < max_rows; k++ {
449 app.screen.PrintAt(0, y, app.ewsgben.EmptyRowContent())
453 // print out the totals at the bottom
454 total := app.ewsgben.TotalRowContent()
455 app.screen.BoldPrintAt(0, last_row, total)
456 app.screen.ClearLine(len(total), last_row)
459 func (app *App) displayStages() {
460 app.screen.BoldPrintAt(0, 2, app.essgben.Headings())
462 // print out the data
463 max_rows := app.screen.Height() - 4
464 last_row := app.screen.Height() - 1
465 row_content := app.essgben.RowContent(max_rows)
468 for k := range row_content {
470 app.screen.PrintAt(0, y, row_content[k])
471 app.screen.ClearLine(len(row_content[k]), y)
473 // print out empty rows
474 for k := len(row_content); k < max_rows; k++ {
477 app.screen.PrintAt(0, y, app.essgben.EmptyRowContent())
481 // print out the totals at the bottom
482 total := app.essgben.TotalRowContent()
483 app.screen.BoldPrintAt(0, last_row, total)
484 app.screen.ClearLine(len(total), last_row)
487 // do we want to show all p_s data?
488 func (app App) WantRelativeStats() bool {
489 return app.want_relative_stats
492 // set if we want data from when we started/reset stats.
493 func (app *App) SetWantRelativeStats(want_relative_stats bool) {
494 app.want_relative_stats = want_relative_stats
496 app.fsbi.SetWantRelativeStats(want_relative_stats)
497 app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
498 app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
499 app.ewsgben.SetWantRelativeStats(app.want_relative_stats)
500 app.essgben.SetWantRelativeStats(app.want_relative_stats)
503 // if there's a better way of doing this do it better ...
504 func now_hhmmss() string {
506 return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
509 // record the latest screen size
510 func (app *App) ScreenSetSize(width, height int) {
511 app.screen.SetSize(width, height)
514 // clean up screen and disconnect database
515 func (app *App) Cleanup() {
518 app.setup_instruments.RestoreConfiguration()
523 // get into a run loop
524 func (app *App) Run() {
525 app.done = make(chan struct{})
526 defer close(app.done)
528 app.sigChan = make(chan os.Signal, 1)
529 signal.Notify(app.sigChan, syscall.SIGINT, syscall.SIGTERM)
531 app.wi.SetWaitInterval(time.Second)
533 termboxChan := app.screen.TermBoxChan()
535 for !app.Finished() {
538 fmt.Println("app.done(): exiting")
540 case sig := <-app.sigChan:
541 fmt.Println("Caught a signal", sig)
542 app.done <- struct{}{}
543 case <-app.wi.WaitNextPeriod():
546 case event := <-termboxChan:
547 // switch on event type
549 case termbox.EventKey: // actions depend on key
551 case termbox.KeyCtrlZ, termbox.KeyCtrlC, termbox.KeyEsc:
553 case termbox.KeyArrowLeft: // left arrow change to previous display mode
554 app.DisplayPrevious()
556 case termbox.KeyTab, termbox.KeyArrowRight: // tab or right arrow - change to next display mode
561 case '-': // decrease the interval if > 1
562 if app.wi.WaitInterval() > time.Second {
563 app.wi.SetWaitInterval(app.wi.WaitInterval() - time.Second)
565 case '+': // increase interval by creating a new ticker
566 app.wi.SetWaitInterval(app.wi.WaitInterval() + time.Second)
567 case 'h', '?': // help
568 app.SetHelp(!app.Help())
571 case 't': // toggle between absolute/relative statistics
572 app.SetWantRelativeStats(!app.WantRelativeStats())
574 case 'z': // reset the statistics to now by taking a query of current values
575 app.ResetDBStatistics()
578 case termbox.EventResize: // set sizes
579 app.ScreenSetSize(event.Width, event.Height)
581 case termbox.EventError: // quit
582 log.Fatalf("Quitting because of termbox error: \n%s\n", event.Err)
588 // pstop requires MySQL 5.6+ or MariaDB 10.0+. Check the version
589 // rather than giving an error message if the requires P_S tables can't
591 func (app *App) validate_mysql_version() error {
592 var tables = [...]string{
593 "performance_schema.events_stages_summary_global_by_event_name",
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")