[#271] Add namespace label to billing metrics
All checks were successful
/ DCO (pull_request) Successful in 2m35s
/ Vulncheck (pull_request) Successful in 3m3s
/ Builds (1.20) (pull_request) Successful in 3m34s
/ Builds (1.21) (pull_request) Successful in 2m20s
/ Lint (pull_request) Successful in 5m27s
/ Tests (1.20) (pull_request) Successful in 3m25s
/ Tests (1.21) (pull_request) Successful in 3m12s

Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
Marina Biryukova 2023-12-01 14:16:19 +03:00
parent 6c5f9b2764
commit 93cf7c462b
8 changed files with 41 additions and 18 deletions

View file

@ -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)

View file

@ -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

View file

@ -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),
)

View file

@ -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

View file

@ -55,6 +55,8 @@ const (
defaultNamespaceHeader = "X-Frostfs-Namespace"
defaultConstraintName = "default"
defaultMetricsNamespace = ""
)
var (

View file

@ -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 {

View file

@ -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,
)
}
}

View file

@ -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: {