Add new mutex page inspired by: http://www.percona.com/blog/2015/01/06/getting-mutex...
[pstop.git] / p_s / events_waits_summary_global_by_event_name / events_waits_summary_global_by_event_name_row.go
1 // This file contains the library routines for managing the
2 // table_io_waits_by_table table.
3 package events_waits_summary_global_by_event_name
4
5 import (
6         "database/sql"
7         "fmt"
8         "log"
9         "sort"
10         "strings"
11
12         "github.com/sjmudd/pstop/lib"
13 )
14
15 // a row from performance_schema.events_waits_summary_global_by_event_name
16 // Note: upper case names to match the performance_schema column names.
17 // This type is _not_ meant to be exported.
18 type events_waits_summary_global_by_event_name_row struct {
19         EVENT_NAME     string
20         SUM_TIMER_WAIT uint64
21         COUNT_STAR     uint64
22 }
23 type events_waits_summary_global_by_event_name_rows []events_waits_summary_global_by_event_name_row
24
25 // trim off the leading 'wait/synch/mutex/innodb/'
26 func (r *events_waits_summary_global_by_event_name_row) name() string {
27         var n string
28         if r.EVENT_NAME == "Totals" {
29                 n += r.EVENT_NAME
30         } else if len(r.EVENT_NAME) >= 24 {
31                 n += r.EVENT_NAME[24:]
32         }
33         return n
34 }
35
36 func (r *events_waits_summary_global_by_event_name_row) pretty_name() string {
37         s := r.name()
38         if len(s) > 30 {
39                 s = s[:29]
40         }
41         return s
42 }
43
44 func (r *events_waits_summary_global_by_event_name_row) headings() string {
45         return fmt.Sprintf("%-30s %10s %6s", "Mutex Name", "Latency", "%")
46 }
47
48 // generate a printable result
49 func (r *events_waits_summary_global_by_event_name_row) row_content(totals events_waits_summary_global_by_event_name_row) string {
50         name := r.pretty_name()
51         if r.COUNT_STAR == 0 && name != "Totals" {
52                 name = ""
53         }
54
55         return fmt.Sprintf("%-30s %10s %6s",
56                 name,
57                 lib.FormatTime(r.SUM_TIMER_WAIT),
58                 lib.FormatPct(lib.MyDivide(r.SUM_TIMER_WAIT, totals.SUM_TIMER_WAIT)))
59 }
60
61 func (this *events_waits_summary_global_by_event_name_row) add(other events_waits_summary_global_by_event_name_row) {
62         this.SUM_TIMER_WAIT += other.SUM_TIMER_WAIT
63         this.COUNT_STAR += other.COUNT_STAR
64 }
65
66 // subtract the countable values in one row from another
67 func (this *events_waits_summary_global_by_event_name_row) subtract(other events_waits_summary_global_by_event_name_row) {
68         // check for issues here (we have a bug) and log it
69         // - this situation should not happen so there's a logic bug somewhere else
70         if this.SUM_TIMER_WAIT >= other.SUM_TIMER_WAIT {
71                 this.SUM_TIMER_WAIT -= other.SUM_TIMER_WAIT
72                 this.COUNT_STAR -= other.COUNT_STAR
73         } else {
74                 lib.Logger.Println("WARNING: events_waits_summary_global_by_event_name_row.subtract() - subtraction problem! (not subtracting)")
75                 lib.Logger.Println("this=", this)
76                 lib.Logger.Println("other=", other)
77         }
78 }
79
80 func (t events_waits_summary_global_by_event_name_rows) totals() events_waits_summary_global_by_event_name_row {
81         var totals events_waits_summary_global_by_event_name_row
82         totals.EVENT_NAME = "Totals"
83
84         for i := range t {
85                 totals.add(t[i])
86         }
87
88         return totals
89 }
90
91 func select_tiwsbt_rows(dbh *sql.DB) events_waits_summary_global_by_event_name_rows {
92         var t events_waits_summary_global_by_event_name_rows
93
94         // we collect all information even if it's mainly empty as we may reference it later
95         sql := "SELECT EVENT_NAME, SUM_TIMER_WAIT, COUNT_STAR FROM events_waits_summary_global_by_event_name WHERE SUM_TIMER_WAIT > 0 AND EVENT_NAME LIKE 'wait/synch/mutex/innodb/%'"
96
97         rows, err := dbh.Query(sql)
98         if err != nil {
99                 log.Fatal(err)
100         }
101         defer rows.Close()
102
103         for rows.Next() {
104                 var r events_waits_summary_global_by_event_name_row
105                 if err := rows.Scan(
106                         &r.EVENT_NAME,
107                         &r.SUM_TIMER_WAIT,
108                         &r.COUNT_STAR); err != nil {
109                         log.Fatal(err)
110                 }
111                 // we collect all information even if it's mainly empty as we may reference it later
112                 t = append(t, r)
113         }
114         if err := rows.Err(); err != nil {
115                 log.Fatal(err)
116         }
117
118         return t
119 }
120
121 func (t events_waits_summary_global_by_event_name_rows) Len() int      { return len(t) }
122 func (t events_waits_summary_global_by_event_name_rows) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
123
124 // sort by value (descending) but also by "name" (ascending) if the values are the same
125 func (t events_waits_summary_global_by_event_name_rows) Less(i, j int) bool {
126         return t[i].SUM_TIMER_WAIT > t[j].SUM_TIMER_WAIT
127 }
128
129 func (t events_waits_summary_global_by_event_name_rows) Sort() {
130         sort.Sort(t)
131 }
132
133 // remove the initial values from those rows where there's a match
134 // - if we find a row we can't match ignore it
135 func (this *events_waits_summary_global_by_event_name_rows) subtract(initial events_waits_summary_global_by_event_name_rows) {
136         initial_by_name := make(map[string]int)
137
138         // iterate over rows by name
139         for i := range initial {
140                 initial_by_name[initial[i].name()] = i
141         }
142
143         for i := range *this {
144                 this_name := (*this)[i].name()
145                 if _, ok := initial_by_name[this_name]; ok {
146                         initial_index := initial_by_name[this_name]
147                         (*this)[i].subtract(initial[initial_index])
148                 }
149         }
150 }
151
152 // if the data in t2 is "newer", "has more values" than t then it needs refreshing.
153 // check this by comparing totals.
154 func (t events_waits_summary_global_by_event_name_rows) needs_refresh(t2 events_waits_summary_global_by_event_name_rows) bool {
155         my_totals := t.totals()
156         t2_totals := t2.totals()
157
158         return my_totals.SUM_TIMER_WAIT > t2_totals.SUM_TIMER_WAIT
159 }
160
161 // describe a whole row
162 func (r events_waits_summary_global_by_event_name_row) String() string {
163         return fmt.Sprintf("%-30s|%10s %10s %10s %10s %10s|%10s %10s|%10s %10s %10s %10s %10s|%10s %10s",
164                 r.pretty_name(),
165                 lib.FormatTime(r.SUM_TIMER_WAIT),
166                 lib.FormatAmount(r.COUNT_STAR))
167 }
168
169 // describe a whole table
170 func (t events_waits_summary_global_by_event_name_rows) String() string {
171         s := make([]string, len(t))
172
173         for i := range t {
174                 s = append(s, t[i].String())
175         }
176
177         return strings.Join(s, "\n")
178 }