trace api: add call stats to trace (#7915)

Stats such as call latency, bytes received and sent have been added
master
poornas 5 years ago committed by kannappanr
parent fbfc9a61ec
commit 041a812ba0
  1. 52
      cmd/http-tracer.go
  2. 8
      pkg/trace/trace.go

@ -43,10 +43,16 @@ type recordRequest struct {
logBody bool logBody bool
// Internal recording buffer // Internal recording buffer
buf bytes.Buffer buf bytes.Buffer
// request headers
headers http.Header
// total bytes read including header size
bytesRead int
} }
func (r *recordRequest) Read(p []byte) (n int, err error) { func (r *recordRequest) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p) n, err = r.Reader.Read(p)
r.bytesRead += n
if r.logBody { if r.logBody {
r.buf.Write(p[:n]) r.buf.Write(p[:n])
} }
@ -55,6 +61,13 @@ func (r *recordRequest) Read(p []byte) (n int, err error) {
} }
return n, err return n, err
} }
func (r *recordRequest) Size() int {
sz := r.bytesRead
for k, v := range r.headers {
sz += len(k) + len(v)
}
return sz
}
// Return the bytes that were recorded. // Return the bytes that were recorded.
func (r *recordRequest) Data() []byte { func (r *recordRequest) Data() []byte {
@ -80,13 +93,17 @@ type recordResponseWriter struct {
statusCode int statusCode int
// Indicate if headers are written in the log // Indicate if headers are written in the log
headersLogged bool headersLogged bool
// number of bytes written
bytesWritten int
} }
// Write the headers into the given buffer // Write the headers into the given buffer
func writeHeaders(w io.Writer, statusCode int, headers http.Header) { func (r *recordResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) {
fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode)) n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode))
r.bytesWritten += n
for k, v := range headers { for k, v := range headers {
fmt.Fprintf(w, "%s: %s\n", k, v[0]) n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0])
r.bytesWritten += n
} }
} }
@ -94,7 +111,7 @@ func writeHeaders(w io.Writer, statusCode int, headers http.Header) {
func (r *recordResponseWriter) WriteHeader(i int) { func (r *recordResponseWriter) WriteHeader(i int) {
r.statusCode = i r.statusCode = i
if !r.headersLogged { if !r.headersLogged {
writeHeaders(&r.headers, i, r.ResponseWriter.Header()) r.writeHeaders(&r.headers, i, r.ResponseWriter.Header())
r.headersLogged = true r.headersLogged = true
} }
r.ResponseWriter.WriteHeader(i) r.ResponseWriter.WriteHeader(i)
@ -102,10 +119,11 @@ func (r *recordResponseWriter) WriteHeader(i int) {
func (r *recordResponseWriter) Write(p []byte) (n int, err error) { func (r *recordResponseWriter) Write(p []byte) (n int, err error) {
n, err = r.ResponseWriter.Write(p) n, err = r.ResponseWriter.Write(p)
r.bytesWritten += n
if !r.headersLogged { if !r.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called, // We assume the response code to be '200 OK' when WriteHeader() is not called,
// that way following Golang HTTP response behavior. // that way following Golang HTTP response behavior.
writeHeaders(&r.headers, http.StatusOK, r.ResponseWriter.Header()) r.writeHeaders(&r.headers, http.StatusOK, r.ResponseWriter.Header())
r.headersLogged = true r.headersLogged = true
} }
if (r.statusCode != http.StatusOK && r.statusCode != http.StatusPartialContent && r.statusCode != 0) || r.logBody { if (r.statusCode != http.StatusOK && r.statusCode != http.StatusPartialContent && r.statusCode != 0) || r.logBody {
@ -115,6 +133,10 @@ func (r *recordResponseWriter) Write(p []byte) (n int, err error) {
return n, err return n, err
} }
func (r *recordResponseWriter) Size() int {
return r.bytesWritten
}
// Calls the underlying Flush. // Calls the underlying Flush.
func (r *recordResponseWriter) Flush() { func (r *recordResponseWriter) Flush() {
r.ResponseWriter.(http.Flusher).Flush() r.ResponseWriter.(http.Flusher).Flush()
@ -151,9 +173,17 @@ func getOpName(name string) (op string) {
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info { func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info {
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()) name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
// Setup a http request body recorder
reqHeaders := cloneHeader(r.Header)
reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
reqHeaders.Set("Host", r.Host)
for _, enc := range r.TransferEncoding {
reqHeaders.Add("Transfer-Encoding", enc)
}
var reqBodyRecorder *recordRequest var reqBodyRecorder *recordRequest
t := trace.Info{FuncName: name} t := trace.Info{FuncName: name}
reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody} reqBodyRecorder = &recordRequest{Reader: r.Body, logBody: logBody, headers: reqHeaders}
r.Body = ioutil.NopCloser(reqBodyRecorder) r.Body = ioutil.NopCloser(reqBodyRecorder)
t.NodeName = r.Host t.NodeName = r.Host
if globalIsDistXL { if globalIsDistXL {
@ -164,14 +194,6 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
t.NodeName = host t.NodeName = host
} }
// Setup a http request body recorder
reqHeaders := cloneHeader(r.Header)
reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
reqHeaders.Set("Host", r.Host)
for _, enc := range r.TransferEncoding {
reqHeaders.Add("Transfer-Encoding", enc)
}
rq := trace.RequestInfo{ rq := trace.RequestInfo{
Time: time.Now().UTC(), Time: time.Now().UTC(),
Method: r.Method, Method: r.Method,
@ -199,5 +221,7 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
t.ReqInfo = rq t.ReqInfo = rq
t.RespInfo = rs t.RespInfo = rs
t.CallStats = trace.CallStats{Latency: rs.Time.Sub(rq.Time), InputBytes: reqBodyRecorder.Size(), OutputBytes: respBodyRecorder.Size()}
return t return t
} }

@ -28,6 +28,14 @@ type Info struct {
FuncName string `json:"funcname"` FuncName string `json:"funcname"`
ReqInfo RequestInfo `json:"request"` ReqInfo RequestInfo `json:"request"`
RespInfo ResponseInfo `json:"response"` RespInfo ResponseInfo `json:"response"`
CallStats CallStats `json:"stats"`
}
// CallStats records request stats
type CallStats struct {
InputBytes int `json:"inputbytes"`
OutputBytes int `json:"outputbytes"`
Latency time.Duration `json:"latency"`
} }
// RequestInfo represents trace of http request // RequestInfo represents trace of http request

Loading…
Cancel
Save