1 // This file contains the library routines for managing the
2 // file_summary_by_instance table.
3 package file_summary_by_instance
12 "github.com/sjmudd/pstop/lib"
16 CREATE TABLE `file_summary_by_instance` (
17 `FILE_NAME` varchar(512) NOT NULL,
18 `EVENT_NAME` varchar(128) NOT NULL, // not collected
19 `OBJECT_INSTANCE_BEGIN` bigint(20) unsigned NOT NULL, // not collected
20 `COUNT_STAR` bigint(20) unsigned NOT NULL,
21 `SUM_TIMER_WAIT` bigint(20) unsigned NOT NULL,
22 `MIN_TIMER_WAIT` bigint(20) unsigned NOT NULL,
23 `AVG_TIMER_WAIT` bigint(20) unsigned NOT NULL,
24 `MAX_TIMER_WAIT` bigint(20) unsigned NOT NULL,
25 `COUNT_READ` bigint(20) unsigned NOT NULL,
26 `SUM_TIMER_READ` bigint(20) unsigned NOT NULL,
27 `MIN_TIMER_READ` bigint(20) unsigned NOT NULL,
28 `AVG_TIMER_READ` bigint(20) unsigned NOT NULL,
29 `MAX_TIMER_READ` bigint(20) unsigned NOT NULL,
30 `SUM_NUMBER_OF_BYTES_READ` bigint(20) NOT NULL,
31 `COUNT_WRITE` bigint(20) unsigned NOT NULL,
32 `SUM_TIMER_WRITE` bigint(20) unsigned NOT NULL,
33 `MIN_TIMER_WRITE` bigint(20) unsigned NOT NULL,
34 `AVG_TIMER_WRITE` bigint(20) unsigned NOT NULL,
35 `MAX_TIMER_WRITE` bigint(20) unsigned NOT NULL,
36 `SUM_NUMBER_OF_BYTES_WRITE` bigint(20) NOT NULL,
37 `COUNT_MISC` bigint(20) unsigned NOT NULL,
38 `SUM_TIMER_MISC` bigint(20) unsigned NOT NULL,
39 `MIN_TIMER_MISC` bigint(20) unsigned NOT NULL,
40 `AVG_TIMER_MISC` bigint(20) unsigned NOT NULL,
41 `MAX_TIMER_MISC` bigint(20) unsigned NOT NULL
42 ) ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8
43 1 row in set (0.00 sec)
46 type file_summary_by_instance_row struct {
59 SUM_NUMBER_OF_BYTES_READ int
60 SUM_NUMBER_OF_BYTES_WRITE int
63 // represents a table or set of rows
64 type file_summary_by_instance_rows []file_summary_by_instance_row
66 // Return the name using the FILE_NAME attribute.
67 func (r *file_summary_by_instance_row) name() string {
71 // Return a formatted pretty name for the row.
72 func (r *file_summary_by_instance_row) pretty_name() string {
77 return fmt.Sprintf("%-30s", s)
80 func (r *file_summary_by_instance_row) headings() string {
81 return fmt.Sprintf("%-30s %10s %6s|%6s %6s %6s|%8s %8s|%8s %6s %6s %6s",
96 // generate a printable result
97 func (row *file_summary_by_instance_row) row_content(totals file_summary_by_instance_row) string {
100 // We assume that if COUNT_STAR = 0 then there's no data at all...
101 // when we have no data we really don't want to show the name either.
102 if row.COUNT_STAR == 0 {
105 name = row.pretty_name()
108 return fmt.Sprintf("%-30s %10s %6s|%6s %6s %6s|%8s %8s|%8s %6s %6s %6s",
110 lib.FormatTime(row.SUM_TIMER_WAIT),
111 lib.FormatPct(lib.MyDivide(row.SUM_TIMER_WAIT, totals.SUM_TIMER_WAIT)),
112 lib.FormatPct(lib.MyDivide(row.SUM_TIMER_READ, row.SUM_TIMER_WAIT)),
113 lib.FormatPct(lib.MyDivide(row.SUM_TIMER_WRITE, row.SUM_TIMER_WAIT)),
114 lib.FormatPct(lib.MyDivide(row.SUM_TIMER_MISC, row.SUM_TIMER_WAIT)),
115 lib.FormatAmount(row.SUM_NUMBER_OF_BYTES_READ),
116 lib.FormatAmount(row.SUM_NUMBER_OF_BYTES_WRITE),
117 lib.FormatAmount(row.COUNT_STAR),
118 lib.FormatPct(lib.MyDivide(row.COUNT_READ, row.COUNT_STAR)),
119 lib.FormatPct(lib.MyDivide(row.COUNT_WRITE, row.COUNT_STAR)),
120 lib.FormatPct(lib.MyDivide(row.COUNT_MISC, row.COUNT_STAR)))
123 func (this *file_summary_by_instance_row) add(other file_summary_by_instance_row) {
124 this.COUNT_STAR += other.COUNT_STAR
125 this.COUNT_READ += other.COUNT_READ
126 this.COUNT_WRITE += other.COUNT_WRITE
127 this.COUNT_MISC += other.COUNT_MISC
129 this.SUM_TIMER_WAIT += other.SUM_TIMER_WAIT
130 this.SUM_TIMER_READ += other.SUM_TIMER_READ
131 this.SUM_TIMER_WRITE += other.SUM_TIMER_WRITE
132 this.SUM_TIMER_MISC += other.SUM_TIMER_MISC
134 this.SUM_NUMBER_OF_BYTES_READ += other.SUM_NUMBER_OF_BYTES_READ
135 this.SUM_NUMBER_OF_BYTES_WRITE += other.SUM_NUMBER_OF_BYTES_WRITE
138 func (this *file_summary_by_instance_row) subtract(other file_summary_by_instance_row) {
139 this.COUNT_STAR -= other.COUNT_STAR
140 this.COUNT_READ -= other.COUNT_READ
141 this.COUNT_WRITE -= other.COUNT_WRITE
142 this.COUNT_MISC -= other.COUNT_MISC
144 this.SUM_TIMER_WAIT -= other.SUM_TIMER_WAIT
145 this.SUM_TIMER_READ -= other.SUM_TIMER_READ
146 this.SUM_TIMER_WRITE -= other.SUM_TIMER_WRITE
147 this.SUM_TIMER_MISC -= other.SUM_TIMER_MISC
149 this.SUM_NUMBER_OF_BYTES_READ -= other.SUM_NUMBER_OF_BYTES_READ
150 this.SUM_NUMBER_OF_BYTES_WRITE -= other.SUM_NUMBER_OF_BYTES_WRITE
153 // return the totals of a slice of rows
154 func (t file_summary_by_instance_rows) totals() file_summary_by_instance_row {
155 var totals file_summary_by_instance_row
156 totals.FILE_NAME = "TOTALS"
165 // clean up the given path reducing redundant stuff and return the clean path
166 func cleanup_path(path string) string {
167 // foo/../bar --> bar perl: $new =~ s{[^/]+/\.\./}{/};
168 // foo/./bar --> foo/bar perl: $new =~ s{/\./}{};
169 // // --> / perl: $new =~ s{//}{/};
171 double_slash_re = `//`
172 slash_dot_slash_re = `/\./`
173 slash_dot_dot_slash_re = `[^/]+/\.\./`
178 r := regexp.MustCompile(double_slash_re)
179 path = r.ReplaceAllString(path, "")
180 r = regexp.MustCompile(slash_dot_slash_re)
181 path = r.ReplaceAllString(path, "")
182 r = regexp.MustCompile(slash_dot_dot_slash_re)
183 path = r.ReplaceAllString(path, "")
184 if orig_path == path { // no change so give up
192 // From the original FILE_NAME we want to generate a simpler name to use.
193 // This simpler name may also merge several different filenames into one.
194 func (t file_summary_by_instance_row) simple_name(global_variables map[string]string) string {
196 auto_cnf_re = `/auto\.cnf$`
197 binlog_re = `/binlog\.(\d{6}|index)$`
198 charset_re = `/share/charsets/Index\.xml$`
199 db_opt_re = `/db\.opt$`
200 error_msg_re = `/share/[^/]+/errmsg\.sys$`
201 ibdata_re = `/ibdata\d+$`
202 redo_log_re = `/ib_logfile\d+$`
203 pid_file_re = `/[^/]+\.pid$`
204 // relay_log_re = `/mysql-relay-bin.(\d{6}|index)$`
205 relative_path_re = `^\.\./`
206 current_dir_re = `^\./`
207 slowlog_re = `/slowlog$`
208 table_file_re = `/([^/]+)/([^/]+)\.(frm|ibd|MYD|MYI|CSM|CSV|par)$`
209 temp_table_re = `#sql-[0-9_]+`
210 part_table_re = `(.+)#P#p\d+`
215 // this should probably be ordered from most expected regexp to least
216 re := regexp.MustCompile(table_file_re)
217 if m1 := re.FindStringSubmatch(path); m1 != nil {
218 // we may match temporary tables so check for them
219 re2 := regexp.MustCompile(temp_table_re)
220 if m2 := re2.FindStringSubmatch(m1[2]); m2 != nil {
221 return "<temp_table>"
224 // we may match partitioned tables so check for them
225 re3 := regexp.MustCompile(part_table_re)
226 if m3 := re3.FindStringSubmatch(m1[2]); m3 != nil {
227 return m1[1] + "." + m3[1] // <schema>.<table> (less partition info)
230 return m1[1] + "." + m1[2] // <schema>.<table>
232 if regexp.MustCompile(ibdata_re).MatchString(path) == true {
235 if regexp.MustCompile(redo_log_re).MatchString(path) == true {
238 if regexp.MustCompile(binlog_re).MatchString(path) == true {
241 if regexp.MustCompile(db_opt_re).MatchString(path) == true {
244 if regexp.MustCompile(slowlog_re).MatchString(path) == true {
247 if regexp.MustCompile(auto_cnf_re).MatchString(path) == true {
250 // relay logs are a bit complicated. If a full path then easy to
251 // identify,but if a relative path we may need to add $datadir,
252 // but also if as I do we have a ../blah/somewhere/path then we
253 // need to make it match too.
254 if len(global_variables["relay_log"]) > 0 {
255 relay_log := global_variables["relay_log"]
256 if relay_log[0] != '/' { // relative path
257 relay_log = cleanup_path(global_variables["datadir"] + relay_log) // datadir always ends in /
259 relay_log_re := relay_log + `\.(\d{6}|index)$`
260 if regexp.MustCompile(relay_log_re).MatchString(path) == true {
264 if regexp.MustCompile(pid_file_re).MatchString(path) == true {
267 if regexp.MustCompile(error_msg_re).MatchString(path) == true {
270 if regexp.MustCompile(charset_re).MatchString(path) == true {
276 // Convert the imported "table" to a merged one with merged data.
277 // Combine all entries with the same "FILE_NAME" by adding their values.
278 func merge_by_table_name(orig file_summary_by_instance_rows, global_variables map[string]string) file_summary_by_instance_rows {
279 t := make(file_summary_by_instance_rows, 0, len(orig))
281 m := make(map[string]file_summary_by_instance_row)
283 // iterate over source table
284 for i := range orig {
286 var new_row file_summary_by_instance_row
289 if orig_row.COUNT_STAR > 0 {
290 file_name = orig_row.simple_name(global_variables)
292 // check if we have an entry in the map
293 if _, found := m[file_name]; found {
294 new_row = m[file_name]
296 new_row.FILE_NAME = file_name
298 new_row.add(orig_row)
299 m[file_name] = new_row // update the map with the new value
303 // add the map contents back into the table
304 for _, row := range m {
311 // Select the raw data from the database into file_summary_by_instance_rows
312 // - filter out empty values
313 // - merge rows with the same name into a single row
314 // - change FILE_NAME into a more descriptive value.
315 func select_fsbi_rows(dbh *sql.DB) file_summary_by_instance_rows {
316 var t file_summary_by_instance_rows
318 sql := "SELECT FILE_NAME, COUNT_STAR, SUM_TIMER_WAIT, COUNT_READ, SUM_TIMER_READ, SUM_NUMBER_OF_BYTES_READ, COUNT_WRITE, SUM_TIMER_WRITE, SUM_NUMBER_OF_BYTES_WRITE, COUNT_MISC, SUM_TIMER_MISC FROM file_summary_by_instance"
320 rows, err := dbh.Query(sql)
327 var r file_summary_by_instance_row
329 if err := rows.Scan(&r.FILE_NAME, &r.COUNT_STAR, &r.SUM_TIMER_WAIT, &r.COUNT_READ, &r.SUM_TIMER_READ, &r.SUM_NUMBER_OF_BYTES_READ, &r.COUNT_WRITE, &r.SUM_TIMER_WRITE, &r.SUM_NUMBER_OF_BYTES_WRITE, &r.COUNT_MISC, &r.SUM_TIMER_MISC); err != nil {
334 if err := rows.Err(); err != nil {
341 // remove the initial values from those rows where there's a match
342 // - if we find a row we can't match ignore it
343 func (this *file_summary_by_instance_rows) subtract(initial file_summary_by_instance_rows) {
344 i_by_name := make(map[string]int)
346 // iterate over rows by name
347 for i := range initial {
348 i_by_name[initial[i].name()] = i
351 for i := range *this {
352 if _, ok := i_by_name[(*this)[i].name()]; ok {
353 initial_i := i_by_name[(*this)[i].name()]
354 (*this)[i].subtract(initial[initial_i])
359 func (t file_summary_by_instance_rows) Len() int { return len(t) }
360 func (t file_summary_by_instance_rows) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
361 func (t file_summary_by_instance_rows) Less(i, j int) bool {
362 return (t[i].SUM_TIMER_WAIT > t[j].SUM_TIMER_WAIT) ||
363 ((t[i].SUM_TIMER_WAIT == t[j].SUM_TIMER_WAIT) && (t[i].FILE_NAME < t[j].FILE_NAME))
366 func (t *file_summary_by_instance_rows) sort() {
370 // if the data in t2 is "newer", "has more values" than t then it needs refreshing.
371 // check this by comparing totals.
372 func (t file_summary_by_instance_rows) needs_refresh(t2 file_summary_by_instance_rows) bool {
373 my_totals := t.totals()
374 t2_totals := t2.totals()
376 return my_totals.SUM_TIMER_WAIT > t2_totals.SUM_TIMER_WAIT