vitess.io/vitess@v0.16.2/go/vt/throttler/throttlerlogz.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 throttler
    18  
    19  import (
    20  	"fmt"
    21  	"html/template"
    22  	"io"
    23  	"net/http"
    24  	"strings"
    25  	"time"
    26  
    27  	"vitess.io/vitess/go/vt/logz"
    28  )
    29  
    30  const logHeaderHTML = `
    31    <style>
    32  		table.gridtable th {
    33  		  /* Override the nowrap default to avoid that the table overflows. */
    34  			white-space: normal;
    35  		}
    36    </style>
    37  	<thead>
    38  		<tr>
    39  			<th>Now</th>
    40  			<th>Rate Change</th>
    41  			<th>Old Rate</th>
    42  			<th>New Rate</th>
    43  			<th>Tablet</th>
    44  			<th>Lag</th>
    45  			<th>Last Change</th>
    46  			<th>Actual Rate</th>
    47  			<th>Good/&#8203;Bad?</th>
    48  			<th>If Skipped</th>
    49  			<th>Highest Good</th>
    50  			<th>Lowest Bad</th>
    51  			<th>Old State</th>
    52  			<th>Tested State</th>
    53  			<th>New State</th>
    54  			<th>Lag Before</th>
    55  			<th>Recorded Ago</th>
    56  			<th>Primary Rate</th>
    57  			<th>Replica Rate</th>
    58  			<th>Old Backlog</th>
    59  			<th>New Backlog</th>
    60  			<th>Reason</th>
    61  	  </tr>
    62    </thead>
    63  `
    64  
    65  const logEntryHTML = `
    66      <tr class="{{.ColorLevel}}">
    67        <td>{{.Now.Format "15:04:05"}}</td>
    68        <td>{{.RateChange}}</td>
    69        <td>{{.OldRate}}</td>
    70        <td>{{.NewRate}}</td>
    71        <td>{{.Alias}}</td>
    72        <td>{{.LagRecordNow.Stats.ReplicationLagSeconds}}s</td>
    73        <td>{{.TimeSinceLastRateChange}}</td>
    74        <td>{{.CurrentRate}}</td>
    75        <td>{{.GoodOrBad}}</td>
    76        <td>{{.MemorySkipReason}}</td>
    77        <td>{{.HighestGood}}</td>
    78        <td>{{.LowestBad}}</td>
    79        <td>{{.OldState}}</td>
    80        <td>{{.TestedState}}</td>
    81        <td>{{.NewState}}</td>
    82        <td>{{.LagBefore}}</td>
    83        <td>{{.AgeOfBeforeLag}}</td>
    84        <td>{{.PrimaryRate}}</td>
    85        <td>{{.GuessedReplicationRate}}</td>
    86        <td>{{.GuessedReplicationBacklogOld}}</td>
    87        <td>{{.GuessedReplicationBacklogNew}}</td>
    88        <td>{{.Reason}}</td>
    89      </tr>
    90  `
    91  
    92  const logFooterHTML = `
    93  {{.Count}} lag records spanning the last {{.TimeSpan}} minutes are displayed.
    94  `
    95  
    96  var (
    97  	logEntryTemplate  = template.Must(template.New("logEntry").Parse(logEntryHTML))
    98  	logFooterTemplate = template.Must(template.New("logFooter").Parse(logFooterHTML))
    99  )
   100  
   101  func init() {
   102  	http.HandleFunc("/throttlerlogz/", func(w http.ResponseWriter, r *http.Request) {
   103  		throttlerlogzHandler(w, r, GlobalManager)
   104  	})
   105  }
   106  
   107  func throttlerlogzHandler(w http.ResponseWriter, r *http.Request, m *managerImpl) {
   108  	// Longest supported URL: /throttlerlogz/<name>
   109  	parts := strings.SplitN(r.URL.Path, "/", 3)
   110  
   111  	if len(parts) != 3 {
   112  		errMsg := fmt.Sprintf("invalid /throttlerlogz path: %q expected paths: /throttlerlogz/ or /throttlerlogz/<throttler name>", r.URL.Path)
   113  		http.Error(w, errMsg, http.StatusInternalServerError)
   114  		return
   115  	}
   116  
   117  	name := parts[2]
   118  	if name == "" {
   119  		// If no name is given, redirect to the list of throttlers at /throttlerz.
   120  		http.Redirect(w, r, "/throttlerz", http.StatusTemporaryRedirect)
   121  		return
   122  	}
   123  
   124  	showThrottlerLog(w, m, name)
   125  }
   126  
   127  func showThrottlerLog(w http.ResponseWriter, m *managerImpl, name string) {
   128  	results, err := m.log(name)
   129  	if err != nil {
   130  		http.Error(w, err.Error(), http.StatusInternalServerError)
   131  		return
   132  	}
   133  
   134  	logz.StartHTMLTable(w)
   135  
   136  	if _, err := io.WriteString(w, logHeaderHTML); err != nil {
   137  		panic(fmt.Sprintf("failed to execute logHeader template: %v", err))
   138  	}
   139  	for _, r := range results {
   140  		// Color based on max(tested state, new state).
   141  		state := r.TestedState
   142  		if stateGreater(r.NewState, state) {
   143  			state = r.NewState
   144  		}
   145  		var colorLevel string
   146  		switch state {
   147  		case stateIncreaseRate:
   148  			colorLevel = "low"
   149  		case stateDecreaseAndGuessRate:
   150  			colorLevel = "medium"
   151  		case stateEmergency:
   152  			colorLevel = "high"
   153  		}
   154  		data := struct {
   155  			result
   156  			ColorLevel string
   157  		}{r, colorLevel}
   158  
   159  		if err := logEntryTemplate.Execute(w, data); err != nil {
   160  			panic(fmt.Sprintf("failed to execute logEntry template: %v", err))
   161  		}
   162  	}
   163  
   164  	logz.EndHTMLTable(w)
   165  
   166  	// Print footer.
   167  	count := len(results)
   168  	var d time.Duration
   169  	if count > 0 {
   170  		d = results[0].Now.Sub(results[count-1].Now)
   171  	}
   172  	if err := logFooterTemplate.Execute(w, map[string]any{
   173  		"Count":    count,
   174  		"TimeSpan": fmt.Sprintf("%.1f", d.Minutes()),
   175  	}); err != nil {
   176  		panic(fmt.Sprintf("failed to execute logFooter template: %v", err))
   177  	}
   178  }