| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  |  * MinIO Cloud Storage, (C) 2017 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 07:01:42 +08:00
										 |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | 	"go.uber.org/atomic" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ConnStats - Network statistics
 | 
					
						
							|  |  |  | // Count total input/output transferred bytes during
 | 
					
						
							|  |  |  | // the server's life.
 | 
					
						
							|  |  |  | type ConnStats struct { | 
					
						
							| 
									
										
										
										
											2019-02-15 09:53:46 +08:00
										 |  |  | 	totalInputBytes  atomic.Uint64 | 
					
						
							|  |  |  | 	totalOutputBytes atomic.Uint64 | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Increase total input bytes
 | 
					
						
							| 
									
										
										
										
											2019-02-15 09:53:46 +08:00
										 |  |  | func (s *ConnStats) incInputBytes(n int) { | 
					
						
							|  |  |  | 	s.totalInputBytes.Add(uint64(n)) | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Increase total output bytes
 | 
					
						
							| 
									
										
										
										
											2019-02-15 09:53:46 +08:00
										 |  |  | func (s *ConnStats) incOutputBytes(n int) { | 
					
						
							|  |  |  | 	s.totalOutputBytes.Add(uint64(n)) | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-21 22:15:53 +08:00
										 |  |  | // Return connection stats (total input/output bytes)
 | 
					
						
							|  |  |  | func (s *ConnStats) toServerConnStats() ServerConnStats { | 
					
						
							|  |  |  | 	return ServerConnStats{ | 
					
						
							|  |  |  | 		TotalInputBytes:  s.getTotalInputBytes(), | 
					
						
							|  |  |  | 		TotalOutputBytes: s.getTotalOutputBytes(), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | // 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) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-04-19 07:01:42 +08:00
										 |  |  | 	// Increment the prometheus http request response histogram with appropriate label
 | 
					
						
							|  |  |  | 	httpRequestsDuration.With(prometheus.Labels{"request_type": r.Method}).Observe(durationSecs) | 
					
						
							| 
									
										
										
										
											2017-04-07 14:08:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Prepare new HTTPStats structure
 | 
					
						
							|  |  |  | func newHTTPStats() *HTTPStats { | 
					
						
							|  |  |  | 	return &HTTPStats{} | 
					
						
							|  |  |  | } |