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  }