2023-02-09 08:59:31 +00:00
|
|
|
package metrics
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
|
2023-03-07 14:38:08 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
2023-02-09 08:59:31 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
)
|
|
|
|
|
|
|
|
const billingSubsystem = "billing"
|
|
|
|
|
|
|
|
type TrafficType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
UnknownTraffic TrafficType = iota
|
|
|
|
INTraffic TrafficType = iota
|
|
|
|
OUTTraffic TrafficType = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
func (t TrafficType) String() string {
|
|
|
|
switch t {
|
|
|
|
case 1:
|
|
|
|
return "IN"
|
|
|
|
case 2:
|
|
|
|
return "OUT"
|
|
|
|
default:
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type (
|
|
|
|
OperationList [6]int
|
|
|
|
|
|
|
|
UsersAPIStats struct {
|
|
|
|
users map[string]*userAPIStats
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
bucketKey struct {
|
|
|
|
name string
|
|
|
|
cid string
|
|
|
|
}
|
|
|
|
|
|
|
|
bucketStat struct {
|
|
|
|
Operations OperationList
|
|
|
|
InTraffic uint64
|
|
|
|
OutTraffic uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
userAPIStats struct {
|
|
|
|
buckets map[bucketKey]bucketStat
|
|
|
|
user string
|
|
|
|
}
|
|
|
|
|
|
|
|
UserBucketInfo struct {
|
|
|
|
User string
|
|
|
|
Bucket string
|
|
|
|
ContainerID string
|
|
|
|
}
|
|
|
|
|
|
|
|
UserMetricsInfo struct {
|
|
|
|
UserBucketInfo
|
|
|
|
Operation api.RequestType
|
|
|
|
Requests int
|
|
|
|
}
|
|
|
|
|
|
|
|
UserTrafficMetricsInfo struct {
|
|
|
|
UserBucketInfo
|
|
|
|
Type TrafficType
|
|
|
|
Value uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
UserMetrics struct {
|
|
|
|
Requests []UserMetricsInfo
|
|
|
|
Traffic []UserTrafficMetricsInfo
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func (u *UsersAPIStats) Update(user, bucket, cnrID string, reqType api.RequestType, in, out uint64) {
|
|
|
|
u.Lock()
|
|
|
|
defer u.Unlock()
|
|
|
|
|
|
|
|
usersStat := u.users[user]
|
|
|
|
if usersStat == nil {
|
|
|
|
if u.users == nil {
|
|
|
|
u.users = make(map[string]*userAPIStats)
|
|
|
|
}
|
|
|
|
usersStat = &userAPIStats{
|
|
|
|
buckets: make(map[bucketKey]bucketStat, 1),
|
|
|
|
user: user,
|
|
|
|
}
|
|
|
|
u.users[user] = usersStat
|
|
|
|
}
|
|
|
|
|
|
|
|
key := bucketKey{
|
|
|
|
name: bucket,
|
|
|
|
cid: cnrID,
|
|
|
|
}
|
|
|
|
|
|
|
|
bktStat := usersStat.buckets[key]
|
|
|
|
bktStat.Operations[reqType]++
|
|
|
|
bktStat.InTraffic += in
|
|
|
|
bktStat.OutTraffic += out
|
|
|
|
usersStat.buckets[key] = bktStat
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *UsersAPIStats) DumpMetrics() UserMetrics {
|
|
|
|
u.Lock()
|
|
|
|
defer u.Unlock()
|
|
|
|
|
|
|
|
result := UserMetrics{
|
|
|
|
Requests: make([]UserMetricsInfo, 0, len(u.users)),
|
|
|
|
Traffic: make([]UserTrafficMetricsInfo, 0, len(u.users)),
|
|
|
|
}
|
|
|
|
|
|
|
|
for user, userStat := range u.users {
|
|
|
|
for key, bktStat := range userStat.buckets {
|
|
|
|
userBktInfo := UserBucketInfo{
|
|
|
|
User: user,
|
|
|
|
Bucket: key.name,
|
|
|
|
ContainerID: key.cid,
|
|
|
|
}
|
|
|
|
|
|
|
|
if bktStat.InTraffic != 0 {
|
|
|
|
result.Traffic = append(result.Traffic, UserTrafficMetricsInfo{
|
|
|
|
UserBucketInfo: userBktInfo,
|
|
|
|
Type: INTraffic,
|
|
|
|
Value: bktStat.InTraffic,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if bktStat.OutTraffic != 0 {
|
|
|
|
result.Traffic = append(result.Traffic, UserTrafficMetricsInfo{
|
|
|
|
UserBucketInfo: userBktInfo,
|
|
|
|
Type: OUTTraffic,
|
|
|
|
Value: bktStat.OutTraffic,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
for op, val := range bktStat.Operations {
|
|
|
|
if val != 0 {
|
|
|
|
result.Requests = append(result.Requests, UserMetricsInfo{
|
|
|
|
UserBucketInfo: userBktInfo,
|
|
|
|
Operation: api.RequestType(op),
|
|
|
|
Requests: val,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u.users = make(map[string]*userAPIStats)
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
type billingMetrics struct {
|
|
|
|
registry *prometheus.Registry
|
|
|
|
|
|
|
|
desc *prometheus.Desc
|
|
|
|
apiStat UsersAPIStats
|
|
|
|
}
|
|
|
|
|
|
|
|
func newBillingMetrics() *billingMetrics {
|
|
|
|
return &billingMetrics{
|
|
|
|
registry: prometheus.NewRegistry(),
|
|
|
|
desc: prometheus.NewDesc("frostfs_s3_billing", "Billing statistics exposed by FrostFS S3 Gate instance", nil, nil),
|
|
|
|
apiStat: UsersAPIStats{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *billingMetrics) register() {
|
|
|
|
b.registry.MustRegister(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *billingMetrics) unregister() {
|
|
|
|
b.registry.Unregister(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *billingMetrics) Describe(ch chan<- *prometheus.Desc) {
|
|
|
|
ch <- b.desc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *billingMetrics) Collect(ch chan<- prometheus.Metric) {
|
|
|
|
userMetrics := b.apiStat.DumpMetrics()
|
|
|
|
|
|
|
|
for _, value := range userMetrics.Requests {
|
|
|
|
ch <- prometheus.MustNewConstMetric(
|
|
|
|
prometheus.NewDesc(
|
|
|
|
prometheus.BuildFQName(namespace, billingSubsystem, "user_requests"),
|
|
|
|
"",
|
|
|
|
[]string{"user", "bucket", "cid", "operation"}, nil),
|
|
|
|
prometheus.CounterValue,
|
|
|
|
float64(value.Requests),
|
|
|
|
value.User,
|
|
|
|
value.Bucket,
|
|
|
|
value.ContainerID,
|
|
|
|
value.Operation.String(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, value := range userMetrics.Traffic {
|
|
|
|
ch <- prometheus.MustNewConstMetric(
|
|
|
|
prometheus.NewDesc(
|
|
|
|
prometheus.BuildFQName(namespace, billingSubsystem, "user_traffic"),
|
|
|
|
"",
|
2023-02-13 13:18:21 +00:00
|
|
|
[]string{"user", "bucket", "cid", "direction"}, nil),
|
2023-02-09 08:59:31 +00:00
|
|
|
prometheus.CounterValue,
|
|
|
|
float64(value.Value),
|
|
|
|
value.User,
|
|
|
|
value.Bucket,
|
|
|
|
value.ContainerID,
|
|
|
|
value.Type.String(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|