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