storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/http-stats.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2017 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"net/http"
    21  	"strings"
    22  	"sync"
    23  	"sync/atomic"
    24  
    25  	"github.com/prometheus/client_golang/prometheus"
    26  
    27  	"storj.io/minio/cmd/logger"
    28  )
    29  
    30  // ConnStats - Network statistics
    31  // Count total input/output transferred bytes during
    32  // the server's life.
    33  type ConnStats struct {
    34  	totalInputBytes  uint64
    35  	totalOutputBytes uint64
    36  	s3InputBytes     uint64
    37  	s3OutputBytes    uint64
    38  }
    39  
    40  // Increase total input bytes
    41  func (s *ConnStats) incInputBytes(n int) {
    42  	atomic.AddUint64(&s.totalInputBytes, uint64(n))
    43  }
    44  
    45  // Increase total output bytes
    46  func (s *ConnStats) incOutputBytes(n int) {
    47  	atomic.AddUint64(&s.totalOutputBytes, uint64(n))
    48  }
    49  
    50  // Return total input bytes
    51  func (s *ConnStats) getTotalInputBytes() uint64 {
    52  	return atomic.LoadUint64(&s.totalInputBytes)
    53  }
    54  
    55  // Return total output bytes
    56  func (s *ConnStats) getTotalOutputBytes() uint64 {
    57  	return atomic.LoadUint64(&s.totalOutputBytes)
    58  }
    59  
    60  // Increase outbound input bytes
    61  func (s *ConnStats) incS3InputBytes(n int) {
    62  	atomic.AddUint64(&s.s3InputBytes, uint64(n))
    63  }
    64  
    65  // Increase outbound output bytes
    66  func (s *ConnStats) incS3OutputBytes(n int) {
    67  	atomic.AddUint64(&s.s3OutputBytes, uint64(n))
    68  }
    69  
    70  // Return outbound input bytes
    71  func (s *ConnStats) getS3InputBytes() uint64 {
    72  	return atomic.LoadUint64(&s.s3InputBytes)
    73  }
    74  
    75  // Return outbound output bytes
    76  func (s *ConnStats) getS3OutputBytes() uint64 {
    77  	return atomic.LoadUint64(&s.s3OutputBytes)
    78  }
    79  
    80  // Return connection stats (total input/output bytes and total s3 input/output bytes)
    81  func (s *ConnStats) toServerConnStats() ServerConnStats {
    82  	return ServerConnStats{
    83  		TotalInputBytes:  s.getTotalInputBytes(),  // Traffic including reserved bucket
    84  		TotalOutputBytes: s.getTotalOutputBytes(), // Traffic including reserved bucket
    85  		S3InputBytes:     s.getS3InputBytes(),     // Traffic for client buckets
    86  		S3OutputBytes:    s.getS3OutputBytes(),    // Traffic for client buckets
    87  	}
    88  }
    89  
    90  // Prepare new ConnStats structure
    91  func newConnStats() *ConnStats {
    92  	return &ConnStats{}
    93  }
    94  
    95  // HTTPAPIStats holds statistics information about
    96  // a given API in the requests.
    97  type HTTPAPIStats struct {
    98  	apiStats map[string]int
    99  	sync.RWMutex
   100  }
   101  
   102  // Inc increments the api stats counter.
   103  func (stats *HTTPAPIStats) Inc(api string) {
   104  	if stats == nil {
   105  		return
   106  	}
   107  	stats.Lock()
   108  	defer stats.Unlock()
   109  	if stats.apiStats == nil {
   110  		stats.apiStats = make(map[string]int)
   111  	}
   112  	stats.apiStats[api]++
   113  }
   114  
   115  // Dec increments the api stats counter.
   116  func (stats *HTTPAPIStats) Dec(api string) {
   117  	if stats == nil {
   118  		return
   119  	}
   120  	stats.Lock()
   121  	defer stats.Unlock()
   122  	if val, ok := stats.apiStats[api]; ok && val > 0 {
   123  		stats.apiStats[api]--
   124  	}
   125  }
   126  
   127  // Load returns the recorded stats.
   128  func (stats *HTTPAPIStats) Load() map[string]int {
   129  	stats.Lock()
   130  	defer stats.Unlock()
   131  	var apiStats = make(map[string]int, len(stats.apiStats))
   132  	for k, v := range stats.apiStats {
   133  		apiStats[k] = v
   134  	}
   135  	return apiStats
   136  }
   137  
   138  // HTTPStats holds statistics information about
   139  // HTTP requests made by all clients
   140  type HTTPStats struct {
   141  	s3RequestsInQueue       int32
   142  	currentS3Requests       HTTPAPIStats
   143  	totalS3Requests         HTTPAPIStats
   144  	totalS3Errors           HTTPAPIStats
   145  	totalS3Canceled         HTTPAPIStats
   146  	rejectedRequestsAuth    uint64
   147  	rejectedRequestsTime    uint64
   148  	rejectedRequestsHeader  uint64
   149  	rejectedRequestsInvalid uint64
   150  }
   151  
   152  func (st *HTTPStats) addRequestsInQueue(i int32) {
   153  	atomic.AddInt32(&st.s3RequestsInQueue, i)
   154  }
   155  
   156  // Converts http stats into struct to be sent back to the client.
   157  func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats {
   158  	serverStats := ServerHTTPStats{}
   159  	serverStats.S3RequestsInQueue = atomic.LoadInt32(&st.s3RequestsInQueue)
   160  	serverStats.TotalS3RejectedAuth = atomic.LoadUint64(&st.rejectedRequestsAuth)
   161  	serverStats.TotalS3RejectedTime = atomic.LoadUint64(&st.rejectedRequestsTime)
   162  	serverStats.TotalS3RejectedHeader = atomic.LoadUint64(&st.rejectedRequestsHeader)
   163  	serverStats.TotalS3RejectedInvalid = atomic.LoadUint64(&st.rejectedRequestsInvalid)
   164  	serverStats.CurrentS3Requests = ServerHTTPAPIStats{
   165  		APIStats: st.currentS3Requests.Load(),
   166  	}
   167  	serverStats.TotalS3Requests = ServerHTTPAPIStats{
   168  		APIStats: st.totalS3Requests.Load(),
   169  	}
   170  	serverStats.TotalS3Errors = ServerHTTPAPIStats{
   171  		APIStats: st.totalS3Errors.Load(),
   172  	}
   173  	serverStats.TotalS3Canceled = ServerHTTPAPIStats{
   174  		APIStats: st.totalS3Canceled.Load(),
   175  	}
   176  	return serverStats
   177  }
   178  
   179  // Update statistics from http request and response data
   180  func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.ResponseWriter) {
   181  	// A successful request has a 2xx response code
   182  	successReq := w.StatusCode >= 200 && w.StatusCode < 300
   183  
   184  	if !strings.HasSuffix(r.URL.Path, prometheusMetricsPathLegacy) ||
   185  		!strings.HasSuffix(r.URL.Path, prometheusMetricsV2ClusterPath) ||
   186  		!strings.HasSuffix(r.URL.Path, prometheusMetricsV2NodePath) {
   187  		st.totalS3Requests.Inc(api)
   188  		if !successReq {
   189  			switch w.StatusCode {
   190  			case 0:
   191  			case 499:
   192  				// 499 is a good error, shall be counted at canceled.
   193  				st.totalS3Canceled.Inc(api)
   194  			default:
   195  				st.totalS3Errors.Inc(api)
   196  			}
   197  		}
   198  	}
   199  
   200  	// Increment the prometheus http request response histogram with appropriate label
   201  	httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(w.TimeToFirstByte.Seconds())
   202  }
   203  
   204  // Prepare new HTTPStats structure
   205  func newHTTPStats() *HTTPStats {
   206  	return &HTTPStats{}
   207  }