| @@ -77,7 +77,7 @@ func (t *CacheMovePackage) do(ctx TaskContext) error { | |||||
| return fmt.Errorf("creating ipfs file: %w", err) | return fmt.Errorf("creating ipfs file: %w", err) | ||||
| } | } | ||||
| ctx.packageStat.AddAccessCounter(t.packageID, *stgglb.Local.NodeID, 1) | |||||
| ctx.accessStat.AddAccessCounter(obj.Object.ObjectID, t.packageID, *stgglb.Local.NodeID, 1) | |||||
| } | } | ||||
| _, err = coorCli.CachePackageMoved(coormq.NewCachePackageMoved(t.packageID, *stgglb.Local.NodeID)) | _, err = coorCli.CachePackageMoved(coormq.NewCachePackageMoved(t.packageID, *stgglb.Local.NodeID)) | ||||
| @@ -99,7 +99,7 @@ func (t *StorageLoadPackage) do(task *task.Task[TaskContext], ctx TaskContext) e | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| ctx.packageStat.AddAccessCounter(t.packageID, *stgglb.Local.NodeID, 1) | |||||
| ctx.accessStat.AddAccessCounter(obj.Object.ObjectID, t.packageID, *stgglb.Local.NodeID, 1) | |||||
| } | } | ||||
| _, err = coorCli.StoragePackageLoaded(coormq.NewStoragePackageLoaded(t.userID, t.storageID, t.packageID, t.pinnedBlocks)) | _, err = coorCli.StoragePackageLoaded(coormq.NewStoragePackageLoaded(t.userID, t.storageID, t.packageID, t.pinnedBlocks)) | ||||
| @@ -3,16 +3,16 @@ package task | |||||
| import ( | import ( | ||||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | "gitlink.org.cn/cloudream/common/pkgs/distlock" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/task" | "gitlink.org.cn/cloudream/common/pkgs/task" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/accessstat" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity" | "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | ||||
| packagestat "gitlink.org.cn/cloudream/storage/common/pkgs/package_stat" | |||||
| ) | ) | ||||
| type TaskContext struct { | type TaskContext struct { | ||||
| distlock *distlock.Service | distlock *distlock.Service | ||||
| connectivity *connectivity.Collector | connectivity *connectivity.Collector | ||||
| downloader *downloader.Downloader | downloader *downloader.Downloader | ||||
| packageStat *packagestat.PackageStat | |||||
| accessStat *accessstat.AccessStat | |||||
| } | } | ||||
| // 需要在Task结束后主动调用,completing函数将在Manager加锁期间被调用, | // 需要在Task结束后主动调用,completing函数将在Manager加锁期间被调用, | ||||
| @@ -27,11 +27,11 @@ type Task = task.Task[TaskContext] | |||||
| type CompleteOption = task.CompleteOption | type CompleteOption = task.CompleteOption | ||||
| func NewManager(distlock *distlock.Service, connectivity *connectivity.Collector, downloader *downloader.Downloader, packageStat *packagestat.PackageStat) Manager { | |||||
| func NewManager(distlock *distlock.Service, connectivity *connectivity.Collector, downloader *downloader.Downloader, accessStat *accessstat.AccessStat) Manager { | |||||
| return task.NewManager(TaskContext{ | return task.NewManager(TaskContext{ | ||||
| distlock: distlock, | distlock: distlock, | ||||
| connectivity: connectivity, | connectivity: connectivity, | ||||
| downloader: downloader, | downloader: downloader, | ||||
| packageStat: packageStat, | |||||
| accessStat: accessStat, | |||||
| }) | }) | ||||
| } | } | ||||
| @@ -12,11 +12,11 @@ import ( | |||||
| "gitlink.org.cn/cloudream/storage/agent/internal/config" | "gitlink.org.cn/cloudream/storage/agent/internal/config" | ||||
| "gitlink.org.cn/cloudream/storage/agent/internal/task" | "gitlink.org.cn/cloudream/storage/agent/internal/task" | ||||
| stgglb "gitlink.org.cn/cloudream/storage/common/globals" | stgglb "gitlink.org.cn/cloudream/storage/common/globals" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/accessstat" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity" | "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/distlock" | "gitlink.org.cn/cloudream/storage/common/pkgs/distlock" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | ||||
| agtrpc "gitlink.org.cn/cloudream/storage/common/pkgs/grpc/agent" | agtrpc "gitlink.org.cn/cloudream/storage/common/pkgs/grpc/agent" | ||||
| packagestat "gitlink.org.cn/cloudream/storage/common/pkgs/package_stat" | |||||
| "google.golang.org/grpc" | "google.golang.org/grpc" | ||||
| @@ -86,11 +86,11 @@ func main() { | |||||
| }) | }) | ||||
| conCol.CollectInPlace() | conCol.CollectInPlace() | ||||
| pkgStat := packagestat.NewPackageStat(packagestat.Config{ | |||||
| acStat := accessstat.NewAccessStat(accessstat.Config{ | |||||
| // TODO 考虑放到配置里 | // TODO 考虑放到配置里 | ||||
| ReportInterval: time.Second * 10, | ReportInterval: time.Second * 10, | ||||
| }) | }) | ||||
| go servePackageStat(pkgStat) | |||||
| go serveAccessStat(acStat) | |||||
| distlock, err := distlock.NewService(&config.Cfg().DistLock) | distlock, err := distlock.NewService(&config.Cfg().DistLock) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -101,7 +101,7 @@ func main() { | |||||
| dlder := downloader.NewDownloader(config.Cfg().Downloader, &conCol) | dlder := downloader.NewDownloader(config.Cfg().Downloader, &conCol) | ||||
| taskMgr := task.NewManager(distlock, &conCol, &dlder, pkgStat) | |||||
| taskMgr := task.NewManager(distlock, &conCol, &dlder, acStat) | |||||
| // 启动命令服务器 | // 启动命令服务器 | ||||
| // TODO 需要设计AgentID持久化机制 | // TODO 需要设计AgentID持久化机制 | ||||
| @@ -175,25 +175,25 @@ func serveDistLock(svc *distlock.Service) { | |||||
| os.Exit(1) | os.Exit(1) | ||||
| } | } | ||||
| func servePackageStat(svc *packagestat.PackageStat) { | |||||
| logger.Info("start serving package stat") | |||||
| func serveAccessStat(svc *accessstat.AccessStat) { | |||||
| logger.Info("start serving access stat") | |||||
| ch := svc.Start() | ch := svc.Start() | ||||
| loop: | loop: | ||||
| for { | for { | ||||
| val, err := ch.Receive() | val, err := ch.Receive() | ||||
| if err != nil { | if err != nil { | ||||
| logger.Errorf("package stat stopped with error: %v", err) | |||||
| logger.Errorf("access stat stopped with error: %v", err) | |||||
| break | break | ||||
| } | } | ||||
| switch val := val.(type) { | switch val := val.(type) { | ||||
| case error: | case error: | ||||
| logger.Errorf("package stat stopped with error: %v", val) | |||||
| logger.Errorf("access stat stopped with error: %v", val) | |||||
| break loop | break loop | ||||
| } | } | ||||
| } | } | ||||
| logger.Info("package stat stopped") | |||||
| logger.Info("access stat stopped") | |||||
| // TODO 仅简单结束了程序 | // TODO 仅简单结束了程序 | ||||
| os.Exit(1) | os.Exit(1) | ||||
| @@ -119,7 +119,7 @@ func getpByID(cmdCtx *CommandContext, id cdssdk.PackageID, output string) { | |||||
| } | } | ||||
| if stgglb.Local.NodeID != nil { | if stgglb.Local.NodeID != nil { | ||||
| cmdCtx.Cmdline.Svc.PackageStat.AddAccessCounter(id, *stgglb.Local.NodeID, 1) | |||||
| cmdCtx.Cmdline.Svc.AccessStat.AddAccessCounter(objInfo.Object.ObjectID, id, *stgglb.Local.NodeID, 1) | |||||
| } | } | ||||
| return nil | return nil | ||||
| }() | }() | ||||
| @@ -131,7 +131,7 @@ func (s *ObjectService) Download(ctx *gin.Context) { | |||||
| // TODO 当client不在某个代理节点上时如何处理? | // TODO 当client不在某个代理节点上时如何处理? | ||||
| if stgglb.Local.NodeID != nil { | if stgglb.Local.NodeID != nil { | ||||
| s.svc.PackageStat.AddAccessCounter(file.Object.PackageID, *stgglb.Local.NodeID, float64(sendSize)/float64(file.Object.Size)) | |||||
| s.svc.AccessStat.AddAccessCounter(file.Object.ObjectID, file.Object.PackageID, *stgglb.Local.NodeID, float64(sendSize)/float64(file.Object.Size)) | |||||
| } | } | ||||
| if err != nil { | if err != nil { | ||||
| @@ -3,22 +3,22 @@ package services | |||||
| import ( | import ( | ||||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | "gitlink.org.cn/cloudream/common/pkgs/distlock" | ||||
| "gitlink.org.cn/cloudream/storage/client/internal/task" | "gitlink.org.cn/cloudream/storage/client/internal/task" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/accessstat" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | ||||
| packagestat "gitlink.org.cn/cloudream/storage/common/pkgs/package_stat" | |||||
| ) | ) | ||||
| type Service struct { | type Service struct { | ||||
| DistLock *distlock.Service | |||||
| TaskMgr *task.Manager | |||||
| Downloader *downloader.Downloader | |||||
| PackageStat *packagestat.PackageStat | |||||
| DistLock *distlock.Service | |||||
| TaskMgr *task.Manager | |||||
| Downloader *downloader.Downloader | |||||
| AccessStat *accessstat.AccessStat | |||||
| } | } | ||||
| func NewService(distlock *distlock.Service, taskMgr *task.Manager, downloader *downloader.Downloader, pkgStat *packagestat.PackageStat) (*Service, error) { | |||||
| func NewService(distlock *distlock.Service, taskMgr *task.Manager, downloader *downloader.Downloader, accStat *accessstat.AccessStat) (*Service, error) { | |||||
| return &Service{ | return &Service{ | ||||
| DistLock: distlock, | |||||
| TaskMgr: taskMgr, | |||||
| Downloader: downloader, | |||||
| PackageStat: pkgStat, | |||||
| DistLock: distlock, | |||||
| TaskMgr: taskMgr, | |||||
| Downloader: downloader, | |||||
| AccessStat: accStat, | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| @@ -14,11 +14,11 @@ import ( | |||||
| "gitlink.org.cn/cloudream/storage/client/internal/services" | "gitlink.org.cn/cloudream/storage/client/internal/services" | ||||
| "gitlink.org.cn/cloudream/storage/client/internal/task" | "gitlink.org.cn/cloudream/storage/client/internal/task" | ||||
| stgglb "gitlink.org.cn/cloudream/storage/common/globals" | stgglb "gitlink.org.cn/cloudream/storage/common/globals" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/accessstat" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity" | "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/distlock" | "gitlink.org.cn/cloudream/storage/common/pkgs/distlock" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | ||||
| coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | ||||
| packagestat "gitlink.org.cn/cloudream/storage/common/pkgs/package_stat" | |||||
| ) | ) | ||||
| func main() { | func main() { | ||||
| @@ -84,17 +84,17 @@ func main() { | |||||
| } | } | ||||
| go serveDistLock(distlockSvc) | go serveDistLock(distlockSvc) | ||||
| pkgStat := packagestat.NewPackageStat(packagestat.Config{ | |||||
| acStat := accessstat.NewAccessStat(accessstat.Config{ | |||||
| // TODO 考虑放到配置里 | // TODO 考虑放到配置里 | ||||
| ReportInterval: time.Second * 10, | ReportInterval: time.Second * 10, | ||||
| }) | }) | ||||
| go servePackageStat(pkgStat) | |||||
| go serveAccessStat(acStat) | |||||
| taskMgr := task.NewManager(distlockSvc, &conCol) | taskMgr := task.NewManager(distlockSvc, &conCol) | ||||
| dlder := downloader.NewDownloader(config.Cfg().Downloader, &conCol) | dlder := downloader.NewDownloader(config.Cfg().Downloader, &conCol) | ||||
| svc, err := services.NewService(distlockSvc, &taskMgr, &dlder, pkgStat) | |||||
| svc, err := services.NewService(distlockSvc, &taskMgr, &dlder, acStat) | |||||
| if err != nil { | if err != nil { | ||||
| logger.Warnf("new services failed, err: %s", err.Error()) | logger.Warnf("new services failed, err: %s", err.Error()) | ||||
| os.Exit(1) | os.Exit(1) | ||||
| @@ -124,25 +124,25 @@ func serveDistLock(svc *distlock.Service) { | |||||
| os.Exit(1) | os.Exit(1) | ||||
| } | } | ||||
| func servePackageStat(svc *packagestat.PackageStat) { | |||||
| logger.Info("start serving package stat") | |||||
| func serveAccessStat(svc *accessstat.AccessStat) { | |||||
| logger.Info("start serving access stat") | |||||
| ch := svc.Start() | ch := svc.Start() | ||||
| loop: | loop: | ||||
| for { | for { | ||||
| val, err := ch.Receive() | val, err := ch.Receive() | ||||
| if err != nil { | if err != nil { | ||||
| logger.Errorf("package stat stopped with error: %v", err) | |||||
| logger.Errorf("access stat stopped with error: %v", err) | |||||
| break | break | ||||
| } | } | ||||
| switch val := val.(type) { | switch val := val.(type) { | ||||
| case error: | case error: | ||||
| logger.Errorf("package stat stopped with error: %v", val) | |||||
| logger.Errorf("access stat stopped with error: %v", val) | |||||
| break loop | break loop | ||||
| } | } | ||||
| } | } | ||||
| logger.Info("package stat stopped") | |||||
| logger.Info("access stat stopped") | |||||
| // TODO 仅简单结束了程序 | // TODO 仅简单结束了程序 | ||||
| os.Exit(1) | os.Exit(1) | ||||
| @@ -176,4 +176,12 @@ create table Location ( | |||||
| insert into | insert into | ||||
| Location (LocationID, Name) | Location (LocationID, Name) | ||||
| values | values | ||||
| (1, "Local"); | |||||
| (1, "Local"); | |||||
| create table ObjectAccessStat ( | |||||
| ObjectID int not null comment '对象ID', | |||||
| NodeID int not null comment '节点ID', | |||||
| Amount float not null comment '前一日流量的滑动平均值', | |||||
| Counter float not null comment '本日的流量', | |||||
| primary key(ObjectID, NodeID) | |||||
| ); | |||||
| @@ -0,0 +1,72 @@ | |||||
| package accessstat | |||||
| import ( | |||||
| "fmt" | |||||
| "sync" | |||||
| "time" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| "gitlink.org.cn/cloudream/common/utils/sync2" | |||||
| stgglb "gitlink.org.cn/cloudream/storage/common/globals" | |||||
| coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | |||||
| ) | |||||
| type AccessStatEvent interface{} | |||||
| type AccessStat struct { | |||||
| cfg Config | |||||
| stats []coormq.AddAccessStatEntry | |||||
| lock sync.Mutex | |||||
| } | |||||
| func NewAccessStat(cfg Config) *AccessStat { | |||||
| return &AccessStat{ | |||||
| cfg: cfg, | |||||
| } | |||||
| } | |||||
| func (p *AccessStat) AddAccessCounter(objID cdssdk.ObjectID, pkgID cdssdk.PackageID, nodeID cdssdk.NodeID, value float64) { | |||||
| p.lock.Lock() | |||||
| defer p.lock.Unlock() | |||||
| p.stats = append(p.stats, coormq.AddAccessStatEntry{ | |||||
| ObjectID: objID, | |||||
| PackageID: pkgID, | |||||
| NodeID: nodeID, | |||||
| Counter: value, | |||||
| }) | |||||
| } | |||||
| func (p *AccessStat) Start() *sync2.UnboundChannel[AccessStatEvent] { | |||||
| ch := sync2.NewUnboundChannel[AccessStatEvent]() | |||||
| go func() { | |||||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||||
| if err != nil { | |||||
| ch.Send(fmt.Errorf("new coordinator client: %w", err)) | |||||
| } | |||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | |||||
| ticker := time.NewTicker(p.cfg.ReportInterval) | |||||
| for { | |||||
| <-ticker.C | |||||
| p.lock.Lock() | |||||
| st := p.stats | |||||
| p.stats = nil | |||||
| p.lock.Unlock() | |||||
| err := coorCli.AddAccessStat(coormq.ReqAddAccessStat(st)) | |||||
| if err != nil { | |||||
| logger.Errorf("add all package access stat counter: %v", err) | |||||
| p.lock.Lock() | |||||
| p.stats = append(p.stats, st...) | |||||
| p.lock.Unlock() | |||||
| continue | |||||
| } | |||||
| } | |||||
| }() | |||||
| return ch | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| package packagestat | |||||
| package accessstat | |||||
| import "time" | import "time" | ||||
| @@ -107,3 +107,10 @@ type PackageAccessStat struct { | |||||
| Amount float64 `db:"Amount" json:"Amount"` // 前一日的读取量的滑动平均值 | Amount float64 `db:"Amount" json:"Amount"` // 前一日的读取量的滑动平均值 | ||||
| Counter float64 `db:"Counter" json:"counter"` // 当日的读取量 | Counter float64 `db:"Counter" json:"counter"` // 当日的读取量 | ||||
| } | } | ||||
| type ObjectAccessStat struct { | |||||
| ObjectID cdssdk.ObjectID `db:"ObjectID" json:"objectID"` | |||||
| NodeID cdssdk.NodeID `db:"NodeID" json:"nodeID"` | |||||
| Amount float64 `db:"Amount" json:"Amount"` // 前一日的读取量的滑动平均值 | |||||
| Counter float64 `db:"Counter" json:"counter"` // 当日的读取量 | |||||
| } | |||||
| @@ -28,6 +28,26 @@ func (db *ObjectDB) GetByID(ctx SQLContext, objectID cdssdk.ObjectID) (model.Obj | |||||
| return ret.ToObject(), err | return ret.ToObject(), err | ||||
| } | } | ||||
| func (db *ObjectDB) BatchTestObjectID(ctx SQLContext, objectIDs []cdssdk.ObjectID) (map[cdssdk.ObjectID]bool, error) { | |||||
| stmt, args, err := sqlx.In("select ObjectID from Object where ObjectID in (?)", lo.Uniq(objectIDs)) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| var avaiIDs []cdssdk.ObjectID | |||||
| err = sqlx.Select(ctx, &avaiIDs, stmt, args...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| avaiIDMap := make(map[cdssdk.ObjectID]bool) | |||||
| for _, pkgID := range avaiIDs { | |||||
| avaiIDMap[pkgID] = true | |||||
| } | |||||
| return avaiIDMap, nil | |||||
| } | |||||
| func (db *ObjectDB) BatchGet(ctx SQLContext, objectIDs []cdssdk.ObjectID) ([]model.Object, error) { | func (db *ObjectDB) BatchGet(ctx SQLContext, objectIDs []cdssdk.ObjectID) ([]model.Object, error) { | ||||
| if len(objectIDs) == 0 { | if len(objectIDs) == 0 { | ||||
| return nil, nil | return nil, nil | ||||
| @@ -0,0 +1,76 @@ | |||||
| package db | |||||
| import ( | |||||
| "github.com/jmoiron/sqlx" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/db/model" | |||||
| coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | |||||
| ) | |||||
| type ObjectAccessStatDB struct { | |||||
| *DB | |||||
| } | |||||
| func (db *DB) ObjectAccessStat() *ObjectAccessStatDB { | |||||
| return &ObjectAccessStatDB{db} | |||||
| } | |||||
| func (*ObjectAccessStatDB) Get(ctx SQLContext, objID cdssdk.ObjectID, nodeID cdssdk.NodeID) (model.ObjectAccessStat, error) { | |||||
| var ret model.ObjectAccessStat | |||||
| err := sqlx.Get(ctx, &ret, "select * from ObjectAccessStat where ObjectID=? and NodeID=?", objID, nodeID) | |||||
| return ret, err | |||||
| } | |||||
| func (*ObjectAccessStatDB) GetByObjectID(ctx SQLContext, objID cdssdk.ObjectID) ([]model.ObjectAccessStat, error) { | |||||
| var ret []model.ObjectAccessStat | |||||
| err := sqlx.Select(ctx, &ret, "select * from ObjectAccessStat where ObjectID=?", objID) | |||||
| return ret, err | |||||
| } | |||||
| func (*ObjectAccessStatDB) BatchAddCounter(ctx SQLContext, entries []coormq.AddAccessStatEntry) error { | |||||
| sql := "insert into ObjectAccessStat(ObjectID, NodeID, Counter, Amount) " + | |||||
| "values(:ObjectID, :NodeID, :Counter, 0) as new" + | |||||
| "on duplicate key update Counter=Counter+new.Counter" | |||||
| err := BatchNamedExec(ctx, sql, 4, entries, nil) | |||||
| return err | |||||
| } | |||||
| func (*ObjectAccessStatDB) BatchUpdateAmount(ctx SQLContext, objIDs []cdssdk.ObjectID, historyWeight float64) error { | |||||
| stmt, args, err := sqlx.In("update ObjectAccessStat set Amount=Amount*?+Counter*(1-?), Counter = 0 where ObjectID in (?)", historyWeight, historyWeight, objIDs) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = ctx.Exec(stmt, args...) | |||||
| return err | |||||
| } | |||||
| func (*ObjectAccessStatDB) UpdateAllAmount(ctx SQLContext, historyWeight float64) error { | |||||
| stmt, args, err := sqlx.In("update ObjectAccessStat set Amount=Amount*?+Counter*(1-?), Counter = 0", historyWeight, historyWeight) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = ctx.Exec(stmt, args...) | |||||
| return err | |||||
| } | |||||
| func (*ObjectAccessStatDB) DeleteByObjectID(ctx SQLContext, objID cdssdk.ObjectID) error { | |||||
| _, err := ctx.Exec("delete from ObjectAccessStat where ObjectID=?", objID) | |||||
| return err | |||||
| } | |||||
| func (*ObjectAccessStatDB) BatchDeleteByObjectID(ctx SQLContext, objIDs []cdssdk.ObjectID) error { | |||||
| stmt, args, err := sqlx.In("delete from ObjectAccessStat where ObjectID in (?)", objIDs) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| _, err = ctx.Exec(stmt, args...) | |||||
| return err | |||||
| } | |||||
| func (*ObjectAccessStatDB) DeleteInPackage(ctx SQLContext, packageID cdssdk.PackageID) error { | |||||
| _, err := ctx.Exec("delete ObjectAccessStat from ObjectAccessStat inner join Object on ObjectAccessStat.ObjectID = Object.ObjectID where PackageID = ?", packageID) | |||||
| return err | |||||
| } | |||||
| @@ -6,6 +6,7 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "github.com/jmoiron/sqlx" | "github.com/jmoiron/sqlx" | ||||
| "github.com/samber/lo" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/db/model" | "gitlink.org.cn/cloudream/storage/common/pkgs/db/model" | ||||
| @@ -31,6 +32,26 @@ func (db *PackageDB) GetByName(ctx SQLContext, bucketID cdssdk.BucketID, name st | |||||
| return ret, err | return ret, err | ||||
| } | } | ||||
| func (db *PackageDB) BatchTestPackageID(ctx SQLContext, pkgIDs []cdssdk.PackageID) (map[cdssdk.PackageID]bool, error) { | |||||
| stmt, args, err := sqlx.In("select PackageID from Package where PackageID in (?)", lo.Uniq(pkgIDs)) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| var avaiIDs []cdssdk.PackageID | |||||
| err = sqlx.Select(ctx, &avaiIDs, stmt, args...) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| avaiIDMap := make(map[cdssdk.PackageID]bool) | |||||
| for _, pkgID := range avaiIDs { | |||||
| avaiIDMap[pkgID] = true | |||||
| } | |||||
| return avaiIDMap, nil | |||||
| } | |||||
| func (*PackageDB) BatchGetAllPackageIDs(ctx SQLContext, start int, count int) ([]cdssdk.PackageID, error) { | func (*PackageDB) BatchGetAllPackageIDs(ctx SQLContext, start int, count int) ([]cdssdk.PackageID, error) { | ||||
| var ret []cdssdk.PackageID | var ret []cdssdk.PackageID | ||||
| err := sqlx.Select(ctx, &ret, "select PackageID from Package limit ?, ?", start, count) | err := sqlx.Select(ctx, &ret, "select PackageID from Package limit ?, ?", start, count) | ||||
| @@ -136,6 +157,11 @@ func (db *PackageDB) SoftDelete(ctx SQLContext, packageID cdssdk.PackageID) erro | |||||
| return fmt.Errorf("change package state failed, err: %w", err) | return fmt.Errorf("change package state failed, err: %w", err) | ||||
| } | } | ||||
| err = db.ObjectAccessStat().DeleteInPackage(ctx, packageID) | |||||
| if err != nil { | |||||
| return fmt.Errorf("delete from object access stat: %w", err) | |||||
| } | |||||
| err = db.ObjectBlock().DeleteInPackage(ctx, packageID) | err = db.ObjectBlock().DeleteInPackage(ctx, packageID) | ||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("delete from object rep failed, err: %w", err) | return fmt.Errorf("delete from object rep failed, err: %w", err) | ||||
| @@ -27,10 +27,10 @@ func (*PackageAccessStatDB) GetByPackageID(ctx SQLContext, pkgID cdssdk.PackageI | |||||
| return ret, err | return ret, err | ||||
| } | } | ||||
| func (*PackageAccessStatDB) BatchAddCounter(ctx SQLContext, entries []coormq.AddPackageAccessStatCounterEntry) error { | |||||
| sql := "insert into PackageAccessStat(PackageID, NodeID, Counter, Amount) " + | |||||
| "values(:PackageID, :NodeID, :Value, 0)" + | |||||
| "on duplicate key update Counter=Counter+:Value" | |||||
| func (*PackageAccessStatDB) BatchAddCounter(ctx SQLContext, entries []coormq.AddAccessStatEntry) error { | |||||
| sql := "insert into PackageAccessStat(PackageID, NodeID, Counter, Amount)" + | |||||
| " values(:PackageID, :NodeID, :Counter, 0) as new" + | |||||
| " on duplicate key update Counter=Counter+new.Counter" | |||||
| err := BatchNamedExec(ctx, sql, 4, entries, nil) | err := BatchNamedExec(ctx, sql, 4, entries, nil) | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -24,6 +24,8 @@ type ObjectService interface { | |||||
| DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, *mq.CodeMessage) | DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, *mq.CodeMessage) | ||||
| GetDatabaseAll(msg *GetDatabaseAll) (*GetDatabaseAllResp, *mq.CodeMessage) | GetDatabaseAll(msg *GetDatabaseAll) (*GetDatabaseAllResp, *mq.CodeMessage) | ||||
| AddAccessStat(msg *AddAccessStat) | |||||
| } | } | ||||
| // 查询Package中的所有Object,返回的Objects会按照ObjectID升序 | // 查询Package中的所有Object,返回的Objects会按照ObjectID升序 | ||||
| @@ -218,3 +220,28 @@ func RespDeleteObjects() *DeleteObjectsResp { | |||||
| func (client *Client) DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, error) { | func (client *Client) DeleteObjects(msg *DeleteObjects) (*DeleteObjectsResp, error) { | ||||
| return mq.Request(Service.DeleteObjects, client.rabbitCli, msg) | return mq.Request(Service.DeleteObjects, client.rabbitCli, msg) | ||||
| } | } | ||||
| // 增加访问计数 | |||||
| var _ = RegisterNoReply(Service.AddAccessStat) | |||||
| type AddAccessStat struct { | |||||
| mq.MessageBodyBase | |||||
| Entries []AddAccessStatEntry `json:"entries"` | |||||
| } | |||||
| type AddAccessStatEntry struct { | |||||
| ObjectID cdssdk.ObjectID `json:"objectID" db:"ObjectID"` | |||||
| PackageID cdssdk.PackageID `json:"packageID" db:"PackageID"` | |||||
| NodeID cdssdk.NodeID `json:"nodeID" db:"NodeID"` | |||||
| Counter float64 `json:"counter" db:"Counter"` | |||||
| } | |||||
| func ReqAddAccessStat(entries []AddAccessStatEntry) *AddAccessStat { | |||||
| return &AddAccessStat{ | |||||
| Entries: entries, | |||||
| } | |||||
| } | |||||
| func (client *Client) AddAccessStat(msg *AddAccessStat) error { | |||||
| return mq.Send(Service.AddAccessStat, client.rabbitCli, msg) | |||||
| } | |||||
| @@ -23,8 +23,6 @@ type PackageService interface { | |||||
| GetPackageCachedNodes(msg *GetPackageCachedNodes) (*GetPackageCachedNodesResp, *mq.CodeMessage) | GetPackageCachedNodes(msg *GetPackageCachedNodes) (*GetPackageCachedNodesResp, *mq.CodeMessage) | ||||
| GetPackageLoadedNodes(msg *GetPackageLoadedNodes) (*GetPackageLoadedNodesResp, *mq.CodeMessage) | GetPackageLoadedNodes(msg *GetPackageLoadedNodes) (*GetPackageLoadedNodesResp, *mq.CodeMessage) | ||||
| AddPackageAccessStatCounter(msg *AddPackageAccessStatCounter) (*AddPackageAccessStatCounterResp, *mq.CodeMessage) | |||||
| } | } | ||||
| // 获取Package基本信息 | // 获取Package基本信息 | ||||
| @@ -256,34 +254,3 @@ func NewGetPackageLoadedNodesResp(nodeIDs []cdssdk.NodeID) *GetPackageLoadedNode | |||||
| func (client *Client) GetPackageLoadedNodes(msg *GetPackageLoadedNodes) (*GetPackageLoadedNodesResp, error) { | func (client *Client) GetPackageLoadedNodes(msg *GetPackageLoadedNodes) (*GetPackageLoadedNodesResp, error) { | ||||
| return mq.Request(Service.GetPackageLoadedNodes, client.rabbitCli, msg) | return mq.Request(Service.GetPackageLoadedNodes, client.rabbitCli, msg) | ||||
| } | } | ||||
| // 更新Pacakge访问统计中的计数值 | |||||
| var _ = Register(Service.AddPackageAccessStatCounter) | |||||
| type AddPackageAccessStatCounter struct { | |||||
| mq.MessageBodyBase | |||||
| Entries []AddPackageAccessStatCounterEntry `json:"entries"` | |||||
| } | |||||
| type AddPackageAccessStatCounterEntry struct { | |||||
| PackageID cdssdk.PackageID `json:"packageID" db:"PackageID"` | |||||
| NodeID cdssdk.NodeID `json:"nodeID" db:"NodeID"` | |||||
| Value float64 `json:"value" db:"Value"` | |||||
| } | |||||
| type AddPackageAccessStatCounterResp struct { | |||||
| mq.MessageBodyBase | |||||
| } | |||||
| func NewAddPackageAccessStatCounter(entries []AddPackageAccessStatCounterEntry) *AddPackageAccessStatCounter { | |||||
| return &AddPackageAccessStatCounter{ | |||||
| Entries: entries, | |||||
| } | |||||
| } | |||||
| func NewAddPackageAccessStatCounterResp() *AddPackageAccessStatCounterResp { | |||||
| return &AddPackageAccessStatCounterResp{} | |||||
| } | |||||
| func (client *Client) AddPackageAccessStatCounter(msg *AddPackageAccessStatCounter) (*AddPackageAccessStatCounterResp, error) { | |||||
| return mq.Request(Service.AddPackageAccessStatCounter, client.rabbitCli, msg) | |||||
| } | |||||
| @@ -1,98 +0,0 @@ | |||||
| package packagestat | |||||
| import ( | |||||
| "fmt" | |||||
| "sync" | |||||
| "time" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| "gitlink.org.cn/cloudream/common/utils/sync2" | |||||
| stgglb "gitlink.org.cn/cloudream/storage/common/globals" | |||||
| coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | |||||
| ) | |||||
| type PackageStatEvent interface{} | |||||
| type amountKey struct { | |||||
| PackageID cdssdk.PackageID | |||||
| NodeID cdssdk.NodeID | |||||
| } | |||||
| type amount struct { | |||||
| Counter float64 | |||||
| } | |||||
| type PackageStat struct { | |||||
| cfg Config | |||||
| amounts map[amountKey]*amount | |||||
| lock sync.Mutex | |||||
| } | |||||
| func NewPackageStat(cfg Config) *PackageStat { | |||||
| return &PackageStat{ | |||||
| cfg: cfg, | |||||
| amounts: make(map[amountKey]*amount), | |||||
| } | |||||
| } | |||||
| func (p *PackageStat) AddAccessCounter(pkgID cdssdk.PackageID, nodeID cdssdk.NodeID, value float64) { | |||||
| p.lock.Lock() | |||||
| defer p.lock.Unlock() | |||||
| key := amountKey{ | |||||
| PackageID: pkgID, | |||||
| NodeID: nodeID, | |||||
| } | |||||
| if _, ok := p.amounts[key]; !ok { | |||||
| p.amounts[key] = &amount{} | |||||
| } | |||||
| p.amounts[key].Counter += value | |||||
| } | |||||
| func (p *PackageStat) Start() *sync2.UnboundChannel[PackageStatEvent] { | |||||
| ch := sync2.NewUnboundChannel[PackageStatEvent]() | |||||
| go func() { | |||||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||||
| if err != nil { | |||||
| ch.Send(fmt.Errorf("new coordinator client: %w", err)) | |||||
| } | |||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | |||||
| ticker := time.NewTicker(p.cfg.ReportInterval) | |||||
| for { | |||||
| <-ticker.C | |||||
| p.lock.Lock() | |||||
| amts := p.amounts | |||||
| p.amounts = make(map[amountKey]*amount) | |||||
| var addEntries []coormq.AddPackageAccessStatCounterEntry | |||||
| for key, amount := range amts { | |||||
| addEntries = append(addEntries, coormq.AddPackageAccessStatCounterEntry{ | |||||
| PackageID: key.PackageID, | |||||
| NodeID: key.NodeID, | |||||
| Value: amount.Counter, | |||||
| }) | |||||
| } | |||||
| p.lock.Unlock() | |||||
| _, err := coorCli.AddPackageAccessStatCounter(coormq.NewAddPackageAccessStatCounter(addEntries)) | |||||
| if err != nil { | |||||
| logger.Errorf("add all package access stat counter: %v", err) | |||||
| p.lock.Lock() | |||||
| for key, a := range amts { | |||||
| if _, ok := p.amounts[key]; !ok { | |||||
| p.amounts[key] = &amount{} | |||||
| } | |||||
| p.amounts[key].Counter += a.Counter | |||||
| } | |||||
| p.lock.Unlock() | |||||
| continue | |||||
| } | |||||
| } | |||||
| }() | |||||
| return ch | |||||
| } | |||||
| @@ -429,6 +429,11 @@ func (svc *Service) DeleteObjects(msg *coormq.DeleteObjects) (*coormq.DeleteObje | |||||
| return fmt.Errorf("batch deleting pinned objects: %w", err) | return fmt.Errorf("batch deleting pinned objects: %w", err) | ||||
| } | } | ||||
| err = svc.db.ObjectAccessStat().BatchDeleteByObjectID(tx, msg.ObjectIDs) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch deleting object access stats: %w", err) | |||||
| } | |||||
| return nil | return nil | ||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -221,13 +221,48 @@ func (svc *Service) GetPackageLoadedNodes(msg *coormq.GetPackageLoadedNodes) (*c | |||||
| return mq.ReplyOK(coormq.NewGetPackageLoadedNodesResp(nodeIDs)) | return mq.ReplyOK(coormq.NewGetPackageLoadedNodesResp(nodeIDs)) | ||||
| } | } | ||||
| func (svc *Service) AddPackageAccessStatCounter(msg *coormq.AddPackageAccessStatCounter) (*coormq.AddPackageAccessStatCounterResp, *mq.CodeMessage) { | |||||
| err := svc.db.PackageAccessStat().BatchAddCounter(svc.db.SQLCtx(), msg.Entries) | |||||
| if err != nil { | |||||
| errMsg := fmt.Sprintf("batch add package access stat counter: %s", err.Error()) | |||||
| logger.Error(errMsg) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, errMsg) | |||||
| func (svc *Service) AddAccessStat(msg *coormq.AddAccessStat) { | |||||
| pkgIDs := make([]cdssdk.PackageID, len(msg.Entries)) | |||||
| objIDs := make([]cdssdk.ObjectID, len(msg.Entries)) | |||||
| for i, e := range msg.Entries { | |||||
| pkgIDs[i] = e.PackageID | |||||
| objIDs[i] = e.ObjectID | |||||
| } | } | ||||
| return mq.ReplyOK(coormq.NewAddPackageAccessStatCounterResp()) | |||||
| err := svc.db.DoTx(sql.LevelSerializable, func(tx *sqlx.Tx) error { | |||||
| avaiPkgIDs, err := svc.db.Package().BatchTestPackageID(tx, pkgIDs) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch test package id: %w", err) | |||||
| } | |||||
| avaiObjIDs, err := svc.db.Object().BatchTestObjectID(tx, objIDs) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch test object id: %w", err) | |||||
| } | |||||
| var willAdds []coormq.AddAccessStatEntry | |||||
| for _, e := range msg.Entries { | |||||
| if avaiPkgIDs[e.PackageID] && avaiObjIDs[e.ObjectID] { | |||||
| willAdds = append(willAdds, e) | |||||
| } | |||||
| } | |||||
| if len(willAdds) > 0 { | |||||
| err := svc.db.PackageAccessStat().BatchAddCounter(tx, willAdds) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch add package access stat counter: %w", err) | |||||
| } | |||||
| err = svc.db.ObjectAccessStat().BatchAddCounter(tx, willAdds) | |||||
| if err != nil { | |||||
| return fmt.Errorf("batch add object access stat counter: %w", err) | |||||
| } | |||||
| } | |||||
| return nil | |||||
| }) | |||||
| if err != nil { | |||||
| logger.Warn(err.Error()) | |||||
| } | |||||
| } | } | ||||