087db1de70
Since we're about to do a v3.0.0 release, let's bump the version to v3. Make this change automatically with the following command to avoid any missed items: git grep -l github.com/git-lfs/git-lfs/v2 | \ xargs sed -i -e 's!github.com/git-lfs/git-lfs/v2!github.com/git-lfs/git-lfs/v3!g'
172 lines
4.2 KiB
Go
172 lines
4.2 KiB
Go
package lfshttp
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptrace"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/git-lfs/git-lfs/v3/tools"
|
|
)
|
|
|
|
type httpTransfer struct {
|
|
// members managed via sync/atomic must be aligned at the top of this
|
|
// structure (see: https://github.com/git-lfs/git-lfs/pull/2880).
|
|
|
|
RequestBodySize int64
|
|
Start int64
|
|
ResponseStart int64
|
|
ConnStart int64
|
|
ConnEnd int64
|
|
DNSStart int64
|
|
DNSEnd int64
|
|
TLSStart int64
|
|
TLSEnd int64
|
|
URL string
|
|
Method string
|
|
Key string
|
|
}
|
|
|
|
type statsContextKey string
|
|
|
|
const transferKey = statsContextKey("transfer")
|
|
|
|
func (c *Client) LogHTTPStats(w io.WriteCloser) {
|
|
fmt.Fprintf(w, "concurrent=%d time=%d version=%s\n", c.ConcurrentTransfers, time.Now().Unix(), UserAgent)
|
|
c.httpLogger = newSyncLogger(w)
|
|
}
|
|
|
|
// LogStats is intended to be called after all HTTP operations for the
|
|
// command have finished. It dumps k/v logs, one line per httpTransfer into
|
|
// a log file with the current timestamp.
|
|
//
|
|
// DEPRECATED: Call LogHTTPStats() before the first HTTP request.
|
|
func (c *Client) LogStats(out io.Writer) {}
|
|
|
|
// LogRequest tells the client to log the request's stats to the http log
|
|
// after the response body has been read.
|
|
func (c *Client) LogRequest(r *http.Request, reqKey string) *http.Request {
|
|
if c.httpLogger == nil {
|
|
return r
|
|
}
|
|
|
|
t := &httpTransfer{
|
|
URL: strings.SplitN(r.URL.String(), "?", 2)[0],
|
|
Method: r.Method,
|
|
Key: reqKey,
|
|
}
|
|
|
|
ctx := httptrace.WithClientTrace(r.Context(), &httptrace.ClientTrace{
|
|
GetConn: func(_ string) {
|
|
atomic.CompareAndSwapInt64(&t.Start, 0, time.Now().UnixNano())
|
|
},
|
|
DNSStart: func(_ httptrace.DNSStartInfo) {
|
|
atomic.CompareAndSwapInt64(&t.DNSStart, 0, time.Now().UnixNano())
|
|
},
|
|
DNSDone: func(_ httptrace.DNSDoneInfo) {
|
|
atomic.CompareAndSwapInt64(&t.DNSEnd, 0, time.Now().UnixNano())
|
|
},
|
|
ConnectStart: func(_, _ string) {
|
|
atomic.CompareAndSwapInt64(&t.ConnStart, 0, time.Now().UnixNano())
|
|
},
|
|
ConnectDone: func(_, _ string, _ error) {
|
|
atomic.CompareAndSwapInt64(&t.ConnEnd, 0, time.Now().UnixNano())
|
|
},
|
|
TLSHandshakeStart: func() {
|
|
atomic.CompareAndSwapInt64(&t.TLSStart, 0, time.Now().UnixNano())
|
|
},
|
|
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
|
|
atomic.CompareAndSwapInt64(&t.TLSEnd, 0, time.Now().UnixNano())
|
|
},
|
|
GotFirstResponseByte: func() {
|
|
atomic.CompareAndSwapInt64(&t.ResponseStart, 0, time.Now().UnixNano())
|
|
},
|
|
})
|
|
|
|
return r.WithContext(context.WithValue(ctx, transferKey, t))
|
|
}
|
|
|
|
// LogResponse sends the current response stats to the http log.
|
|
//
|
|
// DEPRECATED: Use LogRequest() instead.
|
|
func (c *Client) LogResponse(key string, res *http.Response) {}
|
|
|
|
func newSyncLogger(w io.WriteCloser) *syncLogger {
|
|
ch := make(chan string, 100)
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(1)
|
|
|
|
go func(c chan string, w io.Writer, wg *sync.WaitGroup) {
|
|
for l := range c {
|
|
w.Write([]byte(l))
|
|
wg.Done()
|
|
}
|
|
}(ch, w, wg)
|
|
|
|
return &syncLogger{w: w, ch: ch, wg: wg}
|
|
}
|
|
|
|
type syncLogger struct {
|
|
w io.WriteCloser
|
|
ch chan string
|
|
wg *sync.WaitGroup
|
|
}
|
|
|
|
func (l *syncLogger) LogRequest(req *http.Request, bodySize int64) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
|
|
if v := req.Context().Value(transferKey); v != nil {
|
|
l.logTransfer(v.(*httpTransfer), "request", fmt.Sprintf(" body=%d", bodySize))
|
|
}
|
|
}
|
|
|
|
func (l *syncLogger) LogResponse(req *http.Request, status int, bodySize int64) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
|
|
if v := req.Context().Value(transferKey); v != nil {
|
|
t := v.(*httpTransfer)
|
|
now := time.Now().UnixNano()
|
|
l.logTransfer(t, "response",
|
|
fmt.Sprintf(" status=%d body=%d conntime=%d dnstime=%d tlstime=%d restime=%d time=%d",
|
|
status,
|
|
bodySize,
|
|
tools.MaxInt64(t.ConnEnd-t.ConnStart, 0),
|
|
tools.MaxInt64(t.DNSEnd-t.DNSStart, 0),
|
|
tools.MaxInt64(t.TLSEnd-t.TLSStart, 0),
|
|
tools.MaxInt64(now-t.ResponseStart, 0),
|
|
tools.MaxInt64(now-t.Start, 0),
|
|
))
|
|
}
|
|
}
|
|
|
|
func (l *syncLogger) logTransfer(t *httpTransfer, event, extra string) {
|
|
l.wg.Add(1)
|
|
l.ch <- fmt.Sprintf("key=%s event=%s url=%s method=%s%s\n",
|
|
t.Key,
|
|
event,
|
|
t.URL,
|
|
t.Method,
|
|
extra,
|
|
)
|
|
}
|
|
|
|
func (l *syncLogger) Close() error {
|
|
if l == nil {
|
|
return nil
|
|
}
|
|
|
|
l.wg.Done()
|
|
l.wg.Wait()
|
|
return l.w.Close()
|
|
}
|