fix typo: in db name
[pstop.git] / main.go
1 // Top like progream which collects information from MySQL's
2 // performance_schema database.
3 package main
4
5 import (
6         "database/sql"
7         "errors"
8         "flag"
9         "fmt"
10         "log"
11         "os"
12         "os/signal"
13         "regexp"
14         "runtime/pprof"
15         "syscall"
16         "time"
17
18         _ "github.com/go-sql-driver/mysql"
19         "github.com/nsf/termbox-go"
20
21         "github.com/sjmudd/mysql_defaults_file"
22         "github.com/sjmudd/pstop/lib"
23         "github.com/sjmudd/pstop/state"
24         "github.com/sjmudd/pstop/version"
25 )
26
27 const (
28         sql_driver = "mysql"
29         db         = "performance_schema"
30 )
31
32 var (
33         flag_version = flag.Bool("version", false, "Show the version of "+lib.MyName())
34         flag_debug   = flag.Bool("debug", false, "Enabling debug logging")
35         flag_help    = flag.Bool("help", false, "Provide some help for "+lib.MyName())
36         cpuprofile   = flag.String("cpuprofile", "", "write cpu profile to file")
37
38         re_valid_version = regexp.MustCompile(`^(5\.[67]\.|10\.[01])`)
39 )
40
41 func get_db_handle() *sql.DB {
42         var err error
43         var dbh *sql.DB
44         lib.Logger.Println("get_db_handle() connecting to database")
45
46         dbh, err = mysql_defaults_file.OpenUsingDefaultsFile(sql_driver, "", "performance_schema")
47         if err != nil {
48                 log.Fatal(err)
49         }
50         if err = dbh.Ping(); err != nil {
51                 log.Fatal(err)
52         }
53
54         return dbh
55 }
56
57 // make chan for termbox events and run a poller to send events to the channel
58 // - return the channel
59 func new_tb_chan() chan termbox.Event {
60         termboxChan := make(chan termbox.Event)
61         go func() {
62                 for {
63                         termboxChan <- termbox.PollEvent()
64                 }
65         }()
66         return termboxChan
67 }
68
69 func usage() {
70         fmt.Println(lib.MyName() + " - " + lib.Copyright())
71         fmt.Println("")
72         fmt.Println("Top-like program to show MySQL activity by using information collected")
73         fmt.Println("from performance_schema.")
74         fmt.Println("")
75         fmt.Println("Usage: " + lib.MyName() + " <options>")
76         fmt.Println("")
77         fmt.Println("Options:")
78         fmt.Println("-help      show this help message")
79         fmt.Println("-version   show the version")
80 }
81
82 // pstop requires MySQL 5.6+ or MariaDB 10.0+. Check the version
83 // rather than giving an error message if the requires P_S tables can't
84 // be found.
85 func validate_mysql_version(dbh *sql.DB) error {
86         var tables = [...]string{
87                 "performance_schema.file_summary_by_instance",
88                 "performance_schema.table_io_waits_summary_by_table",
89                 "performance_schema.table_lock_waits_summary_by_table",
90         }
91
92         lib.Logger.Println("validate_mysql_version()")
93
94         lib.Logger.Println("- Getting MySQL version")
95         err, mysql_version := lib.SelectGlobalVariableByVariableName(dbh, "VERSION")
96         if err != nil {
97                 return err
98         }
99         lib.Logger.Println("- mysql_version: '" + mysql_version + "'")
100
101         if !re_valid_version.MatchString(mysql_version) {
102                 err := errors.New(lib.MyName() + " does not work with MySQL version " + mysql_version)
103                 return err
104         }
105         lib.Logger.Println("OK: MySQL version is valid, continuing")
106
107         lib.Logger.Println("Checking access to required tables:")
108         for i := range tables {
109                 if err := lib.CheckTableAccess(dbh, tables[i]); err == nil {
110                         lib.Logger.Println("OK: " + tables[i] + " found")
111                 } else {
112                         return err
113                 }
114         }
115         lib.Logger.Println("OK: all table checks passed")
116
117         return nil
118 }
119
120 func main() {
121         flag.Parse()
122
123         // clean me up
124         if *cpuprofile != "" {
125                 f, err := os.Create(*cpuprofile)
126                 if err != nil {
127                         log.Fatal(err)
128                 }
129                 pprof.StartCPUProfile(f)
130                 defer pprof.StopCPUProfile()
131         }
132
133         if *flag_debug {
134                 lib.Logger.EnableLogging(true)
135         }
136         if *flag_version {
137                 fmt.Println(lib.MyName() + " version " + version.Version())
138                 return
139         }
140         if *flag_help {
141                 usage()
142                 return
143         }
144
145         lib.Logger.Println("Starting " + lib.MyName())
146
147         dbh := get_db_handle()
148         if err := validate_mysql_version(dbh); err != nil {
149                 log.Fatal(err)
150         }
151
152         var state state.State
153         interval := time.Second
154         sigChan := make(chan os.Signal, 1)
155         done := make(chan struct{})
156         defer close(done)
157         termboxChan := new_tb_chan()
158
159         signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
160
161         ticker := time.NewTicker(interval) // generate a periodic signal
162
163         state.Setup(dbh)
164
165         finished := false
166         for !finished {
167                 select {
168                 case <-done:
169                         fmt.Println("exiting")
170                         finished = true
171                 case sig := <-sigChan:
172                         fmt.Println("Caught a signal", sig)
173                         done <- struct{}{}
174                 case <-ticker.C:
175                         state.Collect()
176                         state.Display()
177                 case event := <-termboxChan:
178                         // switch on event type
179                         switch event.Type {
180                         case termbox.EventKey: // actions depend on key
181                                 switch event.Key {
182                                 case termbox.KeyCtrlZ, termbox.KeyCtrlC, termbox.KeyEsc:
183                                         finished = true
184                                 case termbox.KeyTab: // tab - change display modes
185                                         state.DisplayNext()
186                                         state.Display()
187                                 }
188                                 switch event.Ch {
189                                 case '-': // decrease the interval if > 1
190                                         if interval > time.Second {
191                                                 ticker.Stop()
192                                                 interval -= time.Second
193                                                 ticker = time.NewTicker(interval)
194                                         }
195                                 case '+': // increase interval by creating a new ticker
196                                         ticker.Stop()
197                                         interval += time.Second
198                                         ticker = time.NewTicker(interval)
199                                 case 'h': // help
200                                         state.SetHelp(!state.Help())
201                                 case 'q': // quit
202                                         finished = true
203                                 case 't': // toggle between absolute/relative statistics
204                                         state.SetWantRelativeStats(!state.WantRelativeStats())
205                                         state.Display()
206                                 case 'z': // reset the statistics to now by taking a query of current values
207                                         state.ResetDBStatistics()
208                                         state.Display()
209                                 }
210                         case termbox.EventResize: // set sizes
211                                 state.ScreenSetSize(event.Width, event.Height)
212                                 state.Display()
213                         case termbox.EventError: // quit
214                                 log.Fatalf("Quitting because of termbox error: \n%s\n", event.Err)
215                         }
216                 }
217         }
218         state.Cleanup()
219         ticker.Stop()
220         lib.Logger.Println("Terminating " + lib.MyName())
221 }