minio/cmd/http-stats.go
2019-02-14 17:53:46 -08:00

200 lines
6.2 KiB
Go

/*
* 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 (
"fmt"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/atomic"
)
// ConnStats - Network statistics
// Count total input/output transferred bytes during
// the server's life.
type ConnStats struct {
totalInputBytes atomic.Uint64
totalOutputBytes atomic.Uint64
}
// Increase total input bytes
func (s *ConnStats) incInputBytes(n int) {
s.totalInputBytes.Add(uint64(n))
}
// Increase total output bytes
func (s *ConnStats) incOutputBytes(n int) {
s.totalOutputBytes.Add(uint64(n))
}
// Return total input bytes
func (s *ConnStats) getTotalInputBytes() uint64 {
return s.totalInputBytes.Load()
}
// Return total output bytes
func (s *ConnStats) getTotalOutputBytes() uint64 {
return s.totalOutputBytes.Load()
}
// Return connection stats (total input/output bytes)
func (s *ConnStats) toServerConnStats() ServerConnStats {
return ServerConnStats{
TotalInputBytes: s.getTotalInputBytes(),
TotalOutputBytes: s.getTotalOutputBytes(),
}
}
// Prepare new ConnStats structure
func newConnStats() *ConnStats {
return &ConnStats{}
}
// HTTPMethodStats holds statistics information about
// a given HTTP method made by all clients
type HTTPMethodStats struct {
Counter atomic.Uint64
Duration atomic.Float64
}
// HTTPStats holds statistics information about
// HTTP requests made by all clients
type HTTPStats struct {
// HEAD request stats.
totalHEADs HTTPMethodStats
successHEADs HTTPMethodStats
// GET request stats.
totalGETs HTTPMethodStats
successGETs HTTPMethodStats
// PUT request stats.
totalPUTs HTTPMethodStats
successPUTs HTTPMethodStats
// POST request stats.
totalPOSTs HTTPMethodStats
successPOSTs HTTPMethodStats
// DELETE request stats.
totalDELETEs HTTPMethodStats
successDELETEs HTTPMethodStats
}
func durationStr(totalDuration, totalCount float64) string {
return fmt.Sprint(time.Duration(totalDuration/totalCount) * time.Second)
}
// Converts http stats into struct to be sent back to the client.
func (st HTTPStats) toServerHTTPStats() ServerHTTPStats {
serverStats := ServerHTTPStats{}
serverStats.TotalHEADStats = ServerHTTPMethodStats{
Count: st.totalHEADs.Counter.Load(),
AvgDuration: durationStr(st.totalHEADs.Duration.Load(), float64(st.totalHEADs.Counter.Load())),
}
serverStats.SuccessHEADStats = ServerHTTPMethodStats{
Count: st.successHEADs.Counter.Load(),
AvgDuration: durationStr(st.successHEADs.Duration.Load(), float64(st.successHEADs.Counter.Load())),
}
serverStats.TotalGETStats = ServerHTTPMethodStats{
Count: st.totalGETs.Counter.Load(),
AvgDuration: durationStr(st.totalGETs.Duration.Load(), float64(st.totalGETs.Counter.Load())),
}
serverStats.SuccessGETStats = ServerHTTPMethodStats{
Count: st.successGETs.Counter.Load(),
AvgDuration: durationStr(st.successGETs.Duration.Load(), float64(st.successGETs.Counter.Load())),
}
serverStats.TotalPUTStats = ServerHTTPMethodStats{
Count: st.totalPUTs.Counter.Load(),
AvgDuration: durationStr(st.totalPUTs.Duration.Load(), float64(st.totalPUTs.Counter.Load())),
}
serverStats.SuccessPUTStats = ServerHTTPMethodStats{
Count: st.successPUTs.Counter.Load(),
AvgDuration: durationStr(st.successPUTs.Duration.Load(), float64(st.successPUTs.Counter.Load())),
}
serverStats.TotalPOSTStats = ServerHTTPMethodStats{
Count: st.totalPOSTs.Counter.Load(),
AvgDuration: durationStr(st.totalPOSTs.Duration.Load(), float64(st.totalPOSTs.Counter.Load())),
}
serverStats.SuccessPOSTStats = ServerHTTPMethodStats{
Count: st.successPOSTs.Counter.Load(),
AvgDuration: durationStr(st.successPOSTs.Duration.Load(), float64(st.successPOSTs.Counter.Load())),
}
serverStats.TotalDELETEStats = ServerHTTPMethodStats{
Count: st.totalDELETEs.Counter.Load(),
AvgDuration: durationStr(st.totalDELETEs.Duration.Load(), float64(st.totalDELETEs.Counter.Load())),
}
serverStats.SuccessDELETEStats = ServerHTTPMethodStats{
Count: st.successDELETEs.Counter.Load(),
AvgDuration: durationStr(st.successDELETEs.Duration.Load(), float64(st.successDELETEs.Counter.Load())),
}
return serverStats
}
// Update statistics from http request and response data
func (st *HTTPStats) updateStats(r *http.Request, w *httpResponseRecorder, durationSecs float64) {
// 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.Counter.Inc()
st.totalHEADs.Duration.Add(durationSecs)
if successReq {
st.successHEADs.Counter.Inc()
st.successHEADs.Duration.Add(durationSecs)
}
case "GET":
st.totalGETs.Counter.Inc()
st.totalGETs.Duration.Add(durationSecs)
if successReq {
st.successGETs.Counter.Inc()
st.successGETs.Duration.Add(durationSecs)
}
case "PUT":
st.totalPUTs.Counter.Inc()
st.totalPUTs.Duration.Add(durationSecs)
if successReq {
st.successPUTs.Counter.Inc()
st.totalPUTs.Duration.Add(durationSecs)
}
case "POST":
st.totalPOSTs.Counter.Inc()
st.totalPOSTs.Duration.Add(durationSecs)
if successReq {
st.successPOSTs.Counter.Inc()
st.totalPOSTs.Duration.Add(durationSecs)
}
case "DELETE":
st.totalDELETEs.Counter.Inc()
st.totalDELETEs.Duration.Add(durationSecs)
if successReq {
st.successDELETEs.Counter.Inc()
st.successDELETEs.Duration.Add(durationSecs)
}
}
// Increment the prometheus http request response histogram with appropriate label
httpRequestsDuration.With(prometheus.Labels{"request_type": r.Method}).Observe(durationSecs)
}
// Prepare new HTTPStats structure
func newHTTPStats() *HTTPStats {
return &HTTPStats{}
}