Initial import of pstop v0.0.10
[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.fsbi.Collect(state.dbh)
79         state.fsbi.UpdateInitialValues()
80
81         state.tlwsbt.Collect(state.dbh)
82         state.tlwsbt.UpdateInitialValues()
83
84         state.tiwsbt.Collect(state.dbh)
85         state.tiwsbt.UpdateInitialValues()
86 }
87
88 func (state State) MySQLVersion() string {
89         return state.mysql_version
90 }
91
92 func (state State) Datadir() string {
93         return state.datadir
94 }
95
96 func (state *State) SetHelp(newHelp bool) {
97         state.help = newHelp
98
99         state.screen.Clear()
100         state.screen.Flush()
101 }
102
103 func (state *State) SetDatadir(datadir string) {
104         state.datadir = datadir
105 }
106
107 func (state *State) SetMySQLVersion(mysql_version string) {
108         state.mysql_version = mysql_version
109 }
110
111 func (state *State) SetHostname(hostname string) {
112         state.hostname = hostname
113 }
114
115 func (state State) Help() bool {
116         return state.help
117 }
118
119 // display the output according to the mode we are in
120 func (state *State) Display() {
121         if state.help {
122                 state.screen.DisplayHelp()
123         } else {
124                 state.displayHeading()
125                 switch state.show {
126                 case showLatency, showOps:
127                         state.displayOpsOrLatency()
128                 case showIO:
129                         state.displayIO()
130                 case showLocks:
131                         state.displayLocks()
132                 }
133         }
134 }
135
136 // change to the next display mode
137 func (state *State) DisplayNext() {
138         if state.show == showLocks {
139                 state.show = showLatency
140         } else {
141                 state.show++
142         }
143         // this needs to be done more cleanly
144         if state.show == showLatency {
145                 state.tiwsbt.SetWantsLatency(true)
146         }
147         if state.show == showOps {
148                 state.tiwsbt.SetWantsLatency(false)
149         }
150         state.screen.Clear()
151         state.screen.Flush()
152 }
153
154 func (state State) displayHeading() {
155         state.displayLine0()
156         state.displayLine1()
157 }
158
159 func (state State) displayLine0() {
160         _, uptime := lib.SelectGlobalStatusByVariableName(state.dbh, "UPTIME")
161         top_line := lib.MyName() + " " + version.Version() + " - " + now_hhmmss() + " " + state.hostname + " / " + state.mysql_version + ", up " + fmt.Sprintf("%-16s", lib.Uptime(uptime))
162         if state.want_relative_stats {
163                 now := time.Now()
164
165                 var initial time.Time
166
167                 switch state.show {
168                 case showLatency, showOps:
169                         initial = state.tiwsbt.Last()
170                 case showIO:
171                         initial = state.fsbi.Last()
172                 case showLocks:
173                         initial = state.tlwsbt.Last()
174                 default:
175                         initial = time.Now() // THIS IS WRONG !!!
176                 }
177
178                 d := now.Sub(initial)
179
180                 top_line = top_line + " [REL] " + fmt.Sprintf("%.0f seconds", d.Seconds())
181         } else {
182                 top_line = top_line + " [ABS]             "
183         }
184         state.screen.PrintAt(0, 0, top_line)
185 }
186
187 func (state State) displayLine1() {
188         switch state.show {
189         case showLatency, showOps:
190                 state.screen.PrintAt(0, 1, state.tiwsbt.Description())
191         case showIO:
192                 state.screen.PrintAt(0, 1, state.fsbi.Description())
193         case showLocks:
194                 state.screen.PrintAt(0, 1, state.tlwsbt.Description())
195         default:
196                 state.screen.PrintAt(0, 1, "UNKNOWN")
197         }
198 }
199
200 func (state *State) displayOpsOrLatency() {
201         state.tiwsbt.Collect(state.dbh)
202
203         state.screen.PrintAt(0, 2, state.tiwsbt.Headings())
204
205         max_rows := state.screen.Height() - 3
206         row_content := state.tiwsbt.RowContent(max_rows)
207
208         // print out rows
209         for k := range row_content {
210                 y := 3 + k
211                 state.screen.PrintAt(0, y, row_content[k])
212         }
213         // print out empty rows
214         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
215                 y := 3 + k
216                 if y < state.screen.Height()-1 {
217                         state.screen.PrintAt(0, y, state.tiwsbt.EmptyRowContent())
218                 }
219         }
220
221         // print out the totals at the bottom
222         state.screen.PrintAt(0, state.screen.Height()-1, state.tiwsbt.TotalRowContent())
223 }
224
225 // show actual I/O latency values
226 func (state State) displayIO() {
227         state.fsbi.Collect(state.dbh)
228
229         state.screen.PrintAt(0, 2, state.fsbi.Headings())
230
231         // print out the data
232         max_rows := state.screen.Height() - 3
233         row_content := state.fsbi.RowContent(max_rows)
234
235         // print out rows
236         for k := range row_content {
237                 y := 3 + k
238                 state.screen.PrintAt(0, y, row_content[k])
239         }
240         // print out empty rows
241         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
242                 y := 3 + k
243                 if y < state.screen.Height()-1 {
244                         state.screen.PrintAt(0, y, state.fsbi.EmptyRowContent())
245                 }
246         }
247
248         // print out the totals at the bottom
249         state.screen.PrintAt(0, state.screen.Height()-1, state.fsbi.TotalRowContent())
250 }
251
252 func (state *State) displayLocks() {
253         state.tlwsbt.Collect(state.dbh)
254
255         state.screen.PrintAt(0, 2, state.tlwsbt.Headings())
256
257         // print out the data
258         max_rows := state.screen.Height() - 3
259         row_content := state.tlwsbt.RowContent(max_rows)
260
261         // print out rows
262         for k := range row_content {
263                 y := 3 + k
264                 state.screen.PrintAt(0, y, row_content[k])
265         }
266         // print out empty rows
267         for k := len(row_content); k < (state.screen.Height() - 3); k++ {
268                 y := 3 + k
269                 if y < state.screen.Height()-1 {
270                         state.screen.PrintAt(0, y, state.tlwsbt.EmptyRowContent())
271                 }
272         }
273
274         // print out the totals at the bottom
275         state.screen.PrintAt(0, state.screen.Height()-1, state.tlwsbt.TotalRowContent())
276 }
277
278 // do we want to show all p_s data?
279 func (state State) WantRelativeStats() bool {
280         return state.want_relative_stats
281 }
282
283 // set if we want data from when we started/reset stats.
284 func (state *State) SetWantRelativeStats(want_relative_stats bool) {
285         state.want_relative_stats = want_relative_stats
286
287         state.fsbi.SetWantRelativeStats(want_relative_stats)
288         state.tlwsbt.SetWantRelativeStats(state.want_relative_stats)
289         state.tiwsbt.SetWantRelativeStats(state.want_relative_stats)
290
291         state.Display()
292 }
293
294 // if there's a better way of doing this do it better ...
295 func now_hhmmss() string {
296         t := time.Now()
297         return fmt.Sprintf("%2d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
298 }
299
300 // record the latest screen size
301 func (state *State) ScreenSetSize(width, height int) {
302         state.screen.SetSize(width, height)
303 }
304
305 // clean up screen and disconnect database
306 func (state *State) Cleanup() {
307         state.screen.Close()
308         if state.dbh != nil {
309                 _ = state.dbh.Close()
310         }
311 }