├── .gitignore ├── LICENSE ├── README.md ├── calculate.go ├── header.go ├── main.go ├── myStatus.go ├── retrieve.go └── screen.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kenken0807 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # myStatusgo 2 | 3 | 4 | `myStatusgo` is a realtime monitoring tool for MySQL with status information,performance schema(MySQL5.6+) and etc..(terminal base) 5 | This tool can monitor multiple MySQL instances. 6 | 7 | `myStatusgo` collects metric and display them. 8 | 1. Normal.. Display QPS info from Com_xx of SHOW GLOBAL STATUS 9 | 1. OS Metric.. Display QPS info and OS metric. OS metric collect from node_expoter when the MySQL server installed node_expoter 10 | 1. Threads.. Display Running Threads from performance_schema.threads table. (MySQL5.6+) 11 | 1. P_S Info.. Display digest_text from performance_schema.statement_digest table (MySQL5.6+) 12 | 1. SlaveStatus.. Display Slave Info from SHOW SLAVE STATUS 13 | 1. Handler/InnoDB_Rows.. Display innodb_row_xx and Handler_xx from SHOW GLOBAL STATUS 14 | 1. InnoDB Lock Info.. Display InnoDB Row Lock Info from information_schema.innodb_trx and innodb_locks and innodb_lock_waits (MySQL5.6+) 15 | 1. InnoDB Buffer Info.. Display InnoDB Buffer Pool Info from information_schema.INNODB_METRICS (MySQL5.6+) 16 | 1. Table IO Statistic.. Display information of I/O request of each table and information on the number of SELECT/DML for each table from performance_schema.file_summary_by_instance and performance_schema.table_io_waits_summary_by_table (MySQL5.6+) 17 | 18 | ## Grants 19 | myStatusgo needs following permissions user 20 | 21 | ``` 22 | GRANT SELECT on performance_schema.* to 'user'@'host'; 23 | GRANT PROCESS,REPLICATION CLIENT on *.* to 'user'@'host'; 24 | ``` 25 | 26 | ## Install 27 | ``` 28 | go get -u github.com/kenken0807/myStatusgo 29 | ``` 30 | The binary will be builted and installed into $GOPATH/bin/. 31 | 32 | ## Quick Start 33 | ``` 34 | $ myStatusgo -hc Hostname:Port[:alias],Hostname:Port[:alias] -u User -p Password 35 | 36 | Example $ myStatusgo -hc 192.168.0.1:3306:Master,192.168.0.2:3306:Slave1 -u user1 -p userpass 37 | ``` 38 | ![oss_sample_qps](https://user-images.githubusercontent.com/13253434/60561472-93acc100-9d8e-11e9-843f-9dad4e564ca9.png) 39 | 40 | ## How to specify Hostname 41 | There are three ways to specify hosts. 42 | 43 | * -h option 44 | JSON format 45 | ``` 46 | $ myStatusgo -h '{"ServiceList":[{"serviceName":"Sample","host":["192.168.0.1:3306","192.168.0.2:3306"]}]}' 47 | ``` 48 | 49 | * -hc option 50 | Comma separated 51 | ``` 52 | $ myStatusgo -hc 192.168.0.1:3306,192.168.0.2:3306 53 | ``` 54 | 55 | * Specify a file as an argument 56 | ``` 57 | $ vim hostlist 58 | # Sample 59 | 192.168.0.1:3306 60 | 192.168.0.2:3306 61 | 62 | $ myStatusgo hostlist 63 | ``` 64 | 65 | ## Options 66 | ``` 67 | Usage of myStatusgo: 68 | -a Show All metrics per server 69 | -c int 70 | Seconds of Automatic Stopping 71 | -d int 72 | Delay between updates in seconds (default 1) 73 | -g Gtid Mode at 5:SlaveStatus 74 | -h string 75 | Hostlist written JSON 76 | -hc string 77 | Hostlist written Comma separated 78 | -j Write as JSON to file with option -o 79 | -m int 80 | Start Mode 1:Normal 2:OSResource 3:Threads 4:PerformaceSchema 5:SlaveStatus 6:Handler/InnoDB_Rows 7:InnoDBLockInfo 8.InnoDB Buffer Info 9.Table IO Statistic (default 1) 81 | -n int 82 | node_exporter port 83 | -o string 84 | Write the content to file and will set autostop 3600 secs 85 | -p string 86 | MySQL Password 87 | -t int 88 | The number of display per instance of Threads ,Performance Schema and Table IO Statistic (default 10) 89 | -u string 90 | MySQL Username 91 | -v Show Version 92 | ``` 93 | 94 | ## Other 95 | * -n *port* option 96 | Also display OS metrics info if MySQL server has node exporter 97 | 98 | ``` 99 | $ myStatusgo -h '{"ServiceList":[{"serviceName":"Sample","host":["192.168.0.1:3306:Master","192.168.0.2:3306:Slave1","192.168.0.3:3306:Slave2"]}]}' -u test -p test -n 9100 100 | ``` 101 | ![oss_sample qpsos](https://user-images.githubusercontent.com/13253434/60561503-b0e18f80-9d8e-11e9-8846-c3797a6fcbda.png) 102 | * -t *number* option(default 10) 103 | The number of display per instance of Threads ,Performance Schema and Table IO Statistic 104 | 105 | ![oss_psinfo](https://user-images.githubusercontent.com/13253434/60561522-c656b980-9d8e-11e9-9147-b2c4edee3351.png) 106 | 107 | * -a Option 108 | show all metrics which retrieved by myStatusgo per server 109 | allmetrics 110 | -------------------------------------------------------------------------------- /calculate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/dustin/go-humanize" 11 | ) 12 | 13 | func (val *HostAllInfo) calculate(screenFlg int, old *HostAllInfo) { 14 | // Calculate All Metrics 15 | metricCalc(val, old) 16 | 17 | // For JSON OutPut 18 | if OutputJSON { 19 | copyMap(val.Metric, val.MetricJSON) 20 | } 21 | } 22 | 23 | func metricCalc(val *HostAllInfo, old *HostAllInfo) { 24 | m.Lock() 25 | 26 | // Prometheus Metric Calc 27 | //CPU rate 28 | usr := val.PromeParam[CPUUSER] - old.PromeParam[CPUUSER] 29 | sys := val.PromeParam[CPUSYS] - old.PromeParam[CPUSYS] 30 | wait := val.PromeParam[CPUIO] - old.PromeParam[CPUIO] 31 | etc := val.PromeParam[CPUIRQ] - old.PromeParam[CPUIRQ] 32 | idle := val.PromeParam[CPUIDLE] - old.PromeParam[CPUIDLE] 33 | cpuTotal := usr + sys + wait + etc + idle 34 | var up, sp, wp, et int64 35 | val.MetricOS[CPUUSER], up = getPacentage(usr, cpuTotal) 36 | val.MetricOS[CPUSYS], sp = getPacentage(sys, cpuTotal) 37 | val.MetricOS[CPUIO], wp = getPacentage(wait, cpuTotal) 38 | val.MetricOS[CPUIRQ], et = getPacentage(etc, cpuTotal) 39 | val.MetricOS[CPUIDLE] = chkminus(100 - up - sp - wp - et) 40 | //DISK and NW and SWAP 41 | val.MetricOS[DISKREAD] = humanRead(uint64(val.PromeParam[DISKREAD]) - uint64(old.PromeParam[DISKREAD])) 42 | val.MetricOS[DISKWRITE] = humanRead(uint64(val.PromeParam[DISKWRITE]) - uint64(old.PromeParam[DISKWRITE])) 43 | val.MetricOS[INBOUND] = humanRead(uint64(val.PromeParam[INBOUND]) - uint64(old.PromeParam[INBOUND])) 44 | val.MetricOS[OUTBOUND] = humanRead(uint64(val.PromeParam[OUTBOUND]) - uint64(old.PromeParam[OUTBOUND])) 45 | val.MetricOS[MEMUSED] = humanRead(uint64(val.PromeParam[MEMTOL]) - uint64(val.PromeParam[MEMBUF]) - uint64(val.PromeParam[MEMCACHE]) - uint64(val.PromeParam[MEMFREE])) 46 | val.MetricOS[MEMBUF] = humanRead(uint64(val.PromeParam[MEMBUF])) 47 | val.MetricOS[MEMCACHE] = humanRead(uint64(val.PromeParam[MEMCACHE])) 48 | val.MetricOS[MEMFREE] = humanRead(uint64(val.PromeParam[MEMFREE])) 49 | val.MetricOS[MEMTOL] = humanRead(uint64(val.PromeParam[MEMTOL])) 50 | val.MetricOS[SWAPUSED] = humanRead(uint64(val.PromeParam[SWAPTOL] - val.PromeParam[SWAPFRE])) 51 | val.MetricOS[SWAPFRE] = humanRead(uint64(val.PromeParam[SWAPFRE])) 52 | val.MetricOS[LOADAVG1] = fmt.Sprintf("%.2f", val.PromeParam[LOADAVG1]) 53 | val.MetricOS[LOADAVG5] = fmt.Sprintf("%.2f", val.PromeParam[LOADAVG5]) 54 | val.MetricOS[LOADAVG15] = fmt.Sprintf("%.2f", val.PromeParam[LOADAVG15]) 55 | // MySQL Metric Calc 56 | //// status 57 | val.Metric[THCON] = val.MyStatu[THCON] 58 | val.Metric[THRUN] = val.MyStatu[THRUN] 59 | val.Metric[ABORTCON] = chkminus(changeStr2Int(val.MyStatu[ABORTCON]) - changeStr2Int(old.MyStatu[ABORTCON])) 60 | csel := changeStr2Int(val.MyStatu[CSEL]) - changeStr2Int(old.MyStatu[CSEL]) 61 | cupd := changeStr2Int(val.MyStatu[CUPD]) - changeStr2Int(old.MyStatu[CUPD]) + changeStr2Int(val.MyStatu[CUPDMUL]) - changeStr2Int(old.MyStatu[CUPDMUL]) 62 | cins := changeStr2Int(val.MyStatu[CINS]) - changeStr2Int(old.MyStatu[CINS]) + changeStr2Int(val.MyStatu[CINSSEL]) - changeStr2Int(old.MyStatu[CINSSEL]) 63 | cdel := changeStr2Int(val.MyStatu[CDEL]) - changeStr2Int(old.MyStatu[CDEL]) + changeStr2Int(val.MyStatu[CDELMUL]) - changeStr2Int(old.MyStatu[CDELMUL]) 64 | crlce := changeStr2Int(val.MyStatu[CRLCE]) - changeStr2Int(old.MyStatu[CRLCE]) + changeStr2Int(val.MyStatu[CRLCESEL]) - changeStr2Int(old.MyStatu[CRLCESEL]) 65 | cQcach := changeStr2Int(val.MyStatu[QCACHHIT]) - changeStr2Int(old.MyStatu[QCACHHIT]) 66 | cPrce := changeStr2Int(val.MyStatu[CCALPROCEDURE]) - changeStr2Int(old.MyStatu[CCALPROCEDURE]) 67 | cStmt := changeStr2Int(val.MyStatu[CSTMT]) - changeStr2Int(old.MyStatu[CSTMT]) 68 | cCommit := changeStr2Int(val.MyStatu[CCOMMIT]) - changeStr2Int(old.MyStatu[CCOMMIT]) 69 | cRollback := changeStr2Int(val.MyStatu[CROLLBACK]) - changeStr2Int(old.MyStatu[CROLLBACK]) 70 | val.Metric[CSEL] = chkminus(csel) 71 | val.Metric[CUPD] = chkminus(cupd) 72 | val.Metric[CINS] = chkminus(cins) 73 | val.Metric[CDEL] = chkminus(cdel) 74 | val.Metric[CRLCE] = chkminus(crlce) 75 | val.Metric[QCACHHIT] = chkminus(cQcach) 76 | val.Metric[CCALPROCEDURE] = chkminus(cPrce) 77 | val.Metric[CSTMT] = chkminus(cStmt) 78 | val.Metric[QPSALL] = chkminus(csel + cupd + cins + cdel + crlce + cQcach + cPrce) 79 | val.Metric[CCOMMIT] = chkminus(cCommit) 80 | val.Metric[CROLLBACK] = chkminus(cRollback) 81 | digestlost := changeStr2Int(val.MyStatu[PSDIGESTLOST]) - changeStr2Int(old.MyStatu[PSDIGESTLOST]) 82 | val.Metric[PSDIGESTLOST] = chkminus(digestlost) 83 | val.Metric[BUFWRITEREQ] = chkminus(changeStr2Int(val.MyStatu[BUFWRITEREQ]) - changeStr2Int(old.MyStatu[BUFWRITEREQ])) 84 | readRequests := changeStr2Int(val.MyStatu[BUFREADREQ]) - changeStr2Int(old.MyStatu[BUFREADREQ]) 85 | reads := changeStr2Int(val.MyStatu[BUFREAD]) - changeStr2Int(old.MyStatu[BUFREAD]) 86 | val.Metric[BUFREADREQ] = chkminus(readRequests) 87 | val.Metric[BUFREAD] = chkminus(reads) 88 | val.Metric[BUFFHITRATE] = calcHitRate(reads, readRequests) 89 | val.Metric[BUFCREATED] = chkminus(changeStr2Int(val.MyStatu[BUFCREATED]) - changeStr2Int(old.MyStatu[BUFCREATED])) 90 | val.Metric[BUFWRITTEN] = chkminus(changeStr2Int(val.MyStatu[BUFWRITTEN]) - changeStr2Int(old.MyStatu[BUFWRITTEN])) 91 | val.Metric[BUFFLUSH] = chkminus(changeStr2Int(val.MyStatu[BUFFLUSH]) - changeStr2Int(old.MyStatu[BUFFLUSH])) 92 | val.Metric[BUFDATAP] = val.MyStatu[BUFDATAP] 93 | val.Metric[BUFDIRTYP] = val.MyStatu[BUFDIRTYP] 94 | val.Metric[BUFFREEP] = val.MyStatu[BUFFREEP] 95 | val.Metric[BUFMISCP] = chkminus(changeStr2Int(val.MyStatu[BUFTOTALP]) - changeStr2Int(val.MyStatu[BUFDATAP]) - changeStr2Int(val.MyStatu[BUFFREEP])) 96 | 97 | var slow int64 98 | if old.MyStatu[SLWLOG] != "" { 99 | slow = changeStr2Int(val.MyStatu[SLWLOG]) - changeStr2Int(old.MyStatu[SLWLOG]) 100 | } else { 101 | slow = 0 102 | } 103 | val.Metric[SLWLOG] = chkminus(slow) 104 | val.slowSum = slow + old.slowSum 105 | val.Metric[SLWSUM] = chkminus(val.slowSum) 106 | val.Metric[HADRRDFIRST] = chkminus(changeStr2Int(val.MyStatu[HADRRDFIRST]) - changeStr2Int(old.MyStatu[HADRRDFIRST])) 107 | val.Metric[HADRRDKEY] = chkminus(changeStr2Int(val.MyStatu[HADRRDKEY]) - changeStr2Int(old.MyStatu[HADRRDKEY])) 108 | val.Metric[HADRRDLAST] = chkminus(changeStr2Int(val.MyStatu[HADRRDLAST]) - changeStr2Int(old.MyStatu[HADRRDLAST])) 109 | val.Metric[HADRRDNXT] = chkminus(changeStr2Int(val.MyStatu[HADRRDNXT]) - changeStr2Int(old.MyStatu[HADRRDNXT])) 110 | val.Metric[HADRRDPRV] = chkminus(changeStr2Int(val.MyStatu[HADRRDPRV]) - changeStr2Int(old.MyStatu[HADRRDPRV])) 111 | val.Metric[HADRRDRND] = chkminus(changeStr2Int(val.MyStatu[HADRRDRND]) - changeStr2Int(old.MyStatu[HADRRDRND])) 112 | val.Metric[HADRRDRNDNXT] = chkminus(changeStr2Int(val.MyStatu[HADRRDRNDNXT]) - changeStr2Int(old.MyStatu[HADRRDRNDNXT])) 113 | 114 | val.Metric[HADRDEL] = chkminus(changeStr2Int(val.MyStatu[HADRDEL]) - changeStr2Int(old.MyStatu[HADRDEL])) 115 | val.Metric[HADRUPD] = chkminus(changeStr2Int(val.MyStatu[HADRUPD]) - changeStr2Int(old.MyStatu[HADRUPD])) 116 | val.Metric[HADRWRT] = chkminus(changeStr2Int(val.MyStatu[HADRWRT]) - changeStr2Int(old.MyStatu[HADRWRT])) 117 | val.Metric[HADRPREP] = chkminus(changeStr2Int(val.MyStatu[HADRPREP]) - changeStr2Int(old.MyStatu[HADRPREP])) 118 | val.Metric[HADRCOMT] = chkminus(changeStr2Int(val.MyStatu[HADRCOMT]) - changeStr2Int(old.MyStatu[HADRCOMT])) 119 | val.Metric[HADRRB] = chkminus(changeStr2Int(val.MyStatu[HADRRB]) - changeStr2Int(old.MyStatu[HADRRB])) 120 | 121 | val.Metric[INNOROWDEL] = chkminus(changeStr2Int(val.MyStatu[INNOROWDEL]) - changeStr2Int(old.MyStatu[INNOROWDEL])) 122 | val.Metric[INNOROWRD] = chkminus(changeStr2Int(val.MyStatu[INNOROWRD]) - changeStr2Int(old.MyStatu[INNOROWRD])) 123 | val.Metric[INNOROWINS] = chkminus(changeStr2Int(val.MyStatu[INNOROWINS]) - changeStr2Int(old.MyStatu[INNOROWINS])) 124 | val.Metric[INNOROWUPD] = chkminus(changeStr2Int(val.MyStatu[INNOROWUPD]) - changeStr2Int(old.MyStatu[INNOROWUPD])) 125 | 126 | // performanceschemas 127 | val.Metric[SQLCNT] = val.MyPerfomanceSchema[SQLCNT] 128 | val.Metric[AVGLATENCY] = formatTime(val.MyPerfomanceSchema[AVGLATENCY]) 129 | val.Metric[MAXLATENCY] = formatTime(val.MyPerfomanceSchema[MAXLATENCY]) 130 | 131 | val.Metric[SECBEHMAS] = val.MySlave[SECBEHMAS] 132 | val.Metric[RDONLY] = val.MyVariable[RDONLY] 133 | val.Metric[RPLSEMIMAS] = val.MyVariable[RPLSEMIMAS] 134 | val.Metric[RPLSEMISLV] = val.MyVariable[RPLSEMISLV] 135 | 136 | //InnoDB Metric 137 | val.Metric[BUFPOOLTOTAL] = humanRead(uint64(val.MyInnoDBBuffer[BUFPOOLTOTAL])) 138 | val.Metric[BUFPOOLDATA] = humanRead(uint64(val.MyInnoDBBuffer[BUFPOOLDATA])) 139 | val.Metric[BUFPOOLDIRTY] = humanRead(uint64(val.MyInnoDBBuffer[BUFPOOLDIRTY])) 140 | val.Metric[BUFPOOLFREE] = humanRead(uint64(val.MyInnoDBBuffer[BUFPOOLFREE])) 141 | if val.MyInnoDBBuffer[LSNMINUSCKPOINT] == -1 { 142 | val.Metric[LSNMINUSCKPOINT] = "-1" 143 | } else { 144 | val.Metric[LSNMINUSCKPOINT] = humanRead(uint64(val.MyInnoDBBuffer[LSNMINUSCKPOINT])) 145 | } 146 | 147 | m.Unlock() 148 | return 149 | 150 | } 151 | 152 | func calcFileIODelta(new *HostAllInfo, old *HostAllInfo) List { 153 | ss := List{} 154 | for tableNm, vStrFileIOperTable := range new.strFileIOperTable { 155 | if old.strFileIOperTable[tableNm] == nil { 156 | old.strFileIOperTable[tableNm] = &FileIOperTable{TableSchema: vStrFileIOperTable.TableSchema, TableName: vStrFileIOperTable.TableName} 157 | } else { 158 | switch SortTableFileIOPosition { 159 | case SORTNUM1: 160 | e := Entry{tableNm, vStrFileIOperTable.CountStar - old.strFileIOperTable[tableNm].CountStar} 161 | ss = append(ss, e) 162 | case SORTNUM2: 163 | e := Entry{tableNm, vStrFileIOperTable.FetchRows - old.strFileIOperTable[tableNm].FetchRows} 164 | ss = append(ss, e) 165 | case SORTNUM3: 166 | e := Entry{tableNm, vStrFileIOperTable.InsertRows - old.strFileIOperTable[tableNm].InsertRows} 167 | ss = append(ss, e) 168 | case SORTNUM4: 169 | e := Entry{tableNm, vStrFileIOperTable.UpdateRows - old.strFileIOperTable[tableNm].UpdateRows} 170 | ss = append(ss, e) 171 | case SORTNUM5: 172 | e := Entry{tableNm, vStrFileIOperTable.DeleteRows - old.strFileIOperTable[tableNm].DeleteRows} 173 | ss = append(ss, e) 174 | case SORTNUM6: 175 | e := Entry{tableNm, vStrFileIOperTable.ReadIOByte - old.strFileIOperTable[tableNm].ReadIOByte} 176 | ss = append(ss, e) 177 | case SORTNUM7: 178 | e := Entry{tableNm, vStrFileIOperTable.WriteIOByte - old.strFileIOperTable[tableNm].WriteIOByte} 179 | ss = append(ss, e) 180 | } 181 | } 182 | } 183 | return ss 184 | } 185 | 186 | func sortFileIO(new *HostAllInfo, old *HostAllInfo) { 187 | //Digest 188 | ss := calcFileIODelta(new, old) 189 | sort.Sort(ss) 190 | 191 | //new.FileIOMetric = make([]FileIOperTable, TopN) 192 | for idx, kv := range ss { 193 | if idx >= TopN { 194 | break 195 | } 196 | contStar := new.strFileIOperTable[kv.sKey].CountStar - old.strFileIOperTable[kv.sKey].CountStar 197 | if contStar == 0 { 198 | continue 199 | } 200 | m.Lock() 201 | new.FileIOMetric[idx] = FileIOperTable{ 202 | TableSchema: new.strFileIOperTable[kv.sKey].TableSchema, 203 | TableName: new.strFileIOperTable[kv.sKey].TableName, 204 | CountStar: contStar, 205 | TotalLatency: new.strFileIOperTable[kv.sKey].TotalLatency - old.strFileIOperTable[kv.sKey].TotalLatency, 206 | FetchRows: new.strFileIOperTable[kv.sKey].FetchRows - old.strFileIOperTable[kv.sKey].FetchRows, 207 | FetchLatency: new.strFileIOperTable[kv.sKey].FetchLatency - old.strFileIOperTable[kv.sKey].FetchLatency, 208 | InsertRows: new.strFileIOperTable[kv.sKey].InsertRows - old.strFileIOperTable[kv.sKey].InsertRows, 209 | InsertLatency: new.strFileIOperTable[kv.sKey].InsertLatency - old.strFileIOperTable[kv.sKey].InsertLatency, 210 | UpdateRows: new.strFileIOperTable[kv.sKey].UpdateRows - old.strFileIOperTable[kv.sKey].UpdateRows, 211 | UpdateLatency: new.strFileIOperTable[kv.sKey].UpdateLatency - old.strFileIOperTable[kv.sKey].UpdateLatency, 212 | DeleteRows: new.strFileIOperTable[kv.sKey].DeleteRows - old.strFileIOperTable[kv.sKey].DeleteRows, 213 | DeleteLatency: new.strFileIOperTable[kv.sKey].DeleteLatency - old.strFileIOperTable[kv.sKey].DeleteLatency, 214 | ReadIORequest: new.strFileIOperTable[kv.sKey].ReadIORequest - old.strFileIOperTable[kv.sKey].ReadIORequest, 215 | ReadIOByte: new.strFileIOperTable[kv.sKey].ReadIOByte - old.strFileIOperTable[kv.sKey].ReadIOByte, 216 | ReadIOLatency: new.strFileIOperTable[kv.sKey].ReadIOLatency - old.strFileIOperTable[kv.sKey].ReadIOLatency, 217 | WriteIORequest: new.strFileIOperTable[kv.sKey].WriteIORequest - old.strFileIOperTable[kv.sKey].WriteIORequest, 218 | WriteIOByte: new.strFileIOperTable[kv.sKey].WriteIOByte - old.strFileIOperTable[kv.sKey].WriteIOByte, 219 | WriteIOLatency: new.strFileIOperTable[kv.sKey].WriteIOLatency - old.strFileIOperTable[kv.sKey].WriteIOLatency, 220 | MiscIORequest: new.strFileIOperTable[kv.sKey].MiscIORequest - old.strFileIOperTable[kv.sKey].MiscIORequest, 221 | MiscIOLatency: new.strFileIOperTable[kv.sKey].MiscIOLatency - old.strFileIOperTable[kv.sKey].MiscIOLatency, 222 | } 223 | m.Unlock() 224 | } 225 | } 226 | 227 | func (val *HostAllInfo) calcDigestDelta(base *HostAllInfo) (mapDigest, uint64) { 228 | var digestAllSQLCnt uint64 229 | digestAllSQLCnt = 0 230 | digestDelta := make(mapDigest, 0) 231 | for key, value := range val.Digest { 232 | digestDelta[key] = value - base.DigestBase[key] 233 | digestAllSQLCnt += digestDelta[key] 234 | base.DigestBase[key] = value 235 | } 236 | return digestDelta, digestAllSQLCnt 237 | } 238 | 239 | func (val *HostAllInfo) sortDigest(base *HostAllInfo) string { 240 | digestDelta, digestAllSQLCnt := val.calcDigestDelta(base) 241 | ss := List{} 242 | for schemaDigest, execnt := range digestDelta { 243 | e := Entry{schemaDigest, execnt} 244 | ss = append(ss, e) 245 | } 246 | sort.Sort(ss) 247 | topDigestQuery := "" 248 | len := len(ss) 249 | m.Lock() 250 | val.DigestALLSqlCnt = fmt.Sprintf("%d", digestAllSQLCnt) 251 | for idx, kv := range ss { 252 | val.DigestMetric[idx] = DigestMetric{Digest: kv.sKey, CntStar: kv.sCnt} 253 | if idx+1 < TopN && idx+1 < len { 254 | topDigestQuery += fmt.Sprintf("'%s',", kv.sKey) 255 | } else { 256 | topDigestQuery += fmt.Sprintf("'%s'", kv.sKey) 257 | break 258 | } 259 | } 260 | m.Unlock() 261 | return topDigestQuery 262 | } 263 | 264 | func (l List) Len() int { 265 | return len(l) 266 | } 267 | 268 | func (l List) Swap(i, j int) { 269 | l[i], l[j] = l[j], l[i] 270 | } 271 | 272 | func (l List) Less(i, j int) bool { 273 | if l[i].sCnt == l[j].sCnt { 274 | return (l[i].sKey > l[j].sKey) 275 | } 276 | return (l[i].sCnt > l[j].sCnt) 277 | } 278 | 279 | func chkminus(i int64) string { 280 | if i < 0 { 281 | return "0" 282 | } 283 | return fmt.Sprintf("%d", i) 284 | } 285 | 286 | func changeStr2Int(v string) int64 { 287 | f, _ := strconv.ParseFloat(v, 64) 288 | i := int64(f) 289 | return i 290 | } 291 | 292 | func getPacentage(rate float64, total float64) (string, int64) { 293 | if rate == 0 && total == 0 { 294 | return "0", 0 295 | } 296 | return fmt.Sprintf("%.0f", rate/total*100), changeStr2Int(fmt.Sprintf("%.0f", rate/total*100)) 297 | } 298 | 299 | func humanRead(v uint64) string { 300 | v1 := humanize.Bytes(v) 301 | v2 := strings.Replace(v1, " ", "", 1) 302 | v3 := strings.Replace(v2, "B", "", 1) 303 | return v3 304 | } 305 | 306 | func calcHitRate(reads int64, readRequests int64) string { 307 | if readRequests == 0 { 308 | return "100.0" 309 | } 310 | rate := fmt.Sprintf("%.2f", ((1.0 - (float64(reads) / float64(readRequests))) * 100.0)) 311 | if rate == "100.00" { 312 | return "100.0" 313 | } 314 | return rate 315 | } 316 | func formatTime(v string) string { 317 | f, _ := strconv.ParseFloat(v, 64) 318 | var out string 319 | switch { 320 | case f >= 604800000000000000: 321 | out = fmt.Sprintf("%.1fw", math.Trunc(f/604800000000000000)) 322 | case f >= 86400000000000000: 323 | out = fmt.Sprintf("%.1fd", math.Trunc(f/86400000000000000)) 324 | case f >= 3600000000000000: 325 | out = fmt.Sprintf("%.1fh", math.Trunc(f/3600000000000000)) 326 | case f >= 60000000000000: 327 | out = fmt.Sprintf("%.1fm", math.Trunc(f/60000000000000)) 328 | case f >= 1000000000000: 329 | out = fmt.Sprintf("%.1fs", math.Trunc(f/1000000000000)) 330 | case f >= 1000000000: 331 | out = fmt.Sprintf("%.1fms", math.Trunc(f/1000000000)) 332 | case f >= 1000000: 333 | out = fmt.Sprintf("%.1fus", math.Trunc(f/1000000)) 334 | case f >= 1000: 335 | out = fmt.Sprintf("%.1fns", math.Trunc(f/1000000)) 336 | default: 337 | out = fmt.Sprintf("%.1fns", f) 338 | } 339 | return out 340 | } 341 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // VERSION no. 10 | const VERSION = "1.1" 11 | 12 | // StartTime .. 13 | var StartTime time.Time 14 | 15 | // OutputJSON is whether JSON output or not 16 | var OutputJSON bool 17 | 18 | // TopN ... are Some Counters 19 | var TopN int 20 | 21 | // TopNPosition .. 22 | var TopNPosition int 23 | 24 | // MaxTopNPosition .. 25 | var MaxTopNPosition int 26 | 27 | // SortTableFileIOPosition .. 28 | var SortTableFileIOPosition int 29 | 30 | // HostnameSize .. 31 | var HostnameSize int 32 | 33 | // HostFormatSpace .. 34 | var HostFormatSpace string 35 | 36 | // GtidMode .. 37 | var GtidMode bool 38 | 39 | // FullSQLStatement 40 | var FullSQLStatement bool 41 | 42 | // Global Mutex 43 | var ( 44 | m = sync.Mutex{} 45 | ) 46 | 47 | var clear map[string]func() 48 | 49 | var ( 50 | Error string 51 | TopFormat string 52 | FinishFormat string 53 | OSFormat1 string 54 | OSFormat2 string 55 | OSFormat3 string 56 | MyFormat1 string 57 | MyFormat2 string 58 | MyFormat3 string 59 | MySlaFormat1 string 60 | MySlaFormat2 string 61 | MySlaFormat3 string 62 | MySlaFormatGTID1 string 63 | MySlaFormatGTID2 string 64 | MySlaFormatGTID3 string 65 | MyHadrFormat1 string 66 | MyHadrFormat2 string 67 | MyHadrFormat3 string 68 | MyHadrAllFormat1 string 69 | MyHadrAllFormat2 string 70 | MyHadrAllFormat3 string 71 | KeyArrowUpDownFormat string 72 | PerfScmFormat1 string 73 | PerfScmFormat2 string 74 | PerfScmFormat3 string 75 | Cursol1 string 76 | Cursol2 string 77 | Cursol3 string 78 | DigestFormat1 string 79 | DigestFormat2 string 80 | DigestFormat3 string 81 | ThreadFormat1 string 82 | ThreadFormat2 string 83 | ThreadFormat3 string 84 | InnoLockFormat1 string 85 | InnoLockFormat2 string 86 | InnoLockFormat3 string 87 | InnoBufFormat1 string 88 | InnoBufFormat2 string 89 | InnoBufFormat3 string 90 | FileIOTabFormat1 string 91 | FileIOTabFormat3 string 92 | FileIOTabFormat4 string 93 | HostNamePort string 94 | HostNamePortTag string 95 | HostFormatSize int 96 | PerfFormatSize int 97 | AllShowMetricFlg bool 98 | ) 99 | 100 | const ( 101 | // TIMESTAMP .. 102 | TIMESTAMP = "timestamp" 103 | // OS Resource 104 | SWAPTOL = "node_memory_SwapTotal" 105 | SWAPFRE = "node_memory_SwapFree" 106 | SWAPUSED = "node_memory_SwapUsed" 107 | INBOUND = "node_network_receive_bytes" 108 | OUTBOUND = "node_network_transmit_bytes" 109 | CPUUSER = "node_cpu_user" 110 | CPUSYS = "node_cpu_system" 111 | CPUIO = "node_cpu_iowait" 112 | CPUIDLE = "node_cpu_idle" 113 | CPUIRQ = "node_cpu_irq" 114 | /* 115 | CPUIRQ is... 116 | node_cpu{cpu="cpu1",mode="irq"} 0 117 | node_cpu{cpu="cpu1",mode="softirq"} 66.51 118 | */ 119 | DISKREAD = "node_disk_bytes_read" 120 | DISKWRITE = "node_disk_bytes_written" 121 | MEMTOL = "node_memory_MemTotal" 122 | MEMFREE = "node_memory_MemFree" 123 | MEMBUF = "node_memory_Buffers" 124 | MEMCACHE = "node_memory_Cached" 125 | MEMUSED = "node_memory_used" 126 | LOADAVG1 = "node_load1" 127 | LOADAVG5 = "node_load5" 128 | LOADAVG15 = "node_load15" 129 | // SHOW SLAVE STATUS 130 | MHOST = "Master_Host" 131 | SLAVEIO = "Slave_IO_Running" 132 | SLAVESQL = "Slave_SQL_Running" 133 | MASLOGFILE = "Master_Log_File" 134 | RMASLOGPOS = "Read_Master_Log_Pos" 135 | RELMASLOGFILE = "Relay_Master_Log_File" 136 | EXEMASLOGPOS = "Exec_Master_Log_Pos" 137 | SECBEHMAS = "Seconds_Behind_Master" 138 | CHNLNM = "Channel_Name" 139 | RETVGTID = "Retrieved_Gtid_Set" 140 | EXEGTID = "Executed_Gtid_Set" 141 | AUTOPOS = "Auto_Position" 142 | 143 | // SHOW GLOBAL STATUS 144 | THCON = "Threads_connected" 145 | THRUN = "Threads_running" 146 | ABORTCON = "Aborted_connects" 147 | SLWLOG = "Slow_queries" 148 | SLWSUM = "Slowlog_sum" 149 | CSEL = "Com_select" 150 | CUPDMUL = "Com_update_multi" 151 | CUPD = "Com_update" 152 | CINSSEL = "Com_insert_select" 153 | CINS = "Com_insert" 154 | CDELMUL = "Com_delete_multi" 155 | CDEL = "Com_delete" 156 | CRLCESEL = "Com_replace_select" 157 | CRLCE = "Com_replace" 158 | QCACHHIT = "Qcache_hits" 159 | CCALPROCEDURE = "Com_call_procedure" 160 | CSTMT = "Com_stmt_execute" 161 | CCOMMIT = "Com_commit" 162 | CROLLBACK = "Com_rollback" 163 | QPSALL = "QPSALL" 164 | HADRRDFIRST = "Handler_read_first" 165 | HADRRDKEY = "Handler_read_key" 166 | HADRRDLAST = "Handler_read_last" 167 | HADRRDNXT = "Handler_read_next" 168 | HADRRDPRV = "Handler_read_prev" 169 | HADRRDRND = "Handler_read_rnd" 170 | HADRRDRNDNXT = "Handler_read_rnd_next" 171 | HADRDEL = "Handler_delete" 172 | HADRUPD = "Handler_update" 173 | HADRWRT = "Handler_write" 174 | HADRPREP = "Handler_prepare" 175 | HADRCOMT = "Handler_commit" 176 | HADRRB = "Handler_rollback" 177 | INNOROWDEL = "Innodb_rows_deleted" 178 | INNOROWRD = "Innodb_rows_read" 179 | INNOROWINS = "Innodb_rows_inserted" 180 | INNOROWUPD = "Innodb_rows_updated" 181 | PSDIGESTLOST = "Performance_schema_digest_lost" 182 | BUFWRITEREQ = "Innodb_buffer_pool_write_requests" 183 | BUFREADREQ = "Innodb_buffer_pool_read_requests" 184 | BUFREAD = "Innodb_buffer_pool_reads" 185 | BUFCREATED = "Innodb_pages_created" 186 | BUFWRITTEN = "Innodb_pages_written" 187 | BUFFLUSH = "Innodb_buffer_pool_pages_flushed" 188 | BUFDATAP = "Innodb_buffer_pool_pages_data" 189 | BUFDIRTYP = "Innodb_buffer_pool_pages_dirty" 190 | BUFFREEP = "Innodb_buffer_pool_pages_free" 191 | BUFMISCP = "Innodb_buffer_pool_pages_misc" 192 | BUFTOTALP = "Innodb_buffer_pool_pages_total" 193 | 194 | // InnoDB Metric 195 | BUFPOOLTOTAL = "buffer_pool_pages_total" 196 | BUFPOOLDATA = "buffer_pool_bytes_data" 197 | BUFPOOLDIRTY = "buffer_pool_bytes_dirty" 198 | BUFPOOLFREE = "buffer_pool_pages_free" 199 | LSNMINUSCKPOINT = "log_lsn_checkpoint_age" 200 | //SHOW GLOBAL VARIABLES 201 | RDONLY = "read_only" 202 | RPLSEMIMAS = "rpl_semi_sync_master_enabled" 203 | RPLSEMISLV = "rpl_semi_sync_slave_enabled" 204 | // Other 205 | PSDIGESTALLSQL = "Performance_schema_digest_allquery" 206 | SQLCNT = "Performance_schema.sqlcnt" 207 | AVGLATENCY = "Performance_schema.avglatency" 208 | MAXLATENCY = "Performance_schema.maxlatency" 209 | BUFFHITRATE = "Bufferpool_hitrate" 210 | ShowStatusSQL = "SHOW GLOBAL STATUS" 211 | ShowSlaveStatusSQL = "SHOW SLAVE STATUS" 212 | 213 | ShowVariableSQL = `SHOW VARIABLES WHERE Variable_name='read_only' or Variable_name='rpl_semi_sync_slave_enabled' or Variable_name='rpl_semi_sync_master_enabled'` 214 | DigestFirstSQL = `/*!50601 SELECT 'myStatusgo.DigestTextSQL' as mystatusgoquery, 215 | concat(schema_name,'|',DIGEST) schema_digest, COUNT_STAR FROM performance_schema.events_statements_summary_by_digest 216 | WHERE SCHEMA_NAME not in ('mysql','sys','information_schema','performance_schema') 217 | AND DIGEST_TEXT not in ('BEGIN') 218 | AND DIGEST_TEXT not like 'SET%' 219 | AND DIGEST_TEXT not like 'USE%' 220 | AND DIGEST_TEXT not like 'SHOW%' 221 | ORDER BY LAST_SEEN DESC,COUNT_STAR DESC,DIGEST DESC LIMIT 5000 */` 222 | DigestRepeatSQL = `/*!50601 SELECT 'myStatusgo.DigestTextSQL' as mystatusgoquery, 223 | concat(schema_name,'|',DIGEST) schema_digest, 224 | COUNT_STAR 225 | FROM performance_schema.events_statements_summary_by_digest 226 | WHERE SCHEMA_NAME not in ('mysql','sys','information_schema','performance_schema') 227 | AND DIGEST_TEXT not in ('BEGIN') 228 | AND DIGEST_TEXT not like 'SET%' 229 | AND DIGEST_TEXT not like 'USE%' 230 | AND DIGEST_TEXT not like 'SHOW%' 231 | AND LAST_SEEN > now() - 1 */` 232 | DigestTextSQL = `/*!50601 SELECT 'myStatusgo.DigestTextSQL' as mystatusgoquery, 233 | DIGEST_TEXT, 234 | concat(schema_name,'|',DIGEST) schema_digest, 235 | DIGEST, 236 | SCHEMA_NAME, 237 | IF(((SUM_NO_GOOD_INDEX_USED > 0) OR (SUM_NO_INDEX_USED > 0)),'Yes','No') AS FULL_SCAN, 238 | AVG_TIMER_WAIT AVG_LATENCY, 239 | ROUND(IFNULL((SUM_ROWS_EXAMINED / NULLIF(COUNT_STAR,0)),0),0) AS rows_examined_avg 240 | FROM performance_schema.events_statements_summary_by_digest 241 | WHERE concat(schema_name,'|',DIGEST) in (%s) ORDER BY FIELD(concat(schema_name,'|',DIGEST), %s) */` 242 | PerfSchemaSQL = `/*!50601 SELECT 'myStatusgo.PerfSchemaSQL' as mystatusgoquery,IFNULL(count(*), 0) SQLCNT, IFNULL(avg(TIMER_END-TIMER_START),0) AVGLATENCY, IFNULL(max(TIMER_END-TIMER_START),0) MAXLATENCY 243 | FROM performance_schema.threads t1 244 | INNER JOIN performance_schema.events_statements_current t2 245 | ON t2.thread_id = t1.thread_id WHERE t1.PROCESSLIST_COMMAND != 'Sleep' AND EVENT_NAME not in ('statement/com/Binlog Dump','statement/com/Binlog Dump GTID')*/` 246 | ThreadsSQL = `/*!50601 SELECT 'myStatusgo.ThreadsSQL' as mystatusgoquery, 247 | CASE NAME WHEN 'thread/sql/slave_sql' THEN 'slave' ELSE PROCESSLIST_USER END as PROCESSLIST_USER, 248 | PROCESSLIST_HOST , 249 | ifnull(PROCESSLIST_DB,'NULL') , 250 | PROCESSLIST_TIME , 251 | PROCESSLIST_COMMAND , 252 | PROCESSLIST_INFO , 253 | PROCESSLIST_STATE 254 | FROM performance_schema.threads 255 | WHERE TYPE='FOREGROUND' 256 | AND NAME in ('thread/sql/one_connection','thread/thread_pool/tp_one_connection','thread/sql/slave_sql') 257 | AND ifnull(PROCESSLIST_DB,'none') not in ('mysql') 258 | AND PROCESSLIST_COMMAND not in('Sleep','Binlog Dump') 259 | AND PROCESSLIST_ID != @@pseudo_thread_id 260 | AND (PROCESSLIST_INFO not like '%%myStatusgo%%' 261 | OR PROCESSLIST_TIME > 0) 262 | ORDER BY PROCESSLIST_TIME DESC LIMIT %d */` 263 | InnodbLockSQL = `/*!50601 SELECT 'myStatusgo.InnodbLockSQL' as mystatusgoquery, 264 | SUBSTRING(GROUP_CONCAT(waiting_pid),1,50) waitPidlist, 265 | count(waiting_pid) waitCnt , 266 | blocking_pid blockPid, 267 | MAX(wait_age_secs) waitAge, 268 | max(locked_table) lockedTable, 269 | max(locked_index) lockedIndex, 270 | max(blocking_query) blockingQuery 271 | FROM 272 | (SELECT 273 | TIMESTAMPDIFF(SECOND,r.trx_wait_started, NOW()) AS wait_age_secs, 274 | rl.lock_table AS locked_table, 275 | rl.lock_index AS locked_index, 276 | r.trx_mysql_thread_id AS waiting_pid, 277 | r.trx_query AS waiting_query, 278 | b.trx_mysql_thread_id AS blocking_pid, 279 | b.trx_query AS blocking_query 280 | FROM 281 | (((( information_schema.innodb_lock_waits w 282 | JOIN information_schema.innodb_trx b ON ((b.trx_id = w.blocking_trx_id))) 283 | JOIN information_schema.innodb_trx r ON ((r.trx_id = w.requesting_trx_id))) 284 | JOIN information_schema.innodb_locks bl ON ((bl.lock_id = w.blocking_lock_id))) 285 | JOIN information_schema.innodb_locks rl ON ((rl.lock_id = w.requested_lock_id))) 286 | ) a 287 | LEFT JOIN (SELECT DISTINCT waiting_pid AS wpid FROM (SELECT 288 | TIMESTAMPDIFF(SECOND,r.trx_wait_started, NOW()) AS wait_age_secs, 289 | rl.lock_table AS locked_table, 290 | rl.lock_index AS locked_index, 291 | r.trx_mysql_thread_id AS waiting_pid, 292 | r.trx_query AS waiting_query, 293 | b.trx_mysql_thread_id AS blocking_pid, 294 | b.trx_query AS blocking_query 295 | FROM 296 | (((( information_schema.innodb_lock_waits w 297 | JOIN information_schema.innodb_trx b ON ((b.trx_id = w.blocking_trx_id))) 298 | JOIN information_schema.innodb_trx r ON ((r.trx_id = w.requesting_trx_id))) 299 | JOIN information_schema.innodb_locks bl ON ((bl.lock_id = w.blocking_lock_id))) 300 | JOIN information_schema.innodb_locks rl ON ((rl.lock_id = w.requested_lock_id))) 301 | ) a) b 302 | ON a.blocking_pid = b.wpid WHERE wpid IS NULL 303 | GROUP BY blocking_pid ORDER BY waitCnt desc */` 304 | InnodbBufferPoolSQL = `/*!50601 SELECT 'myStatusgo.InnodbBufferPoolSQL' as mystatusgoquery, 305 | NAME, 306 | CASE NAME 307 | WHEN 'buffer_pool_pages_total' THEN COUNT*@@innodb_page_size 308 | WHEN 'buffer_pool_pages_free' THEN COUNT*@@innodb_page_size 309 | ELSE COUNT END as COUNT, 310 | STATUS 311 | from information_schema.INNODB_METRICS 312 | WHERE NAME in ('buffer_pool_pages_total','buffer_pool_bytes_dirty','buffer_pool_bytes_data','buffer_pool_pages_free', 'log_lsn_checkpoint_age') */` 313 | 314 | TableFileIOSQL = `/*!50601 SELECT 'myStatusgo.TableFileIOSQL' as mystatusgoquery , 315 | fsbi.table_schema AS tableSchema, 316 | fsbi.table_name AS tableName, 317 | ifnull(pst.COUNT_STAR,0) + ifnull(fsbi.cntstar,0) as countStar, 318 | ifnull(pst.SUM_TIMER_WAIT,0) AS totalLatency, 319 | ifnull(pst.COUNT_FETCH,0) AS fetchRows, 320 | ifnull(pst.SUM_TIMER_FETCH,0) AS fetchLatency, 321 | ifnull(pst.COUNT_INSERT,0) AS insertRows, 322 | ifnull(pst.SUM_TIMER_INSERT,0) AS insertLatency, 323 | ifnull(pst.COUNT_UPDATE,0) AS updateRows, 324 | ifnull(pst.SUM_TIMER_UPDATE,0) AS updateLatency, 325 | ifnull(pst.COUNT_DELETE,0) AS deleteRows, 326 | ifnull(pst.SUM_TIMER_DELETE,0) AS deleteLatency, 327 | fsbi.count_read AS readIORequest, 328 | fsbi.sum_number_of_bytes_read AS readIOByte, 329 | fsbi.sum_timer_read AS readIOLatency, 330 | fsbi.count_write AS writeIORequest, 331 | fsbi.sum_number_of_bytes_write AS writeIOByte, 332 | fsbi.sum_timer_write AS writeIOLatency, 333 | fsbi.count_misc AS miscIORequest, 334 | fsbi.sum_timer_misc AS miscIOLatency 335 | from 336 | ( 337 | select 338 | LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REPLACE(fsbi.FILE_NAME, '\\', '/'), '/', -2), '/', 1), 64) AS table_schema, 339 | -- LEFT(SUBSTRING_INDEX(REPLACE(SUBSTRING_INDEX(REPLACE(fsbi.FILE_NAME, '\\', '/'), '/', -1), '@0024', '$'), '.', 1), 64) AS table_name, 340 | LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REPLACE(SUBSTRING_INDEX(REPLACE(fsbi.FILE_NAME, '\\', '/'), '/', -1), '@0024', '$'), '.', 1),'#',1), 64) AS table_name, 341 | sum(fsbi.COUNT_STAR) as cntstar, 342 | sum(fsbi.COUNT_READ) AS count_read, 343 | sum(fsbi.SUM_NUMBER_OF_BYTES_READ) AS sum_number_of_bytes_read, 344 | sum(fsbi.SUM_TIMER_READ) AS sum_timer_read, 345 | sum(fsbi.COUNT_WRITE) AS count_write, 346 | sum(fsbi.SUM_NUMBER_OF_BYTES_WRITE) AS sum_number_of_bytes_write, 347 | sum(fsbi.SUM_TIMER_WRITE) AS sum_timer_write, 348 | sum(fsbi.COUNT_MISC) AS count_misc, 349 | sum(fsbi.SUM_TIMER_MISC) AS sum_timer_misc 350 | from 351 | performance_schema.file_summary_by_instance fsbi 352 | WHERE COUNT_STAR > 0 353 | and LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(REPLACE(fsbi.FILE_NAME, '\\', '/'), '/', -2), '/', 1), 64) not in ('performance_schema','sys','mysql') 354 | group by 355 | table_schema, 356 | table_name 357 | ORDER BY NULL 358 | ) fsbi 359 | LEFT JOIN 360 | (SELECT OBJECT_SCHEMA,OBJECT_NAME,COUNT_STAR,SUM_TIMER_WAIT,COUNT_FETCH,SUM_TIMER_FETCH,COUNT_INSERT,SUM_TIMER_INSERT,COUNT_UPDATE,SUM_TIMER_UPDATE,COUNT_DELETE,SUM_TIMER_DELETE 361 | FROM performance_schema.table_io_waits_summary_by_table pst WHERE pst.OBJECT_SCHEMA not in ('performance_schema','sys','mysql','PERCONA_SCHEMA') LIMIT 10000) pst 362 | ON 363 | pst.OBJECT_SCHEMA = fsbi.table_schema 364 | and pst.OBJECT_NAME = fsbi.table_name 365 | */` 366 | 367 | SLOWCHECKSQL = `/*!50601 SELECT count(1) FROM performance_schema.threads 368 | WHERE PROCESSLIST_COMMAND='QUERY' and PROCESSLIST_TIME > 0 and PROCESSLIST_INFO like '%%%s%%' */` 369 | FILEIOCHECKSLOW = "myStatusgo.TableFileIOSQL" 370 | DIGESTCHECKSLOW = "myStatusgo.DigestTextSQL" 371 | BUFFERPOOLCHECKSLOW = "myStatusgo.InnodbBufferPoolSQL" 372 | LOCKCHECKSLOW = "myStatusgo.InnodbLockSQL" 373 | THREADCHECKSLOW = "myStatusgo.ThreadsSQL" 374 | PERFCHECKSLOW = "myStatusgo.PerfSchemaSQL" 375 | 376 | // Format 377 | RED = "\x1b[31m" 378 | GREEN = "\x1b[32m" 379 | YELLOW = "\x1b[33m" 380 | BLUE = "\x1b[34m" 381 | MAZENDA = "\x1b[35m" 382 | CYAN = "\x1b[36m" 383 | WHITE = "\x1b[37m" 384 | COLEND = "\x1b[0m" 385 | MODEOFF = 0 386 | MODENORMAL = 1 387 | MODERESOURCE = 2 388 | MODETHREADS = 3 389 | MODEPERFORMACE = 4 390 | MODESLAVE = 5 391 | MODEROW = 6 392 | MODEINNOLOCK = 7 393 | MODEINNOBUFFER = 8 394 | MODEFILEIOTABLE = 9 395 | MODEMAX = 9 396 | SORTNUM1 = 1 397 | SORTNUM2 = 2 398 | SORTNUM3 = 3 399 | SORTNUM4 = 4 400 | SORTNUM5 = 5 401 | SORTNUM6 = 6 402 | SORTNUM7 = 7 403 | SORTNUMMAX = 7 404 | TIMELAYOUT = "2006-01-02 15:04:05" 405 | MAXHOSTNAMESIZE = 25 406 | DEFAULTHOSTNAMESIZE = 8 407 | DEFAULTPORTSIZE = 5 408 | DBERROR = true 409 | DBOK = false 410 | MYSLAFORMATSPACE = " " 411 | DIGESTFORMAT0 = " *" 412 | THREADFORMAT0 = "*" 413 | INNOLOCKFORMAT0 = "*" 414 | FILEIOTABFORMAT0 = "*" 415 | INSTANCECURSOR = "*" 416 | TryCountDBError = 2 417 | WaitCountDBError = TryCountDBError + 60 418 | ) 419 | 420 | type mapMy map[string]string 421 | type mapInnoDBBuffer map[string]int64 422 | type mapDigest map[string]uint64 423 | type mapFileIO map[string]uint64 424 | type mapProme map[string]float64 425 | type mapPrint map[string]interface{} 426 | type mapStrFileIOperTable map[string]*FileIOperTable 427 | 428 | type HostAllInfo struct { 429 | Hname string 430 | port string 431 | alias string 432 | dbuser string `json:"-"` 433 | dbpasswd string `json:"-"` 434 | promPort int `json:"-"` 435 | dberror bool `json:"-"` 436 | promeerror int `json:"-"` 437 | db *sql.DB `json:"-"` 438 | dbErrorPerQuery dbErrorPerQuery `json:"-"` 439 | topNPosition int `json:"-"` 440 | PromeParam mapProme `json:"-"` 441 | MySlave mapMy `json:"-"` 442 | MyStatu mapMy `json:"-"` 443 | MyVariable, MyPerfomanceSchema mapMy `json:"-"` 444 | MyInnoDBBuffer mapInnoDBBuffer `json:"-"` 445 | DigestBase mapDigest `json:"-"` // 最初に全件取得するやつ 446 | Digest mapDigest `json:"-"` // WHERE LAST_SEEN > now() - 1 を保管 447 | DigestALLSqlCnt string `json:"-"` 448 | DigestMetric []DigestMetric 449 | ThreadsInfo []ThreadsInfo 450 | InnodbLockInfo []InnodbLockInfo `json:"-"` 451 | strFileIOperTable mapStrFileIOperTable `json:"-"` 452 | FileIOMetric []FileIOperTable `json:"-"` 453 | Metric mapPrint 454 | MetricOS mapPrint 455 | MetricJSON mapPrint `json:"-"` 456 | slowSum int64 `json:"-"` 457 | SlaveInfo []SlaveInfo 458 | ExecTime string 459 | } 460 | 461 | type Options struct { 462 | dbUser string 463 | dbPasswd string 464 | promPort int 465 | modeflg int 466 | digest int 467 | endSec int64 468 | interval int64 469 | file string 470 | hostJSON string 471 | hostComma string 472 | queryLength int 473 | outputJSON bool 474 | gtid bool 475 | all bool 476 | version bool 477 | } 478 | 479 | type sortDigestCnt struct { 480 | sDigest string 481 | sCnt int64 482 | } 483 | 484 | type DigestMetric struct { 485 | SchemaDigest string 486 | Digest string 487 | CntStar uint64 488 | SchemaName string 489 | FullScan string 490 | DigestText string 491 | LatencyAvg string 492 | RowExmAvg string 493 | } 494 | 495 | type ThreadsInfo struct { 496 | User string `json:"User"` 497 | Host string `json:"Host"` 498 | Db string `json:"Db"` 499 | Time string `json:"Time"` 500 | State string `json:"State"` 501 | Sql string `json:"Sql"` 502 | Cmd string `json:"Cmd"` 503 | } 504 | 505 | type InnodbLockInfo struct { 506 | WaitPidlist string 507 | BlockPid string 508 | WaitAge string 509 | LockedTable string 510 | LockedIndex string 511 | BlockingQuery string 512 | WaitCnt string 513 | } 514 | 515 | type SlaveInfo struct { 516 | Host string `json:"Host"` 517 | SlaveIO string `json:"SlaveIO"` 518 | SlaveSQL string `json:"SlaveSQL"` 519 | MsLogFile string `json:"MsLogFile"` 520 | RMasLogPos string `json:"RMasLogPos"` 521 | RelMasLogFile string `json:"RelMasLogFile"` 522 | ExeMasLogPos string `json:"ExeMasLogPos"` 523 | SecBehdMas string `json:"SecBehdMas"` 524 | ChlNm string `json:"ChlNm"` 525 | RetvGTID string `json:"RetvGTID"` 526 | ExeGTID string `json:"ExeGTID"` 527 | AutoPos string `json:"AutoPos"` 528 | } 529 | 530 | type hostListJson struct { 531 | ServiceList []struct { 532 | ServiceName string `json:"serviceName"` 533 | Host []string `json:"host"` 534 | } `json:"serviceList"` 535 | } 536 | 537 | type FileIOperTable struct { 538 | TableSchema string 539 | TableName string 540 | CountStar uint64 541 | TotalLatency uint64 542 | FetchRows uint64 543 | FetchLatency uint64 544 | InsertRows uint64 545 | InsertLatency uint64 546 | UpdateRows uint64 547 | UpdateLatency uint64 548 | DeleteRows uint64 549 | DeleteLatency uint64 550 | ReadIORequest uint64 551 | ReadIOByte uint64 552 | ReadIOLatency uint64 553 | WriteIORequest uint64 554 | WriteIOByte uint64 555 | WriteIOLatency uint64 556 | MiscIORequest uint64 557 | MiscIOLatency uint64 558 | } 559 | 560 | type dbErrorPerQuery struct { 561 | slave dbError 562 | status dbError 563 | variables dbError 564 | perf dbError 565 | bufferPool dbError 566 | lock dbError 567 | digest dbError 568 | threads dbError 569 | fileIO dbError 570 | } 571 | type dbError struct { 572 | errStatus bool 573 | errMessage string 574 | slowCheckKey string 575 | count int 576 | } 577 | type Entry struct { 578 | sKey string 579 | sCnt uint64 580 | } 581 | type List []Entry 582 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | func init() { 10 | clear = make(map[string]func()) 11 | clear["linux"] = func() { 12 | cmd := exec.Command("clear") 13 | cmd.Stdout = os.Stdout 14 | cmd.Run() 15 | } 16 | clear["darwin"] = func() { 17 | cmd := exec.Command("clear") 18 | cmd.Stdout = os.Stdout 19 | cmd.Run() 20 | } 21 | clear["windows"] = func() { 22 | cmd := exec.Command("cmd", "/c", "cls") 23 | cmd.Stdout = os.Stdout 24 | cmd.Run() 25 | } 26 | } 27 | 28 | func main() { 29 | Opt := Options{} 30 | parseOptions(&Opt) 31 | exitcode := run(&Opt) 32 | os.Exit(exitcode) 33 | } 34 | 35 | func parseOptions(Opt *Options) { 36 | flag.StringVar(&Opt.dbUser, "u", "", "MySQL Username") 37 | flag.StringVar(&Opt.dbPasswd, "p", "", "MySQL Password") 38 | flag.StringVar(&Opt.hostJSON, "h", "", "Hostlist written JSON") 39 | flag.StringVar(&Opt.hostComma, "hc", "", "Hostlist written Comma separated") 40 | flag.IntVar(&Opt.promPort, "n", 0, "node_exporter port") 41 | flag.IntVar(&Opt.modeflg, "m", 1, "Start Mode 1:Normal 2:OSResource 3:Threads 4:PerformaceSchema 5:SlaveStatus 6:Handler/InnoDB_Rows 7:InnoDBLockInfo 8.InnoDB Buffer Info 9.Table IO Statistic") 42 | flag.IntVar(&Opt.digest, "t", 10, "The number of display per instance of Threads ,Performance Schema and Table IO Statistic ") 43 | flag.Int64Var(&Opt.endSec, "c", 0, "Seconds of Automatic Stopping") 44 | flag.Int64Var(&Opt.interval, "d", 1, "Delay between updates in seconds") 45 | flag.StringVar(&Opt.file, "o", "", "Write the content to file and will set autostop 3600 secs") 46 | flag.BoolVar(&Opt.outputJSON, "j", false, "Write as JSON to file with option -o") 47 | flag.BoolVar(&Opt.gtid, "g", false, "Gtid Mode at 5:SlaveStatus") 48 | flag.BoolVar(&Opt.all, "a", false, "Show All metrics per server") 49 | flag.BoolVar(&Opt.version, "v", false, "Show Version") 50 | flag.Parse() 51 | } 52 | -------------------------------------------------------------------------------- /myStatus.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "database/sql" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "os" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | termbox "github.com/nsf/termbox-go" 15 | ) 16 | 17 | func run(Opt *Options) int { 18 | if Opt.version { 19 | fmt.Println("myStatusgo version " + VERSION) 20 | return 0 21 | } 22 | if Opt.dbUser == "" || Opt.dbPasswd == "" { 23 | fmt.Println("Set Username[-u] and Password[-p]") 24 | return -1 25 | } 26 | 27 | if Opt.file == "" && Opt.outputJSON { 28 | fmt.Println("-j option must be with -o option") 29 | return -1 30 | } 31 | 32 | var err error 33 | // Global Variables 34 | OutputJSON = Opt.outputJSON 35 | MaxTopNPosition = 0 36 | GtidMode = Opt.gtid 37 | HostnameSize = DEFAULTHOSTNAMESIZE 38 | AllShowMetricFlg = Opt.all 39 | if Opt.interval < 1 { 40 | Opt.interval = 1 41 | } 42 | // create hostlist 43 | bufText, exitcode := createHostList(Opt) 44 | if exitcode == -1 { 45 | return exitcode 46 | } 47 | 48 | // Set to struct from Hostlist 49 | // screenHostlist conteins only comment line and sequence number of host 50 | hostInfos1 := make([]HostAllInfo, 0) 51 | hostInfos2 := make([]HostAllInfo, 0) 52 | screenHostlist := make([]string, 0) 53 | for _, v := range bufText { 54 | if v == "" { 55 | continue 56 | } 57 | hArr := strings.Split(v, ":") 58 | if strings.Contains(hArr[0], "#") { 59 | screenHostlist = append(screenHostlist, v) 60 | continue 61 | } 62 | if len(hArr) < 2 { 63 | fmt.Printf("Check Format The Line [%s]\n", v) 64 | return -1 65 | } 66 | alias := aliasNameFind(hArr) 67 | if len(alias) > HostnameSize { 68 | HostnameSize = len(alias) 69 | } 70 | db, err := dbconn(hArr[0], hArr[1], Opt.dbUser, Opt.dbPasswd) 71 | if err != nil { 72 | panic(err.Error()) 73 | } 74 | defer db.Close() 75 | db.SetMaxOpenConns(2) 76 | db.SetMaxIdleConns(1) 77 | db.SetConnMaxLifetime(0) 78 | 79 | hostInfos1 = append(hostInfos1, HostAllInfo{ 80 | Hname: hArr[0], 81 | port: hArr[1], 82 | alias: alias, 83 | dbuser: Opt.dbUser, 84 | dbpasswd: Opt.dbPasswd, 85 | promPort: Opt.promPort, 86 | db: db, 87 | PromeParam: make(mapProme, 0), 88 | MySlave: make(mapMy, 0), MyStatu: make(mapMy, 0), MyVariable: make(mapMy, 0), MyPerfomanceSchema: make(mapMy, 0), MyInnoDBBuffer: make(mapInnoDBBuffer, 0), 89 | DigestBase: make(mapDigest, 0), Metric: make(mapPrint, 0), MetricOS: make(mapPrint, 0), MetricJSON: make(mapPrint, 0), 90 | }) 91 | hostInfos2 = append(hostInfos2, HostAllInfo{ 92 | Hname: hArr[0], 93 | port: hArr[1], 94 | alias: alias, 95 | dbuser: Opt.dbUser, 96 | dbpasswd: Opt.dbPasswd, 97 | promPort: Opt.promPort, 98 | db: db, 99 | PromeParam: make(mapProme, 0), 100 | MySlave: make(mapMy, 0), MyStatu: make(mapMy, 0), MyVariable: make(mapMy, 0), MyPerfomanceSchema: make(mapMy, 0), MyInnoDBBuffer: make(mapInnoDBBuffer, 0), 101 | Metric: make(mapPrint, 0), MetricOS: make(mapPrint, 0), MetricJSON: make(mapPrint, 0), 102 | }) 103 | screenHostlist = append(screenHostlist, fmt.Sprintf("%d", MaxTopNPosition)) 104 | MaxTopNPosition++ 105 | } 106 | 107 | // Create Timer 108 | t := time.NewTicker(time.Duration(Opt.interval) * time.Second) 109 | defer t.Stop() 110 | 111 | // Input Keybord 112 | termbox.Init() 113 | eventQueue := make(chan termbox.Event) 114 | go func() { 115 | for { 116 | eventQueue <- termbox.PollEvent() 117 | } 118 | }() 119 | defer termbox.Close() 120 | 121 | // initial val 122 | StartTime = time.Now() 123 | screenFlg := MODEOFF 124 | beforeScreenFlg := beforeScreenFlg() 125 | outputTitleforFile := outputforTitle() 126 | execCounter := 1 127 | if AllShowMetricFlg == false { 128 | TopNPosition = -1 129 | TopN = Opt.digest 130 | } else { 131 | TopNPosition = 0 132 | TopN = 5 133 | } 134 | SortTableFileIOPosition = 1 135 | 136 | // Create OutPut File 137 | var outputFileName string 138 | var outputFile *os.File 139 | if Opt.file != "" { 140 | outputFileName = Opt.file 141 | outputFile, err = createOutputFile(outputFileName) 142 | if err != nil { 143 | fmt.Println(fmt.Sprintf("Failed Create File[%v]", err)) 144 | return -1 145 | } 146 | defer outputFile.Close() 147 | if Opt.endSec == 0 { 148 | Opt.endSec = 3600 149 | } 150 | } 151 | 152 | // Main Loop 153 | baseInfo := hostInfos1 154 | var hostInfos, hostInfosOld []HostAllInfo 155 | for { 156 | select { 157 | case <-t.C: 158 | if ok := checkAutoStop(Opt.endSec, StartTime); ok { 159 | return 0 160 | } 161 | // switching 162 | if execCounter%2 == 1 { 163 | hostInfos = hostInfos1 164 | hostInfosOld = hostInfos2 165 | } else { 166 | hostInfos = hostInfos2 167 | hostInfosOld = hostInfos1 168 | } 169 | wg := &sync.WaitGroup{} 170 | sElaspTime := time.Now() 171 | for i := 0; i < len(hostInfos); i++ { 172 | wg.Add(1) 173 | go func(ii int) { 174 | hostInfos[ii].initializeStruct(&hostInfosOld[ii]) 175 | hostInfos[ii].retrieve(screenFlg, &hostInfosOld[ii], &baseInfo[ii]) 176 | hostInfos[ii].calculate(screenFlg, &hostInfosOld[ii]) 177 | hostInfos[ii].ExecTime = time.Now().Format(TIMELAYOUT) 178 | wg.Done() 179 | }(i) 180 | } 181 | wg.Wait() 182 | title, out := screen(hostInfos, screenFlg, screenHostlist, sElaspTime) 183 | // Write File 184 | if outputFileName != "" { 185 | if OutputJSON { 186 | fmt.Fprintln(outputFile, formatJSON(hostInfos)) 187 | } else { 188 | fmt.Fprintln(outputFile, getMetricForFile(screenFlg, beforeScreenFlg(screenFlg), &title, &out, sElaspTime.Format(TIMELAYOUT), outputTitleforFile(1))) 189 | } 190 | } 191 | 192 | if screenFlg == MODEOFF { 193 | if AllShowMetricFlg == false { 194 | screenFlg = Opt.modeflg 195 | } else { 196 | screenFlg = MODEFILEIOTABLE 197 | } 198 | } 199 | execCounter++ 200 | case ev := <-eventQueue: 201 | screenFlg = switchModeByKeyEvent(ev, screenFlg) 202 | switchEachInstanceByKeyEvent(ev, screenFlg) 203 | // Stop myStatusgo 204 | if (ev.Type == termbox.EventKey) && (ev.Key == termbox.KeyEsc || ev.Key == termbox.KeySpace) { 205 | callClear() 206 | return 0 207 | } 208 | } 209 | } 210 | } 211 | func dbconn(host string, port string, dbuser string, dbpasswd string) (*sql.DB, error) { 212 | return sql.Open("mysql", dbuser+":"+dbpasswd+"@tcp("+host+":"+port+")/information_schema?readTimeout=300ms&timeout=250ms") 213 | } 214 | 215 | func checkAutoStop(endSec int64, startTime time.Time) bool { 216 | if endSec != 0 && (time.Now().Unix()-startTime.Unix()) > endSec { 217 | fmt.Printf(FinishFormat, startTime.Format(TIMELAYOUT), time.Now().Format(TIMELAYOUT)) 218 | return true 219 | } 220 | return false 221 | } 222 | 223 | func (val *HostAllInfo) initializeStruct(old *HostAllInfo) { 224 | m.Lock() 225 | defer m.Unlock() 226 | val.SlaveInfo = make([]SlaveInfo, 0) 227 | val.DigestMetric = make([]DigestMetric, TopN) 228 | val.strFileIOperTable = make(mapStrFileIOperTable, 0) 229 | val.FileIOMetric = make([]FileIOperTable, TopN) 230 | val.ThreadsInfo = make([]ThreadsInfo, TopN) 231 | val.InnodbLockInfo = make([]InnodbLockInfo, TopN) 232 | val.dbErrorPerQuery.addSlowCheckKey() 233 | val.dbErrorPerQuery = old.dbErrorPerQuery 234 | } 235 | func initializeStructIfdbErrorFileIO(old *HostAllInfo, baseInfo *HostAllInfo) { 236 | m.Lock() 237 | defer m.Unlock() 238 | old.strFileIOperTable = make(mapStrFileIOperTable, 0) 239 | old.FileIOMetric = make([]FileIOperTable, TopN) 240 | } 241 | 242 | func initializeStructIfdbErrorDigest(old *HostAllInfo, baseInfo *HostAllInfo) { 243 | m.Lock() 244 | defer m.Unlock() 245 | baseInfo.DigestBase = make(mapDigest, 0) 246 | old.DigestMetric = make([]DigestMetric, TopN) 247 | } 248 | 249 | func beforeScreenFlg() func(int) int { 250 | var v int 251 | return func(now int) int { 252 | before := v 253 | v = now 254 | return before 255 | } 256 | } 257 | 258 | func outputforTitle() func(int) int { 259 | sum := 0 260 | return func(v int) int { 261 | sum = sum + v 262 | if sum == 20 { 263 | sum = 1 264 | } 265 | return sum 266 | } 267 | } 268 | 269 | func createOutputFile(name string) (*os.File, error) { 270 | file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) 271 | if err != nil { 272 | return nil, err 273 | } 274 | return file, nil 275 | } 276 | 277 | func createHostList(Opt *Options) ([]string, int) { 278 | bufText := make([]string, 0) 279 | var err error 280 | //File Type 281 | if Opt.hostJSON == "" && Opt.hostComma == "" { 282 | //Args 283 | if flag.NArg() != 1 { 284 | fmt.Println("Set HostFile or -h JSON or -hc hostname1:3306,hostname2:3306") 285 | return nil, -1 286 | } 287 | //Open File 288 | bufText, err = createHostListByFile(flag.Arg(0)) 289 | if err != nil { 290 | fmt.Println(fmt.Sprintf("Can't Open File [%s]", flag.Arg(0))) 291 | return nil, -1 292 | } 293 | } else if Opt.hostJSON != "" { 294 | // JSON Type 295 | bufText = createHostListByJSON(Opt.hostJSON) 296 | if len(bufText) == 0 { 297 | fmt.Println(fmt.Sprintf("Check JSON Format [%s]", Opt.hostJSON)) 298 | return nil, -1 299 | } 300 | } else if Opt.hostComma != "" { 301 | // Comma Type 302 | bufText = createHostListByComma(Opt.hostComma) 303 | 304 | } else { 305 | fmt.Println("Set HostFile or -h JSON or -hc hostname1:3306,hostname2:3306") 306 | return nil, -1 307 | } 308 | return bufText, 0 309 | } 310 | 311 | func createHostListByComma(hostList string) []string { 312 | bufText := strings.Split(hostList, ",") 313 | return bufText 314 | } 315 | 316 | func createHostListByFile(fName string) ([]string, error) { 317 | bufText := make([]string, 0) 318 | fp, err := os.Open(fName) 319 | if err != nil { 320 | return nil, err 321 | } 322 | defer fp.Close() 323 | // Read line 324 | vScan := bufio.NewScanner(fp) 325 | for vScan.Scan() { 326 | bufText = append(bufText, vScan.Text()) 327 | } 328 | return bufText, nil 329 | } 330 | 331 | func createHostListByJSON(hostList string) []string { 332 | bufText := make([]string, 0) 333 | b := []byte(hostList) 334 | var m hostListJson 335 | json.Unmarshal(b, &m) 336 | for _, v := range m.ServiceList { 337 | bufText = append(bufText, "#"+v.ServiceName) 338 | for _, v2 := range v.Host { 339 | bufText = append(bufText, v2) 340 | } 341 | } 342 | return bufText 343 | } 344 | 345 | func (dbErrorPerQuery *dbErrorPerQuery) addSlowCheckKey() { 346 | if dbErrorPerQuery.fileIO.slowCheckKey != "" { 347 | return 348 | } 349 | dbErrorPerQuery.fileIO.slowCheckKey = FILEIOCHECKSLOW 350 | dbErrorPerQuery.digest.slowCheckKey = DIGESTCHECKSLOW 351 | dbErrorPerQuery.bufferPool.slowCheckKey = BUFFERPOOLCHECKSLOW 352 | dbErrorPerQuery.lock.slowCheckKey = LOCKCHECKSLOW 353 | dbErrorPerQuery.threads.slowCheckKey = THREADCHECKSLOW 354 | dbErrorPerQuery.perf.slowCheckKey = PERFCHECKSLOW 355 | dbErrorPerQuery.slave.slowCheckKey = ShowSlaveStatusSQL 356 | dbErrorPerQuery.status.slowCheckKey = ShowStatusSQL 357 | dbErrorPerQuery.variables.slowCheckKey = ShowVariableSQL 358 | } 359 | 360 | func switchEachInstanceByKeyEvent(ev termbox.Event, screenFlg int) { 361 | if screenFlg == MODEOFF { 362 | return 363 | } 364 | switch { 365 | case ev.Type == termbox.EventKey && (screenFlg == MODETHREADS || screenFlg == MODEPERFORMACE || screenFlg == MODEINNOLOCK || screenFlg == MODEFILEIOTABLE || AllShowMetricFlg) && ev.Key == termbox.KeyArrowDown: 366 | if TopNPosition < MaxTopNPosition-1 { 367 | TopNPosition++ 368 | } 369 | case ev.Type == termbox.EventKey && (screenFlg == MODETHREADS || screenFlg == MODEPERFORMACE || screenFlg == MODEINNOLOCK || screenFlg == MODEFILEIOTABLE || AllShowMetricFlg) && ev.Key == termbox.KeyArrowUp: 370 | if TopNPosition > -1 { 371 | TopNPosition-- 372 | } 373 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyTab && (screenFlg == MODEFILEIOTABLE || AllShowMetricFlg): 374 | if SortTableFileIOPosition == SORTNUMMAX { 375 | SortTableFileIOPosition = SORTNUM1 376 | } else { 377 | SortTableFileIOPosition++ 378 | } 379 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF12: 380 | FullSQLStatement = !FullSQLStatement 381 | } 382 | if (screenFlg == MODEINNOLOCK || screenFlg == MODEFILEIOTABLE) && TopNPosition == -1 { 383 | TopNPosition = 0 384 | } 385 | } 386 | func switchModeByKeyEvent(ev termbox.Event, screenFlg int) int { 387 | if screenFlg == MODEOFF { 388 | return MODEOFF 389 | } 390 | if AllShowMetricFlg == true { 391 | return MODEFILEIOTABLE 392 | } 393 | switch { 394 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF1: 395 | return MODENORMAL 396 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF2: 397 | return MODERESOURCE 398 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF3: 399 | return MODETHREADS 400 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF4: 401 | return MODEPERFORMACE 402 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF5: 403 | return MODESLAVE 404 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF6: 405 | return MODEROW 406 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF7: 407 | return MODEINNOLOCK 408 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF8: 409 | return MODEINNOBUFFER 410 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyF9: 411 | return MODEFILEIOTABLE 412 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyArrowLeft: 413 | if screenFlg > 1 { 414 | return screenFlg - 1 415 | } 416 | return MODEMAX 417 | case ev.Type == termbox.EventKey && ev.Key == termbox.KeyArrowRight: 418 | if screenFlg < MODEMAX { 419 | return screenFlg + 1 420 | } 421 | return MODENORMAL 422 | default: 423 | return screenFlg 424 | } 425 | } 426 | 427 | func aliasNameFind(hArr []string) string { 428 | str := hArr[0] 429 | if len(hArr[0]) > MAXHOSTNAMESIZE { 430 | str = hArr[0][:MAXHOSTNAMESIZE] 431 | } 432 | if len(hArr) == 3 { 433 | if len(hArr[2]) > MAXHOSTNAMESIZE { 434 | str = hArr[2][:MAXHOSTNAMESIZE] 435 | } else { 436 | str = hArr[2] 437 | } 438 | } 439 | return str 440 | } 441 | -------------------------------------------------------------------------------- /retrieve.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/go-sql-driver/mysql" 14 | ) 15 | 16 | func (val *HostAllInfo) retrieve(screenFlg int, old *HostAllInfo, baseInfo *HostAllInfo) { 17 | val.dberror = DBOK 18 | if err := val.db.Ping(); err != nil { 19 | val.dberror = DBERROR 20 | } 21 | 22 | wg := &sync.WaitGroup{} 23 | wg.Add(8) 24 | // OS metric 25 | go func() { 26 | if val.promPort != 0 { 27 | val.getOsMetric() 28 | } 29 | wg.Done() 30 | }() 31 | 32 | // Thread Info 33 | go func() { 34 | val.doExecute(&val.dbErrorPerQuery.threads, 7) 35 | wg.Done() 36 | }() 37 | 38 | // Show Slave Status 39 | // Show Global Status 40 | // Show Variables 41 | go func() { 42 | val.doExecute(&val.dbErrorPerQuery.slave, 1) 43 | val.doExecute(&val.dbErrorPerQuery.status, 2) 44 | val.doExecute(&val.dbErrorPerQuery.variables, 3) 45 | wg.Done() 46 | }() 47 | 48 | // events_statements_current Over MySQL5.6 49 | go func() { 50 | val.doExecute(&val.dbErrorPerQuery.perf, 4) 51 | wg.Done() 52 | }() 53 | 54 | // InnoDB Buffer Pool Info 55 | go func() { 56 | val.doExecute(&val.dbErrorPerQuery.bufferPool, 5) 57 | wg.Done() 58 | }() 59 | // InnoDB Row Lock Info Over MySQL5.6 60 | //// session can not kill if lots of locks 61 | go func() { 62 | if screenFlg == MODEINNOLOCK && baseInfo.topNPosition == TopNPosition { 63 | val.doExecute(&val.dbErrorPerQuery.lock, 6) 64 | } 65 | wg.Done() 66 | }() 67 | 68 | // events_statements_summary_by_digest Over MySQL5.6 69 | //// At First, retrieve events_statements_summary_by_digest limit 5000 70 | go func() { 71 | if screenFlg == MODEOFF || len(baseInfo.DigestBase) == 0 { 72 | if ok := baseInfo.doExecute(&val.dbErrorPerQuery.digest, 8); ok == DBERROR { 73 | initializeStructIfdbErrorDigest(old, baseInfo) 74 | } 75 | } 76 | //// events_statements_summary_by_digest WHERE LAST_SEEN > now() - 1 77 | if ok := val.doExecute(&val.dbErrorPerQuery.digest, 9); ok == DBERROR { 78 | initializeStructIfdbErrorDigest(old, baseInfo) 79 | } 80 | // Success 81 | topDigestQuery := val.sortDigest(baseInfo) 82 | if topDigestQuery != "" { 83 | s, _ := val.sqlExecDigestText(topDigestQuery) 84 | if s != DBOK { 85 | initializeStructIfdbErrorDigest(old, baseInfo) 86 | } 87 | } 88 | wg.Done() 89 | }() 90 | // Table IO Stastics Info 91 | go func() { 92 | if screenFlg == MODEFILEIOTABLE && baseInfo.topNPosition == TopNPosition { 93 | if ok := val.doExecute(&val.dbErrorPerQuery.fileIO, 10); ok == DBERROR { 94 | initializeStructIfdbErrorFileIO(old, baseInfo) 95 | } else { 96 | // Calculate FileIOTable 97 | sortFileIO(val, old) 98 | } 99 | } 100 | wg.Done() 101 | }() 102 | wg.Wait() 103 | } 104 | 105 | func (val *HostAllInfo) doExecute(dbError *dbError, id int) bool { 106 | // IF DB Error means down mysqld , Nothing to do 107 | if val.dberror == DBERROR { 108 | return DBERROR 109 | } 110 | // Error count >= wait , reset counter. do SQL again 111 | if dbError.count >= WaitCountDBError { 112 | dbError.setErrCount(0) 113 | } 114 | // Error count >= try , count++ and nothing to do 115 | if dbError.count >= TryCountDBError { 116 | dbError.setErrCount(1) 117 | return DBERROR 118 | } 119 | // if status == DBERROR, check SQL is still running. 120 | // if running, count++ and nothing to do 121 | // if not running, count++ 122 | if dbError.errStatus == DBERROR { 123 | if ok, _ := val.sqlCheckSlowQuery(dbError); ok == DBERROR { 124 | dbError.setErrCount(1) 125 | return DBERROR 126 | } 127 | } 128 | var errStatus bool 129 | var errMessage string 130 | switch id { 131 | case 1: 132 | errStatus, errMessage = val.sqlExecSlaveStatus() 133 | case 2: 134 | errStatus, errMessage = val.sqlExecShowStatus() 135 | case 3: 136 | errStatus, errMessage = val.sqlExecShowVariables() 137 | case 4: 138 | errStatus, errMessage = val.sqlExecPerfSchema() 139 | case 5: 140 | errStatus, errMessage = val.sqlExecBufferPool() 141 | case 6: 142 | errStatus, errMessage = val.sqlExecInnoDBLock() 143 | case 7: 144 | errStatus, errMessage = val.sqlExecThreads() 145 | case 8: 146 | errStatus, errMessage = val.sqlExecDigestBase() 147 | case 9: 148 | errStatus, errMessage = val.sqlExecDigestRepeat() 149 | case 10: 150 | errStatus, errMessage = val.sqlExecFileIOTable() 151 | } 152 | 153 | dbError.addStatus(errStatus, errMessage) 154 | if errStatus == DBOK { 155 | dbError.resetStatus() 156 | dbError.setErrCount(0) 157 | } else { 158 | dbError.setErrCount(1) 159 | return DBERROR 160 | } 161 | 162 | return DBOK 163 | 164 | } 165 | 166 | func (dbError *dbError) setErrCount(setNum int) { 167 | m.Lock() 168 | defer m.Unlock() 169 | if setNum == 0 { 170 | dbError.count = 0 171 | } else { 172 | dbError.count++ 173 | } 174 | } 175 | 176 | func (dbError *dbError) addStatus(s bool, v string) { 177 | m.Lock() 178 | defer m.Unlock() 179 | dbError.errStatus = s 180 | dbError.errMessage = v 181 | } 182 | 183 | func (dbError *dbError) resetStatus() { 184 | m.Lock() 185 | defer m.Unlock() 186 | dbError.errStatus = false 187 | dbError.errMessage = "" 188 | dbError.count = 0 189 | } 190 | 191 | func (val *HostAllInfo) dbPing() bool { 192 | if err := val.db.Ping(); err != nil { 193 | return DBERROR 194 | } 195 | return DBOK 196 | } 197 | 198 | func copyMap(base interface{}, new interface{}) { 199 | m.Lock() 200 | defer m.Unlock() 201 | switch base.(type) { 202 | case mapProme: 203 | b := base.(mapProme) 204 | n := new.(mapProme) 205 | for key, value := range b { 206 | n[key] = value 207 | } 208 | case mapPrint: 209 | b := base.(mapPrint) 210 | n := new.(mapPrint) 211 | for key, value := range b { 212 | n[key] = value 213 | } 214 | case mapMy: 215 | b := base.(mapMy) 216 | n := new.(mapMy) 217 | for key, value := range b { 218 | n[key] = value 219 | } 220 | } 221 | return 222 | } 223 | 224 | func (val *HostAllInfo) getOsMetric() { 225 | uri := fmt.Sprintf("http://%s:%d/metrics", val.Hname, val.promPort) 226 | c := http.Client{ 227 | Timeout: 300 * time.Millisecond, 228 | } 229 | res, err := c.Get(uri) 230 | if err != nil { 231 | val.promeerror = 1 232 | return 233 | } 234 | val.promeerror = 0 235 | n, err := ioutil.ReadAll(res.Body) 236 | if err != nil { 237 | return 238 | } 239 | defer res.Body.Close() 240 | promeparam := make(mapProme, 0) 241 | for _, v := range strings.Split(string(n), "\n") { 242 | // ignore # 243 | if len(v) > 0 && v[:1] == "#" { 244 | continue 245 | } 246 | // split by space 247 | arr := strings.Split(v, " ") 248 | if len(arr[0]) == 0 || len(arr[1]) == 0 { 249 | continue 250 | } 251 | mtrcname := arr[0] // metoric name 252 | mtrcval := arr[1] // metoric value 253 | switch { 254 | case mtrcname == SWAPTOL: 255 | promeparam[SWAPTOL] = rtnInt2Stg(mtrcval, promeparam[SWAPTOL]) 256 | case mtrcname == SWAPFRE: 257 | promeparam[SWAPFRE] = rtnInt2Stg(mtrcval, promeparam[SWAPFRE]) 258 | case mtrcname == MEMTOL: 259 | promeparam[MEMTOL] = rtnInt2Stg(mtrcval, promeparam[MEMTOL]) 260 | case mtrcname == MEMFREE: 261 | promeparam[MEMFREE] = rtnInt2Stg(mtrcval, promeparam[MEMFREE]) 262 | case mtrcname == MEMBUF: 263 | promeparam[MEMBUF] = rtnInt2Stg(mtrcval, promeparam[MEMBUF]) 264 | case mtrcname == MEMCACHE: 265 | promeparam[MEMCACHE] = rtnInt2Stg(mtrcval, promeparam[MEMCACHE]) 266 | case mtrcname == LOADAVG1: 267 | promeparam[LOADAVG1] = rtnInt2Stg(mtrcval, promeparam[LOADAVG1]) 268 | case mtrcname == LOADAVG5: 269 | promeparam[LOADAVG5] = rtnInt2Stg(mtrcval, promeparam[LOADAVG5]) 270 | case mtrcname == LOADAVG15: 271 | promeparam[LOADAVG15] = rtnInt2Stg(mtrcval, promeparam[LOADAVG15]) 272 | case mtrcname[:8] == "node_cpu": 273 | if strings.LastIndex(mtrcname, "user") > 0 { 274 | promeparam[CPUUSER] = rtnInt2Stg(mtrcval, promeparam[CPUUSER]) 275 | } else if strings.LastIndex(mtrcname, "system") > 0 { 276 | promeparam[CPUSYS] = rtnInt2Stg(mtrcval, promeparam[CPUSYS]) 277 | } else if strings.LastIndex(mtrcname, "iowait") > 0 { 278 | promeparam[CPUIO] = rtnInt2Stg(mtrcval, promeparam[CPUIO]) 279 | } else if strings.LastIndex(mtrcname, "idle") > 0 { 280 | promeparam[CPUIDLE] = rtnInt2Stg(mtrcval, promeparam[CPUIDLE]) 281 | } else if strings.LastIndex(mtrcname, "irq") > 0 { 282 | promeparam[CPUIRQ] = rtnInt2Stg(mtrcval, promeparam[CPUIRQ]) 283 | } 284 | 285 | case len(mtrcname) > 25 && mtrcname[:26] == INBOUND: 286 | promeparam[INBOUND] = rtnInt2Stg(mtrcval, promeparam[INBOUND]) 287 | 288 | case len(mtrcname) > 26 && mtrcname[:27] == OUTBOUND: 289 | promeparam[OUTBOUND] = rtnInt2Stg(mtrcval, promeparam[OUTBOUND]) 290 | 291 | case len(mtrcname) > 19 && mtrcname[:20] == DISKREAD: 292 | promeparam[DISKREAD] = rtnInt2Stg(mtrcval, promeparam[DISKREAD]) 293 | 294 | case len(mtrcname) > 22 && mtrcname[:23] == DISKWRITE: 295 | promeparam[DISKWRITE] = rtnInt2Stg(mtrcval, promeparam[DISKWRITE]) 296 | } 297 | } 298 | copyMap(promeparam, val.PromeParam) 299 | return 300 | } 301 | 302 | func columnIndex(cols []string, colName string) int { 303 | for idx := range cols { 304 | if cols[idx] == colName { 305 | return idx 306 | } 307 | } 308 | return -1 309 | } 310 | 311 | func columnValue(scanArgs []interface{}, cols []string, colName string) string { 312 | var columnIndex = columnIndex(cols, colName) 313 | if columnIndex == -1 { 314 | return "" 315 | } 316 | return string(*scanArgs[columnIndex].(*sql.RawBytes)) 317 | } 318 | 319 | func (val *HostAllInfo) sqlExecBufferPool() (bool, string) { 320 | var rows *sql.Rows 321 | var err error 322 | db := val.db 323 | rows, err = db.Query(InnodbBufferPoolSQL) 324 | if err != nil { 325 | return checkMysqlErr(err) 326 | } 327 | defer rows.Close() 328 | m.Lock() 329 | var myStatusgoQuery, name, status string 330 | var count int64 331 | for rows.Next() { 332 | if err := rows.Scan(&myStatusgoQuery, &name, &count, &status); err != nil { 333 | continue 334 | } 335 | switch name { 336 | case BUFPOOLTOTAL: 337 | val.MyInnoDBBuffer[BUFPOOLTOTAL] = count 338 | case BUFPOOLDATA: 339 | val.MyInnoDBBuffer[BUFPOOLDATA] = count 340 | case BUFPOOLDIRTY: 341 | val.MyInnoDBBuffer[BUFPOOLDIRTY] = count 342 | case BUFPOOLFREE: 343 | val.MyInnoDBBuffer[BUFPOOLFREE] = count 344 | case LSNMINUSCKPOINT: 345 | if status == "disabled" { 346 | val.MyInnoDBBuffer[LSNMINUSCKPOINT] = -1 347 | } else { 348 | val.MyInnoDBBuffer[LSNMINUSCKPOINT] = count 349 | } 350 | } 351 | } 352 | m.Unlock() 353 | return DBOK, "" 354 | } 355 | 356 | func (val *HostAllInfo) sqlExecPerfSchema() (bool, string) { 357 | var rows *sql.Rows 358 | var err error 359 | db := val.db 360 | rows, err = db.Query(PerfSchemaSQL) 361 | if err != nil { 362 | return checkMysqlErr(err) 363 | } 364 | defer rows.Close() 365 | m.Lock() 366 | var myStatusgoQuery, sqlCnt, avgLatency, maxLatency string 367 | for rows.Next() { 368 | if err := rows.Scan(&myStatusgoQuery, &sqlCnt, &avgLatency, &maxLatency); err != nil { 369 | continue 370 | } 371 | val.MyPerfomanceSchema[SQLCNT] = sqlCnt 372 | val.MyPerfomanceSchema[AVGLATENCY] = avgLatency 373 | val.MyPerfomanceSchema[MAXLATENCY] = maxLatency 374 | } 375 | m.Unlock() 376 | return DBOK, "" 377 | } 378 | 379 | func showStatusCompare(variableName string, value string) bool { 380 | switch variableName { 381 | case THCON, THRUN, ABORTCON, SLWLOG, CSEL, CUPDMUL, CUPD, CINSSEL, CINS, CDELMUL, CDEL, CRLCESEL, CRLCE, QCACHHIT, CCALPROCEDURE, CSTMT, CCOMMIT, CROLLBACK: 382 | return true 383 | case HADRRDFIRST, HADRRDKEY, HADRRDLAST, HADRRDNXT, HADRRDPRV, HADRRDRND, HADRRDRNDNXT, HADRDEL, HADRUPD, HADRWRT, HADRPREP, HADRCOMT, HADRRB, INNOROWDEL, INNOROWRD, INNOROWINS, INNOROWUPD, PSDIGESTLOST: 384 | return true 385 | case BUFWRITEREQ, BUFREADREQ, BUFREAD, BUFCREATED, BUFWRITTEN, BUFFLUSH, BUFDATAP, BUFDIRTYP, BUFFREEP, BUFMISCP, BUFTOTALP: 386 | return true 387 | default: 388 | return false 389 | } 390 | } 391 | func (val *HostAllInfo) sqlExecShowStatus() (bool, string) { 392 | var rows *sql.Rows 393 | var err error 394 | db := val.db 395 | rows, err = db.Query(ShowStatusSQL) 396 | if err != nil { 397 | return checkMysqlErr(err) 398 | } 399 | defer rows.Close() 400 | var vName, value string 401 | mapBuf := make(mapMy, 0) 402 | for rows.Next() { 403 | if err := rows.Scan(&vName, &value); err != nil { 404 | continue 405 | } 406 | // if add status val, count the value 407 | if len(mapBuf) == 47 { 408 | break 409 | } 410 | if showStatusCompare(vName, value) { 411 | mapBuf[vName] = value 412 | } 413 | } 414 | copyMap(mapBuf, val.MyStatu) 415 | return DBOK, "" 416 | } 417 | 418 | func (val *HostAllInfo) sqlExecShowVariables() (bool, string) { 419 | var rows *sql.Rows 420 | var err error 421 | db := val.db 422 | rows, err = db.Query(ShowVariableSQL) 423 | if err != nil { 424 | return checkMysqlErr(err) 425 | } 426 | defer rows.Close() 427 | m.Lock() 428 | var VariableName, Value string 429 | for rows.Next() { 430 | if err := rows.Scan(&VariableName, &Value); err != nil { 431 | continue 432 | } 433 | switch VariableName { 434 | case RDONLY: 435 | val.MyVariable[RDONLY] = Value 436 | case RPLSEMIMAS: 437 | val.MyVariable[RPLSEMIMAS] = Value 438 | case RPLSEMISLV: 439 | val.MyVariable[RPLSEMISLV] = Value 440 | } 441 | } 442 | m.Unlock() 443 | return DBOK, "" 444 | } 445 | 446 | func (val *HostAllInfo) sqlExecSlaveStatus() (bool, string) { 447 | var rows *sql.Rows 448 | var err error 449 | db := val.db 450 | rows, err = db.Query(ShowSlaveStatusSQL) 451 | if err != nil { 452 | return checkMysqlErr(err) 453 | } 454 | defer rows.Close() 455 | cols, _ := rows.Columns() 456 | printNormalModeSecndBhMas := 0 457 | m.Lock() 458 | val.SlaveInfo = make([]SlaveInfo, 0) 459 | for rows.Next() { 460 | scanArgs := make([]interface{}, len(cols)) 461 | for i := range scanArgs { 462 | scanArgs[i] = &sql.RawBytes{} 463 | } 464 | if err := rows.Scan(scanArgs...); err != nil { 465 | continue 466 | } 467 | buf := columnValue(scanArgs, cols, SECBEHMAS) 468 | i, _ := strconv.Atoi(buf) 469 | if i > printNormalModeSecndBhMas { 470 | printNormalModeSecndBhMas = i 471 | } 472 | val.MySlave[SECBEHMAS] = fmt.Sprintf("%d", printNormalModeSecndBhMas) 473 | val.SlaveInfo = append(val.SlaveInfo, SlaveInfo{ 474 | Host: columnValue(scanArgs, cols, MHOST), 475 | SlaveIO: columnValue(scanArgs, cols, SLAVEIO), 476 | SlaveSQL: columnValue(scanArgs, cols, SLAVESQL), 477 | MsLogFile: columnValue(scanArgs, cols, MASLOGFILE), 478 | RMasLogPos: columnValue(scanArgs, cols, RMASLOGPOS), 479 | RelMasLogFile: columnValue(scanArgs, cols, RELMASLOGFILE), 480 | ExeMasLogPos: columnValue(scanArgs, cols, EXEMASLOGPOS), 481 | SecBehdMas: columnValue(scanArgs, cols, SECBEHMAS), 482 | ChlNm: columnValue(scanArgs, cols, CHNLNM), 483 | RetvGTID: columnValue(scanArgs, cols, RETVGTID), 484 | ExeGTID: columnValue(scanArgs, cols, EXEGTID), 485 | AutoPos: columnValue(scanArgs, cols, AUTOPOS), 486 | }) 487 | } 488 | m.Unlock() 489 | return DBOK, "" 490 | } 491 | 492 | func (val *HostAllInfo) sqlExecDigestBase() (bool, string) { 493 | var rows *sql.Rows 494 | var err error 495 | db := val.db 496 | rows, err = db.Query(DigestFirstSQL) 497 | if err != nil { 498 | return checkMysqlErr(err) 499 | } 500 | defer rows.Close() 501 | m.Lock() 502 | val.DigestBase = make(mapDigest, 0) 503 | for rows.Next() { 504 | var myStatusgoQuery, schemaDigest string 505 | var execnt uint64 506 | if err := rows.Scan(&myStatusgoQuery, &schemaDigest, &execnt); err != nil { 507 | continue 508 | } 509 | val.DigestBase[schemaDigest] = execnt 510 | } 511 | m.Unlock() 512 | return DBOK, "" 513 | } 514 | 515 | func (val *HostAllInfo) sqlExecDigestRepeat() (bool, string) { 516 | var rows *sql.Rows 517 | var err error 518 | db := val.db 519 | rows, err = db.Query(DigestRepeatSQL) 520 | if err != nil { 521 | return checkMysqlErr(err) 522 | } 523 | defer rows.Close() 524 | m.Lock() 525 | val.Digest = make(mapDigest, 0) 526 | var myStatusgoQuery, schemaDigest string 527 | var execnt uint64 528 | for rows.Next() { 529 | if err := rows.Scan(&myStatusgoQuery, &schemaDigest, &execnt); err != nil { 530 | continue 531 | } 532 | val.Digest[schemaDigest] = execnt 533 | } 534 | m.Unlock() 535 | return DBOK, "" 536 | } 537 | 538 | func (val *HostAllInfo) sqlExecDigestText(topDigestQuery string) (bool, string) { 539 | var rows *sql.Rows 540 | var err error 541 | db := val.db 542 | rows, err = db.Query(fmt.Sprintf(DigestTextSQL, topDigestQuery, topDigestQuery)) 543 | if err != nil { 544 | return checkMysqlErr(err) 545 | } 546 | defer rows.Close() 547 | cnt := 0 548 | m.Lock() 549 | for rows.Next() { 550 | var myStatusgoQuery, digText, schemadigest, digest, schema, full, avglacy, avgExm string 551 | if err := rows.Scan(&myStatusgoQuery, &digText, &schemadigest, &digest, &schema, &full, &avglacy, &avgExm); err != nil { 552 | continue 553 | } 554 | if val.DigestMetric[cnt].Digest == schemadigest { 555 | val.DigestMetric[cnt].SchemaDigest = schemadigest 556 | val.DigestMetric[cnt].SchemaName = schema 557 | val.DigestMetric[cnt].FullScan = full 558 | val.DigestMetric[cnt].DigestText = digText 559 | val.DigestMetric[cnt].LatencyAvg = avglacy 560 | val.DigestMetric[cnt].RowExmAvg = avgExm 561 | cnt++ 562 | } 563 | if cnt == TopN { 564 | break 565 | } 566 | } 567 | m.Unlock() 568 | return DBOK, "" 569 | } 570 | 571 | func (val *HostAllInfo) sqlExecFileIOTable() (bool, string) { 572 | var rows *sql.Rows 573 | var err error 574 | db := val.db 575 | rows, err = db.Query(TableFileIOSQL) 576 | if err != nil { 577 | return checkMysqlErr(err) 578 | } 579 | defer rows.Close() 580 | m.Lock() 581 | val.strFileIOperTable = make(mapStrFileIOperTable, 0) 582 | for rows.Next() { 583 | var myStatusgoQuery, tableSchema, tableName string 584 | var countStar, totalLatency, fetchRows, fetchLatency, insertRows, insertLatency, updateRows, updateLatency, deleteRows, deleteLatency uint64 585 | var readIORequest, readIOByte, readIOLatency, writeIORequest, writeIOByte, writeIOLatency, miscIORequest, miscIOLatency uint64 586 | if err := rows.Scan(&myStatusgoQuery, &tableSchema, &tableName, &countStar, &totalLatency, &fetchRows, &fetchLatency, &insertRows, &insertLatency, &updateRows, &updateLatency, &deleteRows, &deleteLatency, 587 | &readIORequest, &readIOByte, &readIOLatency, &writeIORequest, &writeIOByte, &writeIOLatency, &miscIORequest, &miscIOLatency); err != nil { 588 | continue 589 | } 590 | key := fmt.Sprintf("%s.%s", tableSchema, tableName) 591 | val.strFileIOperTable[key] = &FileIOperTable{ 592 | TableSchema: tableSchema, 593 | TableName: tableName, 594 | CountStar: countStar, 595 | TotalLatency: totalLatency, 596 | FetchRows: fetchRows, 597 | FetchLatency: fetchLatency, 598 | InsertRows: insertRows, 599 | InsertLatency: insertLatency, 600 | UpdateRows: updateRows, 601 | UpdateLatency: updateLatency, 602 | DeleteRows: deleteRows, 603 | DeleteLatency: deleteLatency, 604 | ReadIORequest: readIORequest, 605 | ReadIOByte: readIOByte, 606 | ReadIOLatency: readIOLatency, 607 | WriteIORequest: writeIORequest, 608 | WriteIOByte: writeIOByte, 609 | WriteIOLatency: writeIOLatency, 610 | MiscIORequest: miscIORequest, 611 | MiscIOLatency: miscIOLatency, 612 | } 613 | } 614 | m.Unlock() 615 | return DBOK, "" 616 | } 617 | 618 | func (val *HostAllInfo) sqlExecThreads() (bool, string) { 619 | var rows *sql.Rows 620 | var err error 621 | db := val.db 622 | rows, err = db.Query(fmt.Sprintf(ThreadsSQL, TopN)) 623 | if err != nil { 624 | return checkMysqlErr(err) 625 | } 626 | defer rows.Close() 627 | cnt := 0 628 | m.Lock() 629 | val.ThreadsInfo = make([]ThreadsInfo, TopN) 630 | for rows.Next() { 631 | var myStatusgoQuery, user, host, db, time, cmd, sql, state string 632 | if err := rows.Scan(&myStatusgoQuery, &user, &host, &db, &time, &cmd, &sql, &state); err != nil { 633 | } 634 | if sql == "" { 635 | continue 636 | } 637 | if len(sql) < 1 { 638 | if state == "Slave has read all relay log; waiting for more updates" { 639 | continue 640 | } 641 | } 642 | val.ThreadsInfo[cnt].User = user 643 | val.ThreadsInfo[cnt].Host = host 644 | val.ThreadsInfo[cnt].Db = db 645 | val.ThreadsInfo[cnt].Time = time 646 | val.ThreadsInfo[cnt].State = state 647 | val.ThreadsInfo[cnt].Sql = sql 648 | val.ThreadsInfo[cnt].Cmd = cmd 649 | cnt++ 650 | if cnt == TopN { 651 | break 652 | } 653 | } 654 | m.Unlock() 655 | return DBOK, "" 656 | } 657 | 658 | func (val *HostAllInfo) sqlExecInnoDBLock() (bool, string) { 659 | var rows *sql.Rows 660 | var err error 661 | db := val.db 662 | rows, err = db.Query(InnodbLockSQL) 663 | if err != nil { 664 | return checkMysqlErr(err) 665 | } 666 | defer rows.Close() 667 | cnt := 0 668 | m.Lock() 669 | val.InnodbLockInfo = make([]InnodbLockInfo, TopN) 670 | for rows.Next() { 671 | var myStatusgoQuery, waitPidlist, blockPid, waitAge, lockedTable, lockedIndex, blockingQuery, waitCnt string 672 | if err := rows.Scan(&myStatusgoQuery, &waitPidlist, &waitCnt, &blockPid, &waitAge, &lockedTable, &lockedIndex, &blockingQuery); err != nil { 673 | } 674 | val.InnodbLockInfo[cnt].WaitPidlist = waitPidlist 675 | val.InnodbLockInfo[cnt].WaitCnt = waitCnt 676 | val.InnodbLockInfo[cnt].BlockPid = blockPid 677 | val.InnodbLockInfo[cnt].WaitAge = waitAge 678 | val.InnodbLockInfo[cnt].LockedTable = lockedTable 679 | val.InnodbLockInfo[cnt].LockedIndex = lockedIndex 680 | val.InnodbLockInfo[cnt].BlockingQuery = blockingQuery 681 | cnt++ 682 | if cnt == TopN { 683 | break 684 | } 685 | } 686 | m.Unlock() 687 | return DBOK, "" 688 | } 689 | 690 | func (val *HostAllInfo) sqlCheckSlowQuery(dbError *dbError) (bool, string) { 691 | var rows *sql.Rows 692 | var err error 693 | 694 | db := val.db 695 | rows, err = db.Query(fmt.Sprintf(SLOWCHECKSQL, dbError.slowCheckKey)) 696 | if err != nil { 697 | return checkMysqlErr(err) 698 | } 699 | defer rows.Close() 700 | for rows.Next() { 701 | var cnt int 702 | if err := rows.Scan(&cnt); err != nil { 703 | } 704 | if cnt != 0 { 705 | return DBERROR, "" 706 | } 707 | } 708 | return DBOK, "" 709 | } 710 | 711 | func checkMysqlErr(err error) (bool, string) { 712 | if mysqlErr, ok := err.(*mysql.MySQLError); ok { 713 | return DBOK, mysqlErr.Message 714 | } 715 | return DBERROR, "" 716 | } 717 | 718 | func rtnInt2Stg(v string, nval float64) float64 { 719 | f, _ := strconv.ParseFloat(v, 64) 720 | //i := int64(f) 721 | return nval + f 722 | } 723 | -------------------------------------------------------------------------------- /screen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "runtime" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | tm "github.com/buger/goterm" 14 | color "github.com/fatih/color" 15 | ) 16 | 17 | func metricColoring(metrics mapPrint) { 18 | yellow := color.New(color.FgYellow).SprintFunc() 19 | green := color.New(color.FgGreen).SprintFunc() 20 | magenta := color.New(color.FgMagenta).SprintFunc() 21 | white := color.New(color.FgWhite).SprintFunc() 22 | cyan := color.New(color.FgCyan).SprintFunc() 23 | red := color.New(color.FgRed).SprintFunc() 24 | blue := color.New(color.FgBlue).SprintFunc() 25 | for key, val := range metrics { 26 | v := val.(string) 27 | switch key { 28 | case DISKREAD, DISKWRITE, MEMUSED, MEMCACHE, MEMTOL, MEMBUF, MEMFREE, INBOUND, OUTBOUND, SWAPUSED, SWAPFRE, BUFPOOLTOTAL, BUFPOOLDATA, BUFPOOLDIRTY, BUFPOOLFREE, LSNMINUSCKPOINT: 29 | switch { 30 | case strings.Contains(v, "k"): 31 | metrics[key] = yellow(v) 32 | case strings.Contains(v, "M"): 33 | metrics[key] = green(v) 34 | case strings.Contains(v, "G"): 35 | metrics[key] = magenta(v) 36 | default: 37 | metrics[key] = white(v) 38 | } 39 | case CPUUSER, CPUSYS, CPUIO, CPUIDLE, CPUIRQ: 40 | i, _ := strconv.Atoi(v) 41 | switch { 42 | case i == 0: 43 | metrics[key] = white(v) 44 | case 0 < i && i <= 25: 45 | metrics[key] = cyan(v) 46 | case 25 < i && i <= 50: 47 | metrics[key] = yellow(v) 48 | case 50 < i && i <= 75: 49 | metrics[key] = red(v) 50 | case 75 < i && i <= 100: 51 | metrics[key] = green(v) 52 | } 53 | case RDONLY, RPLSEMIMAS, RPLSEMISLV: 54 | if v == "OFF" { 55 | metrics[key] = white(v) 56 | } else { 57 | metrics[key] = cyan(v) 58 | } 59 | case SLWLOG, SECBEHMAS, PSDIGESTLOST: 60 | i, _ := strconv.Atoi(v) 61 | if i != 0 { 62 | metrics[key] = red(v) 63 | } else { 64 | metrics[key] = white(v) 65 | } 66 | case AVGLATENCY, MAXLATENCY: 67 | switch { 68 | case strings.Contains(v, "ns"): 69 | metrics[key] = white(v) 70 | case strings.Contains(v, "us"): 71 | metrics[key] = blue(v) 72 | case strings.Contains(v, "ms"): 73 | metrics[key] = green(v) 74 | case strings.Contains(v, "s"): 75 | metrics[key] = yellow(v) 76 | default: 77 | metrics[key] = red(v) 78 | } 79 | 80 | case LOADAVG1, LOADAVG5, LOADAVG15: 81 | i := changeStr2Int(v) 82 | switch { 83 | case i < 0: 84 | metrics[key] = red(v) 85 | case 0 <= i && i <= 1: 86 | metrics[key] = white(v) 87 | case 1 < i && i <= 3: 88 | metrics[key] = cyan(v) 89 | case 3 < i && i <= 6: 90 | metrics[key] = green(v) 91 | case 6 < i && i <= 10: 92 | metrics[key] = yellow(v) 93 | case 10 < i: 94 | metrics[key] = red(v) 95 | } 96 | case THRUN, SLWSUM: 97 | i, _ := strconv.Atoi(v) 98 | switch { 99 | case i < 0: 100 | metrics[key] = red(v) 101 | case 0 <= i && i <= 10: 102 | metrics[key] = white(v) 103 | case 10 < i && i <= 30: 104 | metrics[key] = cyan(v) 105 | case 30 < i && i <= 100: 106 | metrics[key] = yellow(v) 107 | case 100 < i: 108 | metrics[key] = red(v) 109 | } 110 | case BUFDATAP, BUFFREEP, BUFMISCP: 111 | metrics[key] = white(v) 112 | default: 113 | i, _ := strconv.Atoi(v) 114 | switch { 115 | case i < 0: 116 | metrics[key] = red(v) 117 | case i == 0: 118 | metrics[key] = white(v) 119 | case 0 < i && i <= 500: 120 | metrics[key] = cyan(v) 121 | case 500 < i && i <= 3000: 122 | metrics[key] = green(v) 123 | case 3000 < i && i <= 10000: 124 | metrics[key] = yellow(v) 125 | case 100 < i: 126 | metrics[key] = red(v) 127 | } 128 | } 129 | } 130 | } 131 | 132 | func formatJSON(hostInfos []HostAllInfo) string { 133 | var buf bytes.Buffer 134 | var b []byte 135 | var o string 136 | for i := 0; i < len(hostInfos); i++ { 137 | b, _ = json.Marshal(hostInfos[i]) 138 | buf.Write(b) 139 | o += buf.String() 140 | } 141 | return o 142 | } 143 | 144 | func sendJSON(metric mapPrint) ([]byte, error) { 145 | outputJSON, err := json.Marshal(metric) 146 | if err != nil { 147 | panic(err) 148 | } 149 | return outputJSON, err 150 | } 151 | 152 | func (val *HostAllInfo) formatDetailEachInstance(idx int) (string, string, string, string) { 153 | listDigestText, listThreadText, listInnodbLockText, listFileIOTableText := "", "", "", "" 154 | errDigestText, errThreadText, errInnodbLockText, errFileIOTableText := "", "", "", "" 155 | if TopNPosition == -1 || idx == TopNPosition { 156 | errDigestText = formatErr(val.dbErrorPerQuery.digest, val.dberror, true) 157 | errThreadText = formatErr(val.dbErrorPerQuery.threads, val.dberror, true) 158 | errInnodbLockText = formatErr(val.dbErrorPerQuery.lock, val.dberror, true) 159 | errFileIOTableText = formatErr(val.dbErrorPerQuery.fileIO, val.dberror, true) 160 | for i := 0; i < TopN; i++ { 161 | listDigestText += val.DigestMetric[i].formatTopN(i) 162 | listThreadText += val.ThreadsInfo[i].formatTopN(i) 163 | listInnodbLockText += val.InnodbLockInfo[i].formatTopN(i) 164 | listFileIOTableText += val.FileIOMetric[i].formatTopN(i) 165 | if TopNPosition == -1 { 166 | break 167 | } 168 | } 169 | } 170 | if errDigestText != "" { 171 | listDigestText = errDigestText 172 | } 173 | if errThreadText != "" { 174 | listThreadText = errThreadText 175 | } 176 | if errInnodbLockText != "" { 177 | listInnodbLockText = errInnodbLockText 178 | } 179 | if errFileIOTableText != "" { 180 | listFileIOTableText = errFileIOTableText 181 | } 182 | return listDigestText, listThreadText, listInnodbLockText, listFileIOTableText 183 | } 184 | 185 | func formatErr(dbError dbError, dberror bool, instanceType bool) string { 186 | red := color.New(color.FgRed).SprintFunc() 187 | cursor := "" 188 | if instanceType { 189 | cursor = INSTANCECURSOR 190 | } 191 | if dberror == DBERROR { 192 | return fmt.Sprintf("%s %s", cursor, Error) 193 | } 194 | if dbError.errStatus == DBERROR { 195 | return fmt.Sprintf("%s %s", cursor, Error) 196 | } 197 | if dbError.errMessage != "" { 198 | return fmt.Sprintf("%s %s", cursor, red(dbError.errMessage)) 199 | } 200 | return "" 201 | } 202 | func (val *HostAllInfo) formatMySQLMetric(idx int) (string, string, string, string, string, string, string, string, string) { 203 | listDigestText, listThreadText, listInnodbLockText, listFileIOTableText := val.formatDetailEachInstance(idx) 204 | // show slave status 205 | sInfo, normalIoTh, normalSQLTh := val.formatSlaveInfoWithColoring() 206 | vNormal := fmt.Sprintf("%13s %12s %14s %15s %15s %15s %15s %16s %15s %13s %15s %16s %15s %14s %13s %13s %13s %15s %12s %12s", 207 | val.Metric[THCON], 208 | val.Metric[THRUN], 209 | val.Metric[ABORTCON], 210 | val.Metric[CSEL], 211 | val.Metric[CUPD], 212 | val.Metric[CINS], 213 | val.Metric[CDEL], 214 | val.Metric[CRLCE], 215 | val.Metric[QCACHHIT], 216 | val.Metric[CCALPROCEDURE], 217 | val.Metric[QPSALL], 218 | val.Metric[CSTMT], 219 | val.Metric[CCOMMIT], 220 | val.Metric[CROLLBACK], 221 | val.Metric[SLWLOG], 222 | val.Metric[SLWSUM], 223 | val.Metric[RDONLY], 224 | val.Metric[SECBEHMAS], 225 | normalIoTh, 226 | normalSQLTh) 227 | vHandler := fmt.Sprintf(" %17s %17s %17s %17s %17s %17s %17s %17s %17s %17s %17s", 228 | val.Metric[INNOROWRD], 229 | val.Metric[INNOROWINS], 230 | val.Metric[INNOROWUPD], 231 | val.Metric[INNOROWDEL], 232 | val.Metric[HADRRDFIRST], 233 | val.Metric[HADRRDKEY], 234 | val.Metric[HADRRDLAST], 235 | val.Metric[HADRRDNXT], 236 | val.Metric[HADRRDPRV], 237 | val.Metric[HADRRDRND], 238 | val.Metric[HADRRDRNDNXT]) 239 | if AllShowMetricFlg { 240 | vHandler = fmt.Sprintf("%s %17s %17s %17s %17s %17s %17s", 241 | vHandler, 242 | val.Metric[HADRWRT], 243 | val.Metric[HADRUPD], 244 | val.Metric[HADRDEL], 245 | val.Metric[HADRPREP], 246 | val.Metric[HADRCOMT], 247 | val.Metric[HADRRB]) 248 | } 249 | vPerf := val.formatPerf() 250 | vBuf := val.formatInnoDBBuffer() 251 | return vNormal, vHandler, sInfo, vPerf, listDigestText, listThreadText, listInnodbLockText, vBuf, listFileIOTableText 252 | } 253 | func (val *HostAllInfo) formatInnoDBBuffer() string { 254 | if val.dbErrorPerQuery.bufferPool.errMessage != "" || val.dbErrorPerQuery.bufferPool.errStatus == DBERROR { 255 | return fmt.Sprintf("%15s %15s %15s %15s %18s %18s %18s %16s %16s %16s %16s %15s %15s %15s %15s %17s", 256 | Error, Error, Error, Error, Error, Error, Error, Error, Error, Error, Error, Error, Error, Error, Error, Error) 257 | } 258 | return fmt.Sprintf("%15s %15s %15s %15s %18s %18s %18s %16s %16s %16s %16s %15s %15s %15s %15s %18s", 259 | val.Metric[BUFPOOLTOTAL], 260 | val.Metric[BUFPOOLDATA], 261 | val.Metric[BUFPOOLDIRTY], 262 | val.Metric[BUFPOOLFREE], 263 | val.Metric[BUFDATAP], 264 | val.Metric[BUFDIRTYP], 265 | val.Metric[BUFFREEP], 266 | val.Metric[BUFMISCP], 267 | val.Metric[BUFREAD], 268 | val.Metric[BUFREADREQ], 269 | val.Metric[BUFWRITEREQ], 270 | val.Metric[BUFWRITTEN], 271 | val.Metric[BUFCREATED], 272 | val.Metric[BUFFLUSH], 273 | val.Metric[LSNMINUSCKPOINT], 274 | fmt.Sprintf("%v/100", val.Metric[BUFFHITRATE])) 275 | 276 | } 277 | 278 | func (val *HostAllInfo) formatPerf() string { 279 | if val.dbErrorPerQuery.perf.errMessage != "" || val.dbErrorPerQuery.perf.errStatus == DBERROR { 280 | return fmt.Sprintf(" %14s %16s %16s %13s ", Error, Error, Error, Error) 281 | } 282 | return fmt.Sprintf(" %14s %16s %16s %13s ", val.Metric[SQLCNT], val.Metric[AVGLATENCY], val.Metric[MAXLATENCY], val.Metric[PSDIGESTLOST]) 283 | } 284 | func (val *HostAllInfo) formatOSMetric() string { 285 | printval := " %12s %12s %12s %12s %12s %13s %13s %13s %13s %13s %13s %13s %13s %13s %13s %13s %13s %13s" 286 | if val.promeerror == 0 && val.promPort != 0 { 287 | return fmt.Sprintf(printval, 288 | val.MetricOS[CPUUSER], 289 | val.MetricOS[CPUSYS], 290 | val.MetricOS[CPUIO], 291 | val.MetricOS[CPUIRQ], 292 | val.MetricOS[CPUIDLE], 293 | val.MetricOS[DISKREAD], 294 | val.MetricOS[DISKWRITE], 295 | val.MetricOS[INBOUND], 296 | val.MetricOS[OUTBOUND], 297 | val.MetricOS[LOADAVG1], 298 | val.MetricOS[LOADAVG5], 299 | val.MetricOS[LOADAVG15], 300 | val.MetricOS[MEMUSED], 301 | val.MetricOS[MEMBUF], 302 | val.MetricOS[MEMCACHE], 303 | val.MetricOS[MEMFREE], 304 | val.MetricOS[SWAPUSED], 305 | val.MetricOS[SWAPFRE]) 306 | } 307 | return fmt.Sprintf(" %12s", Error) 308 | 309 | } 310 | func (val *HostAllInfo) metricErr() { 311 | // dberror 312 | if val.dberror == DBERROR { 313 | for key, _ := range val.Metric { 314 | val.Metric[key] = Error 315 | } 316 | } 317 | } 318 | 319 | func (val *HostAllInfo) screenMetric(screenFlg int, idx int) (string, string, string, string, string, string, string, string, string, string) { 320 | metricColoring(val.Metric) 321 | metricColoring(val.MetricOS) 322 | val.metricErr() 323 | myNormal, myHdlr, mySlv, myPerm, myDigest, myThreads, myInnodbLock, myInnodbBuffer, myFileIOTable := val.formatMySQLMetric(idx) 324 | os := val.formatOSMetric() 325 | return myNormal, myHdlr, mySlv, myPerm, myDigest, myThreads, myInnodbLock, myInnodbBuffer, myFileIOTable, os 326 | 327 | } 328 | 329 | func (val *HostAllInfo) screenAllMetric(screenFlg int, idx int) string { 330 | myNormal, myHdlr, mySlv, myPerm, myDigest, myThreads, _, myInnodbBuffer, myFileIOTable, os := val.screenMetric(screenFlg, idx) 331 | out := modeAllTitleFormat(MODERESOURCE) 332 | out += myNormal + os + "\n\n" 333 | out += modeAllTitleFormat(MODEROW) 334 | out += myHdlr + "\n\n" 335 | out += modeAllTitleFormat(MODEINNOBUFFER) 336 | out += myInnodbBuffer + "\n\n" 337 | out += modeAllTitleFormat(MODETHREADS) 338 | out += myThreads + "\n\n" 339 | out += modeAllTitleFormat(MODEPERFORMACE) 340 | out += myPerm + myDigest + "\n\n" 341 | out += modeAllTitleFormat(MODESLAVE) 342 | out += mySlv + "\n\n" 343 | out += modeAllTitleFormat(MODEFILEIOTABLE) 344 | out += myFileIOTable + "\n\n" 345 | return out 346 | } 347 | 348 | func (val *HostAllInfo) screenMetricPrepare(screenFlg int, idx int) string { 349 | myNormal, myHdlr, mySlv, myPerm, myDigest, myThreads, myInnodbLock, myInnodbBuffer, myFileIOTable, os := val.screenMetric(screenFlg, idx) 350 | switch screenFlg { 351 | case MODENORMAL: 352 | return myNormal 353 | case MODERESOURCE: 354 | return myNormal + os 355 | case MODEPERFORMACE: 356 | return myPerm + myDigest 357 | case MODETHREADS: 358 | return myThreads 359 | case MODESLAVE: 360 | return mySlv 361 | case MODEROW: 362 | return myNormal + myHdlr 363 | case MODEINNOLOCK: 364 | return myInnodbLock 365 | case MODEINNOBUFFER: 366 | return myInnodbBuffer 367 | case MODEFILEIOTABLE: 368 | return myFileIOTable 369 | } 370 | return myNormal 371 | } 372 | func limitString(v string, maxlen int) string { 373 | if maxlen == 0 { 374 | return v 375 | } 376 | if len(v) > maxlen { 377 | v = v[:maxlen-1] + "." 378 | } 379 | return v 380 | } 381 | 382 | func hostNameSpace() string { 383 | if AllShowMetricFlg == false { 384 | return padS(HostnameSize, " ") + padS(DEFAULTPORTSIZE, " ") + " " 385 | } 386 | return "" 387 | } 388 | func (val *DigestMetric) formatTopN(idx int) string { 389 | length := HostFormatSize + len(INSTANCECURSOR) + PerfFormatSize 390 | cursor := INSTANCECURSOR 391 | newline := "" 392 | statementText := val.DigestText 393 | schemaName := val.SchemaName 394 | full := val.FullScan 395 | aLty := val.LatencyAvg 396 | rEa := val.RowExmAvg 397 | if val.CntStar == 0 { 398 | schemaName = "" 399 | statementText = "" 400 | full = "" 401 | aLty = "" 402 | rEa = "" 403 | } 404 | schemaName = limitString(schemaName, 8) 405 | if idx != 0 { 406 | cursor = hostNameSpace() + DIGESTFORMAT0 407 | newline = "\n" 408 | length = 0 409 | } 410 | return getStringUntilWidth(fmt.Sprintf("%s%s %8s %5s %7s %9s %6d %-s", 411 | newline, 412 | cursor, 413 | schemaName, 414 | full, 415 | rEa, 416 | formatTime(aLty), 417 | val.CntStar, 418 | statementText, 419 | ), length) 420 | } 421 | func (val *FileIOperTable) formatTopN(idx int) string { 422 | cursor := INSTANCECURSOR 423 | newline := "" 424 | tableSchema := limitString(val.TableSchema, 10) 425 | tableName := limitString(val.TableName, 20) 426 | totalLatency := formatTime(fmt.Sprintf("%d", val.TotalLatency)) 427 | fetchLatency := formatTime(fmt.Sprintf("%d", val.FetchLatency)) 428 | insertLatency := formatTime(fmt.Sprintf("%d", val.InsertLatency)) 429 | updateLatency := formatTime(fmt.Sprintf("%d", val.UpdateLatency)) 430 | deleteLatency := formatTime(fmt.Sprintf("%d", val.DeleteLatency)) 431 | readIOByte := humanRead(val.ReadIOByte) 432 | readIOLatency := formatTime(fmt.Sprintf("%d", val.ReadIOLatency)) 433 | writeIOByte := humanRead(val.WriteIOByte) 434 | writeIOLatency := formatTime(fmt.Sprintf("%d", val.WriteIOLatency)) 435 | miscIOLatency := formatTime(fmt.Sprintf("%d", val.MiscIOLatency)) 436 | 437 | if idx != 0 { 438 | cursor = hostNameSpace() + FILEIOTABFORMAT0 439 | newline = "\n" 440 | } 441 | return fmt.Sprintf("%s%s %10s %20s %10d %7s %8d %9s %7d %7s %7d %7s %7d %7s %7d %8s %8s %8d %9s %9s %7d %8s", 442 | newline, 443 | cursor, 444 | tableSchema, 445 | tableName, 446 | val.CountStar, 447 | totalLatency, 448 | val.FetchRows, 449 | fetchLatency, 450 | val.InsertRows, 451 | insertLatency, 452 | val.UpdateRows, 453 | updateLatency, 454 | val.DeleteRows, 455 | deleteLatency, 456 | val.ReadIORequest, 457 | readIOByte, 458 | readIOLatency, 459 | val.WriteIORequest, 460 | writeIOByte, 461 | writeIOLatency, 462 | val.MiscIORequest, 463 | miscIOLatency, 464 | ) 465 | } 466 | func (val *InnodbLockInfo) formatTopN(idx int) string { 467 | length := HostFormatSize + len(INSTANCECURSOR) 468 | cursor := INSTANCECURSOR 469 | newline := "" 470 | waitPidlist := limitString(val.WaitPidlist, 50) 471 | waitCnt := val.WaitCnt 472 | blockPid := val.BlockPid 473 | waitAge := val.WaitAge 474 | lockedTable := limitString(val.LockedTable, 30) 475 | lockedIndex := limitString(val.LockedIndex, 30) 476 | 477 | if idx != 0 { 478 | cursor = hostNameSpace() + INNOLOCKFORMAT0 479 | newline = "\n" 480 | length = 0 481 | } 482 | return getStringUntilWidth(fmt.Sprintf("%s%s %10s %30s %30s %4s %4s %-s", 483 | newline, 484 | cursor, 485 | blockPid, 486 | lockedTable, 487 | lockedIndex, 488 | waitAge, 489 | waitCnt, 490 | waitPidlist, 491 | ), length) 492 | } 493 | 494 | func (val *ThreadsInfo) formatTopN(idx int) string { 495 | length := HostFormatSize + len(INSTANCECURSOR) 496 | cursor := INSTANCECURSOR 497 | newline := "" 498 | user := limitString(val.User, 7) 499 | host := val.Host 500 | db := limitString(val.Db, 7) 501 | time := val.Time 502 | state := limitString(val.State, 20) 503 | vsql := removeNewline(val.Sql) 504 | cmd := limitString(val.Cmd, 7) 505 | 506 | if idx != 0 { 507 | cursor = hostNameSpace() + THREADFORMAT0 508 | newline = "\n" 509 | length = 0 510 | } 511 | 512 | return getStringUntilWidth(fmt.Sprintf("%s%s %-7s %-20s %-7s %15s %7s %5s %-s", 513 | newline, 514 | cursor, 515 | cmd, 516 | state, 517 | user, 518 | host, 519 | db, 520 | time, 521 | vsql, 522 | ), length) 523 | } 524 | 525 | func getStringUntilWidth(str string, length int) string { 526 | if FullSQLStatement == true { 527 | return str 528 | } 529 | width := tm.Width() - length 530 | if width < 0 { 531 | return str 532 | } 533 | // "\n" count 1 534 | if length == 0 { 535 | width++ 536 | } 537 | if len(str) > width { 538 | str = limitString(str, width) 539 | } 540 | return str 541 | } 542 | func (val *HostAllInfo) formatSlaveInfoWithColoring() (string, string, string) { 543 | red := color.New(color.FgRed).SprintFunc() 544 | white := color.New(color.FgWhite).SprintFunc() 545 | sInfo := formatErr(val.dbErrorPerQuery.slave, val.dberror, false) 546 | normalIoTh := white(" ") 547 | normalSQLTh := white(" ") 548 | if sInfo != "" { 549 | return sInfo, normalIoTh, normalSQLTh 550 | } 551 | if len(val.SlaveInfo) > 0 { 552 | normalIoTh = white("Yes") 553 | normalSQLTh = white("Yes") 554 | } 555 | for _, SlaveInfo := range val.SlaveInfo { 556 | iothread := "" 557 | sqlthread := "" 558 | secbhdmaster := "" 559 | if strings.Contains(SlaveInfo.SlaveIO, "Yes") { 560 | iothread = white(SlaveInfo.SlaveIO) 561 | } else { 562 | iothread = red(limitString(SlaveInfo.SlaveIO, 3)) 563 | normalIoTh = red(limitString(SlaveInfo.SlaveIO, 3)) 564 | } 565 | if strings.Contains(SlaveInfo.SlaveSQL, "Yes") { 566 | sqlthread = white(SlaveInfo.SlaveSQL) 567 | } else { 568 | sqlthread = red(limitString(SlaveInfo.SlaveSQL, 3)) 569 | normalSQLTh = red(limitString(SlaveInfo.SlaveSQL, 3)) 570 | } 571 | i, _ := strconv.Atoi(SlaveInfo.SecBehdMas) 572 | if i != 0 { 573 | secbhdmaster = red(SlaveInfo.SecBehdMas) 574 | } else { 575 | secbhdmaster = white(SlaveInfo.SecBehdMas) 576 | } 577 | if GtidMode { 578 | retvgtid := removeNewline(SlaveInfo.RetvGTID) 579 | exegtid := removeNewline(SlaveInfo.ExeGTID) 580 | if sInfo == "" { 581 | sInfo += fmt.Sprintf("%15s %12s %12s %15s %14s %14s %14s %-100s\n %s %-100s", 582 | limitString(SlaveInfo.Host, 15), 583 | iothread, 584 | sqlthread, 585 | secbhdmaster, 586 | val.Metric[RPLSEMIMAS], 587 | val.Metric[RPLSEMISLV], 588 | limitString(SlaveInfo.ChlNm, 14), 589 | retvgtid, 590 | padS(HostnameSize, " ")+padS(DEFAULTPORTSIZE, " ")+padS(58, " "), 591 | exegtid) 592 | } else { 593 | sInfo += fmt.Sprintf("\n%25s %5s %15s %12s %12s %15s %14s %14s %14s %-100s\n %s %-100s", 594 | " ", " ", 595 | limitString(SlaveInfo.Host, 15), 596 | iothread, 597 | sqlthread, 598 | secbhdmaster, 599 | val.Metric[RPLSEMIMAS], 600 | val.Metric[RPLSEMISLV], 601 | limitString(SlaveInfo.ChlNm, 14), 602 | retvgtid, 603 | padS(HostnameSize, " ")+padS(DEFAULTPORTSIZE, " ")+padS(58, " "), 604 | exegtid) 605 | } 606 | } else { 607 | if sInfo == "" { 608 | sInfo += fmt.Sprintf("%15s %12s %12s %17s %10s %15s %10s %15s %14s %14s %-17s", 609 | limitString(SlaveInfo.Host, 15), 610 | iothread, 611 | sqlthread, 612 | SlaveInfo.MsLogFile, 613 | SlaveInfo.RMasLogPos, 614 | SlaveInfo.RelMasLogFile, 615 | SlaveInfo.ExeMasLogPos, 616 | secbhdmaster, 617 | val.Metric[RPLSEMIMAS], 618 | val.Metric[RPLSEMISLV], 619 | SlaveInfo.ChlNm) 620 | } else { 621 | sInfo += fmt.Sprintf("\n%25s %5s %15s %12s %12s %17s %10s %15s %10s %15s %14s %14s %-17s", 622 | " ", " ", 623 | limitString(SlaveInfo.Host, 15), 624 | iothread, 625 | sqlthread, 626 | SlaveInfo.MsLogFile, 627 | SlaveInfo.RMasLogPos, 628 | SlaveInfo.RelMasLogFile, 629 | SlaveInfo.ExeMasLogPos, 630 | secbhdmaster, 631 | val.Metric[RPLSEMIMAS], 632 | val.Metric[RPLSEMISLV], 633 | SlaveInfo.ChlNm) 634 | } 635 | } 636 | } 637 | return sInfo, normalIoTh, normalSQLTh 638 | } 639 | 640 | func removeColor(out string) string { 641 | out = strings.Replace(out, BLUE, "", -1) 642 | out = strings.Replace(out, RED, "", -1) 643 | out = strings.Replace(out, GREEN, "", -1) 644 | out = strings.Replace(out, YELLOW, "", -1) 645 | out = strings.Replace(out, MAZENDA, "", -1) 646 | out = strings.Replace(out, WHITE, "", -1) 647 | out = strings.Replace(out, CYAN, "", -1) 648 | out = strings.Replace(out, COLEND, "", -1) 649 | return out 650 | } 651 | 652 | func getMetricForFile(now int, old int, title *string, content *string, nowTime string, outputTitle int) string { 653 | var out string 654 | if now == old && outputTitle != 1 { 655 | out = fmt.Sprintf("%s\n%s", nowTime, *content) 656 | } else { 657 | out = *title + *content 658 | } 659 | return removeColor(out) 660 | } 661 | func flush(title *string, out *string) { 662 | var buf string 663 | //tm.Clear() 664 | callClear() 665 | tm.MoveCursor(1, 1) 666 | // teminal.go overflow pages of Flush() does not work well 667 | for idx, str := range strings.SplitAfter(*title+*out, "\n") { 668 | if idx >= tm.Height()-2 { 669 | break 670 | } 671 | buf += str 672 | } 673 | 674 | //tm.Println(buf) 675 | fmt.Fprint(color.Output, buf) 676 | tm.Output = bufio.NewWriter(color.Output) 677 | tm.Flush() 678 | } 679 | 680 | func callClear() { 681 | value, ok := clear[runtime.GOOS] //runtime.GOOS -> linux, windows, darwin etc. 682 | if ok { //if we defined a clear func for that platform: 683 | value() //we execute it 684 | } else { //unsupported platform 685 | panic("Your platform is unsupported! I can't clear terminal screen :(") 686 | } 687 | } 688 | func fileIOTableHeaderColoring() string { 689 | magenta := color.New(color.FgMagenta).SprintFunc() 690 | blue := color.New(color.FgBlue).SprintFunc() 691 | str := blue(" Schema") 692 | str += blue(" TableName/FileName ") 693 | if SortTableFileIOPosition == SORTNUM1 { 694 | str += magenta("AllRequest ") 695 | } else { 696 | str += blue("AllRequest ") 697 | } 698 | str += blue("AllLtcy ") 699 | if SortTableFileIOPosition == SORTNUM2 { 700 | str += magenta("FetchRow ") 701 | } else { 702 | str += blue("FetchRow ") 703 | } 704 | str += blue("FetchLtcy ") 705 | if SortTableFileIOPosition == SORTNUM3 { 706 | str += magenta("InsRows ") 707 | } else { 708 | str += blue("InsRows ") 709 | } 710 | str += blue("InsLtcy ") 711 | if SortTableFileIOPosition == SORTNUM4 { 712 | str += magenta("UpdRows ") 713 | } else { 714 | str += blue("UpdRows ") 715 | } 716 | str += blue("UpdLtcy ") 717 | if SortTableFileIOPosition == SORTNUM5 { 718 | str += magenta("DelRows ") 719 | } else { 720 | str += blue("DelRows ") 721 | } 722 | str += blue("DelLtcy ") 723 | if SortTableFileIOPosition == SORTNUM6 { 724 | str += magenta("ReadReq ") 725 | } else { 726 | str += blue("ReadReq ") 727 | } 728 | str += blue("ReadByte ReadLtcy ") 729 | if SortTableFileIOPosition == SORTNUM7 { 730 | str += magenta("WriteReq ") 731 | } else { 732 | str += blue("WriteReq ") 733 | } 734 | str += blue("WriteByte WriteLtcy MiscReq MiscLtcy") 735 | 736 | return str 737 | 738 | } 739 | 740 | func removeNewline(str string) string { 741 | buf := "" 742 | for _, v := range strings.Split(str, "\n") { 743 | buf += strings.TrimSpace(v) + " " 744 | } 745 | return buf 746 | } 747 | func padS(no int, char string) string { 748 | rtn := "" 749 | for i := 0; i < no; i++ { 750 | rtn += char 751 | } 752 | return rtn 753 | } 754 | func hostPortFormat(no int) string { 755 | str := "Hostname" 756 | if no > DEFAULTHOSTNAMESIZE { 757 | for i := 0; i < no-DEFAULTHOSTNAMESIZE; i++ { 758 | str = str + " " 759 | } 760 | } 761 | str = str + " Port" 762 | return str 763 | } 764 | 765 | func modeHeaderColoring(screenFlg int) string { 766 | white := color.New(color.FgWhite).SprintFunc() 767 | magenta := color.New(color.FgMagenta).SprintFunc() 768 | buf := " Mode : " 769 | if screenFlg == MODEOFF || screenFlg == MODENORMAL { 770 | buf += magenta("F1.Normal ") 771 | } else { 772 | buf += white("F1.Normal ") 773 | } 774 | if screenFlg == MODERESOURCE { 775 | buf += magenta("F2.OS Metric ") 776 | } else { 777 | buf += white("F2.OS Metric ") 778 | } 779 | if screenFlg == MODETHREADS { 780 | buf += magenta("F3.Threads ") 781 | } else { 782 | buf += white("F3.Threads ") 783 | } 784 | if screenFlg == MODEPERFORMACE { 785 | buf += magenta("F4.P_S Info ") 786 | } else { 787 | buf += white("F4.P_S Info ") 788 | } 789 | if screenFlg == MODESLAVE { 790 | buf += magenta("F5.SlaveStatus ") 791 | } else { 792 | buf += white("F5.SlaveStatus ") 793 | } 794 | if screenFlg == MODEROW { 795 | buf += magenta("F6.Handler/InnoDB_Rows ") 796 | } else { 797 | buf += white("F6.Handler/InnoDB_Rows ") 798 | } 799 | if screenFlg == MODEINNOLOCK { 800 | buf += magenta("F7.InnoDB Lock Info ") 801 | } else { 802 | buf += white("F7.InnoDB Lock Info ") 803 | } 804 | if screenFlg == MODEINNOBUFFER { 805 | buf += magenta("F8.InnoDB Buffer Info ") 806 | } else { 807 | buf += white("F8.InnoDB Buffer Info ") 808 | } 809 | if screenFlg == MODEFILEIOTABLE { 810 | buf += magenta("F9.Table IO Statistic ") 811 | } else { 812 | buf += white("F9.Table IO Statistic ") 813 | } 814 | buf += "(LEFT[KeyArrowLeft]/RIGHT[KeyArrowRight])\n" 815 | return buf 816 | 817 | } 818 | func hostTitleFormat() string { 819 | var drawTitle string 820 | drawTitle += fmt.Sprintf("%s\n", HostNamePortTag) 821 | drawTitle += fmt.Sprintf("%s\n", HostNamePort) 822 | drawTitle += fmt.Sprintf("%s\n", HostNamePortTag) 823 | return drawTitle 824 | } 825 | func modeAllTitleFormat(screenFlg int) string { 826 | var drawTitle string 827 | switch screenFlg { 828 | case MODEOFF, MODENORMAL: 829 | drawTitle += fmt.Sprintf("%s\n", MyFormat1) 830 | drawTitle += fmt.Sprintf("%s\n", MyFormat2) 831 | drawTitle += fmt.Sprintf("%s\n", MyFormat3) 832 | case MODERESOURCE: 833 | drawTitle += fmt.Sprintf("%s %s \n", MyFormat1, OSFormat1) 834 | drawTitle += fmt.Sprintf("%s %s \n", MyFormat2, OSFormat2) 835 | drawTitle += fmt.Sprintf("%s %s \n", MyFormat3, OSFormat3) 836 | case MODETHREADS: 837 | drawTitle += fmt.Sprintf("%s %s \n", Cursol1, ThreadFormat1) 838 | drawTitle += fmt.Sprintf("%s %s \n", Cursol2, ThreadFormat2) 839 | drawTitle += fmt.Sprintf("%s %s \n", Cursol3, ThreadFormat3) 840 | case MODEPERFORMACE: 841 | drawTitle += fmt.Sprintf("%s %s %s \n", PerfScmFormat1, Cursol1, DigestFormat1) 842 | drawTitle += fmt.Sprintf("%s %s %s \n", PerfScmFormat2, Cursol2, DigestFormat2) 843 | drawTitle += fmt.Sprintf("%s %s %s \n", PerfScmFormat3, Cursol3, DigestFormat3) 844 | case MODESLAVE: 845 | sFormat1 := MySlaFormat1 846 | sFormat2 := MySlaFormat2 847 | sFormat3 := MySlaFormat3 848 | if GtidMode { 849 | sFormat1 = MySlaFormatGTID1 850 | sFormat2 = MySlaFormatGTID2 851 | sFormat3 = MySlaFormatGTID3 852 | } 853 | drawTitle += fmt.Sprintf("%s \n", sFormat1) 854 | drawTitle += fmt.Sprintf("%s \n", sFormat2) 855 | drawTitle += fmt.Sprintf("%s \n", sFormat3) 856 | case MODEROW: 857 | drawTitle += fmt.Sprintf(" %s\n", MyHadrAllFormat1) 858 | drawTitle += fmt.Sprintf(" %s\n", MyHadrAllFormat2) 859 | drawTitle += fmt.Sprintf(" %s\n", MyHadrAllFormat3) 860 | case MODEINNOBUFFER: 861 | drawTitle += fmt.Sprintf("%s\n", InnoBufFormat1) 862 | drawTitle += fmt.Sprintf("%s\n", InnoBufFormat2) 863 | drawTitle += fmt.Sprintf("%s\n", InnoBufFormat3) 864 | case MODEFILEIOTABLE: 865 | drawTitle += fmt.Sprintf("%s\n", FileIOTabFormat4) 866 | drawTitle += fmt.Sprintf("%s %s\n", Cursol1, FileIOTabFormat1) 867 | drawTitle += fmt.Sprintf("%s %s\n", Cursol2, fileIOTableHeaderColoring()) 868 | drawTitle += fmt.Sprintf("%s %s\n", Cursol3, FileIOTabFormat3) 869 | } 870 | return drawTitle 871 | } 872 | 873 | func modeTitleFormat(screenFlg int) string { 874 | var drawTitle string 875 | switch screenFlg { 876 | case MODEOFF, MODENORMAL: 877 | drawTitle += fmt.Sprintf("%s %s\n", HostNamePortTag, MyFormat1) 878 | drawTitle += fmt.Sprintf("%s %s\n", HostNamePort, MyFormat2) 879 | drawTitle += fmt.Sprintf("%s %s \n", HostNamePortTag, MyFormat3) 880 | case MODERESOURCE: 881 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, MyFormat1, OSFormat1) 882 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePort, MyFormat2, OSFormat2) 883 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, MyFormat3, OSFormat3) 884 | case MODETHREADS: 885 | drawTitle += fmt.Sprintf("%s\n", KeyArrowUpDownFormat) 886 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, Cursol1, ThreadFormat1) 887 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePort, Cursol2, ThreadFormat2) 888 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, Cursol3, ThreadFormat3) 889 | case MODEPERFORMACE: 890 | drawTitle += fmt.Sprintf("%s\n", KeyArrowUpDownFormat) 891 | drawTitle += fmt.Sprintf("%s %s %s %s\n", HostNamePortTag, PerfScmFormat1, Cursol1, DigestFormat1) 892 | drawTitle += fmt.Sprintf("%s %s %s %s\n", HostNamePort, PerfScmFormat2, Cursol2, DigestFormat2) 893 | drawTitle += fmt.Sprintf("%s %s %s %s\n", HostNamePortTag, PerfScmFormat3, Cursol3, DigestFormat3) 894 | case MODESLAVE: 895 | sFormat1 := MySlaFormat1 896 | sFormat2 := MySlaFormat2 897 | sFormat3 := MySlaFormat3 898 | if GtidMode { 899 | sFormat1 = MySlaFormatGTID1 900 | sFormat2 = MySlaFormatGTID2 901 | sFormat3 = MySlaFormatGTID3 902 | } 903 | drawTitle += fmt.Sprintf("%s %s \n", HostNamePortTag, sFormat1) 904 | drawTitle += fmt.Sprintf("%s %s \n", HostNamePort, sFormat2) 905 | drawTitle += fmt.Sprintf("%s %s \n", HostNamePortTag, sFormat3) 906 | case MODEROW: 907 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, MyFormat1, MyHadrFormat1) 908 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePort, MyFormat2, MyHadrFormat2) 909 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, MyFormat3, MyHadrFormat3) 910 | case MODEINNOLOCK: 911 | drawTitle += fmt.Sprintf("%s\n", KeyArrowUpDownFormat) 912 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, Cursol1, InnoLockFormat1) 913 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePort, Cursol2, InnoLockFormat2) 914 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, Cursol3, InnoLockFormat3) 915 | case MODEINNOBUFFER: 916 | drawTitle += fmt.Sprintf("%s %s\n", HostNamePortTag, InnoBufFormat1) 917 | drawTitle += fmt.Sprintf("%s %s\n", HostNamePort, InnoBufFormat2) 918 | drawTitle += fmt.Sprintf("%s %s\n", HostNamePortTag, InnoBufFormat3) 919 | case MODEFILEIOTABLE: 920 | drawTitle += fmt.Sprintf("%s\n", FileIOTabFormat4) 921 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, Cursol1, FileIOTabFormat1) 922 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePort, Cursol2, fileIOTableHeaderColoring()) 923 | drawTitle += fmt.Sprintf("%s %s %s\n", HostNamePortTag, Cursol3, FileIOTabFormat3) 924 | } 925 | return drawTitle 926 | } 927 | func headerColoring() { 928 | white := color.New(color.FgWhite).SprintFunc() 929 | red := color.New(color.FgRed).SprintFunc() 930 | blue := color.New(color.FgBlue).SprintFunc() 931 | 932 | Error = red("-1") 933 | TopFormat = white(" now() : %s [Start : %s] [Esc or SpaceBar.Exit] \n") 934 | FinishFormat = white(" [Start : %s] [AutoStop : %s ]\n") 935 | OSFormat1 = blue("------- CPU ------- -- Disk - -- NW --- ---load-avg--- ------ memory ---- - swap -- ") 936 | OSFormat2 = blue("usr sys wai irq idl read writ recv send 1m 5m 15m used buff cach free swap free ") 937 | OSFormat3 = blue("--- --- --- --- --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ") 938 | MyFormat1 = blue("------------------------------------------- QPS ------------------------------------------------------------------------") 939 | MyFormat2 = blue("Conn Run Abort Select Update Insert Delete Replace Qcache Call QPSAll StmtExc Commit Rolbk Slow Ssum Read Delay IO SQL") 940 | MyFormat3 = blue("---- --- ----- ------ ------ ------ ------ ------- ------ ---- ------ ------- ------ ----- ---- ---- ---- ------ --- ---") 941 | MySlaFormat1 = blue("---------------------------------------- SLAVE STATUS -------------------------------------------------------------") 942 | MySlaFormat2 = blue(" MasterHost IO SQL Master_Log MasterPos Relay_M_Log Exec_Pos Delay SemiM SemiS ChannelName ") 943 | MySlaFormat3 = blue("--------------- --- --- ----------------- ---------- ----------------- ---------- ------ ----- ----- --------------") 944 | MySlaFormatGTID1 = blue("---------------------------------------- SLAVE STATUS -------------------------------------------------------------") 945 | MySlaFormatGTID2 = blue(" MasterHost IO SQL Delay SemiM SemiS ChannelName Retrieved_Gtid_Set/Executed_Gtid_Set ") 946 | MySlaFormatGTID3 = blue("--------------- --- --- ------ ----- ----- -------------- ---------------------------------------------------------") 947 | MyHadrFormat1 = blue("------------ InnoDB Rows ---------- ---------------------------- Handlar -------------------------") 948 | MyHadrFormat2 = blue(" read inserted updated deleted RdFirst RdKey RdLast RdNxt RdPrev RdRnd RdRndNxt") 949 | MyHadrFormat3 = blue("-------- -------- -------- -------- -------- -------- -------- -------- -------- -------- --------") 950 | MyHadrAllFormat1 = blue("------------ InnoDB Rows ---------- -----------------------------------------------Handlar--------------------------------------------------------------") 951 | MyHadrAllFormat2 = blue(" read inserted updated deleted RdFirst RdKey RdLast RdNxt RdPrev RdRnd RdRndNxt Write Update Delete Prepare Commit Rollback") 952 | MyHadrAllFormat3 = blue("-------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- --------") 953 | KeyArrowUpDownFormat = white(fmt.Sprintf(" Display per Servers (Select Up[KeyArrowUp] or Down[KeyArrowDown] ) , Display Full SQL by pushing F12 [%v]", FullSQLStatement)) 954 | perfBuf := "---- performace_schema ----" 955 | // Space +1 956 | PerfFormatSize = len(perfBuf) + 1 957 | PerfScmFormat1 = blue(perfBuf) 958 | PerfScmFormat2 = blue(" SQL Avg Max Lost") 959 | PerfScmFormat3 = blue("------ ------- ------- ----") 960 | Cursol1 = blue("-") 961 | Cursol2 = blue("*") 962 | Cursol3 = blue("-") 963 | DigestFormat1 = blue("--------------------- Statement Digest -----------------------------------------------------------------------------------------------------") 964 | DigestFormat2 = blue(" Schema Ful AvgEm AvgLtcy ExCnt Query") 965 | DigestFormat3 = blue("-------- ----- ------- --------- ------- ---------------------------------------------------------------------------------------------------") 966 | ThreadFormat1 = blue("------------------------------- Thread Info ----------------------------------------------------------------------------------------------------------") 967 | ThreadFormat2 = blue(" Cmd State User Host DB Time Query ") 968 | ThreadFormat3 = blue("------- -------------------- ------- --------------- ------- ----- -----------------------------------------------------------------------------------") 969 | InnoLockFormat1 = blue("-------------------------- InnoDB Lock Info -----------------------------------------------------------------------------------------------------") 970 | InnoLockFormat2 = blue(" BlockPid Locked Table Locked Index time Cnt Waiting Pid ") 971 | InnoLockFormat3 = blue("---------- ------------------------------ ------------------------------ ---- ---- --------------------------------------------------------------") 972 | InnoBufFormat1 = blue("-------------------------------------------------- InnoDB Buffer Pool Info ----------------------------------------------------") 973 | InnoBufFormat2 = blue(" Total Data Dirty Free DataPage DirtyPage FreePage MiscPag Read RdReq WritReq Writen Create Flush LSN-CK HitRate ") 974 | InnoBufFormat3 = blue("------ ------ ------ ------ --------- --------- --------- ------- ------- ------- ------- ------ ------ ------ ------ ---------") 975 | FileIOTabFormat1 = blue("---------------------------------------------------------------------------------- Table/File IO Statistics ---------------------------------------------------------------------------------") 976 | FileIOTabFormat3 = blue("---------- -------------------- ---------- ------- -------- --------- ------- ------- ------- ------- ------- ------- ------- -------- -------- -------- --------- --------- ------- --------") 977 | FileIOTabFormat4 = white(" (Choose Sort Field by [Tab] key)") 978 | HostNamePortTag = blue(padS(HostnameSize, "-") + " " + padS(DEFAULTPORTSIZE, "-")) 979 | HostNamePort = blue(hostPortFormat(HostnameSize)) 980 | 981 | } 982 | func createHeader(screenFlg int) string { 983 | var buf string 984 | headerColoring() 985 | buf += fmt.Sprintf(TopFormat, time.Now().Format(TIMELAYOUT), StartTime.Format(TIMELAYOUT)) 986 | if AllShowMetricFlg == true { 987 | return buf 988 | } 989 | buf += modeHeaderColoring(screenFlg) 990 | buf += modeTitleFormat(screenFlg) 991 | return buf 992 | } 993 | 994 | func (val *HostAllInfo) createHostFormat() string { 995 | buf := fmt.Sprintf("%-"+fmt.Sprintf("%d", HostnameSize)+"s %"+fmt.Sprintf("%d", DEFAULTPORTSIZE)+"s", val.alias, val.port) 996 | HostFormatSize = len(buf) 997 | return buf 998 | } 999 | 1000 | func screen(hostInfos []HostAllInfo, screenFlg int, screenHostlist []string, sElaspTime time.Time) (string, string) { 1001 | var drawTitle string 1002 | var drawAll string 1003 | drawTitle += createHeader(screenFlg) 1004 | drawBuf := "" 1005 | // loop all hosts 1006 | for i := 0; i < len(screenHostlist); i++ { 1007 | // just show the comment lines 1008 | if strings.Contains(screenHostlist[i], "#") { 1009 | if AllShowMetricFlg == false { 1010 | drawAll += fmt.Sprintf("%s\n", screenHostlist[i]) 1011 | } else { 1012 | drawBuf = fmt.Sprintf("%s\n", screenHostlist[i]) 1013 | } 1014 | continue 1015 | } 1016 | ii, _ := strconv.Atoi(screenHostlist[i]) 1017 | // if host find out, formating metric 1018 | host := hostInfos[ii].createHostFormat() 1019 | if screenFlg != MODEOFF { 1020 | if AllShowMetricFlg == false { 1021 | drawAll += fmt.Sprintf("%s %s\n", host, hostInfos[ii].screenMetricPrepare(screenFlg, ii)) 1022 | } else { 1023 | if TopNPosition == ii { 1024 | drawAll += drawBuf 1025 | drawAll += hostInfos[ii].Hname + ":" + hostInfos[ii].port + "\n" 1026 | drawAll += hostInfos[ii].screenAllMetric(screenFlg, ii) 1027 | } 1028 | } 1029 | } else { 1030 | hostInfos[ii].topNPosition = ii 1031 | drawAll += fmt.Sprintf("%s\n", host) 1032 | } 1033 | } 1034 | 1035 | eElaspTime := (time.Now()).Sub(sElaspTime) 1036 | drawAll += fmt.Sprintf("\n\n>> Elapsed time : %2v\n", eElaspTime) 1037 | flush(&drawTitle, &drawAll) 1038 | return drawTitle, drawAll 1039 | } 1040 | --------------------------------------------------------------------------------