From b1e1e2410036a55394b92ba7803f3afdc3a5b2f5 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 26 Oct 2015 13:46:03 +0200 Subject: [PATCH 1/2] simple stack logger --- engine/balances.go | 2 +- utils/logger.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/engine/balances.go b/engine/balances.go index 26b51b42a..689774179 100644 --- a/engine/balances.go +++ b/engine/balances.go @@ -651,7 +651,7 @@ func (bc BalanceChain) SaveDirtyBalances(acc *Account) { allowNegative := "" disabled := "" if b.account != nil { // only publish modifications for balances with account set - //debug.PrintStack() + //utils.LogStack() accountId = b.account.Id allowNegative = strconv.FormatBool(b.account.AllowNegative) disabled = strconv.FormatBool(b.account.Disabled) diff --git a/utils/logger.go b/utils/logger.go index d5300b93b..cabbe068b 100644 --- a/utils/logger.go +++ b/utils/logger.go @@ -22,6 +22,7 @@ import ( "fmt" "log" "log/syslog" + "runtime" ) var Logger LoggerInterface @@ -85,3 +86,9 @@ func (sl *StdLogger) Warning(m string) (err error) { log.Print("[WARNING]" + m) return } + +func LogStack() { + buf := make([]byte, 300) + runtime.Stack(buf, false) + Logger.Debug(string(buf)) +} From f6ccd50bb5956164a7ef317433d6ca255bba920c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 28 Oct 2015 09:20:38 +0200 Subject: [PATCH 2/2] update callcost to use rounded timespans cost should provide a fix for #260 --- engine/callcost.go | 12 ++++++++++++ engine/calldesc.go | 13 ++----------- engine/calldesc_test.go | 22 ++++++++++++++++++++++ engine/loader_csv_test.go | 12 ++++++++---- engine/timespans.go | 6 +----- sessionmanager/session.go | 2 ++ 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/engine/callcost.go b/engine/callcost.go index 238619491..c692fe963 100644 --- a/engine/callcost.go +++ b/engine/callcost.go @@ -152,3 +152,15 @@ func (cc *CallCost) AsJSON() string { ccJson, _ := json.Marshal(cc) return string(ccJson) } + +func (cc *CallCost) UpdateCost() { + cost := 0.0 + if cc.deductConnectFee { // add back the connectFee + cost += cc.GetConnectFee() + } + for _, ts := range cc.Timespans { + cost += ts.getCost() + cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals + } + cc.Cost = cost +} diff --git a/engine/calldesc.go b/engine/calldesc.go index 0b69069bb..1cc40eb89 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -83,7 +83,7 @@ var ( // Exported method to set the storage getter. func SetRatingStorage(sg RatingStorage) { - ratingStorage = sg + ratingStorage = sg } func SetAccountingStorage(ag AccountingStorage) { @@ -636,16 +636,7 @@ func (cd *CallDescriptor) debit(account *Account, dryRun bool, goNegative bool) utils.Logger.Err(fmt.Sprintf(" Error getting cost for account key <%s>: %s", cd.GetAccountKey(), err.Error())) return nil, err } - cost := 0.0 - // calculate call cost after balances - if cc.deductConnectFee { // add back the connectFee - cost += cc.GetConnectFee() - } - for _, ts := range cc.Timespans { - cost += ts.getCost() - cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE) // just get rid of the extra decimals - } - cc.Cost = cost + cc.UpdateCost() cc.Timespans.Compress() //log.Printf("OUT CC: ", cc) return diff --git a/engine/calldesc_test.go b/engine/calldesc_test.go index 9c1aa542b..81a281075 100644 --- a/engine/calldesc_test.go +++ b/engine/calldesc_test.go @@ -555,6 +555,28 @@ func TestGetCostWithMaxCost(t *testing.T) { t.Errorf("Expected %v was %v", expected, cc.Cost) } } +func TestGetCostRoundingIssue(t *testing.T) { + ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT") + for _, at := range ap { + at.Execute() + } + cd := &CallDescriptor{ + Direction: "*out", + Category: "call", + Tenant: "cgrates.org", + Subject: "dy", + Account: "dy", + Destination: "0723123113", + TimeStart: time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC), + TimeEnd: time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC), + MaxCostSoFar: 0, + } + cc, err := cd.GetCost() + expected := 0.17 + if cc.Cost != expected || err != nil { + t.Errorf("Expected %v was %+v", expected, cc) + } +} func TestMaxSessionTimeWithMaxCostFree(t *testing.T) { ap, _ := ratingStorage.GetActionPlans("TOPUP10_AT") diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 794836b6c..d81143d97 100644 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -70,6 +70,7 @@ RT_UK_Mobile_BIG5_PKG,0.01,0,20s,20s,0s RT_UK_Mobile_BIG5,0.01,0.10,1s,1s,0s R_URG,0,0,1,1,0 MX,0,1,1s,1s,0 +DY,0.15,0.05,60s,1s,0s ` destinationRates = ` RT_STANDARD,GERMANY,R1,*middle,4,0, @@ -91,6 +92,7 @@ DATA_RATE,*any,LANDLINE_OFFPEAK,*middle,4,0, RT_URG,URG,R_URG,*middle,4,0, MX_FREE,RET,MX,*middle,4,10,*free MX_DISC,RET,MX,*middle,4,10,*disconnect +RT_DY,RET,DY,*up,2,0, ` ratingPlans = ` STANDARD,RT_STANDARD,WORKDAYS_00,10 @@ -115,6 +117,7 @@ RP_MX,MX_DISC,WORKDAYS_00,10 RP_MX,MX_FREE,WORKDAYS_18,10 GER_ONLY,GER,*any,10 ANY_PLAN,DATA_RATE,*any,10 +DY_PLAN,RT_DY,*any,10 ` ratingProfiles = ` *out,CUSTOMER_1,0,rif:from:tm,2012-01-01T00:00:00Z,PREMIUM,danb, @@ -141,6 +144,7 @@ ANY_PLAN,DATA_RATE,*any,10 *out,cgrates.org,call,nt,2012-02-28T00:00:00Z,GER_ONLY,, *in,cgrates.org,LCR_STANDARD,max,2013-03-23T00:00:00Z,RP_MX,, *out,cgrates.org,call,money,2015-02-28T00:00:00Z,EVENING,, +*out,cgrates.org,call,dy,2015-02-28T00:00:00Z,DY_PLAN,, ` sharedGroups = ` SG1,*any,*lowest, @@ -391,7 +395,7 @@ func TestLoadTimimgs(t *testing.T) { } func TestLoadRates(t *testing.T) { - if len(csvr.rates) != 13 { + if len(csvr.rates) != 14 { t.Error("Failed to load rates: ", len(csvr.rates)) } rate := csvr.rates["R1"].RateSlots[0] @@ -461,7 +465,7 @@ func TestLoadRates(t *testing.T) { } func TestLoadDestinationRates(t *testing.T) { - if len(csvr.destinationRates) != 14 { + if len(csvr.destinationRates) != 15 { t.Error("Failed to load destinationrates: ", len(csvr.destinationRates)) } drs := csvr.destinationRates["RT_STANDARD"] @@ -609,7 +613,7 @@ func TestLoadDestinationRates(t *testing.T) { } func TestLoadRatingPlans(t *testing.T) { - if len(csvr.ratingPlans) != 13 { + if len(csvr.ratingPlans) != 14 { t.Error("Failed to load rating plans: ", len(csvr.ratingPlans)) } rplan := csvr.ratingPlans["STANDARD"] @@ -781,7 +785,7 @@ func TestLoadRatingPlans(t *testing.T) { } func TestLoadRatingProfiles(t *testing.T) { - if len(csvr.ratingProfiles) != 21 { + if len(csvr.ratingProfiles) != 22 { t.Error("Failed to load rating profiles: ", len(csvr.ratingProfiles), csvr.ratingProfiles) } rp := csvr.ratingProfiles["*out:test:0:trp"] diff --git a/engine/timespans.go b/engine/timespans.go index a497ab4ef..24df295d7 100644 --- a/engine/timespans.go +++ b/engine/timespans.go @@ -263,11 +263,7 @@ func (ts *TimeSpan) getCost() float64 { ts.Cost = utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) return ts.Cost } else { - cost := 0.0 - // some increments may have 0 cost because of the max cost strategy - for _, inc := range ts.Increments { - cost += inc.Cost - } + cost := ts.Increments.GetTotalCost() if ts.RateInterval != nil && ts.RateInterval.Rating != nil { return utils.Round(cost, ts.RateInterval.Rating.RoundingDecimals, ts.RateInterval.Rating.RoundingMethod) } else { diff --git a/sessionmanager/session.go b/sessionmanager/session.go index 813058f65..faa5c9b7d 100644 --- a/sessionmanager/session.go +++ b/sessionmanager/session.go @@ -233,6 +233,8 @@ func (s *Session) SaveOperations() { firstCC.Merge(cc) //utils.Logger.Debug(fmt.Sprintf("AFTER MERGE: %s", utils.ToJSON(firstCC))) } + // make sure we have rounded timespans final cost + firstCC.UpdateCost() var reply string err := s.sessionManager.CdrSrv().LogCallCost(&engine.CallCostLog{