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