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