frostfs-s3-gw/metrics/stats.go

210 lines
4.9 KiB
Go

package metrics
import (
"sync"
"sync/atomic"
"github.com/prometheus/client_golang/prometheus"
)
type (
// httpAPIStats holds statistics information about
// the API given in the requests.
httpAPIStats struct {
apiStats map[string]int
sync.RWMutex
}
// httpStats holds statistics information about
// HTTP requests made by all clients.
httpStats struct {
currentS3Requests httpAPIStats
totalS3Requests httpAPIStats
totalS3Errors httpAPIStats
totalInputBytes uint64
totalOutputBytes uint64
currentS3RequestsDesc *prometheus.Desc
totalS3RequestsDesc *prometheus.Desc
totalS3ErrorsDesc *prometheus.Desc
bytesTotalDesc *prometheus.Desc
}
APIStatMetrics struct {
stats *httpStats
httpRequestsDuration *prometheus.HistogramVec
}
)
const (
statisticSubsystem = "statistic"
)
const (
requestsSecondsMetric = "requests_seconds"
requestsCurrentMetric = "requests_current"
requestsTotalMetric = "requests_total"
errorsTotalMetric = "errors_total"
bytesTotalMetric = "bytes_total"
)
const (
INDirection = "IN"
OUTDirection = "OUT"
)
func newAPIStatMetrics() *APIStatMetrics {
histogramDesc := appMetricsDesc[statisticSubsystem][requestsSecondsMetric]
return &APIStatMetrics{
stats: newHTTPStats(),
httpRequestsDuration: mustNewHistogramVec(histogramDesc,
[]float64{.05, .1, .25, .5, 1, 2.5, 5, 10}),
}
}
func (a *APIStatMetrics) CurrentS3RequestsInc(api string) {
if a == nil {
return
}
a.stats.currentS3Requests.Inc(api)
}
func (a *APIStatMetrics) CurrentS3RequestsDec(api string) {
if a == nil {
return
}
a.stats.currentS3Requests.Dec(api)
}
func (a *APIStatMetrics) TotalS3RequestsInc(api string) {
if a == nil {
return
}
a.stats.totalS3Requests.Inc(api)
}
func (a *APIStatMetrics) TotalS3ErrorsInc(api string) {
if a == nil {
return
}
a.stats.totalS3Errors.Inc(api)
}
func (a *APIStatMetrics) TotalInputBytesAdd(val uint64) {
if a == nil {
return
}
atomic.AddUint64(&a.stats.totalInputBytes, val)
}
func (a *APIStatMetrics) TotalOutputBytesAdd(val uint64) {
if a == nil {
return
}
atomic.AddUint64(&a.stats.totalOutputBytes, val)
}
func (a *APIStatMetrics) RequestDurationsUpdate(api string, durationSecs float64) {
if a == nil {
return
}
a.httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(durationSecs)
}
func (a *APIStatMetrics) Describe(ch chan<- *prometheus.Desc) {
if a == nil {
return
}
a.stats.Describe(ch)
a.httpRequestsDuration.Describe(ch)
}
func (a *APIStatMetrics) Collect(ch chan<- prometheus.Metric) {
if a == nil {
return
}
a.stats.Collect(ch)
a.httpRequestsDuration.Collect(ch)
}
func newHTTPStats() *httpStats {
return &httpStats{
currentS3RequestsDesc: newDesc(appMetricsDesc[statisticSubsystem][requestsCurrentMetric]),
totalS3RequestsDesc: newDesc(appMetricsDesc[statisticSubsystem][requestsTotalMetric]),
totalS3ErrorsDesc: newDesc(appMetricsDesc[statisticSubsystem][errorsTotalMetric]),
bytesTotalDesc: newDesc(appMetricsDesc[statisticSubsystem][bytesTotalMetric]),
}
}
func (s *httpStats) Describe(desc chan<- *prometheus.Desc) {
desc <- s.currentS3RequestsDesc
desc <- s.totalS3RequestsDesc
desc <- s.totalS3ErrorsDesc
desc <- s.bytesTotalDesc
}
func (s *httpStats) Collect(ch chan<- prometheus.Metric) {
for api, value := range s.currentS3Requests.Load() {
ch <- prometheus.MustNewConstMetric(s.currentS3RequestsDesc, prometheus.CounterValue, float64(value), api)
}
for api, value := range s.totalS3Requests.Load() {
ch <- prometheus.MustNewConstMetric(s.totalS3RequestsDesc, prometheus.CounterValue, float64(value), api)
}
for api, value := range s.totalS3Errors.Load() {
ch <- prometheus.MustNewConstMetric(s.totalS3ErrorsDesc, prometheus.CounterValue, float64(value), api)
}
// Network Sent/Received Bytes (Outbound)
ch <- prometheus.MustNewConstMetric(s.bytesTotalDesc, prometheus.CounterValue, float64(s.getInputBytes()), INDirection)
ch <- prometheus.MustNewConstMetric(s.bytesTotalDesc, prometheus.CounterValue, float64(s.getOutputBytes()), OUTDirection)
}
// Inc increments the api stats counter.
func (s *httpAPIStats) Inc(api string) {
if s == nil {
return
}
s.Lock()
defer s.Unlock()
if s.apiStats == nil {
s.apiStats = make(map[string]int)
}
s.apiStats[api]++
}
// Dec increments the api stats counter.
func (s *httpAPIStats) Dec(api string) {
if s == nil {
return
}
s.Lock()
defer s.Unlock()
if val, ok := s.apiStats[api]; ok && val > 0 {
s.apiStats[api]--
}
}
// Load returns the recorded stats.
func (s *httpAPIStats) Load() map[string]int {
s.Lock()
defer s.Unlock()
var apiStats = make(map[string]int, len(s.apiStats))
for k, v := range s.apiStats {
apiStats[k] = v
}
return apiStats
}
func (s *httpStats) getInputBytes() uint64 {
return atomic.LoadUint64(&s.totalInputBytes)
}
func (s *httpStats) getOutputBytes() uint64 {
return atomic.LoadUint64(&s.totalOutputBytes)
}