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