33a86ea89cf5ee511e15824c995ed2e23c54ea93
[pstop.git] / state / state.go
1 // lib - library routines for pstop.
2 //
3 // this file contains the library routines related to the stored state in pstop.
4 package state
5
6 import (
7         "database/sql"
8         "fmt"
9         "strings"
10         "time"
11
12         "github.com/sjmudd/pstop/i_s"
13         "github.com/sjmudd/pstop/lib"
14         ewsgben "github.com/sjmudd/pstop/p_s/events_waits_summary_global_by_event_name"
15         essgben "github.com/sjmudd/pstop/p_s/events_stages_summary_global_by_event_name"
16         fsbi "github.com/sjmudd/pstop/p_s/file_summary_by_instance"
17         "github.com/sjmudd/pstop/p_s/ps_table"
18         "github.com/sjmudd/pstop/p_s/setup_instruments"
19         tiwsbt "github.com/sjmudd/pstop/p_s/table_io_waits_summary_by_table"
20         tlwsbt "github.com/sjmudd/pstop/p_s/table_lock_waits_summary_by_table"
21         "github.com/sjmudd/pstop/screen"
22         "github.com/sjmudd/pstop/version"
23         "github.com/sjmudd/pstop/wait_info"
24 )
25
26 // what information to show
27 type Show int
28
29 const (
30         showLatency = iota
31         showOps     = iota
32         showIO      = iota
33         showLocks   = iota
34         showUsers   = iota
35         showMutex   = iota
36         showStages  = iota
37 )
38
39 type State struct {
40         finished            bool
41         datadir             string
42         dbh                 *sql.DB
43         help                bool
44         hostname            string
45         fsbi                ps_table.Tabler // ufsbi.File_summary_by_instance
46         tiwsbt              tiwsbt.Table_io_waits_summary_by_table
47         tlwsbt              ps_table.Tabler // tlwsbt.Table_lock_waits_summary_by_table
48         ewsgben             ps_table.Tabler // ewsgben.Events_waits_summary_global_by_event_name
49         essgben             ps_table.Tabler // essgben.Events_stages_summary_global_by_event_name
50         users               i_s.Processlist
51         screen              screen.TermboxScreen
52         show                Show
53         mysql_version       string
54         want_relative_stats bool
55         wait_info.WaitInfo  // embedded
56         setup_instruments   setup_instruments.SetupInstruments
57 }
58
59 func (state *State) Setup(dbh *sql.DB) {
60         state.dbh = dbh
61         state.finished = false
62
63         state.screen.Initialise()
64
65         state.setup_instruments.EnableMutexMonitoring(dbh)
66         _, variables := lib.SelectAllGlobalVariablesByVariableName(state.dbh)
67         // setup to their initial types/values
68         state.fsbi = fsbi.NewFileSummaryByInstance(variables)
69         state.tlwsbt = new(tlwsbt.Table_lock_waits_summary_by_table)
70         state.ewsgben = new(ewsgben.Table_events_waits_summary_global_by_event_name)
71         state.essgben = new(essgben.Object)
72
73         state.want_relative_stats = true // we show info from the point we start collecting data
74         state.fsbi.SetWantRelativeStats(state.want_relative_stats)
75         state.fsbi.SetNow()
76         state.tlwsbt.SetWantRelativeStats(state.want_relative_stats)
77         state.tlwsbt.SetNow()
78         state.tiwsbt.SetWantRelativeStats(state.want_relative_stats)
79         state.tiwsbt.SetNow()
80         state.users.SetWantRelativeStats(state.want_relative_stats)   // ignored
81         state.users.SetNow()                                          // ignored
82         state.essgben.SetWantRelativeStats(state.want_relative_stats)
83         state.essgben.SetNow()
84         state.ewsgben.SetWantRelativeStats(state.want_relative_stats) // ignored
85         state.ewsgben.SetNow()                                        // ignored
86
87         state.ResetDBStatistics()
88
89         state.SetHelp(false)
90         state.show = showLatency
91         state.tiwsbt.SetWantsLatency(true)
92
93         // get short name (to save space)
94         _, hostname := lib.SelectGlobalVariableByVariableName(state.dbh, "HOSTNAME")
95         if index := strings.Index(hostname, "."); index >= 0 {
96                 hostname = hostname[0:index]
97         }
98         _, mysql_version := lib.SelectGlobalVariableByVariableName(state.dbh, "VERSION")
99         _, datadir := lib.SelectGlobalVariableByVariableName(state.dbh, "DATADIR")
100         state.SetHostname(hostname)
101         state.SetMySQLVersion(mysql_version)
102         state.SetDatadir(datadir)
103 }
104
105 // have we finished ?
106 func (state State) Finished() bool {
107         return state.finished
108 }
109
110 // indicate we have finished
111 func (state *State) SetFinished() {
112         state.finished = true
113 }
114
115 // do a fresh collection of data and then update the initial values based on that.
116 func (state *State) ResetDBStatistics() {
117         state.CollectAll()
118         state.SyncReferenceValues()
119 }
120
121 func (state *State) SyncReferenceValues() {
122         start := time.Now()
123         state.fsbi.SyncReferenceValues()
124         state.tlwsbt.SyncReferenceValues()
125         state.tiwsbt.SyncReferenceValues()
126         state.essgben.SyncReferenceValues()
127         lib.Logger.Println("state.SyncReferenceValues() took", time.Duration(time.Since(start)).String())
128 }
129
130 // collect all initial values on startup / reset
131 func (state *State) CollectAll() {
132         state.fsbi.Collect(state.dbh)
133         state.tlwsbt.Collect(state.dbh)
134         state.tiwsbt.Collect(state.dbh)
135 }
136
137 // Only collect the data we are looking at.
138 func (state *State) Collect() {
139         start := time.Now()
140
141         switch state.show {
142         case showLatency, showOps:
143                 state.tiwsbt.Collect(state.dbh)
144         case showIO:
145                 state.fsbi.Collect(state.dbh)
146         case showLocks:
147                 state.tlwsbt.Collect(state.dbh)
148         case showUsers:
149                 state.users.Collect(state.dbh)
150         case showMutex:
151                 state.ewsgben.Collect(state.dbh)
152         case showStages:
153                 state.essgben.Collect(state.dbh)
154         }
155         lib.Logger.Println("state.Collect() took", time.Duration(time.Since(start)).String())
156 }
157
158 func (state State) MySQLVersion() string {
159         return state.mysql_version
160 }
161
162 func (state State) Datadir() string {
163         return state.datadir
164 }
165
166 func (state *State) SetHelp(newHelp bool) {
167         state.help = newHelp
168
169         state.screen.Clear()
170         state.screen.Flush()
171 }
172
173 func (state *State) SetDatadir(datadir string) {
174         state.datadir = datadir
175 }
176
177 func (state *State) SetMySQLVersion(mysql_version string) {
178         state.mysql_version = mysql_version
179 }
180
181 func (state *State) SetHostname(hostname string) {
182         state.hostname = hostname
183 }
184
185 func (state State) Help() bool {
186         return state.help
187 }
188
189 // states go: showLatency -> showOps -> showIO -> showLocks -> showUsers -> showMutex -> showStages
190
191 // display the output according to the mode we are in
192 func (state *State) Display() {
193         if state.help {
194                 state.screen.DisplayHelp()
195         } else {
196                 state.displayHeading()
197                 switch state.show {
198                 case showLatency, showOps:
199                         state.displayOpsOrLatency()
200                 case showIO:
201                         state.displayIO()
202                 case showLocks:
203                         state.displayLocks()
204                 case showUsers:
205                         state.displayUsers()
206                 case showMutex:
207                         state.displayMutex()
208                 case showStages:
209                         state.displayStages()
210                 }
211         }
212 }
213
214 // fix_latency_setting() ensures the SetWantsLatency() value is
215 // correct. This needs to be done more cleanly.
216 func (state *State) fix_latency_setting() {
217         if state.show == showLatency {
218                 state.tiwsbt.SetWantsLatency(true)
219         }
220         if state.show == showOps {
221                 state.tiwsbt.SetWantsLatency(false)
222         }
223 }
224
225 // change to the previous display mode
226 func (state *State) DisplayPrevious() {
227         if state.show == showLatency {
228                 state.show = showStages
229         } else {
230                 state.show--
231         }
232         state.fix_latency_setting()
233         state.screen.Clear()
234         state.screen.Flush()
235 }
236
237 // change to the next display mode
238 func (state *State) DisplayNext() {
239         if state.show == showStages {
240                 state.show = showLatency
241         } else {
242                 state.show++
243         }
244         state.fix_latency_setting()
245         state.screen.Clear()
246         state.screen.Flush()
247 }
248
249 func (state State) displayHeading() {
250         state.displayLine0()
251         state.displayDescription()
252 }
253
254 func (state State) displayLine0() {
255         _, uptime := lib.SelectGlobalStatusByVariableName(state.dbh, "UPTIME")
256         top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + state.hostname + " / " + state.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
257         if state.want_relative_stats {
258                 now := time.Now()
259
260                 var initial time.Time
261
262                 switch state.show {
263                 case showLatency, showOps:
264                         initial = state.tiwsbt.Last()
265                 case showIO:
266                         initial = state.fsbi.Last()
267                 case showLocks:
268                         initial = state.tlwsbt.Last()
269                 case showUsers:
270                         initial = state.users.Last()
271                 case showStages:
272                         initial = state.essgben.Last()
273                 case showMutex:
274                         initial = state.ewsgben.Last()
275                 default:
276                         // should not get here !
277                 }
278
279                 d := now.Sub(initial)
280
281                 top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
282         } else {
283                 top_line = top_line + " [ABS]             "
284         }
285         state.screen.PrintAt(0, 0, top_line)
286 }
287
288 func (state State) displayDescription() {
289         description := "UNKNOWN"
290
291         switch state.show {
292         case showLatency, showOps:
293                 description = state.tiwsbt.Description()
294         case showIO:
295                 description = state.fsbi.Description()
296         case showLocks:
297                 description = state.tlwsbt.Description()
298         case showUsers:
299                 description = state.users.Description()
300         case showMutex:
301                 description = state.ewsgben.Description()
302         case showStages:
303                 description = state.essgben.Description()
304         }
305
306         state.screen.PrintAt(0, 1, description)
307 }
308
309 func (state *State) displayOpsOrLatency() {
310         state.screen.BoldPrintAt(0, 2, state.tiwsbt.Headings())
311
312         max_rows := state.screen.Height() - 3
313         row_content := state.tiwsbt.RowContent(max_rows)
314
315         // print out rows
316         for k := range row_content {
317                 y := 3 + k
318                 state.screen.PrintAt(0, y, row_content[k])
319         }
320         // print out empty rows
321         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
322                 y := 3 + k
323                 if y < state.screen.Height()-1 {
324                         state.screen.PrintAt(0, y, state.tiwsbt.EmptyRowContent())
325                 }
326         }
327
328         // print out the totals at the bottom
329         state.screen.BoldPrintAt(0, state.screen.Height()-1, state.tiwsbt.TotalRowContent())
330 }
331
332 // show actual I/O latency values
333 func (state State) displayIO() {
334         state.screen.BoldPrintAt(0, 2, state.fsbi.Headings())
335
336         // print out the data
337         max_rows := state.screen.Height() - 3
338         row_content := state.fsbi.RowContent(max_rows)
339
340         // print out rows
341         for k := range row_content {
342                 y := 3 + k
343                 state.screen.PrintAt(0, y, row_content[k])
344         }
345         // print out empty rows
346         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
347                 y := 3 + k
348                 if y < state.screen.Height()-1 {
349                         state.screen.PrintAt(0, y, state.fsbi.EmptyRowContent())
350                 }
351         }
352
353         // print out the totals at the bottom
354         state.screen.BoldPrintAt(0, state.screen.Height()-1, state.fsbi.TotalRowContent())
355 }
356
357 func (state *State) displayLocks() {
358         state.screen.BoldPrintAt(0, 2, state.tlwsbt.Headings())
359
360         // print out the data
361         max_rows := state.screen.Height() - 3
362         row_content := state.tlwsbt.RowContent(max_rows)
363
364         // print out rows
365         for k := range row_content {
366                 y := 3 + k
367                 state.screen.PrintAt(0, y, row_content[k])
368         }
369         // print out empty rows
370         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
371                 y := 3 + k
372                 if y < state.screen.Height()-1 {
373                         state.screen.PrintAt(0, y, state.tlwsbt.EmptyRowContent())
374                 }
375         }
376
377         // print out the totals at the bottom
378         state.screen.BoldPrintAt(0, state.screen.Height()-1, state.tlwsbt.TotalRowContent())
379 }
380
381 func (state *State) displayUsers() {
382         state.screen.BoldPrintAt(0, 2, state.users.Headings())
383
384         // print out the data
385         max_rows := state.screen.Height() - 3
386         row_content := state.users.RowContent(max_rows)
387
388         // print out rows
389         for k := range row_content {
390                 y := 3 + k
391                 state.screen.PrintAt(0, y, row_content[k])
392         }
393         // print out empty rows
394         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
395                 y := 3 + k
396                 if y < state.screen.Height()-1 {
397                         state.screen.PrintAt(0, y, state.users.EmptyRowContent())
398                 }
399         }
400
401         // print out the totals at the bottom
402         state.screen.BoldPrintAt(0, state.screen.Height()-1, state.users.TotalRowContent())
403 }
404
405 func (state *State) displayMutex() {
406         state.screen.BoldPrintAt(0, 2, state.ewsgben.Headings())
407
408         // print out the data
409         max_rows := state.screen.Height() - 3
410         row_content := state.ewsgben.RowContent(max_rows)
411
412         // print out rows
413         for k := range row_content {
414                 y := 3 + k
415                 state.screen.PrintAt(0, y, row_content[k])
416         }
417         // print out empty rows
418         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
419                 y := 3 + k
420                 if y < state.screen.Height()-1 {
421                         state.screen.PrintAt(0, y, state.ewsgben.EmptyRowContent())
422                 }
423         }
424
425         // print out the totals at the bottom
426         state.screen.BoldPrintAt(0, state.screen.Height()-1, state.ewsgben.TotalRowContent())
427 }
428
429 func (state *State) displayStages() {
430         state.screen.BoldPrintAt(0, 2, state.essgben.Headings())
431
432         // print out the data
433         max_rows := state.screen.Height() - 3
434         row_content := state.essgben.RowContent(max_rows)
435
436         // print out rows
437         for k := range row_content {
438                 y := 3 + k
439                 state.screen.PrintAt(0, y, row_content[k])
440         }
441         // print out empty rows
442         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
443                 y := 3 + k
444                 if y < state.screen.Height()-1 {
445                         state.screen.PrintAt(0, y, state.essgben.EmptyRowContent())
446                 }
447         }
448
449         // print out the totals at the bottom
450         state.screen.BoldPrintAt(0, state.screen.Height()-1, state.essgben.TotalRowContent())
451 }
452
453
454 // do we want to show all p_s data?
455 func (state State) WantRelativeStats() bool {
456         return state.want_relative_stats
457 }
458
459 // set if we want data from when we started/reset stats.
460 func (state *State) SetWantRelativeStats(want_relative_stats bool) {
461         state.want_relative_stats = want_relative_stats
462
463         state.fsbi.SetWantRelativeStats(want_relative_stats)
464         state.tlwsbt.SetWantRelativeStats(state.want_relative_stats)
465         state.tiwsbt.SetWantRelativeStats(state.want_relative_stats)
466         state.ewsgben.SetWantRelativeStats(state.want_relative_stats)
467 }
468
469 // if there's a better way of doing this do it better ...
470 func now_hhmmss() string {
471         t := time.Now()
472         return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
473 }
474
475 // record the latest screen size
476 func (state *State) ScreenSetSize(width, height int) {
477         state.screen.SetSize(width, height)
478 }
479
480 // clean up screen and disconnect database
481 func (state *State) Cleanup() {
482         state.screen.Close()
483         if state.dbh != nil {
484                 state.setup_instruments.Restore(state.dbh)
485                 _ = state.dbh.Close()
486         }
487 }