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