From 93cf7c462ba6bf436e93f25b8a4e6c7f085590cf Mon Sep 17 00:00:00 2001 From: Marina Biryukova Date: Fri, 1 Dec 2023 14:16:19 +0300 Subject: [PATCH] [#271] Add namespace label to billing metrics Signed-off-by: Marina Biryukova --- CHANGELOG.md | 1 + api/middleware/metrics.go | 17 +++++++++-------- api/router.go | 4 +++- cmd/s3-gw/app.go | 11 +++++++++++ cmd/s3-gw/app_settings.go | 2 ++ metrics/app.go | 4 ++-- metrics/billing.go | 16 +++++++++++----- metrics/desc.go | 4 ++-- 8 files changed, 41 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb5f055..638750a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ This document outlines major changes between releases. - Support frostfsid contract. See `frostfsid` config section (#260) - Support per namespace placement policies configuration (see `namespaces.config` config param) (#266) - Support control api to manage policies. See `control` config section (#258) +- Add `namespace` label to billing metrics (#271) ### Changed - Update prometheus to v1.15.0 (#94) diff --git a/api/middleware/metrics.go b/api/middleware/metrics.go index d6d73284..74d990f7 100644 --- a/api/middleware/metrics.go +++ b/api/middleware/metrics.go @@ -17,10 +17,6 @@ import ( ) type ( - UsersStat interface { - Update(user, bucket, cnrID string, reqType int, in, out uint64) - } - readCounter struct { io.ReadCloser countBytes uint64 @@ -39,6 +35,10 @@ type ( startTime time.Time } + AliasResolver interface { + ResolveNamespaceAlias(namespace string) string + } + // BucketResolveFunc is a func to resolve bucket info by name. BucketResolveFunc func(ctx context.Context, bucket string) (*data.BucketInfo, error) @@ -49,14 +49,14 @@ type ( const systemPath = "/system" // 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 stats(h.ServeHTTP, resolveCID(log, resolveBucket), appMetrics) + return stats(h.ServeHTTP, resolveCID(log, resolveBucket), appMetrics, aliasResolver) } } // 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) { reqInfo := GetReqInfo(r.Context()) @@ -82,7 +82,8 @@ func stats(f http.HandlerFunc, resolveCID cidResolveFunc, appMetrics *metrics.Ap user := resolveUser(r.Context()) 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 // A successful request has a 2xx response code diff --git a/api/router.go b/api/router.go index dce9873f..22070192 100644 --- a/api/router.go +++ b/api/router.go @@ -98,6 +98,8 @@ type Config struct { RequestMiddlewareSettings s3middleware.RequestSettings + AliasResolver s3middleware.AliasResolver + // Domains optional. If empty no virtual hosted domains will be attached. Domains []string @@ -112,7 +114,7 @@ func NewRouter(cfg Config) *chi.Mux { middleware.ThrottleWithOpts(cfg.Throttle), middleware.Recoverer, 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.Auth(cfg.Center, cfg.Log), ) diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index e333809e..7bdf88b4 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -385,6 +385,16 @@ func (s *appSettings) setAuthorizedControlAPIKeys(keys keys.PublicKeys) { 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) { a.initLayer(ctx) a.initHandler() @@ -620,6 +630,7 @@ func (a *App) Serve(ctx context.Context) { Domains: domains, RequestMiddlewareSettings: a.settings, + AliasResolver: a.settings, } // We cannot make direct assignment if frostfsid.FrostFSID is nil diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index 5d36f945..dc482049 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -55,6 +55,8 @@ const ( defaultNamespaceHeader = "X-Frostfs-Namespace" defaultConstraintName = "default" + + defaultMetricsNamespace = "" ) var ( diff --git a/metrics/app.go b/metrics/app.go index f07a8c96..aa652386 100644 --- a/metrics/app.go +++ b/metrics/app.go @@ -65,12 +65,12 @@ func (m *AppMetrics) Handler() http.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() { 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 { diff --git a/metrics/billing.go b/metrics/billing.go index 609fb921..565fedaf 100644 --- a/metrics/billing.go +++ b/metrics/billing.go @@ -69,8 +69,9 @@ type ( } bucketKey struct { - name string - cid string + name string + cid string + namespace string } bucketStat struct { @@ -88,6 +89,7 @@ type ( User string Bucket string ContainerID string + Namespace string } 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() defer u.Unlock() @@ -125,8 +127,9 @@ func (u *UsersAPIStats) Update(user, bucket, cnrID string, reqType RequestType, } key := bucketKey{ - name: bucket, - cid: cnrID, + name: bucket, + cid: cnrID, + namespace: ns, } bktStat := usersStat.buckets[key] @@ -151,6 +154,7 @@ func (u *UsersAPIStats) DumpMetrics() UserMetrics { User: user, Bucket: key.name, ContainerID: key.cid, + Namespace: key.namespace, } if bktStat.InTraffic != 0 { @@ -232,6 +236,7 @@ func (b *billingMetrics) Collect(ch chan<- prometheus.Metric) { value.Bucket, value.ContainerID, value.Operation.String(), + value.Namespace, ) } @@ -244,6 +249,7 @@ func (b *billingMetrics) Collect(ch chan<- prometheus.Metric) { value.Bucket, value.ContainerID, value.Type.String(), + value.Namespace, ) } } diff --git a/metrics/desc.go b/metrics/desc.go index 318f57ad..ae9fc0f4 100644 --- a/metrics/desc.go +++ b/metrics/desc.go @@ -64,7 +64,7 @@ var appMetricsDesc = map[string]map[string]Description{ Subsystem: billingSubsystem, Name: userRequestsMetric, Help: "Accumulated user requests", - VariableLabels: []string{"user", "bucket", "cid", "operation"}, + VariableLabels: []string{"user", "bucket", "cid", "operation", "namespace"}, }, userTrafficMetric: Description{ Type: dto.MetricType_GAUGE, @@ -72,7 +72,7 @@ var appMetricsDesc = map[string]map[string]Description{ Subsystem: billingSubsystem, Name: userTrafficMetric, Help: "Accumulated user traffic", - VariableLabels: []string{"user", "bucket", "cid", "direction"}, + VariableLabels: []string{"user", "bucket", "cid", "direction", "namespace"}, }, }, stateSubsystem: {