go.uber.org/yarpc@v1.72.1/x/debug/debug.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package debug
    22  
    23  import (
    24  	"html/template"
    25  	"io"
    26  	"net/http"
    27  	"runtime/debug"
    28  
    29  	"go.uber.org/yarpc"
    30  	"go.uber.org/yarpc/internal/introspection"
    31  	"go.uber.org/zap"
    32  )
    33  
    34  var (
    35  	// _defaultTmpl is the default template used.
    36  	_defaultTmpl = template.Must(template.New("tmpl").Parse(`
    37  <html>
    38  	<head>
    39  	<title>/debug/yarpc</title>
    40  	<style type="text/css">
    41  		body {
    42  			font-family: "Courier New", Courier, monospace;
    43  		}
    44  		table {
    45  			color:#333333;
    46  			border-width: 1px;
    47  			border-color: #3A3A3A;
    48  			border-collapse: collapse;
    49  		}
    50  		table th {
    51  			border-width: 1px;
    52  			padding: 8px;
    53  			border-style: solid;
    54  			border-color: #3A3A3A;
    55  			background-color: #B3B3B3;
    56  		}
    57  		table td {
    58  			border-width: 1px;
    59  			padding: 8px;
    60  			border-style: solid;
    61  			border-color: #3A3A3A;
    62  			background-color: #ffffff;
    63  		}
    64  		header::after {
    65  			content: "";
    66  			clear: both;
    67  			display: table;
    68  		}
    69  		h1 {
    70  			width: 40%;
    71  			float: left;
    72  			margin: 0;
    73  		}
    74  		div.dependencies {
    75  			width: 60%;
    76  			float: left;
    77  			font-size: small;
    78  			text-align: right;
    79  		}
    80  	</style>
    81  	</head>
    82  	<body>
    83  
    84  <header>
    85  <h1>/debug/yarpc</h1>
    86  <div class="dependencies">
    87  	{{range .PackageVersions}}
    88  	<span>{{.Name}}={{.Version}}</span>
    89  	{{end}}
    90  </div>
    91  </header>
    92  
    93  {{range .Dispatchers}}
    94  	<hr />
    95  	<h2>Dispatcher "{{.Name}}" <small>({{.ID}})</small></h2>
    96  	<table>
    97  		<tr>
    98  			<th>Procedure</th>
    99  			<th>Encoding</th>
   100  			<th>Signature</th>
   101  			<th>RPC Type</th>
   102  		</tr>
   103  		{{range .Procedures}}
   104  		<tr>
   105  			<td>{{.ProcedureName}}</td>
   106  			<td>{{.Encoding}}</td>
   107  			<td>{{.Signature}}</td>
   108  			<td>{{.RPCType}}</td>
   109  		</tr>
   110  		{{end}}
   111  	</table>
   112  	<h3>Inbounds</h3>
   113  	<table>
   114  		<tr>
   115  			<th>Transport</th>
   116  			<th>Endpoint</th>
   117  			<th>State</th>
   118  		</tr>
   119  		{{range .Inbounds}}
   120  		<tr>
   121  			<td>{{.Transport}}</td>
   122  			<td>{{.Endpoint}}</td>
   123  			<td>{{.State}}</td>
   124  		</tr>
   125  		{{end}}
   126  	</table>
   127  	<h3>Outbounds</h3>
   128  	<table>
   129  		<thead>
   130  		<tr>
   131  			<th>Outbound Key</th>
   132  			<th>Service</th>
   133  			<th>Transport</th>
   134  			<th>RPC Type</th>
   135  			<th>Endpoint</th>
   136  			<th>State</th>
   137  			<th colspan="3">Chooser</th>
   138  		</tr>
   139  		<tr>
   140  			<th></th>
   141  			<th></th>
   142  			<th></th>
   143  			<th></th>
   144  			<th></th>
   145  			<th>Name</th>
   146  			<th>State</th>
   147  			<th>Peers</th>
   148  		</tr>
   149  		</thead>
   150  		<tbody>
   151  		{{range .Outbounds}}
   152  		<tr>
   153  			<td>{{.OutboundKey}}</td>
   154  			<td>{{.Service}}</td>
   155  			<td>{{.Transport}}</td>
   156  			<td>{{.RPCType}}</td>
   157  			<td>{{.Endpoint}}</td>
   158  			<td>{{.State}}</td>
   159  			<td>{{.Chooser.Name}}</td>
   160  			<td>{{.Chooser.State}}</td>
   161  			<td>
   162  				<ul>
   163  				{{range .Chooser.Peers}}
   164  					<li>{{.Identifier}} ({{.State}})</li>
   165  				{{end}}
   166  				</ul>
   167  			</td>
   168  		</tr>
   169  		</tbody>
   170  		{{end}}
   171  	</table>
   172  {{end}}
   173  	</body>
   174  </html>
   175  `))
   176  )
   177  
   178  // NewHandler returns a http.HandlerFunc to expose dispatcher status and package versions.
   179  func NewHandler(dispatcher *yarpc.Dispatcher, opts ...Option) http.HandlerFunc {
   180  	return newHandler(dispatcher, opts...).handle
   181  }
   182  
   183  type handler struct {
   184  	dispatcher *yarpc.Dispatcher
   185  	logger     *zap.Logger
   186  	tmpl       templateIface
   187  }
   188  
   189  func newHandler(dispatcher *yarpc.Dispatcher, options ...Option) *handler {
   190  	opts := applyOptions(options...)
   191  	return &handler{
   192  		dispatcher: dispatcher,
   193  		logger:     opts.logger,
   194  		tmpl:       opts.tmpl,
   195  	}
   196  }
   197  
   198  func (h *handler) handle(responseWriter http.ResponseWriter, _ *http.Request) {
   199  	defer func() {
   200  		if r := recover(); r != nil {
   201  			responseWriter.WriteHeader(http.StatusInternalServerError)
   202  			h.logger.Error("Unary handler panicked:", zap.Any("recover", r), zap.ByteString("stacktrace", debug.Stack()))
   203  		}
   204  	}()
   205  	responseWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
   206  	if err := h.tmpl.Execute(responseWriter, newTmplData(h.dispatcher.Introspect())); err != nil {
   207  		// TODO: does this work, since we already tried a write?
   208  		responseWriter.WriteHeader(http.StatusInternalServerError)
   209  		h.logger.Error("yarpc/debug: failed executing template", zap.Error(err))
   210  	}
   211  }
   212  
   213  type tmplData struct {
   214  	Dispatchers     []introspection.DispatcherStatus
   215  	PackageVersions []introspection.PackageVersion
   216  }
   217  
   218  func newTmplData(dispatcherStatus introspection.DispatcherStatus) *tmplData {
   219  	// TODO: Why don't we just use dispatcherStatus as the data directly, it has
   220  	// PackageVersions on it already, do we want to use multiple dispatchers in the future?
   221  	return &tmplData{
   222  		Dispatchers: []introspection.DispatcherStatus{
   223  			dispatcherStatus,
   224  		},
   225  		PackageVersions: yarpc.PackageVersions,
   226  	}
   227  }
   228  
   229  // templateIface represents a template created from either the html/template
   230  // or text/template packages.
   231  type templateIface interface {
   232  	Execute(io.Writer, interface{}) error
   233  }