vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/queryz.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tabletserver 18 19 import ( 20 "fmt" 21 "html/template" 22 "net/http" 23 "sort" 24 "time" 25 26 "vitess.io/vitess/go/acl" 27 "vitess.io/vitess/go/vt/log" 28 "vitess.io/vitess/go/vt/logz" 29 "vitess.io/vitess/go/vt/sqlparser" 30 "vitess.io/vitess/go/vt/vttablet/tabletserver/planbuilder" 31 ) 32 33 var ( 34 queryzHeader = []byte(`<thead> 35 <tr> 36 <th>Query</th> 37 <th>Table</th> 38 <th>Plan</th> 39 <th>Count</th> 40 <th>Time</th> 41 <th>MySQL Time</th> 42 <th>Rows affected</th> 43 <th>Rows returned</th> 44 <th>Errors</th> 45 <th>Time per query</th> 46 <th>MySQL Time per query</th> 47 <th>Rows affected per query</th> 48 <th>Rows returned per query</th> 49 <th>Errors per query</th> 50 </tr> 51 </thead> 52 `) 53 queryzTmpl = template.Must(template.New("example").Parse(` 54 <tr class="{{.Color}}"> 55 <td>{{.Query}}</td> 56 <td>{{.Table}}</td> 57 <td>{{.Plan}}</td> 58 <td>{{.Count}}</td> 59 <td>{{.Time}}</td> 60 <td>{{.MysqlTime}}</td> 61 <td>{{.RowsAffected}}</td> 62 <td>{{.RowsReturned}}</td> 63 <td>{{.Errors}}</td> 64 <td>{{.TimePQ}}</td> 65 <td>{{.MysqlTimePQ}}</td> 66 <td>{{.RowsAffectedPQ}}</td> 67 <td>{{.RowsReturnedPQ}}</td> 68 <td>{{.ErrorsPQ}}</td> 69 </tr> 70 `)) 71 ) 72 73 // queryzRow is used for rendering query stats 74 // using go's template. 75 type queryzRow struct { 76 Query string 77 Table string 78 Plan planbuilder.PlanType 79 Count uint64 80 tm time.Duration 81 mysqlTime time.Duration 82 RowsAffected uint64 83 RowsReturned uint64 84 Errors uint64 85 Color string 86 } 87 88 // Time returns the total time as a string. 89 func (qzs *queryzRow) Time() string { 90 return fmt.Sprintf("%.6f", float64(qzs.tm)/1e9) 91 } 92 93 func (qzs *queryzRow) timePQ() float64 { 94 return float64(qzs.tm) / (1e9 * float64(qzs.Count)) 95 } 96 97 // TimePQ returns the time per query as a string. 98 func (qzs *queryzRow) TimePQ() string { 99 return fmt.Sprintf("%.6f", qzs.timePQ()) 100 } 101 102 // MysqlTime returns the MySQL time as a string. 103 func (qzs *queryzRow) MysqlTime() string { 104 return fmt.Sprintf("%.6f", float64(qzs.mysqlTime)/1e9) 105 } 106 107 // MysqlTimePQ returns the time per query as a string. 108 func (qzs *queryzRow) MysqlTimePQ() string { 109 val := float64(qzs.mysqlTime) / (1e9 * float64(qzs.Count)) 110 return fmt.Sprintf("%.6f", val) 111 } 112 113 // RowsReturnedPQ returns the row count per query as a string. 114 func (qzs *queryzRow) RowsReturnedPQ() string { 115 val := float64(qzs.RowsReturned) / float64(qzs.Count) 116 return fmt.Sprintf("%.6f", val) 117 } 118 119 // RowsAffectedPQ returns the row count per query as a string. 120 func (qzs *queryzRow) RowsAffectedPQ() string { 121 val := float64(qzs.RowsAffected) / float64(qzs.Count) 122 return fmt.Sprintf("%.6f", val) 123 } 124 125 // ErrorsPQ returns the error count per query as a string. 126 func (qzs *queryzRow) ErrorsPQ() string { 127 return fmt.Sprintf("%.6f", float64(qzs.Errors)/float64(qzs.Count)) 128 } 129 130 type queryzSorter struct { 131 rows []*queryzRow 132 less func(row1, row2 *queryzRow) bool 133 } 134 135 func (s *queryzSorter) Len() int { return len(s.rows) } 136 func (s *queryzSorter) Swap(i, j int) { s.rows[i], s.rows[j] = s.rows[j], s.rows[i] } 137 func (s *queryzSorter) Less(i, j int) bool { return s.less(s.rows[i], s.rows[j]) } 138 139 func queryzHandler(qe *QueryEngine, w http.ResponseWriter, r *http.Request) { 140 if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { 141 acl.SendError(w, err) 142 return 143 } 144 logz.StartHTMLTable(w) 145 defer logz.EndHTMLTable(w) 146 w.Write(queryzHeader) 147 148 sorter := queryzSorter{ 149 rows: nil, 150 less: func(row1, row2 *queryzRow) bool { 151 return row1.timePQ() > row2.timePQ() 152 }, 153 } 154 qe.plans.ForEach(func(value any) bool { 155 plan := value.(*TabletPlan) 156 if plan == nil { 157 return true 158 } 159 Value := &queryzRow{ 160 Query: logz.Wrappable(sqlparser.TruncateForUI(plan.Original)), 161 Table: plan.TableName().String(), 162 Plan: plan.PlanID, 163 } 164 Value.Count, Value.tm, Value.mysqlTime, Value.RowsAffected, Value.RowsReturned, Value.Errors = plan.Stats() 165 var timepq time.Duration 166 if Value.Count != 0 { 167 timepq = Value.tm / time.Duration(Value.Count) 168 } 169 if timepq < 10*time.Millisecond { 170 Value.Color = "low" 171 } else if timepq < 100*time.Millisecond { 172 Value.Color = "medium" 173 } else { 174 Value.Color = "high" 175 } 176 sorter.rows = append(sorter.rows, Value) 177 return true 178 }) 179 sort.Sort(&sorter) 180 for _, Value := range sorter.rows { 181 if err := queryzTmpl.Execute(w, Value); err != nil { 182 log.Errorf("queryz: couldn't execute template: %v", err) 183 } 184 } 185 }