forked from TrueCloudLab/frostfs-s3-gw
[#271] Add namespace label to billing metrics
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
6c5f9b2764
commit
93cf7c462b
8 changed files with 41 additions and 18 deletions
|
@ -42,6 +42,7 @@ This document outlines major changes between releases.
|
||||||
- Support frostfsid contract. See `frostfsid` config section (#260)
|
- Support frostfsid contract. See `frostfsid` config section (#260)
|
||||||
- Support per namespace placement policies configuration (see `namespaces.config` config param) (#266)
|
- Support per namespace placement policies configuration (see `namespaces.config` config param) (#266)
|
||||||
- Support control api to manage policies. See `control` config section (#258)
|
- Support control api to manage policies. See `control` config section (#258)
|
||||||
|
- Add `namespace` label to billing metrics (#271)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update prometheus to v1.15.0 (#94)
|
- Update prometheus to v1.15.0 (#94)
|
||||||
|
|
|
@ -17,10 +17,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
UsersStat interface {
|
|
||||||
Update(user, bucket, cnrID string, reqType int, in, out uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
readCounter struct {
|
readCounter struct {
|
||||||
io.ReadCloser
|
io.ReadCloser
|
||||||
countBytes uint64
|
countBytes uint64
|
||||||
|
@ -39,6 +35,10 @@ type (
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AliasResolver interface {
|
||||||
|
ResolveNamespaceAlias(namespace string) string
|
||||||
|
}
|
||||||
|
|
||||||
// BucketResolveFunc is a func to resolve bucket info by name.
|
// BucketResolveFunc is a func to resolve bucket info by name.
|
||||||
BucketResolveFunc func(ctx context.Context, bucket string) (*data.BucketInfo, error)
|
BucketResolveFunc func(ctx context.Context, bucket string) (*data.BucketInfo, error)
|
||||||
|
|
||||||
|
@ -49,14 +49,14 @@ type (
|
||||||
const systemPath = "/system"
|
const systemPath = "/system"
|
||||||
|
|
||||||
// Metrics wraps http handler for api with basic statistics collection.
|
// Metrics wraps http handler for api with basic statistics collection.
|
||||||
func Metrics(log *zap.Logger, resolveBucket BucketResolveFunc, appMetrics *metrics.AppMetrics) Func {
|
func Metrics(log *zap.Logger, resolveBucket BucketResolveFunc, appMetrics *metrics.AppMetrics, aliasResolver AliasResolver) Func {
|
||||||
return func(h http.Handler) http.Handler {
|
return func(h http.Handler) http.Handler {
|
||||||
return stats(h.ServeHTTP, resolveCID(log, resolveBucket), appMetrics)
|
return stats(h.ServeHTTP, resolveCID(log, resolveBucket), appMetrics, aliasResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats is a handler that update metrics.
|
// Stats is a handler that update metrics.
|
||||||
func stats(f http.HandlerFunc, resolveCID cidResolveFunc, appMetrics *metrics.AppMetrics) http.HandlerFunc {
|
func stats(f http.HandlerFunc, resolveCID cidResolveFunc, appMetrics *metrics.AppMetrics, aliasResolver AliasResolver) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := GetReqInfo(r.Context())
|
reqInfo := GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
@ -82,7 +82,8 @@ func stats(f http.HandlerFunc, resolveCID cidResolveFunc, appMetrics *metrics.Ap
|
||||||
|
|
||||||
user := resolveUser(r.Context())
|
user := resolveUser(r.Context())
|
||||||
cnrID := resolveCID(r.Context(), reqInfo)
|
cnrID := resolveCID(r.Context(), reqInfo)
|
||||||
appMetrics.Update(user, reqInfo.BucketName, cnrID, requestTypeFromAPI(reqInfo.API), in.countBytes, out.countBytes)
|
appMetrics.Update(user, reqInfo.BucketName, cnrID, aliasResolver.ResolveNamespaceAlias(reqInfo.Namespace),
|
||||||
|
requestTypeFromAPI(reqInfo.API), in.countBytes, out.countBytes)
|
||||||
|
|
||||||
code := statsWriter.statusCode
|
code := statsWriter.statusCode
|
||||||
// A successful request has a 2xx response code
|
// A successful request has a 2xx response code
|
||||||
|
|
|
@ -98,6 +98,8 @@ type Config struct {
|
||||||
|
|
||||||
RequestMiddlewareSettings s3middleware.RequestSettings
|
RequestMiddlewareSettings s3middleware.RequestSettings
|
||||||
|
|
||||||
|
AliasResolver s3middleware.AliasResolver
|
||||||
|
|
||||||
// Domains optional. If empty no virtual hosted domains will be attached.
|
// Domains optional. If empty no virtual hosted domains will be attached.
|
||||||
Domains []string
|
Domains []string
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ func NewRouter(cfg Config) *chi.Mux {
|
||||||
middleware.ThrottleWithOpts(cfg.Throttle),
|
middleware.ThrottleWithOpts(cfg.Throttle),
|
||||||
middleware.Recoverer,
|
middleware.Recoverer,
|
||||||
s3middleware.Tracing(),
|
s3middleware.Tracing(),
|
||||||
s3middleware.Metrics(cfg.Log, cfg.Handler.ResolveBucket, cfg.Metrics),
|
s3middleware.Metrics(cfg.Log, cfg.Handler.ResolveBucket, cfg.Metrics, cfg.AliasResolver),
|
||||||
s3middleware.LogSuccessResponse(cfg.Log),
|
s3middleware.LogSuccessResponse(cfg.Log),
|
||||||
s3middleware.Auth(cfg.Center, cfg.Log),
|
s3middleware.Auth(cfg.Center, cfg.Log),
|
||||||
)
|
)
|
||||||
|
|
|
@ -385,6 +385,16 @@ func (s *appSettings) setAuthorizedControlAPIKeys(keys keys.PublicKeys) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *appSettings) ResolveNamespaceAlias(namespace string) string {
|
||||||
|
s.mu.RLock()
|
||||||
|
namespaces := s.defaultNamespaces
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if slices.Contains(namespaces, namespace) {
|
||||||
|
return defaultMetricsNamespace
|
||||||
|
}
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) initAPI(ctx context.Context) {
|
func (a *App) initAPI(ctx context.Context) {
|
||||||
a.initLayer(ctx)
|
a.initLayer(ctx)
|
||||||
a.initHandler()
|
a.initHandler()
|
||||||
|
@ -620,6 +630,7 @@ func (a *App) Serve(ctx context.Context) {
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
|
|
||||||
RequestMiddlewareSettings: a.settings,
|
RequestMiddlewareSettings: a.settings,
|
||||||
|
AliasResolver: a.settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We cannot make direct assignment if frostfsid.FrostFSID is nil
|
// We cannot make direct assignment if frostfsid.FrostFSID is nil
|
||||||
|
|
|
@ -55,6 +55,8 @@ const (
|
||||||
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
||||||
|
|
||||||
defaultConstraintName = "default"
|
defaultConstraintName = "default"
|
||||||
|
|
||||||
|
defaultMetricsNamespace = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -65,12 +65,12 @@ func (m *AppMetrics) Handler() http.Handler {
|
||||||
return m.gate.Handler()
|
return m.gate.Handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *AppMetrics) Update(user, bucket, cnrID string, reqType RequestType, in, out uint64) {
|
func (m *AppMetrics) Update(user, bucket, cnrID, ns string, reqType RequestType, in, out uint64) {
|
||||||
if !m.isEnabled() {
|
if !m.isEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.gate.Billing.apiStat.Update(user, bucket, cnrID, reqType, in, out)
|
m.gate.Billing.apiStat.Update(user, bucket, cnrID, ns, reqType, in, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *AppMetrics) Statistic() *APIStatMetrics {
|
func (m *AppMetrics) Statistic() *APIStatMetrics {
|
||||||
|
|
|
@ -69,8 +69,9 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketKey struct {
|
bucketKey struct {
|
||||||
name string
|
name string
|
||||||
cid string
|
cid string
|
||||||
|
namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketStat struct {
|
bucketStat struct {
|
||||||
|
@ -88,6 +89,7 @@ type (
|
||||||
User string
|
User string
|
||||||
Bucket string
|
Bucket string
|
||||||
ContainerID string
|
ContainerID string
|
||||||
|
Namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
UserMetricsInfo struct {
|
UserMetricsInfo struct {
|
||||||
|
@ -108,7 +110,7 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *UsersAPIStats) Update(user, bucket, cnrID string, reqType RequestType, in, out uint64) {
|
func (u *UsersAPIStats) Update(user, bucket, cnrID, ns string, reqType RequestType, in, out uint64) {
|
||||||
u.Lock()
|
u.Lock()
|
||||||
defer u.Unlock()
|
defer u.Unlock()
|
||||||
|
|
||||||
|
@ -125,8 +127,9 @@ func (u *UsersAPIStats) Update(user, bucket, cnrID string, reqType RequestType,
|
||||||
}
|
}
|
||||||
|
|
||||||
key := bucketKey{
|
key := bucketKey{
|
||||||
name: bucket,
|
name: bucket,
|
||||||
cid: cnrID,
|
cid: cnrID,
|
||||||
|
namespace: ns,
|
||||||
}
|
}
|
||||||
|
|
||||||
bktStat := usersStat.buckets[key]
|
bktStat := usersStat.buckets[key]
|
||||||
|
@ -151,6 +154,7 @@ func (u *UsersAPIStats) DumpMetrics() UserMetrics {
|
||||||
User: user,
|
User: user,
|
||||||
Bucket: key.name,
|
Bucket: key.name,
|
||||||
ContainerID: key.cid,
|
ContainerID: key.cid,
|
||||||
|
Namespace: key.namespace,
|
||||||
}
|
}
|
||||||
|
|
||||||
if bktStat.InTraffic != 0 {
|
if bktStat.InTraffic != 0 {
|
||||||
|
@ -232,6 +236,7 @@ func (b *billingMetrics) Collect(ch chan<- prometheus.Metric) {
|
||||||
value.Bucket,
|
value.Bucket,
|
||||||
value.ContainerID,
|
value.ContainerID,
|
||||||
value.Operation.String(),
|
value.Operation.String(),
|
||||||
|
value.Namespace,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +249,7 @@ func (b *billingMetrics) Collect(ch chan<- prometheus.Metric) {
|
||||||
value.Bucket,
|
value.Bucket,
|
||||||
value.ContainerID,
|
value.ContainerID,
|
||||||
value.Type.String(),
|
value.Type.String(),
|
||||||
|
value.Namespace,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ var appMetricsDesc = map[string]map[string]Description{
|
||||||
Subsystem: billingSubsystem,
|
Subsystem: billingSubsystem,
|
||||||
Name: userRequestsMetric,
|
Name: userRequestsMetric,
|
||||||
Help: "Accumulated user requests",
|
Help: "Accumulated user requests",
|
||||||
VariableLabels: []string{"user", "bucket", "cid", "operation"},
|
VariableLabels: []string{"user", "bucket", "cid", "operation", "namespace"},
|
||||||
},
|
},
|
||||||
userTrafficMetric: Description{
|
userTrafficMetric: Description{
|
||||||
Type: dto.MetricType_GAUGE,
|
Type: dto.MetricType_GAUGE,
|
||||||
|
@ -72,7 +72,7 @@ var appMetricsDesc = map[string]map[string]Description{
|
||||||
Subsystem: billingSubsystem,
|
Subsystem: billingSubsystem,
|
||||||
Name: userTrafficMetric,
|
Name: userTrafficMetric,
|
||||||
Help: "Accumulated user traffic",
|
Help: "Accumulated user traffic",
|
||||||
VariableLabels: []string{"user", "bucket", "cid", "direction"},
|
VariableLabels: []string{"user", "bucket", "cid", "direction", "namespace"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
stateSubsystem: {
|
stateSubsystem: {
|
||||||
|
|
Loading…
Reference in a new issue