From 188ff2010ce49fba504d294132235cce9949f88c Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 22 Apr 2015 19:14:50 +0300 Subject: [PATCH 1/3] fix console crash when it cannot connect --- cmd/cgr-console/cgr-console.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/cgr-console/cgr-console.go b/cmd/cgr-console/cgr-console.go index 1658931fa..1759e7d58 100644 --- a/cmd/cgr-console/cgr-console.go +++ b/cmd/cgr-console/cgr-console.go @@ -84,6 +84,7 @@ func executeCommand(command string) { param = param.(*console.StringWrapper).Item } //log.Printf("Param: %+v", param) + if rpcErr := client.Call(cmd.RpcMethod(), param, res); rpcErr != nil { fmt.Println("Error executing command: " + rpcErr.Error()) } else { From 5c193bda8edd4245f1e7ed6f80003f4807e08936 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 22 Apr 2015 22:31:50 +0300 Subject: [PATCH 2/3] added total call cost metric --- engine/stats_metrics.go | 25 +++++++++++++++++++++++++ engine/stats_test.go | 5 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/engine/stats_metrics.go b/engine/stats_metrics.go index 0c78574b0..006459b4a 100644 --- a/engine/stats_metrics.go +++ b/engine/stats_metrics.go @@ -33,6 +33,7 @@ type Metric interface { const ASR = "ASR" const ACD = "ACD" const ACC = "ACC" +const TCC = "TCC" func CreateMetric(metric string) Metric { switch metric { @@ -42,6 +43,8 @@ func CreateMetric(metric string) Metric { return &ACDMetric{} case ACC: return &ACCMetric{} + case TCC: + return &TCCMetric{} } return nil } @@ -132,3 +135,25 @@ func (acc *ACCMetric) GetValue() float64 { val := acc.sum / acc.count return utils.Round(val, globalRoundingDecimals, utils.ROUNDING_MIDDLE) } + +// TCC – Total Call Cost +// the sum of cost of answered calls +type TCCMetric struct { + sum float64 +} + +func (tcc *TCCMetric) AddCdr(cdr *QCdr) { + if !cdr.AnswerTime.IsZero() && cdr.Cost >= 0 { + tcc.sum += cdr.Cost + } +} + +func (tcc *TCCMetric) RemoveCdr(cdr *QCdr) { + if !cdr.AnswerTime.IsZero() && cdr.Cost >= 0 { + tcc.sum -= cdr.Cost + } +} + +func (tcc *TCCMetric) GetValue() float64 { + return utils.Round(tcc.sum, globalRoundingDecimals, utils.ROUNDING_MIDDLE) +} diff --git a/engine/stats_test.go b/engine/stats_test.go index 1f85f6816..28d3689d0 100644 --- a/engine/stats_test.go +++ b/engine/stats_test.go @@ -33,7 +33,7 @@ func TestStatsQueueInit(t *testing.T) { } func TestStatsValue(t *testing.T) { - sq := NewStatsQueue(&CdrStats{Metrics: []string{ASR, ACD, ACC}}) + sq := NewStatsQueue(&CdrStats{Metrics: []string{ASR, ACD, ACC, TCC}}) cdr := &StoredCdr{ AnswerTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), Usage: 10 * time.Second, @@ -47,7 +47,8 @@ func TestStatsValue(t *testing.T) { s := sq.GetStats() if s[ASR] != 100 || s[ACD] != 10 || - s[ACC] != 2 { + s[ACC] != 2 || + s[TCC] != 6 { t.Errorf("Error getting stats: %+v", s) } } From ee413d19c667a430fd10deb12c1f765fc4ed1e56 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Wed, 22 Apr 2015 23:16:29 +0300 Subject: [PATCH 3/3] added ACC and TCC to LCR what a nice commit message! --- engine/calldesc.go | 30 +++++++++++++++++++++++++++--- engine/lcr.go | 21 +++++++++++++++++---- engine/lcr_test.go | 32 +++++++++++++++++++------------- engine/responder_test.go | 26 +++++++++++++------------- engine/stats_queue.go | 2 ++ 5 files changed, 78 insertions(+), 33 deletions(-) diff --git a/engine/calldesc.go b/engine/calldesc.go index 279d6ce52..7fb034f14 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -749,7 +749,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { lcrCD.Category = category lcrCD.Account = supplier lcrCD.Subject = supplier - var asrMean, acdMean float64 + var asrMean, acdMean, accMean, tccMean float64 var qosSortParams []string if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS || lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_THRESHOLD { rpfKey := utils.ConcatenatedKey(ratingProfileSearchKey, supplier) @@ -766,6 +766,8 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { } var asrValues sort.Float64Slice var acdValues sort.Float64Slice + var accValues sort.Float64Slice + var tccValues sort.Float64Slice for _, qId := range cdrStatsQueueIds { statValues := make(map[string]float64) if err := stats.GetValues(qId, &statValues); err != nil { @@ -777,18 +779,28 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if acd, exists := statValues[ACD]; exists { acdValues = append(acdValues, acd) } + if acc, exists := statValues[ACC]; exists { + accValues = append(accValues, acc) + } + if tcc, exists := statValues[TCC]; exists { + tccValues = append(tccValues, tcc) + } } asrValues.Sort() acdValues.Sort() + accValues.Sort() + tccValues.Sort() asrMean = utils.Avg(asrValues) acdMean = utils.Avg(acdValues) + accMean = utils.Avg(accValues) + tccMean = utils.Avg(tccValues) //log.Print(asrValues, acdValues) if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_THRESHOLD || lcrCost.Entry.Strategy == LCR_STRATEGY_QOS { qosSortParams = lcrCost.Entry.GetParams() } if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_THRESHOLD { // filter suppliers by qos thresholds - asrMin, asrMax, acdMin, acdMax := lcrCost.Entry.GetQOSLimits() + asrMin, asrMax, acdMin, acdMax, accMin, accMax, tccMin, tccMax := lcrCost.Entry.GetQOSLimits() //log.Print(asrMin, asrMax, acdMin, acdMax) // skip current supplier if off limits if asrMin > 0 && len(asrValues) != 0 && asrValues[0] < asrMin { @@ -803,6 +815,18 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if acdMax > 0 && len(acdValues) != 0 && acdValues[len(acdValues)-1] > acdMax.Seconds() { continue } + if accMin > 0 && len(accValues) != 0 && accValues[0] < accMin { + continue + } + if accMax > 0 && len(accValues) != 0 && accValues[len(accValues)-1] > accMax { + continue + } + if tccMin > 0 && len(tccValues) != 0 && tccValues[0] < tccMin { + continue + } + if tccMax > 0 && len(tccValues) != 0 && tccValues[len(tccValues)-1] > tccMax { + continue + } } } } @@ -830,7 +854,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { Duration: cc.GetDuration(), } if utils.IsSliceMember([]string{LCR_STRATEGY_QOS, LCR_STRATEGY_QOS_THRESHOLD}, lcrCost.Entry.Strategy) { - supplCost.QOS = map[string]float64{"ASR": asrMean, "ACD": acdMean} + supplCost.QOS = map[string]float64{ASR: asrMean, ACD: acdMean, ACC: accMean, TCC: tccMean} supplCost.qosSortParams = qosSortParams } lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, supplCost) diff --git a/engine/lcr.go b/engine/lcr.go index 0b4d6327f..78c79478f 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -91,10 +91,11 @@ func (lcr *LCR) Sort() { sort.Sort(lcr) } -func (le *LCREntry) GetQOSLimits() (minASR, maxASR float64, minACD, maxACD time.Duration) { - // MIN_ASR;MAX_ASR;MIN_ACD;MAX_ACD +func (le *LCREntry) GetQOSLimits() (minASR, maxASR float64, minACD, maxACD time.Duration, minACC, maxACC, minTCC, maxTCC float64) { + // MIN_ASR;MAX_ASR;MIN_ACD;MAX_ACD;MIN_ACC;MAX_ACC;MIN_TCC;MAX_TCC + minASR, maxASR, minACD, maxACD, minACC, maxACC, minTCC, maxTCC = -1, -1, -1, -1, -1, -1, -1, -1 params := strings.Split(le.StrategyParams, utils.INFIELD_SEP) - if len(params) == 4 { + if len(params) == 8 { var err error if minASR, err = strconv.ParseFloat(params[0], 64); err != nil { minASR = -1 @@ -108,6 +109,18 @@ func (le *LCREntry) GetQOSLimits() (minASR, maxASR float64, minACD, maxACD time. if maxACD, err = time.ParseDuration(params[3]); err != nil { maxACD = -1 } + if minACC, err = strconv.ParseFloat(params[4], 64); err != nil { + minACC = -1 + } + if maxACC, err = strconv.ParseFloat(params[5], 64); err != nil { + maxACC = -1 + } + if minTCC, err = strconv.ParseFloat(params[6], 64); err != nil { + minTCC = -1 + } + if maxTCC, err = strconv.ParseFloat(params[7], 64); err != nil { + maxTCC = -1 + } } return } @@ -124,7 +137,7 @@ func (le *LCREntry) GetParams() []string { } } if len(cleanParams) == 0 && le.Strategy == LCR_STRATEGY_QOS { - return []string{ASR, ACD} // Default QoS stats if none configured + return []string{ASR, ACD, ACC, TCC} // Default QoS stats if none configured } return cleanParams } diff --git a/engine/lcr_test.go b/engine/lcr_test.go index 6131fe383..c1914ab2d 100644 --- a/engine/lcr_test.go +++ b/engine/lcr_test.go @@ -90,33 +90,39 @@ func TestLcrQOSSorterOACD(t *testing.T) { func TestLcrGetQosLimitsAll(t *testing.T) { le := &LCREntry{ - StrategyParams: "1.2;2.3;45s;67m", + StrategyParams: "1.2;2.3;45s;67m;8.9;10.11;12.13;14.15", } - minAsr, maxAsr, minAcd, maxAcd := le.GetQOSLimits() + minAsr, maxAsr, minAcd, maxAcd, minAcc, maxAcc, minTcc, maxTcc := le.GetQOSLimits() if minAsr != 1.2 || maxAsr != 2.3 || - minAcd != 45*time.Second || maxAcd != 67*time.Minute { - t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd) + minAcd != 45*time.Second || maxAcd != 67*time.Minute || + minAcc != 8.9 || maxAcc != 10.11 || + minTcc != 12.13 || maxTcc != 14.15 { + t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd, minAcc, maxAcc, minTcc, maxTcc) } } func TestLcrGetQosLimitsSome(t *testing.T) { le := &LCREntry{ - StrategyParams: "1.2;;;67m", + StrategyParams: "1.2;;;67m;1;;3;", } - minAsr, maxAsr, minAcd, maxAcd := le.GetQOSLimits() + minAsr, maxAsr, minAcd, maxAcd, minAcc, maxAcc, minTcc, maxTcc := le.GetQOSLimits() if minAsr != 1.2 || maxAsr != -1 || - minAcd != -1 || maxAcd != 67*time.Minute { + minAcd != -1 || maxAcd != 67*time.Minute || + minAcc != 1 || maxAcc != -1 || + minTcc != 3 || maxTcc != -1 { t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd) } } func TestLcrGetQosLimitsNone(t *testing.T) { le := &LCREntry{ - StrategyParams: ";;;", + StrategyParams: ";;;;;;;", } - minAsr, maxAsr, minAcd, maxAcd := le.GetQOSLimits() + minAsr, maxAsr, minAcd, maxAcd, minAcc, maxAcc, minTcc, maxTcc := le.GetQOSLimits() if minAsr != -1 || maxAsr != -1 || - minAcd != -1 || maxAcd != -1 { + minAcd != -1 || maxAcd != -1 || + minAcc != -1 || maxAcc != -1 || + minTcc != -1 || maxTcc != -1 { t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd) } } @@ -127,7 +133,7 @@ func TestLcrGetQosSortParamsNone(t *testing.T) { StrategyParams: "", } sort := le.GetParams() - if sort[0] != ASR || sort[1] != ACD { + if sort[0] != ASR || sort[1] != ACD || sort[2] != ACC || sort[3] != TCC { t.Error("Wrong qos sort params parsed: ", sort) } } @@ -135,10 +141,10 @@ func TestLcrGetQosSortParamsNone(t *testing.T) { func TestLcrGetQosSortParamsEmpty(t *testing.T) { le := &LCREntry{ Strategy: LCR_STRATEGY_QOS, - StrategyParams: ";", + StrategyParams: ";;;", } sort := le.GetParams() - if sort[0] != ASR || sort[1] != ACD { + if sort[0] != ASR || sort[1] != ACD || sort[2] != ACC || sort[3] != TCC { t.Error("Wrong qos sort params parsed: ", sort) } } diff --git a/engine/responder_test.go b/engine/responder_test.go index f9b2c649a..376cda54a 100644 --- a/engine/responder_test.go +++ b/engine/responder_test.go @@ -282,7 +282,7 @@ func TestGetLCR(t *testing.T) { } } danStatsId := "dan12_stats" - rsponder.Stats.AddQueue(&CdrStats{Id: danStatsId, Supplier: []string{"dan12"}, Metrics: []string{ASR, ACD}}, nil) + rsponder.Stats.AddQueue(&CdrStats{Id: danStatsId, Supplier: []string{"dan12"}, Metrics: []string{ASR, ACD, ACC, TCC}}, nil) danRpfl := &RatingProfile{Id: "*out:tenant12:call:dan12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), @@ -292,7 +292,7 @@ func TestGetLCR(t *testing.T) { }}, } rifStatsId := "rif12_stats" - rsponder.Stats.AddQueue(&CdrStats{Id: rifStatsId, Supplier: []string{"rif12"}, Metrics: []string{ASR, ACD}}, nil) + rsponder.Stats.AddQueue(&CdrStats{Id: rifStatsId, Supplier: []string{"rif12"}, Metrics: []string{ASR, ACD, ACC, TCC}}, nil) rifRpfl := &RatingProfile{Id: "*out:tenant12:call:rif12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), @@ -302,7 +302,7 @@ func TestGetLCR(t *testing.T) { }}, } ivoStatsId := "ivo12_stats" - rsponder.Stats.AddQueue(&CdrStats{Id: ivoStatsId, Supplier: []string{"ivo12"}, Metrics: []string{ASR, ACD}}, nil) + rsponder.Stats.AddQueue(&CdrStats{Id: ivoStatsId, Supplier: []string{"ivo12"}, Metrics: []string{ASR, ACD, ACC, TCC}}, nil) ivoRpfl := &RatingProfile{Id: "*out:tenant12:call:ivo12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), @@ -339,7 +339,7 @@ func TestGetLCR(t *testing.T) { &LCRActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), Entries: []*LCREntry{ - &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;4m;", Weight: 10.0}}, + &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;4m;;;;;", Weight: 10.0}}, }, }, } @@ -456,7 +456,7 @@ func TestGetLCR(t *testing.T) { Subject: "dan", } eQTLcr := &LCRCost{ - Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;4m;", Weight: 10.0}, + Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;4m;;;;;", Weight: 10.0}, } var lcrQT LCRCost if err := rsponder.GetLCR(cdQosThreshold, &lcrQT); err != nil { @@ -467,15 +467,15 @@ func TestGetLCR(t *testing.T) { } else if !reflect.DeepEqual(eQTLcr.SupplierCosts, lcrQT.SupplierCosts) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.SupplierCosts, lcrQT.SupplierCosts) } - cdr := &StoredCdr{Supplier: "rif12", AnswerTime: time.Now(), Usage: 3 * time.Minute} + cdr := &StoredCdr{Supplier: "rif12", AnswerTime: time.Now(), Usage: 3 * time.Minute, Cost: 1} rsponder.Stats.AppendCDR(cdr, nil) - cdr = &StoredCdr{Supplier: "dan12", AnswerTime: time.Now(), Usage: 5 * time.Minute} + cdr = &StoredCdr{Supplier: "dan12", AnswerTime: time.Now(), Usage: 5 * time.Minute, Cost: 2} rsponder.Stats.AppendCDR(cdr, nil) eQTLcr = &LCRCost{ - Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;4m;", Weight: 10.0}, + Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS_THRESHOLD, StrategyParams: "35;;4m;;;;;", Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, - QOS: map[string]float64{ACD: 300, ASR: 100}, qosSortParams: []string{"35", "4m"}}, + QOS: map[string]float64{ACD: 300, ASR: 100, ACC: 2, TCC: 2}, qosSortParams: []string{"35", "4m"}}, }, } if err := rsponder.GetLCR(cdQosThreshold, &lcrQT); err != nil { @@ -501,9 +501,9 @@ func TestGetLCR(t *testing.T) { eQosLcr := &LCRCost{ Entry: &LCREntry{DestinationId: utils.ANY, RPCategory: "call", Strategy: LCR_STRATEGY_QOS, Weight: 10.0}, SupplierCosts: []*LCRSupplierCost{ - &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 300, ASR: 100}, qosSortParams: []string{ASR, ACD}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 180, ASR: 100}, qosSortParams: []string{ASR, ACD}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 0, ASR: 0}, qosSortParams: []string{ASR, ACD}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 300, ASR: 100, ACC: 2, TCC: 2}, qosSortParams: []string{ASR, ACD, ACC, TCC}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 180, ASR: 100, ACC: 1, TCC: 1}, qosSortParams: []string{ASR, ACD, ACC, TCC}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 0, ASR: 0, ACC: 0, TCC: 0}, qosSortParams: []string{ASR, ACD, ACC, TCC}}, }, } var lcrQ LCRCost @@ -513,7 +513,7 @@ func TestGetLCR(t *testing.T) { t.Errorf("Expecting: %+v, received: %+v", eQosLcr.Entry, lcrQ.Entry) } else if !reflect.DeepEqual(eQosLcr.SupplierCosts, lcrQ.SupplierCosts) { - t.Errorf("Expecting: %+v, received: %+v", eQosLcr.SupplierCosts[0], lcrQ.SupplierCosts[0]) + t.Errorf("Expecting: %+v, received: %+v", eQosLcr.SupplierCosts[1], lcrQ.SupplierCosts[1]) for _, sc := range lcrQ.SupplierCosts { log.Printf("%+v", sc) } diff --git a/engine/stats_queue.go b/engine/stats_queue.go index 46d9665fd..37c1673ec 100644 --- a/engine/stats_queue.go +++ b/engine/stats_queue.go @@ -38,6 +38,8 @@ var METRIC_TRIGGER_MAP = map[string]string{ "*max_acd": ACD, "*min_acc": ACC, "*max_acc": ACC, + "*min_tcc": ACC, + "*max_tcc": ACC, } // Simplified cdr structure containing only the necessary info