package metrics import ( "context" "errors" "sync" "time" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket" meta2 "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/meta" "go.uber.org/zap" ) type ( // Collector is an interface of the metrics collector. Collector interface { Start(ctx context.Context) UpdateSpaceUsage() SetCounter(ObjectCounter) SetIterator(iter meta2.Iterator) UpdateContainer(cid refs.CID, size uint64, op SpaceOp) } collector struct { log *zap.Logger interval time.Duration counter *counterWrapper sizes *syncStore metas *metaWrapper updateSpaceSize func() updateObjectCount func() } // Params groups the parameters of metrics collector's constructor. Params struct { Options []string Logger *zap.Logger Interval time.Duration MetricsStore bucket.Bucket } // ObjectCounter is an interface of object number storage. ObjectCounter interface { ObjectsCount() (uint64, error) } // CounterSetter is an interface of ObjectCounter container. CounterSetter interface { SetCounter(ObjectCounter) } counterWrapper struct { sync.Mutex counter ObjectCounter } ) var ( errEmptyCounter = errors.New("empty object counter") errEmptyLogger = errors.New("empty logger") errEmptyMetaStore = errors.New("empty meta store") errEmptyMetricsStore = errors.New("empty metrics store") ) const defaultMetricsInterval = 5 * time.Second // New constructs metrics collector and returns Collector interface. func New(p Params) (Collector, error) { switch { case p.Logger == nil: return nil, errEmptyLogger case p.MetricsStore == nil: return nil, errEmptyMetricsStore } if p.Interval <= 0 { p.Interval = defaultMetricsInterval } metas := newMetaWrapper() sizes := newSyncStore(p.Logger, p.MetricsStore) sizes.Load() return &collector{ log: p.Logger, interval: p.Interval, counter: new(counterWrapper), metas: metas, sizes: sizes, updateSpaceSize: spaceUpdater(sizes), updateObjectCount: metricsUpdater(p.Options), }, nil } func (c *counterWrapper) SetCounter(counter ObjectCounter) { c.Lock() defer c.Unlock() c.counter = counter } func (c *counterWrapper) ObjectsCount() (uint64, error) { c.Lock() defer c.Unlock() if c.counter == nil { return 0, errEmptyCounter } return c.counter.ObjectsCount() } func (c *collector) SetCounter(counter ObjectCounter) { c.counter.SetCounter(counter) } func (c *collector) SetIterator(iter meta2.Iterator) { c.metas.changeIter(iter) } func (c *collector) UpdateContainer(cid refs.CID, size uint64, op SpaceOp) { c.sizes.Update(cid, size, op) c.updateSpaceSize() } func (c *collector) UpdateSpaceUsage() { sizes := make(map[refs.CID]uint64) err := c.metas.Iterate(func(obj *object.Object) error { if !obj.IsTombstone() { cid := obj.SystemHeader.CID sizes[cid] += obj.SystemHeader.PayloadLength } return nil }) if err != nil { c.log.Error("could not update space metrics", zap.Error(err)) } c.sizes.Reset(sizes) c.updateSpaceSize() } func (c *collector) Start(ctx context.Context) { t := time.NewTicker(c.interval) loop: for { select { case <-ctx.Done(): c.log.Warn("stop collecting metrics", zap.Error(ctx.Err())) break loop case <-t.C: count, err := c.counter.ObjectsCount() if err != nil { c.log.Warn("get object count failure", zap.Error(err)) continue loop } counter.Store(float64(count)) c.updateObjectCount() } } t.Stop() }