github.com/v2fly/tools@v0.100.0/internal/lsp/debug/rpc.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package debug 6 7 import ( 8 "context" 9 "fmt" 10 "html/template" 11 "net/http" 12 "sort" 13 "sync" 14 "time" 15 16 "github.com/v2fly/tools/internal/event" 17 "github.com/v2fly/tools/internal/event/core" 18 "github.com/v2fly/tools/internal/event/export" 19 "github.com/v2fly/tools/internal/event/label" 20 "github.com/v2fly/tools/internal/lsp/debug/tag" 21 ) 22 23 var RPCTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 24 {{define "title"}}RPC Information{{end}} 25 {{define "body"}} 26 <H2>Inbound</H2> 27 {{template "rpcSection" .Inbound}} 28 <H2>Outbound</H2> 29 {{template "rpcSection" .Outbound}} 30 {{end}} 31 {{define "rpcSection"}} 32 {{range .}}<P> 33 <b>{{.Method}}</b> {{.Started}} <a href="/trace/{{.Method}}">traces</a> ({{.InProgress}} in progress) 34 <br> 35 <i>Latency</i> {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}} 36 <i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}} 37 <br> 38 <i>Received</i> {{.Received}} (avg. {{.ReceivedMean}}) 39 <i>Sent</i> {{.Sent}} (avg. {{.SentMean}}) 40 <br> 41 <i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}} 42 </P> 43 {{end}} 44 {{end}} 45 `)) 46 47 type Rpcs struct { // exported for testing 48 mu sync.Mutex 49 Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name 50 Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name 51 } 52 53 type rpcStats struct { 54 Method string 55 Started int64 56 Completed int64 57 58 Latency rpcTimeHistogram 59 Received byteUnits 60 Sent byteUnits 61 Codes []*rpcCodeBucket 62 } 63 64 type rpcTimeHistogram struct { 65 Sum timeUnits 66 Count int64 67 Min timeUnits 68 Max timeUnits 69 Values []rpcTimeBucket 70 } 71 72 type rpcTimeBucket struct { 73 Limit timeUnits 74 Count int64 75 } 76 77 type rpcCodeBucket struct { 78 Key string 79 Count int64 80 } 81 82 func (r *Rpcs) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { 83 r.mu.Lock() 84 defer r.mu.Unlock() 85 switch { 86 case event.IsStart(ev): 87 if _, stats := r.getRPCSpan(ctx, ev); stats != nil { 88 stats.Started++ 89 } 90 case event.IsEnd(ev): 91 span, stats := r.getRPCSpan(ctx, ev) 92 if stats != nil { 93 endRPC(ctx, ev, span, stats) 94 } 95 case event.IsMetric(ev): 96 sent := byteUnits(tag.SentBytes.Get(lm)) 97 rec := byteUnits(tag.ReceivedBytes.Get(lm)) 98 if sent != 0 || rec != 0 { 99 if _, stats := r.getRPCSpan(ctx, ev); stats != nil { 100 stats.Sent += sent 101 stats.Received += rec 102 } 103 } 104 } 105 return ctx 106 } 107 108 func endRPC(ctx context.Context, ev core.Event, span *export.Span, stats *rpcStats) { 109 // update the basic counts 110 stats.Completed++ 111 112 // get and record the status code 113 if status := getStatusCode(span); status != "" { 114 var b *rpcCodeBucket 115 for c, entry := range stats.Codes { 116 if entry.Key == status { 117 b = stats.Codes[c] 118 break 119 } 120 } 121 if b == nil { 122 b = &rpcCodeBucket{Key: status} 123 stats.Codes = append(stats.Codes, b) 124 sort.Slice(stats.Codes, func(i int, j int) bool { 125 return stats.Codes[i].Key < stats.Codes[j].Key 126 }) 127 } 128 b.Count++ 129 } 130 131 // calculate latency if this was an rpc span 132 elapsedTime := span.Finish().At().Sub(span.Start().At()) 133 latencyMillis := timeUnits(elapsedTime) / timeUnits(time.Millisecond) 134 if stats.Latency.Count == 0 { 135 stats.Latency.Min = latencyMillis 136 stats.Latency.Max = latencyMillis 137 } else { 138 if stats.Latency.Min > latencyMillis { 139 stats.Latency.Min = latencyMillis 140 } 141 if stats.Latency.Max < latencyMillis { 142 stats.Latency.Max = latencyMillis 143 } 144 } 145 stats.Latency.Count++ 146 stats.Latency.Sum += latencyMillis 147 for i := range stats.Latency.Values { 148 if stats.Latency.Values[i].Limit > latencyMillis { 149 stats.Latency.Values[i].Count++ 150 break 151 } 152 } 153 } 154 155 func (r *Rpcs) getRPCSpan(ctx context.Context, ev core.Event) (*export.Span, *rpcStats) { 156 // get the span 157 span := export.GetSpan(ctx) 158 if span == nil { 159 return nil, nil 160 } 161 // use the span start event look up the correct stats block 162 // we do this because it prevents us matching a sub span 163 return span, r.getRPCStats(span.Start()) 164 } 165 166 func (r *Rpcs) getRPCStats(lm label.Map) *rpcStats { 167 method := tag.Method.Get(lm) 168 if method == "" { 169 return nil 170 } 171 set := &r.Inbound 172 if tag.RPCDirection.Get(lm) != tag.Inbound { 173 set = &r.Outbound 174 } 175 // get the record for this method 176 index := sort.Search(len(*set), func(i int) bool { 177 return (*set)[i].Method >= method 178 }) 179 180 if index < len(*set) && (*set)[index].Method == method { 181 return (*set)[index] 182 } 183 184 old := *set 185 *set = make([]*rpcStats, len(old)+1) 186 copy(*set, old[:index]) 187 copy((*set)[index+1:], old[index:]) 188 stats := &rpcStats{Method: method} 189 stats.Latency.Values = make([]rpcTimeBucket, len(millisecondsDistribution)) 190 for i, m := range millisecondsDistribution { 191 stats.Latency.Values[i].Limit = timeUnits(m) 192 } 193 (*set)[index] = stats 194 return stats 195 } 196 197 func (s *rpcStats) InProgress() int64 { return s.Started - s.Completed } 198 func (s *rpcStats) SentMean() byteUnits { return s.Sent / byteUnits(s.Started) } 199 func (s *rpcStats) ReceivedMean() byteUnits { return s.Received / byteUnits(s.Started) } 200 201 func (h *rpcTimeHistogram) Mean() timeUnits { return h.Sum / timeUnits(h.Count) } 202 203 func getStatusCode(span *export.Span) string { 204 for _, ev := range span.Events() { 205 if status := tag.StatusCode.Get(ev); status != "" { 206 return status 207 } 208 } 209 return "" 210 } 211 212 func (r *Rpcs) getData(req *http.Request) interface{} { 213 return r 214 } 215 216 func units(v float64, suffixes []string) string { 217 s := "" 218 for _, s = range suffixes { 219 n := v / 1000 220 if n < 1 { 221 break 222 } 223 v = n 224 } 225 return fmt.Sprintf("%.2f%s", v, s) 226 } 227 228 type timeUnits float64 229 230 func (v timeUnits) String() string { 231 v = v * 1000 * 1000 232 return units(float64(v), []string{"ns", "μs", "ms", "s"}) 233 } 234 235 type byteUnits float64 236 237 func (v byteUnits) String() string { 238 return units(float64(v), []string{"B", "KB", "MB", "GB", "TB"}) 239 }