EventCost fixes, more testing

This commit is contained in:
DanB
2017-08-21 20:49:18 +02:00
parent edb2003c86
commit c02e49cbd1
2 changed files with 259 additions and 124 deletions

View File

@@ -19,6 +19,7 @@ package engine
import (
"errors"
"fmt"
"time"
"github.com/cgrates/cgrates/utils"
@@ -264,6 +265,7 @@ func (ec *EventCost) AsCallCost() *CallCost {
ts.TimeEnd = ts.TimeStart.Add(
time.Duration(cIl.Usage().Nanoseconds() * int64(cIl.CompressFactor)))
if cIl.RatingID != "" {
//fmt.Printf("Checking RatingID: <%s>\n", cIl.RatingID)
if ec.Rating[cIl.RatingID].RatingFiltersID != "" {
rfs := ec.RatingFilters[ec.Rating[cIl.RatingID].RatingFiltersID]
ts.MatchedSubject = rfs["Subject"].(string)
@@ -460,6 +462,7 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error
if ec.Usage == nil {
ec.GetUsage()
}
fmt.Printf("Trim, atUsage: %v, eventCostUsage: %v\n", atUsage, ec.Usage)
origECUsage := ec.GetUsage()
if atUsage >= *ec.Usage {
return // no trim
@@ -480,7 +483,7 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error
srplusEC.StartTime = ec.StartTime
srplusEC.AccountSummary = ec.AccountSummary.Clone()
var lastActiveCIlIdx *int // marks last index which should stay with ec
var lastActiveCIlIdx *int // mark last index which should stay with ec
for i, cIl := range ec.Charges {
if cIl.ecUsageIdx == nil {
ec.ComputeEventCostUsageIndexes()
@@ -504,12 +507,14 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error
}
srplusEC.Charges = ec.Charges[*lastActiveCIlIdx+1:]
ec.Charges = ec.Charges[:*lastActiveCIlIdx+1]
if lastActiveCIl.CompressFactor != 1 { // Split based on compress factor if needed
ec.Usage = nil
ec.Cost = nil
if lastActiveCIl.CompressFactor != 1 &&
*lastActiveCIl.ecUsageIdx+*lastActiveCIl.TotalUsage() > atUsage { // Split based on compress factor if needed
var laCF int
for ciCnt := 1; ciCnt <= lastActiveCIl.CompressFactor; ciCnt++ {
if *lastActiveCIl.ecUsageIdx+
time.Duration(lastActiveCIl.usage.Nanoseconds()*int64(ciCnt)) > atUsage {
time.Duration(lastActiveCIl.usage.Nanoseconds()*int64(ciCnt)) >= atUsage {
laCF = ciCnt
break
}
@@ -523,72 +528,83 @@ func (ec *EventCost) Trim(atUsage time.Duration) (srplusEC *EventCost, err error
srplsCIl.CompressFactor = lastActiveCIl.CompressFactor - laCF
srplusEC.Charges = append([]*ChargingInterval{srplsCIl}, srplusEC.Charges...) // prepend surplus CIl
lastActiveCIl.CompressFactor = laCF // correct compress factor
ec.Usage = nil
ec.Cost = nil
}
}
atUsage = atUsage - time.Duration(lastActiveCIl.ecUsageIdx.Nanoseconds()*int64(lastActiveCIl.CompressFactor)) // remaining duration to cover in increments
// find out last increment covering duration
var lastActiveCItIdx *int
var incrementsUsage time.Duration
for i, cIt := range lastActiveCIl.Increments {
incrementsUsage += cIt.TotalUsage()
if incrementsUsage >= atUsage {
lastActiveCItIdx = utils.IntPointer(i)
break
}
}
if lastActiveCItIdx == nil { // bug in increments
return nil, errors.New("no active increment found")
}
lastActiveCIts := lastActiveCIl.Increments // so we can modify the reference in case we have surplus
lastIncrement := lastActiveCIts[*lastActiveCItIdx]
if lastIncrement.CompressFactor == 0 {
return nil, errors.New("empty compress factor in increment")
}
var srplsIncrements []*ChargingIncrement
if *lastActiveCItIdx < len(lastActiveCIl.Increments)-1 { // less that complete increments, have surplus
srplsIncrements = lastActiveCIts[*lastActiveCItIdx+1:]
lastActiveCIts = lastActiveCIts[:*lastActiveCItIdx+1]
}
var laItCF int
if lastIncrement.CompressFactor != 1 { // detect the increment covering the last part of usage
incrementsUsage -= lastIncrement.TotalUsage()
for cnt := 1; cnt <= lastIncrement.CompressFactor; cnt++ {
incrementsUsage += lastIncrement.Usage
if atUsage != ec.GetUsage()+*lastActiveCIl.TotalUsage() { // lastInterval covering more than needed, need split
atUsage -= (ec.GetUsage() - *lastActiveCIl.TotalUsage()) // remaining duration to cover in increments of the last charging interval
// find out last increment covering duration
var lastActiveCItIdx *int
var incrementsUsage time.Duration
for i, cIt := range lastActiveCIl.Increments {
incrementsUsage += cIt.TotalUsage()
if incrementsUsage >= atUsage {
laItCF = cnt
lastActiveCItIdx = utils.IntPointer(i)
break
}
}
if laItCF == 0 {
return nil, errors.New("cannot detect last active CompressFactor in ChargingIncrement")
if lastActiveCItIdx == nil { // bug in increments
return nil, errors.New("no active increment found")
}
if laItCF != lastIncrement.CompressFactor {
srplsIncrement := lastIncrement.Clone()
srplsIncrement.CompressFactor = srplsIncrement.CompressFactor - laItCF
srplsIncrements = append([]*ChargingIncrement{srplsIncrement}, srplsIncrements...) // prepend the surplus out of compress
lastActiveCIts := lastActiveCIl.Increments // so we can modify the reference in case we have surplus
lastIncrement := lastActiveCIts[*lastActiveCItIdx]
if lastIncrement.CompressFactor == 0 {
return nil, errors.New("empty compress factor in increment")
}
}
var srplsIncrements []*ChargingIncrement
if *lastActiveCItIdx < len(lastActiveCIl.Increments)-1 { // less that complete increments, have surplus
srplsIncrements = lastActiveCIts[*lastActiveCItIdx+1:]
lastActiveCIts = lastActiveCIts[:*lastActiveCItIdx+1]
ec.Usage = nil
ec.Cost = nil
}
var laItCF int
if lastIncrement.CompressFactor != 1 && atUsage != incrementsUsage {
// last increment compress factor is higher that we need to cover
incrementsUsage -= lastIncrement.TotalUsage()
for cnt := 1; cnt <= lastIncrement.CompressFactor; cnt++ {
incrementsUsage += lastIncrement.Usage
if incrementsUsage >= atUsage {
laItCF = cnt
break
}
}
if laItCF == 0 {
return nil, errors.New("cannot detect last active CompressFactor in ChargingIncrement")
}
if laItCF != lastIncrement.CompressFactor {
srplsIncrement := lastIncrement.Clone()
srplsIncrement.CompressFactor = srplsIncrement.CompressFactor - laItCF
srplsIncrements = append([]*ChargingIncrement{srplsIncrement}, srplsIncrements...) // prepend the surplus out of compress
lastIncrement.CompressFactor = laItCF
ec.Usage = nil
ec.Cost = nil
if len(srplsIncrements) != 0 { // partially covering, need trim
}
}
if lastActiveCIl.CompressFactor > 1 { // ChargingInterval not covering in full, need to split it
lastActiveCIl.CompressFactor -= 1
ec.Charges = append(ec.Charges, lastActiveCIl.Clone())
lastActiveCIl = ec.Charges[len(ec.Charges)-1]
lastActiveCIl.CompressFactor = 1
}
srplsCIl := lastActiveCIl.Clone()
srplsCIl.Increments = srplsIncrements
srplusEC.Charges = append([]*ChargingInterval{srplsCIl}, srplusEC.Charges...)
lastActiveCIl.Increments = make([]*ChargingIncrement, len(lastActiveCIts))
for i, incr := range lastActiveCIts {
lastActiveCIl.Increments[i] = incr.Clone() // avoid pointer references to the other interval
}
if laItCF != 0 {
lastActiveCIl.Increments[len(lastActiveCIl.Increments)-1].CompressFactor = laItCF // correct the compressFactor for the last increment
if len(srplsIncrements) != 0 { // partially covering, need trim
if lastActiveCIl.CompressFactor > 1 { // ChargingInterval not covering in full, need to split it
lastActiveCIl.CompressFactor -= 1
ec.Charges = append(ec.Charges, lastActiveCIl.Clone())
lastActiveCIl = ec.Charges[len(ec.Charges)-1]
lastActiveCIl.CompressFactor = 1
ec.Usage = nil
ec.Cost = nil
}
srplsCIl := lastActiveCIl.Clone()
srplsCIl.Increments = srplsIncrements
srplusEC.Charges = append([]*ChargingInterval{srplsCIl}, srplusEC.Charges...)
lastActiveCIl.Increments = make([]*ChargingIncrement, len(lastActiveCIts))
for i, incr := range lastActiveCIts {
lastActiveCIl.Increments[i] = incr.Clone() // avoid pointer references to the other interval
}
if laItCF != 0 {
lastActiveCIl.Increments[len(lastActiveCIl.Increments)-1].CompressFactor = laItCF // correct the compressFactor for the last increment
ec.Usage = nil
ec.Cost = nil
}
}
}
ec.ResetCounters()

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"fmt"
"reflect"
"testing"
"time"
@@ -73,6 +74,30 @@ var testEC = &EventCost{
},
CompressFactor: 4,
},
&ChargingInterval{
RatingID: "c1a5ab9",
Increments: []*ChargingIncrement{
&ChargingIncrement{
Usage: time.Duration(1 * time.Second),
Cost: 0,
AccountingID: "3455b83",
CompressFactor: 10,
},
&ChargingIncrement{
Usage: time.Duration(10 * time.Second),
Cost: 0.01,
AccountingID: "a012888",
CompressFactor: 2,
},
&ChargingIncrement{
Usage: time.Duration(1 * time.Second),
Cost: 0.005,
AccountingID: "44d6c02",
CompressFactor: 30,
},
},
CompressFactor: 5,
},
},
AccountSummary: &AccountSummary{
Tenant: "cgrates.org",
@@ -185,22 +210,79 @@ func TestECClone(t *testing.T) {
t.Errorf("Expecting: %s, received: %s",
utils.ToJSON(testEC), utils.ToJSON(ec))
}
// making sure we don't influence the original values
ec.Usage = utils.DurationPointer(time.Duration(1 * time.Second))
if testEC.Usage != nil {
t.Error("Usage is not nil")
}
ec.Cost = utils.Float64Pointer(1.0)
if testEC.Cost != nil {
t.Error("Cost is not nil")
}
ec.Charges[0].Increments[0].Cost = 1.0
if testEC.Charges[0].Increments[0].Cost == 1.0 {
t.Error("Cost is 1.0")
}
ec.AccountSummary.Disabled = true
if testEC.AccountSummary.Disabled {
t.Error("Account is disabled")
}
ec.AccountSummary.BalanceSummaries[0].Value = 5.0
if testEC.AccountSummary.BalanceSummaries[0].Value == 5.0 {
t.Error("Wrong balance summary")
}
ec.Rates["ec1a177"][0].Value = 5.0
if testEC.Rates["ec1a177"][0].Value == 5.0 {
t.Error("Wrong Value")
}
delete(ec.Rates, "ec1a177")
if _, has := testEC.Rates["ec1a177"]; !has {
t.Error("Key removed from testEC")
}
ec.Timings["7f324ab"].StartTime = "10:00:00"
if testEC.Timings["7f324ab"].StartTime == "10:00:00" {
t.Error("Wrong StartTime")
}
delete(ec.Timings, "7f324ab")
if _, has := testEC.Timings["7f324ab"]; !has {
t.Error("Key removed from testEC")
}
ec.RatingFilters["43e77dc"]["DestinationID"] = "GERMANY_MOBILE"
if testEC.RatingFilters["43e77dc"]["DestinationID"] == "GERMANY_MOBILE" {
t.Error("Wrong DestinationID")
}
delete(ec.RatingFilters, "43e77dc")
if _, has := testEC.RatingFilters["43e77dc"]; !has {
t.Error("Key removed from testEC")
}
ec.Accounting["a012888"].Units = 5.0
if testEC.Accounting["a012888"].Units == 5.0 {
t.Error("Wrong Units")
}
delete(ec.Accounting, "a012888")
if _, has := testEC.Accounting["a012888"]; !has {
t.Error("Key removed from testEC")
}
}
func TestECComputeAndReset(t *testing.T) {
ec := testEC.Clone()
eEc := testEC.Clone()
eEc.Usage = utils.DurationPointer(time.Duration(5 * time.Minute))
eEc.Cost = utils.Float64Pointer(2.67)
eEc.Usage = utils.DurationPointer(time.Duration(10 * time.Minute))
eEc.Cost = utils.Float64Pointer(3.52)
eEc.Charges[0].ecUsageIdx = utils.DurationPointer(time.Duration(0))
eEc.Charges[0].usage = utils.DurationPointer(time.Duration(1 * time.Minute))
eEc.Charges[0].cost = utils.Float64Pointer(0.27)
eEc.Charges[1].ecUsageIdx = utils.DurationPointer(time.Duration(1 * time.Minute))
eEc.Charges[1].usage = utils.DurationPointer(time.Duration(1 * time.Minute))
eEc.Charges[1].cost = utils.Float64Pointer(0.6)
eEc.Charges[2].ecUsageIdx = utils.DurationPointer(time.Duration(5 * time.Minute))
eEc.Charges[2].usage = utils.DurationPointer(time.Duration(1 * time.Minute))
eEc.Charges[2].cost = utils.Float64Pointer(0.17)
ec.Compute()
if !reflect.DeepEqual(eEc, ec) {
t.Errorf("Expecting: %+v, received: %+v", eEc, ec)
t.Errorf("Expecting: %s\n, received: %s", utils.ToJSON(eEc), utils.ToJSON(ec))
}
ec.ResetCounters()
if !reflect.DeepEqual(testEC, ec) {
@@ -903,15 +985,16 @@ func TestECAsCallCost(t *testing.T) {
func TestECTrimZeroAndFull(t *testing.T) {
ec := testEC.Clone()
if srplsEC, err := ec.Trim(time.Duration(5 * time.Minute)); err != nil {
if srplsEC, err := ec.Trim(time.Duration(10 * time.Minute)); err != nil {
t.Error(err)
} else if srplsEC != nil {
t.Errorf("Expecting nil, got: %+v", srplsEC)
}
eFullSrpls := testEC.Clone()
eFullSrpls.Usage = utils.DurationPointer(time.Duration(5 * time.Minute))
eFullSrpls.Usage = utils.DurationPointer(time.Duration(10 * time.Minute))
eFullSrpls.Charges[0].usage = utils.DurationPointer(time.Duration(1 * time.Minute))
eFullSrpls.Charges[1].usage = utils.DurationPointer(time.Duration(1 * time.Minute))
eFullSrpls.Charges[2].usage = utils.DurationPointer(time.Duration(1 * time.Minute))
if srplsEC, err := ec.Trim(time.Duration(0)); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eFullSrpls, srplsEC) {
@@ -981,6 +1064,7 @@ func TestECTrimMiddle1(t *testing.T) {
},
}
eSrplsEC := testEC.Clone()
eSrplsEC.StartTime = time.Date(2017, 1, 9, 16, 21, 31, 0, time.UTC)
eSrplsEC.Charges = []*ChargingInterval{
&ChargingInterval{
RatingID: "c1a5ab9",
@@ -1006,86 +1090,120 @@ func TestECTrimMiddle1(t *testing.T) {
},
CompressFactor: 1,
},
&ChargingInterval{
RatingID: "c1a5ab9",
Increments: []*ChargingIncrement{
&ChargingIncrement{
Usage: time.Duration(1 * time.Second),
Cost: 0,
AccountingID: "3455b83",
CompressFactor: 10,
},
&ChargingIncrement{
Usage: time.Duration(10 * time.Second),
Cost: 0.01,
AccountingID: "a012888",
CompressFactor: 2,
},
&ChargingIncrement{
Usage: time.Duration(1 * time.Second),
Cost: 0.005,
AccountingID: "44d6c02",
CompressFactor: 30,
},
},
CompressFactor: 5,
},
}
reqDuration := time.Duration(190 * time.Second)
initDur := ec.GetUsage()
srplsEC, err := ec.Trim(reqDuration)
if err != nil {
t.Error(err)
}
if reqDuration != *ec.Usage {
t.Logf("\teEC: %s\n\tEC: %s\n\torigEC: %s\n", utils.ToJSON(eEC), utils.ToJSON(ec), utils.ToJSON(testEC))
t.Errorf("Expecting request duration: %v, received: %v", reqDuration, *ec.Usage)
}
if srplsUsage := srplsEC.GetUsage(); srplsUsage != time.Duration(110*time.Second) {
t.Errorf("Expecting surplus duration: %v, received: %v", initDur-reqDuration, srplsUsage)
eSrplsDur := time.Duration(410 * time.Second)
if srplsUsage := srplsEC.GetUsage(); srplsUsage != eSrplsDur {
t.Errorf("Expecting surplus duration: %v, received: %v", eSrplsDur, srplsUsage)
}
ec.ResetCounters()
srplsEC.ResetCounters()
if !reflect.DeepEqual(eEC, ec) {
//t.Errorf("Expecting: %s, received: %s", utils.ToJSON(eEC), utils.ToJSON(ec))
} else if !reflect.DeepEqual(eSrplsEC, srplsEC) {
//t.Errorf("Expecting: %s, received: %s", utils.ToJSON(eSrplsEC), utils.ToJSON(srplsEC))
t.Errorf("Expecting: %s\n, received: %s", utils.ToIJSON(eEC), utils.ToIJSON(ec))
}
// test surplus, which is not easy to estimate due it's different item ids
if !eSrplsEC.StartTime.Equal(srplsEC.StartTime) ||
len(eSrplsEC.Charges) != len(srplsEC.Charges) {
t.Errorf("Expecting: \n%s, received: \n%s", utils.ToIJSON(eSrplsEC), utils.ToIJSON(srplsEC))
}
}
// TestECTrimMUsage is targeting simpler testing of the durations trimmed/remainders
// using subtests so we can cover the tests with less code
func TestECTrimMUsage(t *testing.T) {
ec := testEC.Clone()
atUsage := time.Duration(5 * time.Second)
srplsEC, _ := ec.Trim(atUsage)
if ec.GetUsage() != atUsage {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
// each subtest will trim at some usage duration
testCases := []struct {
atUsage time.Duration
ecUsage time.Duration
ecCost float64
srplsUsage time.Duration
srplsCost float64
}{
{time.Duration(5 * time.Second), time.Duration(5 * time.Second), 0.1,
time.Duration(595 * time.Second), 3.42},
{time.Duration(10 * time.Second), time.Duration(10 * time.Second), 0.1,
time.Duration(590 * time.Second), 3.42},
{time.Duration(15 * time.Second), time.Duration(20 * time.Second), 0.11,
time.Duration(580 * time.Second), 3.41},
{time.Duration(20 * time.Second), time.Duration(20 * time.Second), 0.11,
time.Duration(580 * time.Second), 3.41},
{time.Duration(25 * time.Second), time.Duration(30 * time.Second), 0.12,
time.Duration(570 * time.Second), 3.40},
{time.Duration(38 * time.Second), time.Duration(38 * time.Second), 0.16,
time.Duration(562 * time.Second), 3.36},
{time.Duration(60 * time.Second), time.Duration(60 * time.Second), 0.27,
time.Duration(540 * time.Second), 3.25},
{time.Duration(62 * time.Second), time.Duration(62 * time.Second), 0.29,
time.Duration(538 * time.Second), 3.23},
{time.Duration(120 * time.Second), time.Duration(120 * time.Second), 0.87,
time.Duration(480 * time.Second), 2.65},
}
if srplsEC.GetUsage() != time.Duration(295*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(ec))
}
ec = testEC.Clone()
atUsage = time.Duration(10 * time.Second)
srplsEC, _ = ec.Trim(atUsage)
if ec.GetUsage() != atUsage {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
}
if srplsEC.GetUsage() != time.Duration(290*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC))
}
ec = testEC.Clone()
atUsage = time.Duration(15 * time.Second)
srplsEC, _ = ec.Trim(atUsage)
if ec.GetUsage() != time.Duration(20*time.Second) {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
}
if srplsEC.GetUsage() != time.Duration(280*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC))
}
ec = testEC.Clone()
atUsage = time.Duration(25 * time.Second)
srplsEC, _ = ec.Trim(atUsage)
if ec.GetUsage() != time.Duration(30*time.Second) {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
}
if srplsEC.GetUsage() != time.Duration(270*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC))
}
ec = testEC.Clone()
atUsage = time.Duration(38 * time.Second)
srplsEC, _ = ec.Trim(atUsage)
if ec.GetUsage() != atUsage {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
}
if srplsEC.GetUsage() != time.Duration(262*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC))
}
ec = testEC.Clone()
atUsage = time.Duration(61 * time.Second)
srplsEC, _ = ec.Trim(atUsage)
if ec.GetUsage() != atUsage {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
}
if srplsEC.GetUsage() != time.Duration(239*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC))
for _, tC := range testCases {
t.Run(fmt.Sprintf("AtUsage:%s", tC.atUsage), func(t *testing.T) {
var ec, srplsEC *EventCost
ec = testEC.Clone()
if srplsEC, err = ec.Trim(tC.atUsage); err != nil {
t.Error(err)
}
if ec.GetUsage() != tC.ecUsage {
t.Errorf("Wrongly trimmed EC: %s", utils.ToIJSON(ec))
} else if ec.GetCost() != tC.ecCost {
t.Errorf("Wrong cost for event: %s", utils.ToIJSON(ec))
}
if srplsEC.GetUsage() != tC.srplsUsage {
t.Errorf("Wrong usage: %v for surplusEC: %s", srplsEC.GetUsage(), utils.ToIJSON(srplsEC))
} else if srplsEC.GetCost() != tC.srplsCost {
t.Errorf("Wrong cost: %f in surplus: %s", srplsEC.GetCost(), utils.ToIJSON(srplsEC))
}
})
}
/*
ec = testEC.Clone()
atUsage = time.Duration(61 * time.Second)
srplsEC, _ = ec.Trim(atUsage)
if ec.GetUsage() != atUsage {
t.Errorf("Wrongly trimmed EC: %s", utils.ToJSON(ec))
}
if srplsEC.GetUsage() != time.Duration(239*time.Second) {
t.Errorf("Wrong surplusEC: %s", utils.ToJSON(srplsEC))
}
*/
}
/*
func TestECMerge(t *testing.T) {
ec := NewBareEventCost()
ec.CGRID = testEC.CGRID
@@ -1135,3 +1253,4 @@ func TestECMerge(t *testing.T) {
t.Errorf("Unexpected EC after merge: %s", utils.ToJSON(ec))
}
}
*/