[#645] blobtree: Add metrics

Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
Dmitrii Stepanov 2023-10-04 14:35:13 +03:00
parent 76855bddac
commit fba369ec34
16 changed files with 362 additions and 11 deletions

View file

@ -847,6 +847,13 @@ func (c *cfg) getSubstorageOpts(shCfg shardCfg) []blobstor.SubStorage {
blobtree.WithDepth(sRead.depth), blobtree.WithDepth(sRead.depth),
blobtree.WithTargetSize(sRead.size), blobtree.WithTargetSize(sRead.size),
} }
if c.metricsCollector != nil {
blobTreeOpts = append(blobTreeOpts,
blobtree.WithMetrics(
lsmetrics.NewBlobTreeMetrics(sRead.path, c.metricsCollector.BlobTreeMetrics()),
),
)
}
ss = append(ss, blobstor.SubStorage{ ss = append(ss, blobstor.SubStorage{
Storage: blobtree.New(blobTreeOpts...), Storage: blobtree.New(blobTreeOpts...),
Policy: func(_ *objectSDK.Object, data []byte) bool { Policy: func(_ *objectSDK.Object, data []byte) bool {

View file

@ -33,6 +33,7 @@ func New(opts ...Option) *BlobTree {
depth: 3, depth: 3,
permissions: 0700, permissions: 0700,
initWorkersCount: 1000, initWorkersCount: 1000,
metrics: &noopMetrics{},
}, },
dirLock: utilSync.NewKeyLocker[string](), dirLock: utilSync.NewKeyLocker[string](),
fileLock: utilSync.NewKeyLocker[string](), fileLock: utilSync.NewKeyLocker[string](),

View file

@ -11,4 +11,5 @@ type cfg struct {
permissions fs.FileMode permissions fs.FileMode
readOnly bool readOnly bool
initWorkersCount int initWorkersCount int
metrics Metrics
} }

View file

@ -14,6 +14,7 @@ var Type = "blobtree"
func (b *BlobTree) Open(readOnly bool) error { func (b *BlobTree) Open(readOnly bool) error {
b.cfg.readOnly = readOnly b.cfg.readOnly = readOnly
b.cfg.metrics.SetMode(readOnly)
return nil return nil
} }
@ -59,6 +60,7 @@ func (b *BlobTree) initDir(eg *errgroup.Group, dir string, depth uint64) error {
continue continue
} }
b.dispatcher.Init(dir, idx) b.dispatcher.Init(dir, idx)
b.cfg.metrics.IncFilesCount()
stat, err := os.Stat(filepath.Join(dir, entity.Name())) stat, err := os.Stat(filepath.Join(dir, entity.Name()))
if err != nil { if err != nil {
@ -80,6 +82,7 @@ func (b *BlobTree) parseIdx(name string) (uint64, error) {
} }
func (b *BlobTree) Close() error { func (b *BlobTree) Close() error {
b.cfg.metrics.Close()
return nil return nil
} }
@ -95,4 +98,7 @@ func (b *BlobTree) Compressor() *compression.Config {
} }
func (b *BlobTree) SetReportErrorFunc(_ func(string, error)) {} func (b *BlobTree) SetReportErrorFunc(_ func(string, error)) {}
func (b *BlobTree) SetParentID(_ string) {}
func (b *BlobTree) SetParentID(parentID string) {
b.cfg.metrics.SetParentID(parentID)
}

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"os" "os"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
@ -12,14 +13,27 @@ import (
) )
func (b *BlobTree) Delete(_ context.Context, prm common.DeletePrm) (common.DeleteRes, error) { func (b *BlobTree) Delete(_ context.Context, prm common.DeletePrm) (common.DeleteRes, error) {
var (
success = false
startedAt = time.Now()
)
defer func() {
b.cfg.metrics.Delete(time.Since(startedAt), success, prm.StorageID != nil)
}()
if b.cfg.readOnly { if b.cfg.readOnly {
return common.DeleteRes{}, common.ErrReadOnly return common.DeleteRes{}, common.ErrReadOnly
} }
var res common.DeleteRes
var err error
if len(prm.StorageID) == storageIDLength { if len(prm.StorageID) == storageIDLength {
return b.deleteFromIdx(prm.Address, binary.LittleEndian.Uint64(prm.StorageID)) res, err = b.deleteFromIdx(prm.Address, binary.LittleEndian.Uint64(prm.StorageID))
} else {
res, err = b.findAndDelete(prm.Address)
} }
return b.findAndDelete(prm.Address) success = err == nil
return res, err
} }
func (b *BlobTree) deleteFromIdx(addr oid.Address, idx uint64) (common.DeleteRes, error) { func (b *BlobTree) deleteFromIdx(addr oid.Address, idx uint64) (common.DeleteRes, error) {
@ -50,7 +64,7 @@ func (b *BlobTree) deleteFromIdx(addr oid.Address, idx uint64) (common.DeleteRes
err = os.Remove(path) err = os.Remove(path)
if err == nil { if err == nil {
b.dispatcher.ReturnIdx(dir, idx) b.dispatcher.ReturnIdx(dir, idx)
// decrease files metric b.cfg.metrics.DecFilesCount()
} }
return common.DeleteRes{}, err return common.DeleteRes{}, err
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
@ -11,10 +12,23 @@ import (
) )
func (b *BlobTree) Exists(_ context.Context, prm common.ExistsPrm) (common.ExistsRes, error) { func (b *BlobTree) Exists(_ context.Context, prm common.ExistsPrm) (common.ExistsRes, error) {
var (
startedAt = time.Now()
success = false
)
defer func() {
b.cfg.metrics.Exists(time.Since(startedAt), success, prm.StorageID != nil)
}()
var res common.ExistsRes
var err error
if len(prm.StorageID) == storageIDLength { if len(prm.StorageID) == storageIDLength {
return b.existsFromIdx(prm.Address, binary.LittleEndian.Uint64(prm.StorageID)) res, err = b.existsFromIdx(prm.Address, binary.LittleEndian.Uint64(prm.StorageID))
} else {
res, err = b.findAndCheck(prm.Address)
} }
return b.findAndCheck(prm.Address) success = err == nil
return res, err
} }
func (b *BlobTree) existsFromIdx(addr oid.Address, idx uint64) (common.ExistsRes, error) { func (b *BlobTree) existsFromIdx(addr oid.Address, idx uint64) (common.ExistsRes, error) {

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"os" "os"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
@ -13,6 +14,22 @@ import (
) )
func (b *BlobTree) Get(_ context.Context, prm common.GetPrm) (common.GetRes, error) { func (b *BlobTree) Get(_ context.Context, prm common.GetPrm) (common.GetRes, error) {
var (
startedAt = time.Now()
success = false
size = 0
)
defer func() {
b.cfg.metrics.Get(time.Since(startedAt), size, success, prm.StorageID != nil)
}()
res, err := b.get(prm)
success = err == nil
size = len(res.RawData)
return res, err
}
func (b *BlobTree) get(prm common.GetPrm) (common.GetRes, error) {
if len(prm.StorageID) == storageIDLength { if len(prm.StorageID) == storageIDLength {
return b.getFromIdx(prm.Address, binary.LittleEndian.Uint64(prm.StorageID)) return b.getFromIdx(prm.Address, binary.LittleEndian.Uint64(prm.StorageID))
} }

View file

@ -2,6 +2,7 @@ package blobtree
import ( import (
"context" "context"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
@ -9,12 +10,21 @@ import (
) )
func (b *BlobTree) GetRange(ctx context.Context, prm common.GetRangePrm) (common.GetRangeRes, error) { func (b *BlobTree) GetRange(ctx context.Context, prm common.GetRangePrm) (common.GetRangeRes, error) {
res, err := b.Get(ctx, common.GetPrm{Address: prm.Address, StorageID: prm.StorageID}) var (
startedAt = time.Now()
success = false
size = 0
)
defer func() {
b.cfg.metrics.GetRange(time.Since(startedAt), size, success, prm.StorageID != nil)
}()
gRes, err := b.get(common.GetPrm{Address: prm.Address, StorageID: prm.StorageID})
if err != nil { if err != nil {
return common.GetRangeRes{}, err return common.GetRangeRes{}, err
} }
payload := res.Object.Payload() payload := gRes.Object.Payload()
from := prm.Range.GetOffset() from := prm.Range.GetOffset()
to := from + prm.Range.GetLength() to := from + prm.Range.GetLength()
@ -22,7 +32,10 @@ func (b *BlobTree) GetRange(ctx context.Context, prm common.GetRangePrm) (common
return common.GetRangeRes{}, logicerr.Wrap(new(apistatus.ObjectOutOfRange)) return common.GetRangeRes{}, logicerr.Wrap(new(apistatus.ObjectOutOfRange))
} }
return common.GetRangeRes{ res := common.GetRangeRes{
Data: payload[from:to], Data: payload[from:to],
}, nil }
size = len(res.Data)
success = true
return res, nil
} }

View file

@ -5,12 +5,22 @@ import (
"encoding/binary" "encoding/binary"
"os" "os"
"path/filepath" "path/filepath"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
) )
func (b *BlobTree) Iterate(_ context.Context, prm common.IteratePrm) (common.IterateRes, error) { func (b *BlobTree) Iterate(_ context.Context, prm common.IteratePrm) (common.IterateRes, error) {
return common.IterateRes{}, b.iterateDir(b.cfg.rootPath, 0, prm) var (
startedAt = time.Now()
err error
)
defer func() {
b.cfg.metrics.Iterate(time.Since(startedAt), err == nil)
}()
err = b.iterateDir(b.cfg.rootPath, 0, prm)
return common.IterateRes{}, err
} }
func (b *BlobTree) iterateDir(dir string, depth uint64, prm common.IteratePrm) error { func (b *BlobTree) iterateDir(dir string, depth uint64, prm common.IteratePrm) error {

View file

@ -0,0 +1,34 @@
package blobtree
import "time"
type Metrics interface {
SetParentID(parentID string)
SetMode(readOnly bool)
Close()
Delete(d time.Duration, success, withStorageID bool)
Exists(d time.Duration, success, withStorageID bool)
GetRange(d time.Duration, size int, success, withStorageID bool)
Get(d time.Duration, size int, success, withStorageID bool)
Iterate(d time.Duration, success bool)
Put(d time.Duration, size int, success bool)
IncFilesCount()
DecFilesCount()
}
type noopMetrics struct{}
func (m *noopMetrics) SetParentID(string) {}
func (m *noopMetrics) SetMode(bool) {}
func (m *noopMetrics) Close() {}
func (m *noopMetrics) Delete(time.Duration, bool, bool) {}
func (m *noopMetrics) Exists(time.Duration, bool, bool) {}
func (m *noopMetrics) GetRange(time.Duration, int, bool, bool) {}
func (m *noopMetrics) Get(time.Duration, int, bool, bool) {}
func (m *noopMetrics) Iterate(time.Duration, bool) {}
func (m *noopMetrics) Put(time.Duration, int, bool) {}
func (m *noopMetrics) IncFilesCount() {}
func (m *noopMetrics) DecFilesCount() {}

View file

@ -27,3 +27,9 @@ func WithTargetSize(size uint64) Option {
c.targetFileSizeBytes = size c.targetFileSizeBytes = size
} }
} }
func WithMetrics(m Metrics) Option {
return func(c *cfg) {
c.metrics = m
}
}

View file

@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"os" "os"
"strconv" "strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
) )
@ -15,6 +16,15 @@ const (
) )
func (b *BlobTree) Put(_ context.Context, prm common.PutPrm) (common.PutRes, error) { func (b *BlobTree) Put(_ context.Context, prm common.PutPrm) (common.PutRes, error) {
var (
success bool
size int
startedAt = time.Now()
)
defer func() {
b.cfg.metrics.Put(time.Since(startedAt), size, success)
}()
if b.cfg.readOnly { if b.cfg.readOnly {
return common.PutRes{}, common.ErrReadOnly return common.PutRes{}, common.ErrReadOnly
} }
@ -34,6 +44,9 @@ func (b *BlobTree) Put(_ context.Context, prm common.PutPrm) (common.PutRes, err
return common.PutRes{}, err return common.PutRes{}, err
} }
success = true
size = len(prm.RawData)
storageID := make([]byte, storageIDLength) storageID := make([]byte, storageIDLength)
binary.LittleEndian.PutUint64(storageID, idx) binary.LittleEndian.PutUint64(storageID, idx)
return common.PutRes{StorageID: storageID}, nil return common.PutRes{StorageID: storageID}, nil
@ -79,10 +92,24 @@ func (b *BlobTree) writeToTmpAndRename(records []objectData, path string) (uint6
return 0, err return 0, err
} }
newFile := false
_, err = os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
newFile = true
} else {
return 0, err
}
}
if err := os.Rename(tmpFile, path); err != nil { if err := os.Rename(tmpFile, path); err != nil {
_ = os.Remove(tmpFile) _ = os.Remove(tmpFile)
return 0, err return 0, err
} }
if newFile {
b.cfg.metrics.IncFilesCount()
}
return size, nil return size, nil
} }

View file

@ -0,0 +1,74 @@
package metrics
import (
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobtree"
metrics_impl "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
)
func NewBlobTreeMetrics(path string, m metrics_impl.BlobTreeMetrics) blobtree.Metrics {
return &blobTreeMetrics{
path: path,
m: m,
}
}
type blobTreeMetrics struct {
shardID string
path string
m metrics_impl.BlobTreeMetrics
}
func (m *blobTreeMetrics) SetParentID(parentID string) {
m.shardID = parentID
}
func (m *blobTreeMetrics) SetMode(readOnly bool) {
m.m.SetBlobTreeMode(m.shardID, m.path, readOnly)
}
func (m *blobTreeMetrics) Close() {
m.m.CloseBlobTree(m.shardID, m.path)
}
func (m *blobTreeMetrics) Delete(d time.Duration, success, withStorageID bool) {
m.m.BlobTreeMethodDuration(m.shardID, m.path, "Delete", d, success, metrics_impl.NullBool{Valid: true, Bool: withStorageID})
}
func (m *blobTreeMetrics) Exists(d time.Duration, success, withStorageID bool) {
m.m.BlobTreeMethodDuration(m.shardID, m.path, "Exists", d, success, metrics_impl.NullBool{Valid: true, Bool: withStorageID})
}
func (m *blobTreeMetrics) GetRange(d time.Duration, size int, success, withStorageID bool) {
m.m.BlobTreeMethodDuration(m.shardID, m.path, "GetRange", d, success, metrics_impl.NullBool{Valid: true, Bool: withStorageID})
if success {
m.m.AddBlobTreeGet(m.shardID, m.path, size)
}
}
func (m *blobTreeMetrics) Get(d time.Duration, size int, success, withStorageID bool) {
m.m.BlobTreeMethodDuration(m.shardID, m.path, "Get", d, success, metrics_impl.NullBool{Valid: true, Bool: withStorageID})
if success {
m.m.AddBlobTreeGet(m.shardID, m.path, size)
}
}
func (m *blobTreeMetrics) Iterate(d time.Duration, success bool) {
m.m.BlobTreeMethodDuration(m.shardID, m.path, "Iterate", d, success, metrics_impl.NullBool{})
}
func (m *blobTreeMetrics) Put(d time.Duration, size int, success bool) {
m.m.BlobTreeMethodDuration(m.shardID, m.path, "Put", d, success, metrics_impl.NullBool{})
if success {
m.m.AddBlobTreePut(m.shardID, m.path, size)
}
}
func (m *blobTreeMetrics) IncFilesCount() {
m.m.IncBlobTreeFilesCount(m.shardID, m.path)
}
func (m *blobTreeMetrics) DecFilesCount() {
m.m.DecBlobTreeFilesCount(m.shardID, m.path)
}

120
pkg/metrics/blobtree.go Normal file
View file

@ -0,0 +1,120 @@
package metrics
import (
"strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
"github.com/prometheus/client_golang/prometheus"
)
type BlobTreeMetrics interface {
SetBlobTreeMode(shardID, path string, readOnly bool)
CloseBlobTree(shardID, path string)
BlobTreeMethodDuration(shardID, path string, method string, d time.Duration, success bool, withStorageID NullBool)
IncBlobTreeFilesCount(shardID, path string)
DecBlobTreeFilesCount(shardID, path string)
AddBlobTreePut(shardID, path string, size int)
AddBlobTreeGet(shardID, path string, size int)
}
type blobTreeMetrics struct {
mode *shardIDPathModeValue
reqDuration *prometheus.HistogramVec
put *prometheus.CounterVec
get *prometheus.CounterVec
filesCount *prometheus.GaugeVec
}
func newBlobTreeMetrics() *blobTreeMetrics {
return &blobTreeMetrics{
mode: newShardIDPathMode(blobTreeSubSystem, "mode", "Blob tree mode"),
reqDuration: metrics.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: blobTreeSubSystem,
Name: "request_duration_seconds",
Help: "Accumulated Blob tree request process duration",
}, []string{shardIDLabel, pathLabel, successLabel, methodLabel, withStorageIDLabel}),
put: metrics.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: blobTreeSubSystem,
Name: "put_bytes",
Help: "Accumulated payload size written to Blob tree",
}, []string{shardIDLabel, pathLabel}),
get: metrics.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: blobTreeSubSystem,
Name: "get_bytes",
Help: "Accumulated payload size read from Blob tree",
}, []string{shardIDLabel, pathLabel}),
filesCount: metrics.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: blobTreeSubSystem,
Name: "files_count",
Help: "Count of data files in Blob tree",
}, []string{shardIDLabel, pathLabel}),
}
}
func (b *blobTreeMetrics) SetBlobTreeMode(shardID, path string, readOnly bool) {
b.mode.SetMode(shardID, path, modeFromBool(readOnly))
}
func (b *blobTreeMetrics) CloseBlobTree(shardID, path string) {
b.mode.SetMode(shardID, path, closedMode)
b.reqDuration.DeletePartialMatch(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
})
b.get.DeletePartialMatch(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
})
b.put.DeletePartialMatch(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
})
b.filesCount.DeletePartialMatch(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
})
}
func (b *blobTreeMetrics) BlobTreeMethodDuration(shardID, path string, method string, d time.Duration, success bool, withStorageID NullBool) {
b.reqDuration.With(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
successLabel: strconv.FormatBool(success),
methodLabel: method,
withStorageIDLabel: withStorageID.String(),
}).Observe(d.Seconds())
}
func (b *blobTreeMetrics) IncBlobTreeFilesCount(shardID, path string) {
b.filesCount.With(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
}).Inc()
}
func (b *blobTreeMetrics) DecBlobTreeFilesCount(shardID, path string) {
b.filesCount.With(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
}).Dec()
}
func (b *blobTreeMetrics) AddBlobTreePut(shardID, path string, size int) {
b.put.With(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
}).Add(float64(size))
}
func (b *blobTreeMetrics) AddBlobTreeGet(shardID, path string, size int) {
b.get.With(prometheus.Labels{
shardIDLabel: shardID,
pathLabel: path,
}).Add(float64(size))
}

View file

@ -7,6 +7,7 @@ const (
fstreeSubSystem = "fstree" fstreeSubSystem = "fstree"
blobstoreSubSystem = "blobstore" blobstoreSubSystem = "blobstore"
blobovniczaTreeSubSystem = "blobovnicza_tree" blobovniczaTreeSubSystem = "blobovnicza_tree"
blobTreeSubSystem = "blobtree"
metabaseSubSystem = "metabase" metabaseSubSystem = "metabase"
piloramaSubSystem = "pilorama" piloramaSubSystem = "pilorama"
engineSubsystem = "engine" engineSubsystem = "engine"

View file

@ -20,6 +20,7 @@ type NodeMetrics struct {
metabase *metabaseMetrics metabase *metabaseMetrics
pilorama *piloramaMetrics pilorama *piloramaMetrics
grpc *grpcServerMetrics grpc *grpcServerMetrics
blobTree *blobTreeMetrics
policer *policerMetrics policer *policerMetrics
morphClient *morphClientMetrics morphClient *morphClientMetrics
morphCache *morphCacheMetrics morphCache *morphCacheMetrics
@ -49,6 +50,7 @@ func NewNodeMetrics() *NodeMetrics {
morphClient: newMorphClientMetrics(), morphClient: newMorphClientMetrics(),
morphCache: newMorphCacheMetrics(namespace), morphCache: newMorphCacheMetrics(namespace),
log: logger.NewLogMetrics(namespace), log: logger.NewLogMetrics(namespace),
blobTree: newBlobTreeMetrics(),
} }
} }
@ -116,3 +118,7 @@ func (m *NodeMetrics) MorphCacheMetrics() MorphCacheMetrics {
func (m *NodeMetrics) LogMetrics() logger.LogMetrics { func (m *NodeMetrics) LogMetrics() logger.LogMetrics {
return m.log return m.log
} }
func (m *NodeMetrics) BlobTreeMetrics() BlobTreeMetrics {
return m.blobTree
}