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  }