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