2019-01-24 20:15:10 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-11-08 16:18:28 +00:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2018-11-14 01:22:18 +00:00
|
|
|
package tally
|
2018-11-08 16:18:28 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gogo/protobuf/proto"
|
2019-02-01 18:50:12 +00:00
|
|
|
"github.com/zeebo/errs"
|
2018-11-08 16:18:28 +00:00
|
|
|
"go.uber.org/zap"
|
2018-12-07 09:59:31 +00:00
|
|
|
|
2018-12-05 14:03:23 +00:00
|
|
|
"storj.io/storj/pkg/accounting"
|
2018-12-07 09:59:31 +00:00
|
|
|
"storj.io/storj/pkg/bwagreement"
|
2018-11-08 16:18:28 +00:00
|
|
|
"storj.io/storj/pkg/pb"
|
|
|
|
"storj.io/storj/pkg/pointerdb"
|
2018-11-29 18:39:27 +00:00
|
|
|
"storj.io/storj/pkg/storj"
|
2018-11-08 16:18:28 +00:00
|
|
|
"storj.io/storj/storage"
|
|
|
|
)
|
|
|
|
|
2019-01-23 19:58:44 +00:00
|
|
|
// Config contains configurable values for tally
|
|
|
|
type Config struct {
|
|
|
|
Interval time.Duration `help:"how frequently tally should run" default:"30s"`
|
2018-11-08 16:18:28 +00:00
|
|
|
}
|
|
|
|
|
2019-01-23 19:58:44 +00:00
|
|
|
// Tally is the service for accounting for data stored on each storage node
|
|
|
|
type Tally struct { // TODO: rename Tally to Service
|
2019-01-19 18:58:53 +00:00
|
|
|
pointerdb *pointerdb.Service
|
2019-01-23 19:58:44 +00:00
|
|
|
overlay pb.OverlayServer // TODO: this should be *overlay.Service
|
2018-12-14 14:27:21 +00:00
|
|
|
limit int
|
|
|
|
logger *zap.Logger
|
|
|
|
ticker *time.Ticker
|
|
|
|
accountingDB accounting.DB
|
|
|
|
bwAgreementDB bwagreement.DB // bwagreements database
|
2018-11-08 16:18:28 +00:00
|
|
|
}
|
|
|
|
|
2019-01-23 19:58:44 +00:00
|
|
|
// New creates a new Tally
|
|
|
|
func New(logger *zap.Logger, accountingDB accounting.DB, bwAgreementDB bwagreement.DB, pointerdb *pointerdb.Service, overlay pb.OverlayServer, limit int, interval time.Duration) *Tally {
|
|
|
|
return &Tally{
|
2018-12-14 14:27:21 +00:00
|
|
|
pointerdb: pointerdb,
|
|
|
|
overlay: overlay,
|
|
|
|
limit: limit,
|
|
|
|
logger: logger,
|
|
|
|
ticker: time.NewTicker(interval),
|
|
|
|
accountingDB: accountingDB,
|
|
|
|
bwAgreementDB: bwAgreementDB,
|
2018-12-05 14:03:23 +00:00
|
|
|
}
|
2018-11-08 16:18:28 +00:00
|
|
|
}
|
|
|
|
|
2019-01-23 19:58:44 +00:00
|
|
|
// Run the Tally loop
|
|
|
|
func (t *Tally) Run(ctx context.Context) (err error) {
|
2019-01-24 20:05:53 +00:00
|
|
|
t.logger.Info("Tally service starting up")
|
2018-11-08 16:18:28 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
for {
|
2019-02-01 18:50:12 +00:00
|
|
|
if err = t.Tally(ctx); err != nil {
|
|
|
|
t.logger.Error("Tally failed", zap.Error(err))
|
2018-11-08 16:18:28 +00:00
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-t.ticker.C: // wait for the next interval to happen
|
2019-01-23 19:58:44 +00:00
|
|
|
case <-ctx.Done(): // or the Tally is canceled via context
|
2018-11-08 16:18:28 +00:00
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-01 18:50:12 +00:00
|
|
|
//Tally calculates data-at-rest and bandwidth usage once
|
|
|
|
func (t *Tally) Tally(ctx context.Context) error {
|
|
|
|
//data at rest
|
|
|
|
var errAtRest, errBWA error
|
|
|
|
latestTally, nodeData, err := t.calculateAtRestData(ctx)
|
|
|
|
if err != nil {
|
|
|
|
errAtRest = errs.New("Query for data-at-rest failed : %v", err)
|
|
|
|
} else if len(nodeData) > 0 {
|
2019-02-07 04:16:24 +00:00
|
|
|
err = t.SaveAtRestRaw(ctx, latestTally, time.Now().UTC(), nodeData)
|
2019-02-01 18:50:12 +00:00
|
|
|
if err != nil {
|
|
|
|
errAtRest = errs.New("Saving data-at-rest failed : %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//bandwdith
|
|
|
|
tallyEnd, bwTotals, err := t.QueryBW(ctx)
|
|
|
|
if err != nil {
|
|
|
|
errBWA = errs.New("Query for bandwidth failed: %v", err)
|
|
|
|
} else if len(bwTotals) > 0 {
|
2019-02-07 04:16:24 +00:00
|
|
|
err = t.SaveBWRaw(ctx, tallyEnd, time.Now().UTC(), bwTotals)
|
2019-02-01 18:50:12 +00:00
|
|
|
if err != nil {
|
|
|
|
errBWA = errs.New("Saving for bandwidth failed : %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errs.Combine(errAtRest, errBWA)
|
|
|
|
}
|
|
|
|
|
2018-12-12 21:24:08 +00:00
|
|
|
// calculateAtRestData iterates through the pieces on pointerdb and calculates
|
|
|
|
// the amount of at-rest data stored on each respective node
|
2019-02-01 18:50:12 +00:00
|
|
|
func (t *Tally) calculateAtRestData(ctx context.Context) (latestTally time.Time, nodeData map[storj.NodeID]float64, err error) {
|
2018-11-08 16:18:28 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-01-16 19:30:33 +00:00
|
|
|
|
2019-02-01 18:50:12 +00:00
|
|
|
latestTally, err = t.accountingDB.LastTimestamp(ctx, accounting.LastAtRestTally)
|
2019-01-16 19:30:33 +00:00
|
|
|
if err != nil {
|
2019-02-01 18:50:12 +00:00
|
|
|
return latestTally, nodeData, Error.Wrap(err)
|
2019-01-16 19:30:33 +00:00
|
|
|
}
|
2019-02-04 00:42:20 +00:00
|
|
|
nodeData = make(map[storj.NodeID]float64)
|
2019-01-16 19:30:33 +00:00
|
|
|
|
2019-01-19 18:58:53 +00:00
|
|
|
err = t.pointerdb.Iterate("", "", true, false,
|
2018-11-08 16:18:28 +00:00
|
|
|
func(it storage.Iterator) error {
|
|
|
|
var item storage.ListItem
|
2018-12-12 21:24:08 +00:00
|
|
|
for it.Next(&item) {
|
2018-11-08 16:18:28 +00:00
|
|
|
pointer := &pb.Pointer{}
|
|
|
|
err = proto.Unmarshal(item.Value, pointer)
|
|
|
|
if err != nil {
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
2018-12-12 21:24:08 +00:00
|
|
|
remote := pointer.GetRemote()
|
|
|
|
if remote == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pieces := remote.GetRemotePieces()
|
|
|
|
if pieces == nil {
|
|
|
|
t.logger.Debug("no pieces on remote segment")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
segmentSize := pointer.GetSegmentSize()
|
|
|
|
redundancy := remote.GetRedundancy()
|
|
|
|
if redundancy == nil {
|
|
|
|
t.logger.Debug("no redundancy scheme present")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
minReq := redundancy.GetMinReq()
|
|
|
|
if minReq <= 0 {
|
|
|
|
t.logger.Debug("pointer minReq must be an int greater than 0")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pieceSize := segmentSize / int64(minReq)
|
|
|
|
for _, piece := range pieces {
|
2019-01-17 19:39:32 +00:00
|
|
|
t.logger.Info("found piece on Node ID" + piece.NodeId.String())
|
2019-01-16 19:30:33 +00:00
|
|
|
nodeData[piece.NodeId] += float64(pieceSize)
|
2018-11-08 16:18:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
)
|
2019-01-04 19:54:54 +00:00
|
|
|
if len(nodeData) == 0 {
|
2019-02-01 18:50:12 +00:00
|
|
|
return latestTally, nodeData, nil
|
2019-01-04 19:54:54 +00:00
|
|
|
}
|
2018-12-18 17:18:42 +00:00
|
|
|
if err != nil {
|
2019-02-01 18:50:12 +00:00
|
|
|
return latestTally, nodeData, Error.Wrap(err)
|
2018-12-18 17:18:42 +00:00
|
|
|
}
|
2019-01-16 19:30:33 +00:00
|
|
|
//store byte hours, not just bytes
|
2019-02-01 18:50:12 +00:00
|
|
|
numHours := time.Now().Sub(latestTally).Hours()
|
|
|
|
if latestTally.IsZero() {
|
|
|
|
numHours = 1.0 //todo: something more considered?
|
2018-12-18 17:18:42 +00:00
|
|
|
}
|
2019-02-01 18:50:12 +00:00
|
|
|
latestTally = time.Now()
|
2019-01-16 19:30:33 +00:00
|
|
|
for k := range nodeData {
|
2019-02-01 18:50:12 +00:00
|
|
|
nodeData[k] *= numHours //calculate byte hours
|
2018-12-18 17:18:42 +00:00
|
|
|
}
|
2019-02-01 18:50:12 +00:00
|
|
|
return latestTally, nodeData, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveAtRestRaw records raw tallies of at-rest-data and updates the LastTimestamp
|
2019-02-07 04:16:24 +00:00
|
|
|
func (t *Tally) SaveAtRestRaw(ctx context.Context, latestTally time.Time, created time.Time, nodeData map[storj.NodeID]float64) error {
|
|
|
|
return t.accountingDB.SaveAtRestRaw(ctx, latestTally, created, nodeData)
|
2018-11-08 16:18:28 +00:00
|
|
|
}
|
2018-12-05 14:03:23 +00:00
|
|
|
|
2019-01-23 19:58:44 +00:00
|
|
|
// QueryBW queries bandwidth allocation database, selecting all new contracts since the last collection run time.
|
2019-01-10 11:41:57 +00:00
|
|
|
// Grouping by action type, storage node ID and adding total of bandwidth to granular data table.
|
2019-02-01 18:50:12 +00:00
|
|
|
func (t *Tally) QueryBW(ctx context.Context) (time.Time, map[storj.NodeID][]int64, error) {
|
|
|
|
var bwTotals map[storj.NodeID][]int64
|
|
|
|
now := time.Now()
|
|
|
|
lastBwTally, err := t.accountingDB.LastTimestamp(ctx, accounting.LastBandwidthTally)
|
2018-12-05 14:03:23 +00:00
|
|
|
if err != nil {
|
2019-02-01 18:50:12 +00:00
|
|
|
return now, bwTotals, Error.Wrap(err)
|
2018-12-14 14:27:21 +00:00
|
|
|
}
|
2019-02-01 18:50:12 +00:00
|
|
|
bwTotals, err = t.bwAgreementDB.GetTotals(ctx, lastBwTally, now)
|
2018-12-14 14:27:21 +00:00
|
|
|
if err != nil {
|
2019-02-01 18:50:12 +00:00
|
|
|
return now, bwTotals, Error.Wrap(err)
|
2018-12-05 14:03:23 +00:00
|
|
|
}
|
2019-02-01 18:50:12 +00:00
|
|
|
if len(bwTotals) == 0 {
|
2018-12-05 14:03:23 +00:00
|
|
|
t.logger.Info("Tally found no new bandwidth allocations")
|
2019-02-01 18:50:12 +00:00
|
|
|
return now, bwTotals, nil
|
2018-12-05 14:03:23 +00:00
|
|
|
}
|
2019-02-01 18:50:12 +00:00
|
|
|
return now, bwTotals, nil
|
|
|
|
}
|
2018-12-05 14:03:23 +00:00
|
|
|
|
2019-02-01 18:50:12 +00:00
|
|
|
// SaveBWRaw records granular tallies (sums of bw agreement values) to the database and updates the LastTimestamp
|
2019-02-07 04:16:24 +00:00
|
|
|
func (t *Tally) SaveBWRaw(ctx context.Context, tallyEnd time.Time, created time.Time, bwTotals map[storj.NodeID][]int64) error {
|
|
|
|
return t.accountingDB.SaveBWRaw(ctx, tallyEnd, created, bwTotals)
|
2018-12-05 14:03:23 +00:00
|
|
|
}
|