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