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 {
82 app.screen.Initialise()
83 app.setup_instruments = setup_instruments.NewSetupInstruments(dbh)
84 app.setup_instruments.EnableMonitoring()
86 _, variables := lib.SelectAllGlobalVariablesByVariableName(app.dbh)
87 // setup to their initial types/values
88 app.fsbi = fsbi.NewFileSummaryByInstance(variables)
89 app.tlwsbt = new(tlwsbt.Object)
90 app.ewsgben = new(ewsgben.Object)
91 app.essgben = new(essgben.Object)
93 app.want_relative_stats = true // we show info from the point we start collecting data
94 app.fsbi.SetWantRelativeStats(app.want_relative_stats)
96 app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
98 app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
100 app.users.SetWantRelativeStats(app.want_relative_stats) // ignored
101 app.users.SetNow() // ignored
102 app.essgben.SetWantRelativeStats(app.want_relative_stats)
104 app.ewsgben.SetWantRelativeStats(app.want_relative_stats) // ignored
105 app.ewsgben.SetNow() // ignored
107 app.ResetDBStatistics()
110 app.show = showLatency
111 app.tiwsbt.SetWantsLatency(true)
113 // get short name (to save space)
114 _, hostname := lib.SelectGlobalVariableByVariableName(app.dbh, "HOSTNAME")
115 if index := strings.Index(hostname, "."); index >= 0 {
116 hostname = hostname[0:index]
118 _, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
119 _, datadir := lib.SelectGlobalVariableByVariableName(app.dbh, "DATADIR")
120 app.SetHostname(hostname)
121 app.SetMySQLVersion(mysql_version)
122 app.SetDatadir(datadir)
125 // have we finished ?
126 func (app App) Finished() bool {
130 // indicate we have finished
131 func (app *App) SetFinished() {
135 // do a fresh collection of data and then update the initial values based on that.
136 func (app *App) ResetDBStatistics() {
138 app.SyncReferenceValues()
141 func (app *App) SyncReferenceValues() {
143 app.fsbi.SyncReferenceValues()
144 app.tlwsbt.SyncReferenceValues()
145 app.tiwsbt.SyncReferenceValues()
146 app.essgben.SyncReferenceValues()
147 lib.Logger.Println("app.SyncReferenceValues() took", time.Duration(time.Since(start)).String())
150 // collect all initial values on startup / reset
151 func (app *App) CollectAll() {
152 app.fsbi.Collect(app.dbh)
153 app.tlwsbt.Collect(app.dbh)
154 app.tiwsbt.Collect(app.dbh)
157 // Only collect the data we are looking at.
158 func (app *App) Collect() {
162 case showLatency, showOps:
163 app.tiwsbt.Collect(app.dbh)
165 app.fsbi.Collect(app.dbh)
167 app.tlwsbt.Collect(app.dbh)
169 app.users.Collect(app.dbh)
171 app.ewsgben.Collect(app.dbh)
173 app.essgben.Collect(app.dbh)
175 app.wi.CollectedNow()
176 lib.Logger.Println("app.Collect() took", time.Duration(time.Since(start)).String())
179 func (app App) MySQLVersion() string {
180 return app.mysql_version
183 func (app App) Datadir() string {
187 func (app *App) SetHelp(newHelp bool) {
194 func (app *App) SetDatadir(datadir string) {
195 app.datadir = datadir
198 func (app *App) SetMySQLVersion(mysql_version string) {
199 app.mysql_version = mysql_version
202 func (app *App) SetHostname(hostname string) {
203 app.hostname = hostname
206 func (app App) Help() bool {
210 // apps go: showLatency -> showOps -> showIO -> showLocks -> showUsers -> showMutex -> showStages
212 // display the output according to the mode we are in
213 func (app *App) Display() {
215 app.screen.DisplayHelp()
219 case showLatency, showOps:
220 app.displayOpsOrLatency()
235 // fix_latency_setting() ensures the SetWantsLatency() value is
236 // correct. This needs to be done more cleanly.
237 func (app *App) fix_latency_setting() {
238 if app.show == showLatency {
239 app.tiwsbt.SetWantsLatency(true)
241 if app.show == showOps {
242 app.tiwsbt.SetWantsLatency(false)
246 // change to the previous display mode
247 func (app *App) DisplayPrevious() {
248 if app.show == showLatency {
249 app.show = showStages
253 app.fix_latency_setting()
258 // change to the next display mode
259 func (app *App) DisplayNext() {
260 if app.show == showStages {
261 app.show = showLatency
265 app.fix_latency_setting()
270 func (app App) displayHeading() {
272 app.displayDescription()
275 func (app App) displayLine0() {
276 _, uptime := lib.SelectGlobalStatusByVariableName(app.dbh, "UPTIME")
277 top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + app.hostname + " / " + app.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
278 if app.want_relative_stats {
281 var initial time.Time
284 case showLatency, showOps:
285 initial = app.tiwsbt.Last()
287 initial = app.fsbi.Last()
289 initial = app.tlwsbt.Last()
291 initial = app.users.Last()
293 initial = app.essgben.Last()
295 initial = app.ewsgben.Last()
297 // should not get here !
300 d := now.Sub(initial)
302 top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
304 top_line = top_line + " [ABS] "
306 app.screen.PrintAt(0, 0, top_line)
309 func (app App) displayDescription() {
310 description := "UNKNOWN"
313 case showLatency, showOps:
314 description = app.tiwsbt.Description()
316 description = app.fsbi.Description()
318 description = app.tlwsbt.Description()
320 description = app.users.Description()
322 description = app.ewsgben.Description()
324 description = app.essgben.Description()
327 app.screen.PrintAt(0, 1, description)
330 func (app *App) displayOpsOrLatency() {
331 app.screen.BoldPrintAt(0, 2, app.tiwsbt.Headings())
333 max_rows := app.screen.Height() - 3
334 row_content := app.tiwsbt.RowContent(max_rows)
337 for k := range row_content {
339 app.screen.PrintAt(0, y, row_content[k])
341 // print out empty rows
342 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
344 if y < app.screen.Height()-1 {
345 app.screen.PrintAt(0, y, app.tiwsbt.EmptyRowContent())
349 // print out the totals at the bottom
350 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.tiwsbt.TotalRowContent())
353 // show actual I/O latency values
354 func (app App) displayIO() {
355 app.screen.BoldPrintAt(0, 2, app.fsbi.Headings())
357 // print out the data
358 max_rows := app.screen.Height() - 3
359 row_content := app.fsbi.RowContent(max_rows)
362 for k := range row_content {
364 app.screen.PrintAt(0, y, row_content[k])
366 // print out empty rows
367 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
369 if y < app.screen.Height()-1 {
370 app.screen.PrintAt(0, y, app.fsbi.EmptyRowContent())
374 // print out the totals at the bottom
375 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.fsbi.TotalRowContent())
378 func (app *App) displayLocks() {
379 app.screen.BoldPrintAt(0, 2, app.tlwsbt.Headings())
381 // print out the data
382 max_rows := app.screen.Height() - 3
383 row_content := app.tlwsbt.RowContent(max_rows)
386 for k := range row_content {
388 app.screen.PrintAt(0, y, row_content[k])
390 // print out empty rows
391 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
393 if y < app.screen.Height()-1 {
394 app.screen.PrintAt(0, y, app.tlwsbt.EmptyRowContent())
398 // print out the totals at the bottom
399 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.tlwsbt.TotalRowContent())
402 func (app *App) displayUsers() {
403 app.screen.BoldPrintAt(0, 2, app.users.Headings())
405 // print out the data
406 max_rows := app.screen.Height() - 3
407 row_content := app.users.RowContent(max_rows)
410 for k := range row_content {
412 app.screen.PrintAt(0, y, row_content[k])
414 // print out empty rows
415 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
417 if y < app.screen.Height()-1 {
418 app.screen.PrintAt(0, y, app.users.EmptyRowContent())
422 // print out the totals at the bottom
423 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.users.TotalRowContent())
426 func (app *App) displayMutex() {
427 app.screen.BoldPrintAt(0, 2, app.ewsgben.Headings())
429 // print out the data
430 max_rows := app.screen.Height() - 3
431 row_content := app.ewsgben.RowContent(max_rows)
434 for k := range row_content {
436 app.screen.PrintAt(0, y, row_content[k])
438 // print out empty rows
439 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
441 if y < app.screen.Height()-1 {
442 app.screen.PrintAt(0, y, app.ewsgben.EmptyRowContent())
446 // print out the totals at the bottom
447 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.ewsgben.TotalRowContent())
450 func (app *App) displayStages() {
451 app.screen.BoldPrintAt(0, 2, app.essgben.Headings())
453 // print out the data
454 max_rows := app.screen.Height() - 3
455 row_content := app.essgben.RowContent(max_rows)
458 for k := range row_content {
460 app.screen.PrintAt(0, y, row_content[k])
462 // print out empty rows
463 for k := len(row_content); k < (app.screen.Height() - 3); k++ {
465 if y < app.screen.Height()-1 {
466 app.screen.PrintAt(0, y, app.essgben.EmptyRowContent())
470 // print out the totals at the bottom
471 app.screen.BoldPrintAt(0, app.screen.Height()-1, app.essgben.TotalRowContent())
474 // do we want to show all p_s data?
475 func (app App) WantRelativeStats() bool {
476 return app.want_relative_stats
479 // set if we want data from when we started/reset stats.
480 func (app *App) SetWantRelativeStats(want_relative_stats bool) {
481 app.want_relative_stats = want_relative_stats
483 app.fsbi.SetWantRelativeStats(want_relative_stats)
484 app.tlwsbt.SetWantRelativeStats(app.want_relative_stats)
485 app.tiwsbt.SetWantRelativeStats(app.want_relative_stats)
486 app.ewsgben.SetWantRelativeStats(app.want_relative_stats)
487 app.essgben.SetWantRelativeStats(app.want_relative_stats)
490 // if there's a better way of doing this do it better ...
491 func now_hhmmss() string {
493 return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
496 // record the latest screen size
497 func (app *App) ScreenSetSize(width, height int) {
498 app.screen.SetSize(width, height)
501 // clean up screen and disconnect database
502 func (app *App) Cleanup() {
505 app.setup_instruments.RestoreConfiguration()
510 // make chan for termbox events and run a poller to send events to the channel
511 // - return the channel
512 func new_tb_chan() chan termbox.Event {
513 termboxChan := make(chan termbox.Event)
516 termboxChan <- termbox.PollEvent()
522 // get into a run loop
523 func (app *App) Run() {
524 app.done = make(chan struct{})
525 defer close(app.done)
527 app.sigChan = make(chan os.Signal, 1)
528 signal.Notify(app.sigChan, syscall.SIGINT, syscall.SIGTERM)
530 app.wi.SetWaitInterval(time.Second)
532 termboxChan := new_tb_chan()
534 for !app.Finished() {
537 fmt.Println("app.done(): exiting")
539 case sig := <-app.sigChan:
540 fmt.Println("Caught a signal", sig)
541 app.done <- struct{}{}
542 case <-app.wi.WaitNextPeriod():
545 case event := <-termboxChan:
546 // switch on event type
548 case termbox.EventKey: // actions depend on key
550 case termbox.KeyCtrlZ, termbox.KeyCtrlC, termbox.KeyEsc:
552 case termbox.KeyArrowLeft: // left arrow change to previous display mode
553 app.DisplayPrevious()
555 case termbox.KeyTab, termbox.KeyArrowRight: // tab or right arrow - change to next display mode
560 case '-': // decrease the interval if > 1
561 if app.wi.WaitInterval() > time.Second {
562 app.wi.SetWaitInterval(app.wi.WaitInterval() - time.Second)
564 case '+': // increase interval by creating a new ticker
565 app.wi.SetWaitInterval(app.wi.WaitInterval() + time.Second)
566 case 'h', '?': // help
567 app.SetHelp(!app.Help())
570 case 't': // toggle between absolute/relative statistics
571 app.SetWantRelativeStats(!app.WantRelativeStats())
573 case 'z': // reset the statistics to now by taking a query of current values
574 app.ResetDBStatistics()
577 case termbox.EventResize: // set sizes
578 app.ScreenSetSize(event.Width, event.Height)
580 case termbox.EventError: // quit
581 log.Fatalf("Quitting because of termbox error: \n%s\n", event.Err)
587 // pstop requires MySQL 5.6+ or MariaDB 10.0+. Check the version
588 // rather than giving an error message if the requires P_S tables can't
590 func (app *App) validate_mysql_version() error {
591 var tables = [...]string{
592 "performance_schema.events_waits_summary_global_by_event_name",
593 "performance_schema.file_summary_by_instance",
594 "performance_schema.table_io_waits_summary_by_table",
595 "performance_schema.table_lock_waits_summary_by_table",
598 lib.Logger.Println("validate_mysql_version()")
600 lib.Logger.Println("- Getting MySQL version")
601 err, mysql_version := lib.SelectGlobalVariableByVariableName(app.dbh, "VERSION")
605 lib.Logger.Println("- mysql_version: '" + mysql_version + "'")
607 if !re_valid_version.MatchString(mysql_version) {
608 return errors.New(lib.MyName() + " does not work with MySQL version " + mysql_version)
610 lib.Logger.Println("OK: MySQL version is valid, continuing")
612 lib.Logger.Println("Checking access to required tables:")
613 for i := range tables {
614 if err := lib.CheckTableAccess(app.dbh, tables[i]); err == nil {
615 lib.Logger.Println("OK: " + tables[i] + " found")
620 lib.Logger.Println("OK: all table checks passed")