vitess.io/vitess@v0.16.2/go/vt/vtgate/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 vtgate 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/vtgate/engine" 31 ) 32 33 var ( 34 queryzHeader = []byte(`<thead> 35 <tr> 36 <th>Query</th> 37 <th>Count</th> 38 <th>Time</th> 39 <th>Shard Queries</th> 40 <th>RowsAffected</th> 41 <th>RowsReturned</th> 42 <th>Errors</th> 43 <th>Time per query</th> 44 <th>Shard queries per query</th> 45 <th>RowsAffected per query</th> 46 <th>RowsReturned per query</th> 47 <th>Errors per query</th> 48 </tr> 49 </thead> 50 `) 51 queryzTmpl = template.Must(template.New("example").Parse(` 52 <tr class="{{.Color}}"> 53 <td>{{.Query}}</td> 54 <td>{{.Count}}</td> 55 <td>{{.Time}}</td> 56 <td>{{.ShardQueries}}</td> 57 <td>{{.RowsAffected}}</td> 58 <td>{{.RowsReturned}}</td> 59 <td>{{.Errors}}</td> 60 <td>{{.TimePQ}}</td> 61 <td>{{.ShardQueriesPQ}}</td> 62 <td>{{.RowsAffectedPQ}}</td> 63 <td>{{.RowsReturnedPQ}}</td> 64 <td>{{.ErrorsPQ}}</td> 65 </tr> 66 `)) 67 ) 68 69 // queryzRow is used for rendering query stats 70 // using go's template. 71 type queryzRow struct { 72 Query string 73 Table string 74 Count uint64 75 tm time.Duration 76 ShardQueries uint64 77 RowsAffected uint64 78 RowsReturned uint64 79 Errors uint64 80 Color string 81 } 82 83 // Time returns the total time as a string. 84 func (qzs *queryzRow) Time() string { 85 return fmt.Sprintf("%.6f", float64(qzs.tm)/1e9) 86 } 87 88 func (qzs *queryzRow) timePQ() float64 { 89 return float64(qzs.tm) / (1e9 * float64(qzs.Count)) 90 } 91 92 // TimePQ returns the time per query as a string. 93 func (qzs *queryzRow) TimePQ() string { 94 return fmt.Sprintf("%.6f", qzs.timePQ()) 95 } 96 97 // ShardQueriesPQ returns the shard query count per query as a string. 98 func (qzs *queryzRow) ShardQueriesPQ() string { 99 val := float64(qzs.ShardQueries) / float64(qzs.Count) 100 return fmt.Sprintf("%.6f", val) 101 } 102 103 // RowsAffectedPQ returns the row affected per query as a string. 104 func (qzs *queryzRow) RowsAffectedPQ() string { 105 val := float64(qzs.RowsAffected) / float64(qzs.Count) 106 return fmt.Sprintf("%.6f", val) 107 } 108 109 // RowsReturnedPQ returns the row returned per query as a string. 110 func (qzs *queryzRow) RowsReturnedPQ() string { 111 val := float64(qzs.RowsReturned) / float64(qzs.Count) 112 return fmt.Sprintf("%.6f", val) 113 } 114 115 // ErrorsPQ returns the error count per query as a string. 116 func (qzs *queryzRow) ErrorsPQ() string { 117 return fmt.Sprintf("%.6f", float64(qzs.Errors)/float64(qzs.Count)) 118 } 119 120 type queryzSorter struct { 121 rows []*queryzRow 122 less func(row1, row2 *queryzRow) bool 123 } 124 125 func (s *queryzSorter) Len() int { return len(s.rows) } 126 func (s *queryzSorter) Swap(i, j int) { s.rows[i], s.rows[j] = s.rows[j], s.rows[i] } 127 func (s *queryzSorter) Less(i, j int) bool { return s.less(s.rows[i], s.rows[j]) } 128 129 func queryzHandler(e *Executor, w http.ResponseWriter, r *http.Request) { 130 if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { 131 acl.SendError(w, err) 132 return 133 } 134 logz.StartHTMLTable(w) 135 defer logz.EndHTMLTable(w) 136 w.Write(queryzHeader) 137 138 sorter := queryzSorter{ 139 rows: nil, 140 less: func(row1, row2 *queryzRow) bool { 141 return row1.timePQ() > row2.timePQ() 142 }, 143 } 144 145 e.plans.ForEach(func(value any) bool { 146 plan := value.(*engine.Plan) 147 Value := &queryzRow{ 148 Query: logz.Wrappable(sqlparser.TruncateForUI(plan.Original)), 149 } 150 Value.Count, Value.tm, Value.ShardQueries, Value.RowsAffected, Value.RowsReturned, Value.Errors = plan.Stats() 151 var timepq time.Duration 152 if Value.Count != 0 { 153 timepq = time.Duration(uint64(Value.tm) / Value.Count) 154 } 155 if timepq < 10*time.Millisecond { 156 Value.Color = "low" 157 } else if timepq < 100*time.Millisecond { 158 Value.Color = "medium" 159 } else { 160 Value.Color = "high" 161 } 162 sorter.rows = append(sorter.rows, Value) 163 return true 164 }) 165 166 sort.Sort(&sorter) 167 for _, row := range sorter.rows { 168 if err := queryzTmpl.Execute(w, row); err != nil { 169 log.Errorf("queryz: couldn't execute template: %v", err) 170 } 171 } 172 }