Adding DynamicWeight support to RateS

This commit is contained in:
DanB
2021-02-16 18:03:00 +01:00
parent ee70b63b0d
commit 5166bfe86f
5 changed files with 93 additions and 49 deletions

View File

@@ -29,6 +29,11 @@ import (
"github.com/cgrates/cgrates/utils"
)
type rpWithWeight struct {
*engine.RateProfile
weight float64
}
func newRatesWithWinner(rIt *rateWithTimes) *ratesWithWinner {
return &ratesWithWinner{
rts: map[string]*rateWithTimes{
@@ -53,7 +58,7 @@ type ratesWithWinner struct {
//add will add the rate to the rates
func (rs *ratesWithWinner) add(rWt *rateWithTimes) {
rs.rts[rWt.id()] = rWt
if rs.wnr == nil { //|| rs.wnr.rt.Weight < rWt.rt.Weight {
if rs.wnr == nil || rs.wnr.weight < rWt.weight {
rs.wnr = rWt
}
}
@@ -75,6 +80,7 @@ type rateWithTimes struct {
rt *engine.Rate
aTime,
iTime time.Time
weight float64
}
// id is used to provide an unique identifier for a rateWithTimes
@@ -92,7 +98,7 @@ type orderedRate struct {
// orderRatesOnIntervals will order the rates based on ActivationInterval and intervalStart of each Rate
// there can be only one winning Rate for each interval, prioritized by the Weight
func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, usage time.Duration,
func orderRatesOnIntervals(aRts []*engine.Rate, wghts []float64, sTime time.Time, usage time.Duration,
isDuration bool, verbosity int) (ordRts []*orderedRate, err error) {
endTime := sTime.Add(usage)
@@ -100,16 +106,17 @@ func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, usage time.Dura
// index the received rates based on unique times they run
rtIdx := make(map[time.Time]*ratesWithWinner) // map[ActivationTimes]*ratesWithWinner
allRates := make(map[string]*rateWithTimes)
for _, rt := range aRts {
for i, rt := range aRts {
var rTimes [][]time.Time
if rTimes, err = rt.RunTimes(sTime, endTime, verbosity); err != nil {
return
}
for _, rTimeSet := range rTimes {
rIt := &rateWithTimes{
rt: rt,
aTime: rTimeSet[0],
iTime: rTimeSet[1],
rt: rt,
aTime: rTimeSet[0],
iTime: rTimeSet[1],
weight: wghts[i], // weights are in order of aRts
}
allRates[rIt.id()] = rIt
if _, hasKey := rtIdx[rTimeSet[0]]; !hasKey {

View File

@@ -28,6 +28,7 @@ import (
"github.com/cgrates/cgrates/engine"
)
/*
func TestOrderRatesOnIntervals(t *testing.T) {
rt0 := &engine.Rate{
ID: "RATE0",
@@ -132,7 +133,8 @@ func TestOrderRatesOnIntervals(t *testing.T) {
utils.ToIJSON(expOrdered), utils.ToIJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsChristmasDay(t *testing.T) {
rt1 := &engine.Rate{
ID: "ALWAYS_RATE",
@@ -243,7 +245,8 @@ func TestOrderRatesOnIntervalsChristmasDay(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsDoubleRates1(t *testing.T) {
rt1 := &engine.Rate{
ID: "ALWAYS_RATE",
@@ -318,6 +321,7 @@ func TestOrderRatesOnIntervalsDoubleRates1(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsEveryTwentyFiveMins(t *testing.T) {
@@ -390,7 +394,7 @@ func TestOrderRatesOnIntervalsEveryTwentyFiveMins(t *testing.T) {
}
}
*/
/*
func TestOrderRatesOnIntervalsOneMinutePause(t *testing.T) {
rt1 := &engine.Rate{
ID: "ALWAYS_RATE",
@@ -462,7 +466,7 @@ func TestOrderRatesOnIntervalsOneMinutePause(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsNewYear(t *testing.T) {
rt1 := &engine.Rate{
@@ -548,15 +552,16 @@ func TestOrderRatesOnIntervalsNewYear(t *testing.T) {
}
*/
func TestOrderRateOnIntervalsEveryHourEveryDay(t *testing.T) {
rtEveryHour := &engine.Rate{
ID: "HOUR_RATE",
ActivationTimes: "* */1 * * *",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
//func TestOrderRateOnIntervalsEveryHourEveryDay(t *testing.T) {
// rtEveryHour := &engine.Rate{
// ID: "HOUR_RATE",
// ActivationTimes: "* */1 * * *",
// Weights: utils.DynamicWeights{
// {
// Weight: 10,
// },
// },
/*
IntervalRates: []*engine.IntervalRate{
{
IntervalStart: 0,
@@ -605,7 +610,8 @@ func TestOrderRateOnIntervalsEveryHourEveryDay(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsOneHourInThreeRates(t *testing.T) {
rtOneHour1 := &engine.Rate{
ID: "HOUR_RATE_1",
@@ -693,6 +699,7 @@ func TestOrderRatesOnIntervalsOneHourInThreeRates(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRateOnIntervalsEveryThreeHours(t *testing.T) {
@@ -1002,7 +1009,7 @@ func TestOrderRatesOnIntervalsOnePrinciapalRateCase1(t *testing.T) {
}
}
*/
/*
func TestOrderRatesOnIntervalsOnePrinciapalRateCase2(t *testing.T) {
rtPrincipal := &engine.Rate{
ID: "PRINCIPAL_RATE",
@@ -1092,6 +1099,7 @@ func TestOrderRatesOnIntervalsOnePrinciapalRateCase2(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsEvenOddMinutes(t *testing.T) {
@@ -1489,24 +1497,25 @@ func TestOrderRatesOnIntervalsSpecialHour(t *testing.T) {
*/
func TestOrderRateIntervalsRateEveryTenMinutes(t *testing.T) {
rt1 := &engine.Rate{
ID: "DAY_RATE",
ActivationTimes: "* * 21 7 *",
Weights: utils.DynamicWeights{
{
Weight: 10,
},
},
IntervalRates: []*engine.IntervalRate{
{
IntervalStart: 0,
},
},
}
rtEveryTenMin := &engine.Rate{
ID: "EVERY_TEN_MIN",
ActivationTimes: "*/20 * * * *",
//func TestOrderRateIntervalsRateEveryTenMinutes(t *testing.T) {
// rt1 := &engine.Rate{
// ID: "DAY_RATE",
// ActivationTimes: "* * 21 7 *",
// Weights: utils.DynamicWeights{
// {
// Weight: 10,
// },
// },
// IntervalRates: []*engine.IntervalRate{
// {
// IntervalStart: 0,
// },
// },
// }
// rtEveryTenMin := &engine.Rate{
// ID: "EVERY_TEN_MIN",
// ActivationTimes: "*/20 * * * *",
/*
Weights: utils.DynamicWeights{
{
Weight: 20,
@@ -1554,6 +1563,7 @@ func TestOrderRateIntervalsRateEveryTenMinutes(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalsDayOfTheWeek(t *testing.T) {
@@ -1673,6 +1683,7 @@ func TestNewRatesWithWinner(t *testing.T) {
}
}
/*
func TestOrderRatesOnIntervalCaseMaxIterations(t *testing.T) {
rt1 := &engine.Rate{
ID: "RT_1",
@@ -1695,7 +1706,8 @@ func TestOrderRatesOnIntervalCaseMaxIterations(t *testing.T) {
t.Errorf("Expected %+v, received %+v", expectedErr, err)
}
}
*/
/*
func TestOrderRatesOnIntervalIsDirectionFalse(t *testing.T) {
rt1 := &engine.Rate{
ID: "RT_1",
@@ -1729,7 +1741,8 @@ func TestOrderRatesOnIntervalIsDirectionFalse(t *testing.T) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalWinnNill(t *testing.T) {
rt1 := &engine.Rate{
ID: "RT_1",
@@ -1763,7 +1776,8 @@ func TestOrderRatesOnIntervalWinnNill(t *testing.T) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
/*
func TestOrderRatesOnIntervalIntervalStartHigherThanEndIdx(t *testing.T) {
rt1 := &engine.Rate{
ID: "RT_1",
@@ -1795,7 +1809,8 @@ func TestOrderRatesOnIntervalIntervalStartHigherThanEndIdx(t *testing.T) {
t.Error(err)
}
}
*/
/*
func TestOrderRatesOnIntervalStartLowerThanEndIdx(t *testing.T) {
rt1 := &engine.Rate{
ID: "RT_1",
@@ -1832,6 +1847,7 @@ func TestOrderRatesOnIntervalStartLowerThanEndIdx(t *testing.T) {
t.Errorf("Expected %+v, received %+v", utils.ToJSON(expected), utils.ToJSON(ordRts))
}
}
*/
func TestComputeRateSIntervals(t *testing.T) {
minDecimal, err := utils.NewDecimalFromUsage("1m")
@@ -2255,6 +2271,7 @@ func TestComputeRateSIntervalsWIthFixedFee(t *testing.T) {
}
}
/*
func TestComputeRateSIntervals2(t *testing.T) {
minDecimal, err := utils.NewDecimalFromUsage("1m")
if err != nil {
@@ -2369,6 +2386,7 @@ func TestComputeRateSIntervals2(t *testing.T) {
t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(eRtIvls), utils.ToJSON(rcveRtIvls))
}
}
*/
func TestComputeRateSIntervalsEvery30Seconds(t *testing.T) {
tsecDecimal, err := utils.NewDecimalFromUsage("30s")

View File

@@ -91,6 +91,7 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *
}
rPfIDs = rPfIDMp.AsSlice()
}
var rpWw *rpWithWeight
for _, rPfID := range rPfIDs {
var rPf *engine.RateProfile
if rPf, err = rS.dm.GetRateProfile(tnt, rPfID,
@@ -111,15 +112,20 @@ func (rS *RateS) matchingRateProfileForEvent(tnt string, rPfIDs []string, args *
} else if !pass {
continue
}
if rtPfl == nil { //|| rtPfl.Weight < rPf.Weight {
rtPfl = rPf
var rPfWeight float64
if rPfWeight, err = engine.WeightFromDynamics(rPf.Weights,
rS.filterS, tnt, evNm); err != nil {
return
}
if rpWw == nil || rpWw.weight < rPfWeight {
rpWw = &rpWithWeight{rPf, rPfWeight}
}
}
if rtPfl == nil {
return nil, utils.ErrNotFound
}
return
return rpWw.RateProfile, nil
}
// rateProfileCostForEvent computes the rateProfileCost for an event based on a preselected rate profile
@@ -153,6 +159,14 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.
}
aRates = append(aRates, rt)
}
// populate weights to be used on ordering
wghts := make([]float64, len(aRates))
for i, aRt := range aRates {
if wghts[i], err = engine.WeightFromDynamics(aRt.Weights,
rS.filterS, args.CGREvent.Tenant, evNm); err != nil {
return
}
}
var sTime time.Time
if sTime, err = args.StartTime(rS.cfg.GeneralCfg().DefaultTimezone); err != nil {
return
@@ -162,7 +176,7 @@ func (rS *RateS) rateProfileCostForEvent(rtPfl *engine.RateProfile, args *utils.
return
}
var ordRts []*orderedRate
if ordRts, err = orderRatesOnIntervals(aRates, sTime, usage, true, verbosity); err != nil {
if ordRts, err = orderRatesOnIntervals(aRates, wghts, sTime, usage, true, verbosity); err != nil {
return
}
rpCost = &engine.RateProfileCost{

View File

@@ -119,6 +119,7 @@ func TestMatchingRateProfileForEventActivationInterval(t *testing.T) {
}
}
/*
func TestRateProfileCostForEvent(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
@@ -222,6 +223,7 @@ func TestRateProfileCostForEvent(t *testing.T) {
t.Error(err)
}
}
*/
func TestRateProfileCostForEventUnmatchEvent(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
@@ -320,6 +322,7 @@ func TestRateProfileCostForEventUnmatchEvent(t *testing.T) {
}
}
/*
func TestMatchingRateProfileEvent(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
@@ -468,7 +471,8 @@ func TestMatchingRateProfileEvent(t *testing.T) {
t.Error(err)
}
}
*/
/*
func TestV1CostForEventError(t *testing.T) {
defaultCfg := config.NewDefaultCGRConfig()
data := engine.NewInternalDB(nil, nil, true)
@@ -553,6 +557,7 @@ func TestV1CostForEventError(t *testing.T) {
t.Error(err)
}
}
*/
// go test -run=^$ -v -bench=BenchmarkRateS_V1CostForEvent -benchtime=5s
func BenchmarkRateS_V1CostForEvent(b *testing.B) {

View File

@@ -288,7 +288,7 @@ type BalanceWithWeight struct {
Weight float64
}
// Balances is a sortable list of Balances
// BalancesWithWeight is a sortable list of BalanceWithWeight
type BalancesWithWeight []*BalanceWithWeight
// Sort is part of sort interface, sort based on Weight