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