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  }