package metrics import ( "sync" "github.com/prometheus/client_golang/prometheus" ) const billingSubsystem = "billing" const ( userRequestsMetric = "user_requests" userTrafficMetric = "user_traffic" ) type RequestType int const ( UNKNOWNRequest RequestType = iota HEADRequest RequestType = iota PUTRequest RequestType = iota LISTRequest RequestType = iota GETRequest RequestType = iota DELETERequest RequestType = iota ) func (t RequestType) String() string { switch t { case 1: return "HEAD" case 2: return "PUT" case 3: return "LIST" case 4: return "GET" case 5: return "DELETE" default: return "Unknown" } } 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 namespace 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 Namespace string } UserMetricsInfo struct { UserBucketInfo Operation RequestType Requests int } UserTrafficMetricsInfo struct { UserBucketInfo Type TrafficType Value uint64 } UserMetrics struct { Requests []UserMetricsInfo Traffic []UserTrafficMetricsInfo } ) func (u *UsersAPIStats) Update(user, bucket, cnrID, ns string, reqType 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, namespace: ns, } 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, Namespace: key.namespace, } 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: RequestType(op), Requests: val, }) } } } } u.users = make(map[string]*userAPIStats) return result } type billingMetrics struct { registry *prometheus.Registry userRequestsDesc *prometheus.Desc userTrafficDesc *prometheus.Desc apiStat UsersAPIStats } func newBillingMetrics() *billingMetrics { return &billingMetrics{ registry: prometheus.NewRegistry(), userRequestsDesc: newDesc(appMetricsDesc[billingSubsystem][userRequestsMetric]), userTrafficDesc: newDesc(appMetricsDesc[billingSubsystem][userTrafficMetric]), apiStat: UsersAPIStats{}, } } func (b *billingMetrics) Gatherer() prometheus.Gatherer { return b.registry } 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.userRequestsDesc ch <- b.userTrafficDesc } func (b *billingMetrics) Collect(ch chan<- prometheus.Metric) { userMetrics := b.apiStat.DumpMetrics() for _, value := range userMetrics.Requests { ch <- prometheus.MustNewConstMetric( b.userRequestsDesc, prometheus.CounterValue, float64(value.Requests), value.User, value.Bucket, value.ContainerID, value.Operation.String(), value.Namespace, ) } for _, value := range userMetrics.Traffic { ch <- prometheus.MustNewConstMetric( b.userTrafficDesc, prometheus.CounterValue, float64(value.Value), value.User, value.Bucket, value.ContainerID, value.Type.String(), value.Namespace, ) } }