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 }