RateS computeRateSIntervals implementation, engine.CostForIntervals

This commit is contained in:
DanB
2020-11-12 17:54:56 +01:00
parent 811884db62
commit 20c086e42a
5 changed files with 187 additions and 31 deletions

View File

@@ -3091,6 +3091,9 @@ func (dm *DataManager) GetRateProfile(tenant, id string, cacheRead, cacheWrite b
return nil, err
}
}
if err = rpp.Compile(); err != nil {
return nil, err
}
if cacheWrite {
if errCh := Cache.Set(utils.CacheRateProfiles, tntID, rpp, nil,
cacheCommit(transactionID), transactionID); errCh != nil {

View File

@@ -73,7 +73,7 @@ type Rate struct {
Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
IntervalRates []*IntervalRate
sched cron.Schedule // compiled version of activation time as cron.Schedule interface
sched cron.Schedule // compiled version of activation times as cron.Schedule interface
uID string
}
@@ -88,7 +88,9 @@ type IntervalRate struct {
Increment time.Duration // RateIncrement
Value float64 // RateValue
val *utils.Decimal // cached version of the Decimal
decVal *utils.Decimal // cached version of the Value converted to Decimal for operations
decUnit *utils.Decimal // cached version of the Unit converted to Decimal for operations
decIcrm *utils.Decimal // cached version of the Increment converted to Decimal for operations
}
func (rt *Rate) Compile() (err error) {
@@ -99,6 +101,11 @@ func (rt *Rate) Compile() (err error) {
if rt.sched, err = cron.ParseStandard(aTime); err != nil {
return
}
for _, iRt := range rt.IntervalRates {
iRt.decVal = utils.NewDecimalFromFloat64(iRt.Value)
iRt.decUnit = utils.NewDecimalFromUint64(uint64(iRt.Unit))
iRt.decIcrm = utils.NewDecimalFromUint64(uint64(iRt.Increment))
}
return
}
@@ -126,6 +133,21 @@ func (rt *Rate) RunTimes(sTime, eTime time.Time, verbosity int) (aTimes [][]time
return nil, utils.ErrMaxIterationsReached
}
// DecimalValue exports the decVal variable
func (rIt *IntervalRate) DecimalValue() *utils.Decimal {
return rIt.decVal
}
// DecimalUnit exports the decUnit variable
func (rIt *IntervalRate) DecimalUnit() *utils.Decimal {
return rIt.decUnit
}
// DecimalIncrement exports the decUnit variable
func (rIt *IntervalRate) DecimalIncrement() *utils.Decimal {
return rIt.decIcrm
}
// RateProfileWithOpts is used in replicatorV1 for dispatcher
type RateProfileWithOpts struct {
*RateProfile
@@ -138,16 +160,16 @@ type RateSInterval struct {
Increments []*RateSIncrement
CompressFactor int64
cost *utils.Decimal // unexported total cost
cost *utils.Decimal // unexported total interval cost
}
type RateSIncrement struct {
Rate *Rate
UsageStart time.Duration
Usage time.Duration
Rate *Rate
IntervalRateIndex int
CompressFactor int64
Cost float64
cost *utils.Decimal // unexported total increment cost
}
@@ -165,6 +187,15 @@ func (rIv *RateSInterval) CompressEquals(rIv2 *RateSInterval) (eq bool) {
return
}
func (rIv *RateSInterval) Cost() *utils.Decimal {
if rIv.cost == nil {
for _, incrm := range rIv.Increments {
rIv.cost = utils.NewDecimal().Add(rIv.cost, incrm.Cost())
}
}
return rIv.cost
}
// CompressEquals compares two RateSIncrement for Compress function
func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
if rIcr.Rate.UID() != rIcr2.Rate.UID() {
@@ -173,11 +204,33 @@ func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
if rIcr.Usage != rIcr2.Usage {
return
}
if rIcr.Cost != rIcr2.Cost {
return
}
if rIcr.CompressFactor != rIcr2.CompressFactor {
return
}
return true
}
// Cost computes the Cost on RateSIncrement
func (rIcr *RateSIncrement) Cost() *utils.Decimal {
if rIcr.cost == nil {
icrRt := rIcr.Rate.IntervalRates[rIcr.IntervalRateIndex]
icrCost := icrRt.DecimalValue()
if icrRt.Unit != icrRt.Increment {
icrCost = utils.NewDecimal().Divide(
utils.NewDecimal().Multiply(icrCost, icrRt.DecimalIncrement()),
icrRt.DecimalUnit())
}
rIcr.cost = utils.NewDecimal().Multiply(
icrCost,
utils.NewDecimalFromUint64(uint64(rIcr.CompressFactor)))
}
return rIcr.cost
}
// CostForIntervals sums the costs for all intervals
func CostForIntervals(rtIvls []*RateSInterval) (cost *utils.Decimal) {
for _, rtIvl := range rtIvls {
cost = utils.NewDecimal().Add(cost, rtIvl.Cost())
}
return
}

View File

@@ -177,25 +177,72 @@ func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, usage time.Dura
return
}
// costWithRates will give out the cost projection for the given orderedRates and usage
func costWithRates(rts []*orderedRate, usage time.Duration) (rtIvls []*engine.RateSInterval, err error) {
//var usageSIdx time.Duration // usageStart for one rate
// computeRateSIntervals will give out the cost projection for the given orderedRates and usage
func computeRateSIntervals(rts []*orderedRate, usage time.Duration) (rtIvls []*engine.RateSInterval, err error) {
var rtUsageSIdx time.Duration // rtUsageSIdx for one rate
for i, rt := range rts {
var usageEIdx time.Duration
if i != len(rts)-1 {
usageEIdx = rts[i+1].Duration
isLastRt := i == len(rts)-1
var rtUsageEIdx time.Duration
if !isLastRt {
rtUsageEIdx = rts[i+1].Duration
} else {
rtUsageEIdx = usage
}
var iRts []*engine.IntervalRate
for _, iRt := range rt.IntervalRates {
if usageEIdx == 0 || iRt.IntervalStart < usageEIdx {
iRts = append(iRts, iRt)
var rIcmts []*engine.RateSIncrement
iRtUsageSIdx := rtUsageSIdx
iRtUsageEIdx := rtUsageEIdx
for j, iRt := range rt.IntervalRates {
if iRtUsageSIdx >= rtUsageEIdx { // charged enough for interval
break
}
// make sure we bill from start
if j == 0 && iRt.IntervalStart > iRtUsageSIdx {
return nil, fmt.Errorf("intervalStart for rate: <%s> higher than usage: %v",
rt.UID(), iRtUsageSIdx)
}
isLastIRt := j == len(rt.IntervalRates)-1
if iRt.IntervalStart > iRtUsageSIdx ||
(!isLastIRt && rt.IntervalRates[j+1].IntervalStart <= iRtUsageSIdx) {
break // the rates should be already ordered, break here
}
if !isLastIRt {
iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart
} else {
iRtUsageEIdx = rtUsageEIdx
}
iRtUsage := iRtUsageEIdx - iRtUsageSIdx
if iRtUsageEIdx == time.Duration(0) {
return nil, fmt.Errorf("zero usage to be charged with rate: <%s>", rt.UID())
}
if iRt.Increment == time.Duration(0) {
return nil, fmt.Errorf("zero increment to be charged within rate: <%s>", rt.UID())
}
intUsage := int64(iRtUsage)
intIncrm := int64(iRt.Increment)
cmpFactor := intUsage / intIncrm
if intUsage%intIncrm != 0 {
cmpFactor += 1 // int division has used math.Floor, need Ceil
}
rIcrm := &engine.RateSIncrement{
UsageStart: iRtUsageSIdx,
Usage: iRtUsage,
Rate: rt.Rate,
IntervalRateIndex: j,
CompressFactor: cmpFactor,
}
rIcmts = append(rIcmts, rIcrm)
iRtUsageSIdx += iRtUsage
}
//fmt.Printf("iRts: %+v\n", iRts)
if usageEIdx == 0 {
rtIvls = append(rtIvls,
&engine.RateSInterval{
UsageStart: rtUsageSIdx,
Increments: rIcmts,
CompressFactor: 1})
if iRtUsageSIdx >= usage { // charged enough for the usage
break
}
//usageSIdx = usageEIdx // continue for the next interval
rtUsageSIdx = rtUsageEIdx // continue for the next interval
}
return
}

View File

@@ -1575,7 +1575,7 @@ func TestOrderRatesOnIntervalStartLowerThanEndIdx(t *testing.T) {
}
}
func TestCostWithRates(t *testing.T) {
func TestComputeRateSIntervals(t *testing.T) {
rt0 := &engine.Rate{
ID: "RATE0",
IntervalRates: []*engine.IntervalRate{
@@ -1627,9 +1627,49 @@ func TestCostWithRates(t *testing.T) {
},
}
//eRtIvls := []*engine.RateSInterval{}
var eRtIvls []*engine.RateSInterval
if rtIvls, err := costWithRates(rts, time.Duration(130*time.Second)); err != nil {
eRtIvls := []*engine.RateSInterval{
{
UsageStart: time.Duration(0),
Increments: []*engine.RateSIncrement{
{
UsageStart: time.Duration(0),
Usage: time.Duration(time.Minute),
Rate: rt0,
IntervalRateIndex: 0,
CompressFactor: 1,
},
{
UsageStart: time.Duration(time.Minute),
Usage: time.Duration(30 * time.Second),
Rate: rt0,
IntervalRateIndex: 1,
CompressFactor: 30,
},
},
CompressFactor: 1,
},
{
UsageStart: time.Duration(90 * time.Second),
Increments: []*engine.RateSIncrement{
{
UsageStart: time.Duration(90 * time.Second),
Usage: time.Duration(30 * time.Second),
Rate: rt1,
IntervalRateIndex: 0,
CompressFactor: 30,
},
{
UsageStart: time.Duration(2 * time.Minute),
Usage: time.Duration(10 * time.Second),
Rate: rt1,
IntervalRateIndex: 1,
CompressFactor: 10,
},
},
CompressFactor: 1,
},
}
if rtIvls, err := computeRateSIntervals(rts, time.Duration(130*time.Second)); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eRtIvls, rtIvls) {
t.Errorf("expecting: %+v, received: %+v", eRtIvls, rtIvls)

View File

@@ -26,6 +26,10 @@ func NewDecimalFromFloat64(x float64) *Decimal {
return &Decimal{new(decimal.Big).SetFloat64(x)}
}
func NewDecimalFromUint64(x uint64) *Decimal {
return &Decimal{new(decimal.Big).SetUint64(x)}
}
func NewDecimal() *Decimal {
return &Decimal{new(decimal.Big)}
}
@@ -41,15 +45,24 @@ func (d *Decimal) Float64() (f float64) {
}
func (d *Decimal) MarshalJSON() ([]byte, error) {
if d.Big == nil {
d.Big = new(decimal.Big)
}
return d.Big.MarshalText()
}
func (d *Decimal) UnmarshalJSON(data []byte) error {
if d.Big == nil {
d.Big = new(decimal.Big)
}
return d.Big.UnmarshalJSON(data)
}
func (d *Decimal) Divide(x, y *Decimal) *Decimal {
d.Big.Quo(x.Big, y.Big)
return d
}
func (d *Decimal) Multiply(x, y *Decimal) *Decimal {
d.Big.Mul(x.Big, y.Big)
return d
}
func (d *Decimal) Add(x, y *Decimal) *Decimal {
d.Big.Add(x.Big, y.Big)
return d
}