github.com/ungtb10d/git-lfs@v2.5.2+incompatible/lfsapi/stats.go (about) 1 package lfsapi 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/httptrace" 10 "strings" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/git-lfs/git-lfs/tools" 16 ) 17 18 type httpTransfer struct { 19 // members managed via sync/atomic must be aligned at the top of this 20 // structure (see: https://github.com/git-lfs/git-lfs/pull/2880). 21 22 RequestBodySize int64 23 Start int64 24 ResponseStart int64 25 ConnStart int64 26 ConnEnd int64 27 DNSStart int64 28 DNSEnd int64 29 TLSStart int64 30 TLSEnd int64 31 URL string 32 Method string 33 Key string 34 } 35 36 type statsContextKey string 37 38 const transferKey = statsContextKey("transfer") 39 40 func (c *Client) LogHTTPStats(w io.WriteCloser) { 41 fmt.Fprintf(w, "concurrent=%d time=%d version=%s\n", c.ConcurrentTransfers, time.Now().Unix(), UserAgent) 42 c.httpLogger = newSyncLogger(w) 43 } 44 45 // LogStats is intended to be called after all HTTP operations for the 46 // commmand have finished. It dumps k/v logs, one line per httpTransfer into 47 // a log file with the current timestamp. 48 // 49 // DEPRECATED: Call LogHTTPStats() before the first HTTP request. 50 func (c *Client) LogStats(out io.Writer) {} 51 52 // LogRequest tells the client to log the request's stats to the http log 53 // after the response body has been read. 54 func (c *Client) LogRequest(r *http.Request, reqKey string) *http.Request { 55 if c.httpLogger == nil { 56 return r 57 } 58 59 t := &httpTransfer{ 60 URL: strings.SplitN(r.URL.String(), "?", 2)[0], 61 Method: r.Method, 62 Key: reqKey, 63 } 64 65 ctx := httptrace.WithClientTrace(r.Context(), &httptrace.ClientTrace{ 66 GetConn: func(_ string) { 67 atomic.CompareAndSwapInt64(&t.Start, 0, time.Now().UnixNano()) 68 }, 69 DNSStart: func(_ httptrace.DNSStartInfo) { 70 atomic.CompareAndSwapInt64(&t.DNSStart, 0, time.Now().UnixNano()) 71 }, 72 DNSDone: func(_ httptrace.DNSDoneInfo) { 73 atomic.CompareAndSwapInt64(&t.DNSEnd, 0, time.Now().UnixNano()) 74 }, 75 ConnectStart: func(_, _ string) { 76 atomic.CompareAndSwapInt64(&t.ConnStart, 0, time.Now().UnixNano()) 77 }, 78 ConnectDone: func(_, _ string, _ error) { 79 atomic.CompareAndSwapInt64(&t.ConnEnd, 0, time.Now().UnixNano()) 80 }, 81 TLSHandshakeStart: func() { 82 atomic.CompareAndSwapInt64(&t.TLSStart, 0, time.Now().UnixNano()) 83 }, 84 TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { 85 atomic.CompareAndSwapInt64(&t.TLSEnd, 0, time.Now().UnixNano()) 86 }, 87 GotFirstResponseByte: func() { 88 atomic.CompareAndSwapInt64(&t.ResponseStart, 0, time.Now().UnixNano()) 89 }, 90 }) 91 92 return r.WithContext(context.WithValue(ctx, transferKey, t)) 93 } 94 95 // LogResponse sends the current response stats to the http log. 96 // 97 // DEPRECATED: Use LogRequest() instead. 98 func (c *Client) LogResponse(key string, res *http.Response) {} 99 100 func newSyncLogger(w io.WriteCloser) *syncLogger { 101 ch := make(chan string, 100) 102 wg := &sync.WaitGroup{} 103 wg.Add(1) 104 105 go func(c chan string, w io.Writer, wg *sync.WaitGroup) { 106 for l := range c { 107 w.Write([]byte(l)) 108 wg.Done() 109 } 110 }(ch, w, wg) 111 112 return &syncLogger{w: w, ch: ch, wg: wg} 113 } 114 115 type syncLogger struct { 116 w io.WriteCloser 117 ch chan string 118 wg *sync.WaitGroup 119 } 120 121 func (l *syncLogger) LogRequest(req *http.Request, bodySize int64) { 122 if l == nil { 123 return 124 } 125 126 if v := req.Context().Value(transferKey); v != nil { 127 l.logTransfer(v.(*httpTransfer), "request", fmt.Sprintf(" body=%d", bodySize)) 128 } 129 } 130 131 func (l *syncLogger) LogResponse(req *http.Request, status int, bodySize int64) { 132 if l == nil { 133 return 134 } 135 136 if v := req.Context().Value(transferKey); v != nil { 137 t := v.(*httpTransfer) 138 now := time.Now().UnixNano() 139 l.logTransfer(t, "response", 140 fmt.Sprintf(" status=%d body=%d conntime=%d dnstime=%d tlstime=%d restime=%d time=%d", 141 status, 142 bodySize, 143 tools.MaxInt64(t.ConnEnd-t.ConnStart, 0), 144 tools.MaxInt64(t.DNSEnd-t.DNSStart, 0), 145 tools.MaxInt64(t.TLSEnd-t.TLSStart, 0), 146 tools.MaxInt64(now-t.ResponseStart, 0), 147 tools.MaxInt64(now-t.Start, 0), 148 )) 149 } 150 } 151 152 func (l *syncLogger) logTransfer(t *httpTransfer, event, extra string) { 153 l.wg.Add(1) 154 l.ch <- fmt.Sprintf("key=%s event=%s url=%s method=%s%s\n", 155 t.Key, 156 event, 157 t.URL, 158 t.Method, 159 extra, 160 ) 161 } 162 163 func (l *syncLogger) Close() error { 164 if l == nil { 165 return nil 166 } 167 168 l.wg.Done() 169 l.wg.Wait() 170 return l.w.Close() 171 }