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