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