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  }