vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/querylogz.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  	"io"
    22  	"net/http"
    23  	"strconv"
    24  	"strings"
    25  	"text/template"
    26  	"time"
    27  
    28  	"vitess.io/vitess/go/acl"
    29  	"vitess.io/vitess/go/vt/log"
    30  	"vitess.io/vitess/go/vt/logz"
    31  	"vitess.io/vitess/go/vt/sqlparser"
    32  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    33  )
    34  
    35  var (
    36  	querylogzHeader = []byte(`
    37  		<thead>
    38  			<tr>
    39  				<th>Method</th>
    40  				<th>Context</th>
    41  				<th>Effective Caller</th>
    42  				<th>Immediate Caller</th>
    43  				<th>Start</th>
    44  				<th>End</th>
    45  				<th>Duration</th>
    46  				<th>MySQL time</th>
    47  				<th>Conn wait</th>
    48  				<th>Plan</th>
    49  				<th>SQL</th>
    50  				<th>Queries</th>
    51  				<th>Sources</th>
    52  				<th>RowsAffected</th>
    53  				<th>Response Size</th>
    54  				<th>Transaction ID</th>
    55  				<th>Reserved ID</th>
    56  				<th>Error</th>
    57  			</tr>
    58  		</thead>
    59  	`)
    60  	querylogzFuncMap = template.FuncMap{
    61  		"stampMicro":    func(t time.Time) string { return t.Format(time.StampMicro) },
    62  		"cssWrappable":  logz.Wrappable,
    63  		"truncateQuery": sqlparser.TruncateForUI,
    64  		"unquote":       func(s string) string { return strings.Trim(s, "\"") },
    65  	}
    66  	querylogzTmpl = template.Must(template.New("example").Funcs(querylogzFuncMap).Parse(`
    67  		<tr class="{{.ColorLevel}}">
    68  			<td>{{.Method}}</td>
    69  			<td>{{.ContextHTML}}</td>
    70  			<td>{{.EffectiveCaller}}</td>
    71  			<td>{{.ImmediateCaller}}</td>
    72  			<td>{{.StartTime | stampMicro}}</td>
    73  			<td>{{.EndTime | stampMicro}}</td>
    74  			<td>{{.TotalTime.Seconds}}</td>
    75  			<td>{{.MysqlResponseTime.Seconds}}</td>
    76  			<td>{{.WaitingForConnection.Seconds}}</td>
    77  			<td>{{.PlanType}}</td>
    78  			<td>{{.OriginalSQL | truncateQuery | unquote | cssWrappable}}</td>
    79  			<td>{{.NumberOfQueries}}</td>
    80  			<td>{{.FmtQuerySources}}</td>
    81  			<td>{{.RowsAffected}}</td>
    82  			<td>{{.SizeOfResponse}}</td>
    83  			<td>{{.TransactionID}}</td>
    84  			<td>{{.ReservedID}}</td>
    85  			<td>{{.ErrorStr}}</td>
    86  		</tr>
    87  	`))
    88  )
    89  
    90  func init() {
    91  	http.HandleFunc("/querylogz", func(w http.ResponseWriter, r *http.Request) {
    92  		ch := tabletenv.StatsLogger.Subscribe("querylogz")
    93  		defer tabletenv.StatsLogger.Unsubscribe(ch)
    94  		querylogzHandler(ch, w, r)
    95  	})
    96  }
    97  
    98  // querylogzHandler serves a human readable snapshot of the
    99  // current query log.
   100  func querylogzHandler(ch chan any, w http.ResponseWriter, r *http.Request) {
   101  	if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil {
   102  		acl.SendError(w, err)
   103  		return
   104  	}
   105  	timeout, limit := parseTimeoutLimitParams(r)
   106  	logz.StartHTMLTable(w)
   107  	defer logz.EndHTMLTable(w)
   108  	w.Write(querylogzHeader)
   109  
   110  	tmr := time.NewTimer(timeout)
   111  	defer tmr.Stop()
   112  	for i := 0; i < limit; i++ {
   113  		select {
   114  		case out := <-ch:
   115  			select {
   116  			case <-tmr.C:
   117  				return
   118  			default:
   119  			}
   120  			stats, ok := out.(*tabletenv.LogStats)
   121  			if !ok {
   122  				err := fmt.Errorf("unexpected value in %s: %#v (expecting value of type %T)", tabletenv.TxLogger.Name(), out, &tabletenv.LogStats{})
   123  				io.WriteString(w, `<tr class="error">`)
   124  				io.WriteString(w, err.Error())
   125  				io.WriteString(w, "</tr>")
   126  				log.Error(err)
   127  				continue
   128  			}
   129  			var level string
   130  			if stats.TotalTime().Seconds() < 0.01 {
   131  				level = "low"
   132  			} else if stats.TotalTime().Seconds() < 0.1 {
   133  				level = "medium"
   134  			} else {
   135  				level = "high"
   136  			}
   137  			tmplData := struct {
   138  				*tabletenv.LogStats
   139  				ColorLevel string
   140  			}{stats, level}
   141  			if err := querylogzTmpl.Execute(w, tmplData); err != nil {
   142  				log.Errorf("querylogz: couldn't execute template: %v", err)
   143  			}
   144  		case <-tmr.C:
   145  			return
   146  		}
   147  	}
   148  }
   149  
   150  func parseTimeoutLimitParams(req *http.Request) (time.Duration, int) {
   151  	timeout := 10
   152  	limit := 300
   153  	if ts, ok := req.URL.Query()["timeout"]; ok {
   154  		if t, err := strconv.Atoi(ts[0]); err == nil {
   155  			timeout = adjustValue(t, 0, 60)
   156  		}
   157  	}
   158  	if l, ok := req.URL.Query()["limit"]; ok {
   159  		if lim, err := strconv.Atoi(l[0]); err == nil {
   160  			limit = adjustValue(lim, 1, 200000)
   161  		}
   162  	}
   163  	return time.Duration(timeout) * time.Second, limit
   164  }
   165  
   166  func adjustValue(val int, lower int, upper int) int {
   167  	if val < lower {
   168  		return lower
   169  	} else if val > upper {
   170  		return upper
   171  	}
   172  	return val
   173  }