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