// Copyright 2012, Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package tabletserver import ( "fmt" "html/template" "net/http" "sort" "time" "github.com/youtube/vitess/go/acl" "github.com/youtube/vitess/go/vt/tabletserver/planbuilder" ) var ( queryzHeader = []byte(` Query Table Plan Reason Count Time Rows Errors Time per query Rows per query Errors per query `) queryzTmpl = template.Must(template.New("example").Parse(` {{.Query}} {{.Table}} {{.Plan}} {{.Reason}} {{.Count}} {{.Time}} {{.Rows}} {{.Errors}} {{.TimePQ}} {{.RowsPQ}} {{.ErrorsPQ}} `)) ) // queryzRow is used for rendering query stats // using go's template. type queryzRow struct { Query string Table string Plan planbuilder.PlanType Reason planbuilder.ReasonType Count int64 tm time.Duration Rows int64 Errors int64 Color string } // Time returns the total time as a string. func (qzs *queryzRow) Time() string { return fmt.Sprintf("%.6f", float64(qzs.tm)/1e9) } func (qzs *queryzRow) timePQ() float64 { return float64(qzs.tm) / (1e9 * float64(qzs.Count)) } // TimePQ returns the time per query as a string. func (qzs *queryzRow) TimePQ() string { return fmt.Sprintf("%.6f", qzs.timePQ()) } func (qzs *queryzRow) rowsPQ() float64 { return float64(qzs.Rows) / float64(qzs.Count) } // RowsPQ returns the row count per query as a string. func (qzs *queryzRow) RowsPQ() string { return fmt.Sprintf("%.6f", qzs.rowsPQ()) } // ErrorsPQ returns the error count per query as a string. func (qzs *queryzRow) ErrorsPQ() string { return fmt.Sprintf("%.6f", float64(qzs.Errors)/float64(qzs.Count)) } type queryzSorter struct { rows []*queryzRow less func(row1, row2 *queryzRow) bool } func (sorter *queryzSorter) Len() int { return len(sorter.rows) } func (sorter *queryzSorter) Swap(i, j int) { sorter.rows[i], sorter.rows[j] = sorter.rows[j], sorter.rows[i] } func (sorter *queryzSorter) Less(i, j int) bool { return sorter.less(sorter.rows[i], sorter.rows[j]) } func init() { http.HandleFunc("/queryz", queryzHandler) } // queryzHandler displays the query stats. func queryzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(queryzHeader) si := SqlQueryRpcService.qe.schemaInfo keys := si.queries.Keys() sorter := queryzSorter{ rows: make([]*queryzRow, 0, len(keys)), less: func(row1, row2 *queryzRow) bool { return row1.timePQ() > row2.timePQ() }, } for _, v := range si.queries.Keys() { plan := si.getQuery(v) if plan == nil { continue } Value := &queryzRow{ Query: wrappable(v), Table: plan.TableName, Plan: plan.PlanId, Reason: plan.Reason, } Value.Count, Value.tm, Value.Rows, Value.Errors = plan.Stats() timepq := time.Duration(int64(Value.tm) / Value.Count) if timepq < 10*time.Millisecond { Value.Color = "low" } else if timepq < 100*time.Millisecond { Value.Color = "medium" } else { Value.Color = "high" } sorter.rows = append(sorter.rows, Value) } sort.Sort(&sorter) for _, Value := range sorter.rows { queryzTmpl.Execute(w, Value) } }