stats: Add network and http statisics (#3686)

Network: total bytes of incoming and outgoing server's data
by taking advantage of our ConnMux Read/Write wrapping

HTTP: total number of different http verbs passed in http
requests and different status codes passed in http responses.
This is counted in a new http handler.
This commit is contained in:
Anis Elleuch 2017-02-06 18:29:53 +01:00 committed by Harshavardhana
parent 6717a0b68c
commit 93fd269329
5 changed files with 208 additions and 12 deletions

View file

@ -356,3 +356,52 @@ func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Serve HTTP.
h.handler.ServeHTTP(w, r)
}
// httpResponseRecorder wraps http.ResponseWriter
// to record some useful http response data.
type httpResponseRecorder struct {
http.ResponseWriter
http.Flusher
respStatusCode int
}
// Wraps ResponseWriter's Write()
func (rww *httpResponseRecorder) Write(b []byte) (int, error) {
return rww.ResponseWriter.Write(b)
}
// Wraps ResponseWriter's Flush()
func (rww *httpResponseRecorder) Flush() {
f, ok := rww.ResponseWriter.(http.Flusher)
if ok {
f.Flush()
}
}
// Wraps ResponseWriter's WriteHeader() and record
// the response status code
func (rww *httpResponseRecorder) WriteHeader(httpCode int) {
rww.respStatusCode = httpCode
rww.ResponseWriter.WriteHeader(httpCode)
}
// httpStatsHandler definition: gather HTTP statistics
type httpStatsHandler struct {
handler http.Handler
}
// setHttpStatsHandler sets a http Stats Handler
func setHTTPStatsHandler(h http.Handler) http.Handler {
return httpStatsHandler{handler: h}
}
func (h httpStatsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Wraps w to record http response information
ww := &httpResponseRecorder{ResponseWriter: w}
// Execute the request
h.handler.ServeHTTP(ww, r)
// Update http statistics
globalHTTPStats.updateStats(r, ww)
}

View file

@ -122,6 +122,11 @@ var (
// url.URL endpoints of disks that belong to the object storage.
globalEndpoints = []*url.URL{}
// Global server's network statistics
globalConnStats = newConnStats()
// Global HTTP request statisitics
globalHTTPStats = newHTTPStats()
// Add new variable global values here.
)

View file

@ -84,6 +84,8 @@ func configureServerHandler(srvCmdConfig serverCmdConfig) (http.Handler, error)
// List of some generic handlers which are applied for all incoming requests.
var handlerFns = []HandlerFunc{
// Network statistics
setHTTPStatsHandler,
// Limits all requests size to a maximum fixed limit
setRequestSizeLimitHandler,
// Adds 'crossdomain.xml' policy handler to serve legacy flash clients.

View file

@ -58,16 +58,16 @@ var defaultHTTP1Methods = []string{
// connections on the same listeners.
type ConnMux struct {
net.Conn
bufrw *bufio.ReadWriter
// To peek net.Conn incoming data
peeker *bufio.Reader
}
// NewConnMux - creates a new ConnMux instance
func NewConnMux(c net.Conn) *ConnMux {
br := bufio.NewReader(c)
bw := bufio.NewWriter(c)
return &ConnMux{
Conn: c,
bufrw: bufio.NewReadWriter(br, bw),
Conn: c,
peeker: bufio.NewReader(br),
}
}
@ -83,7 +83,7 @@ const (
// errors in peeking over the connection.
func (c *ConnMux) PeekProtocol() (string, error) {
// Peek for HTTP verbs.
buf, err := c.bufrw.Peek(maxHTTPVerbLen)
buf, err := c.peeker.Peek(maxHTTPVerbLen)
if err != nil {
return "", err
}
@ -110,20 +110,31 @@ func (c *ConnMux) PeekProtocol() (string, error) {
// Read - streams the ConnMux buffer when reset flag is activated, otherwise
// streams from the incoming network connection
func (c *ConnMux) Read(b []byte) (int, error) {
func (c *ConnMux) Read(b []byte) (n int, e error) {
// Push read deadline
c.Conn.SetReadDeadline(time.Now().Add(defaultTCPReadTimeout))
return c.bufrw.Read(b)
// Update server's connection statistics
defer func() {
globalConnStats.incInputBytes(n)
}()
return c.peeker.Read(b)
}
func (c *ConnMux) Write(b []byte) (n int, e error) {
// Update server's connection statistics
defer func() {
globalConnStats.incOutputBytes(n)
}()
// Run the underlying net.Conn Write() func
return c.Conn.Write(b)
}
// Close the connection.
func (c *ConnMux) Close() (err error) {
// Make sure that we always close a connection,
// even if the bufioWriter flush sends an error.
defer c.Conn.Close()
// Flush and write to the connection.
return c.bufrw.Flush()
return c.Conn.Close()
}
// ListenerMux wraps the standard net.Listener to inspect

129
cmd/stats.go Normal file
View file

@ -0,0 +1,129 @@
/*
* Minio Cloud Storage, (C) 2017 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"net/http"
"sync/atomic"
)
// counter - simplify atomic counting
type counter struct {
val uint64
}
// Inc increases counter atomically
func (c *counter) Inc(n uint64) {
atomic.AddUint64(&c.val, n)
}
// Value fetches counter's value atomically
func (c *counter) Value() uint64 {
return atomic.LoadUint64(&c.val)
}
// ConnStats - Network statistics
// Count total input/output transferred bytes during
// the server's life.
type ConnStats struct {
totalInputBytes counter
totalOutputBytes counter
}
// Increase total input bytes
func (s *ConnStats) incInputBytes(n int) {
s.totalInputBytes.Inc(uint64(n))
}
// Increase total output bytes
func (s *ConnStats) incOutputBytes(n int) {
s.totalOutputBytes.Inc(uint64(n))
}
// Return total input bytes
func (s *ConnStats) getTotalInputBytes() uint64 {
return s.totalInputBytes.Value()
}
// Return total output bytes
func (s *ConnStats) getTotalOutputBytes() uint64 {
return s.totalOutputBytes.Value()
}
// Prepare new ConnStats structure
func newConnStats() *ConnStats {
return &ConnStats{}
}
// httpStats holds statistics information about
// HTTP requests made by all clients
type httpStats struct {
// HEAD request stats
totalHEADs counter
successHEADs counter
// GET request stats
totalGETs counter
successGETs counter
// PUT request
totalPUTs counter
successPUTs counter
// POST request
totalPOSTs counter
successPOSTs counter
// DELETE request
totalDELETEs counter
successDELETEs counter
}
// Update statistics from http request and response data
func (st *httpStats) updateStats(r *http.Request, w *httpResponseRecorder) {
// A successful request has a 2xx response code
successReq := (w.respStatusCode >= 200 && w.respStatusCode < 300)
// Update stats according to method verb
switch r.Method {
case "HEAD":
st.totalHEADs.Inc(1)
if successReq {
st.successHEADs.Inc(1)
}
case "GET":
st.totalGETs.Inc(1)
if successReq {
st.successGETs.Inc(1)
}
case "PUT":
st.totalPUTs.Inc(1)
if successReq {
st.successPUTs.Inc(1)
}
case "POST":
st.totalPOSTs.Inc(1)
if successReq {
st.successPOSTs.Inc(1)
}
case "DELETE":
st.totalDELETEs.Inc(1)
if successReq {
st.successDELETEs.Inc(1)
}
}
}
// Prepare new HttpStats structure
func newHTTPStats() *httpStats {
return &httpStats{}
}