forked from TrueCloudLab/frostfs-node
167 lines
4.5 KiB
Go
167 lines
4.5 KiB
Go
package engine
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"sync"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type ContainerStatPrm struct {
|
|
ContainerID []cid.ID
|
|
|
|
Limit uint32
|
|
StartFromContainerID *cid.ID
|
|
}
|
|
|
|
type ContainerStatRes struct {
|
|
ContainerStats []ContainerStat
|
|
Partial bool
|
|
}
|
|
|
|
type ContainerStat struct {
|
|
ContainerID cid.ID
|
|
SizeLogic uint64
|
|
CountPhy, CountLogic, CountUser uint64
|
|
}
|
|
|
|
var errInvalidLimit = errors.New("limit must be greater than zero")
|
|
|
|
func (e *StorageEngine) ContainerStat(ctx context.Context, prm ContainerStatPrm) (*ContainerStatRes, error) {
|
|
if e.metrics != nil {
|
|
defer elapsed("ContainerStat", e.metrics.AddMethodDuration)()
|
|
}
|
|
|
|
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.ContainerStat",
|
|
trace.WithAttributes(
|
|
attribute.Int("container_ids", len(prm.ContainerID)),
|
|
attribute.Int64("limit", int64(prm.Limit)),
|
|
attribute.Bool("start_from_container_id", prm.StartFromContainerID != nil),
|
|
))
|
|
defer span.End()
|
|
|
|
if len(prm.ContainerID) == 0 && prm.Limit == 0 {
|
|
return nil, errInvalidLimit
|
|
}
|
|
|
|
var result *ContainerStatRes
|
|
err := e.execIfNotBlocked(func() error {
|
|
var sErr error
|
|
result, sErr = e.containerStat(ctx, prm)
|
|
return sErr
|
|
})
|
|
return result, err
|
|
}
|
|
|
|
func (e *StorageEngine) containerStat(ctx context.Context, prm ContainerStatPrm) (*ContainerStatRes, error) {
|
|
e.mtx.RLock()
|
|
defer e.mtx.RUnlock()
|
|
|
|
if len(prm.ContainerID) > 0 {
|
|
sort.Slice(prm.ContainerID, func(i, j int) bool {
|
|
return bytes.Compare(prm.ContainerID[i][:], prm.ContainerID[j][:]) < 0
|
|
})
|
|
}
|
|
|
|
shardResults, partial, err := e.collectShardContainerStats(ctx, prm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ContainerStatRes{
|
|
ContainerStats: e.mergeShardContainerStats(shardResults, int(prm.Limit)),
|
|
Partial: partial,
|
|
}, nil
|
|
}
|
|
|
|
func (e *StorageEngine) collectShardContainerStats(ctx context.Context, prm ContainerStatPrm) ([][]shard.ContainerStat, bool, error) {
|
|
if len(prm.ContainerID) > 0 {
|
|
sort.Slice(prm.ContainerID, func(i, j int) bool {
|
|
return bytes.Compare(prm.ContainerID[i][:], prm.ContainerID[j][:]) < 0
|
|
})
|
|
}
|
|
|
|
var shardResults [][]shard.ContainerStat
|
|
var shardErrors []error
|
|
var resultsGuard sync.Mutex
|
|
|
|
shPrm := shard.ContainerStatPrm{
|
|
ContainerID: prm.ContainerID,
|
|
Limit: prm.Limit,
|
|
StartFromContainerID: prm.StartFromContainerID,
|
|
}
|
|
|
|
eg, egCtx := errgroup.WithContext(ctx)
|
|
var shardsCount int
|
|
e.iterateOverUnsortedShards(func(hs hashedShard) (stop bool) {
|
|
shardsCount++
|
|
eg.Go(func() error {
|
|
s, err := hs.ContainerStat(egCtx, shPrm)
|
|
resultsGuard.Lock()
|
|
defer resultsGuard.Unlock()
|
|
|
|
if err != nil {
|
|
shardErrors = append(shardErrors, err)
|
|
return nil
|
|
}
|
|
|
|
if len(s) > 0 {
|
|
shardResults = append(shardResults, s)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
return false
|
|
})
|
|
if err := eg.Wait(); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if shardsCount == len(shardErrors) {
|
|
return nil, false, errors.Join(shardErrors...)
|
|
}
|
|
|
|
return shardResults, len(shardErrors) > 0, nil
|
|
}
|
|
|
|
func (e *StorageEngine) mergeShardContainerStats(shardResults [][]shard.ContainerStat, limit int) []ContainerStat {
|
|
var stats []ContainerStat
|
|
for len(stats) <= limit && len(shardResults) > 0 {
|
|
// shard results are sorted by container ID
|
|
sort.Slice(shardResults, func(i, j int) bool {
|
|
return bytes.Compare(shardResults[i][0].ContainerID[:], shardResults[j][0].ContainerID[:]) < 0
|
|
})
|
|
|
|
if len(stats) > 0 && stats[len(stats)-1].ContainerID == shardResults[0][0].ContainerID {
|
|
stats[len(stats)-1].SizeLogic += shardResults[0][0].SizeLogic
|
|
stats[len(stats)-1].CountPhy += shardResults[0][0].CountPhy
|
|
stats[len(stats)-1].CountLogic += shardResults[0][0].CountLogic
|
|
stats[len(stats)-1].CountUser += shardResults[0][0].CountUser
|
|
} else {
|
|
stats = append(stats, ContainerStat{
|
|
ContainerID: shardResults[0][0].ContainerID,
|
|
SizeLogic: shardResults[0][0].SizeLogic,
|
|
CountPhy: shardResults[0][0].CountPhy,
|
|
CountLogic: shardResults[0][0].CountLogic,
|
|
CountUser: shardResults[0][0].CountUser,
|
|
})
|
|
}
|
|
|
|
if len(shardResults[0]) == 1 { // last item for shard
|
|
shardResults = shardResults[1:]
|
|
} else {
|
|
shardResults[0] = shardResults[0][1:]
|
|
}
|
|
}
|
|
if len(stats) > limit {
|
|
stats = stats[:limit]
|
|
}
|
|
return stats
|
|
}
|