This commit is contained in:
DanB
2015-04-23 11:41:49 +02:00
8 changed files with 107 additions and 35 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)
}
}