diff --git a/engine/calldesc.go b/engine/calldesc.go index 0f3562638..0a102533f 100644 --- a/engine/calldesc.go +++ b/engine/calldesc.go @@ -829,6 +829,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { var tcdValues sort.Float64Slice var accValues sort.Float64Slice var tccValues sort.Float64Slice + var ddcValues sort.Float64Slice // track if one value is never calculated asrNeverConsidered := true pddNeverConsidered := true @@ -836,6 +837,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { tcdNeverConsidered := true accNeverConsidered := true tccNeverConsidered := true + ddcNeverConsidered := true if utils.IsSliceMember([]string{LCR_STRATEGY_QOS, LCR_STRATEGY_QOS_THRESHOLD}, lcrCost.Entry.Strategy) { if stats == nil { lcrCost.SupplierCosts = append(lcrCost.SupplierCosts, &LCRSupplierCost{ @@ -909,6 +911,12 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { } tccNeverConsidered = false } + if ddc, exists := statValues[TCC]; exists { + if ddc > STATS_NA { + ddcValues = append(ddcValues, ddc) + } + ddcNeverConsidered = false + } } if statsErr { // Stats error in loop, to go next supplier continue @@ -919,6 +927,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { tcdValues.Sort() accValues.Sort() tccValues.Sort() + ddcValues.Sort() //log.Print(asrValues, acdValues) if utils.IsSliceMember([]string{LCR_STRATEGY_QOS_THRESHOLD, LCR_STRATEGY_QOS}, lcrCost.Entry.Strategy) { @@ -926,7 +935,7 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { } if lcrCost.Entry.Strategy == LCR_STRATEGY_QOS_THRESHOLD { // filter suppliers by qos thresholds - asrMin, asrMax, pddMin, pddMax, acdMin, acdMax, tcdMin, tcdMax, accMin, accMax, tccMin, tccMax := lcrCost.Entry.GetQOSLimits() + asrMin, asrMax, pddMin, pddMax, acdMin, acdMax, tcdMin, tcdMax, accMin, accMax, tccMin, tccMax, ddcMin, ddcMax := lcrCost.Entry.GetQOSLimits() //log.Print(asrMin, asrMax, acdMin, acdMax) // skip current supplier if off limits if asrMin > 0 && len(asrValues) != 0 && asrValues[0] < asrMin { @@ -965,6 +974,12 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if tccMax > 0 && len(tccValues) != 0 && tccValues[len(tccValues)-1] > tccMax { continue } + if ddcMin > 0 && len(ddcValues) != 0 && ddcValues[0] < ddcMin { + continue + } + if ddcMax > 0 && len(ddcValues) != 0 && ddcValues[len(ddcValues)-1] > ddcMax { + continue + } } } } @@ -1019,6 +1034,9 @@ func (cd *CallDescriptor) GetLCR(stats StatsInterface) (*LCRCost, error) { if !tccNeverConsidered { qos[TCC] = utils.AvgNegative(tccValues) } + if !ddcNeverConsidered { + qos[DDC] = utils.AvgNegative(ddcValues) + } if utils.IsSliceMember([]string{LCR_STRATEGY_QOS, LCR_STRATEGY_QOS_THRESHOLD}, lcrCost.Entry.Strategy) { supplCost.QOS = qos supplCost.qosSortParams = qosSortParams diff --git a/engine/lcr.go b/engine/lcr.go index d7b7965dc..ba78df927 100644 --- a/engine/lcr.go +++ b/engine/lcr.go @@ -162,11 +162,11 @@ func (lcr *LCR) Sort() { sort.Sort(lcr) } -func (le *LCREntry) GetQOSLimits() (minASR, maxASR float64, minPDD, maxPDD, minACD, maxACD, minTCD, maxTCD time.Duration, minACC, maxACC, minTCC, maxTCC float64) { - // MIN_ASR;MAX_ASR;MIN_PDD;MAX_PDD;MIN_ACD;MAX_ACD;MIN_TCD;MAX_TCD;MIN_ACC;MAX_ACC;MIN_TCC;MAX_TCC - minASR, maxASR, minPDD, maxPDD, minACD, maxACD, minTCD, maxTCD, minACC, maxACC, minTCC, maxTCC = -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +func (le *LCREntry) GetQOSLimits() (minASR, maxASR float64, minPDD, maxPDD, minACD, maxACD, minTCD, maxTCD time.Duration, minACC, maxACC, minTCC, maxTCC, minDDC, maxDDC float64) { + // MIN_ASR;MAX_ASR;MIN_PDD;MAX_PDD;MIN_ACD;MAX_ACD;MIN_TCD;MAX_TCD;MIN_ACC;MAX_ACC;MIN_TCC;MAX_TCC;MIN_DDC;MAX_DDC + minASR, maxASR, minPDD, maxPDD, minACD, maxACD, minTCD, maxTCD, minACC, maxACC, minTCC, maxTCC, minDDC, maxDDC = -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 params := strings.Split(le.StrategyParams, utils.INFIELD_SEP) - if len(params) == 12 { + if len(params) == 14 { var err error if minASR, err = strconv.ParseFloat(params[0], 64); err != nil { minASR = -1 @@ -204,6 +204,12 @@ func (le *LCREntry) GetQOSLimits() (minASR, maxASR float64, minPDD, maxPDD, minA if maxTCC, err = strconv.ParseFloat(params[11], 64); err != nil { maxTCC = -1 } + if minDDC, err = strconv.ParseFloat(params[12], 64); err != nil { + minDDC = -1 + } + if maxDDC, err = strconv.ParseFloat(params[13], 64); err != nil { + maxDDC = -1 + } } return } @@ -220,7 +226,7 @@ func (le *LCREntry) GetParams() []string { } } if len(cleanParams) == 0 && le.Strategy == LCR_STRATEGY_QOS { - return []string{ASR, PDD, ACD, TCD, ACC, TCC} // Default QoS stats if none configured + return []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC} // Default QoS stats if none configured } return cleanParams } diff --git a/engine/lcr_test.go b/engine/lcr_test.go index 5d5a0d09e..0364c4652 100644 --- a/engine/lcr_test.go +++ b/engine/lcr_test.go @@ -94,46 +94,49 @@ func TestLcrQOSSorterOACD(t *testing.T) { func TestLcrGetQosLimitsAll(t *testing.T) { le := &LCREntry{ - StrategyParams: "1.2;2.3;4;7;45s;67m;16s;17m;8.9;10.11;12.13;14.15", + StrategyParams: "1.2;2.3;4;7;45s;67m;16s;17m;8.9;10.11;12.13;14.15;2;3", } - minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc := le.GetQOSLimits() + minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc, minDdc, maxDdc := le.GetQOSLimits() if minAsr != 1.2 || maxAsr != 2.3 || minPdd != 4*time.Second || maxPdd != 7*time.Second || minAcd != 45*time.Second || maxAcd != 67*time.Minute || minTcd != 16*time.Second || maxTcd != 17*time.Minute || minAcc != 8.9 || maxAcc != 10.11 || - minTcc != 12.13 || maxTcc != 14.15 { - t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc) + minTcc != 12.13 || maxTcc != 14.15 || + minDdc != 2 || maxDdc != 3 { + t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc, minDdc, maxDdc) } } func TestLcrGetQosLimitsSome(t *testing.T) { le := &LCREntry{ - StrategyParams: "1.2;;3;;;67m;;30m;1;;3;", + StrategyParams: "1.2;;3;;;67m;;30m;1;;3;;;2", } - minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc := le.GetQOSLimits() + minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc, minDdc, maxDdc := le.GetQOSLimits() if minAsr != 1.2 || maxAsr != -1 || minPdd != 3*time.Second || maxPdd != -1 || minAcd != -1 || maxAcd != 67*time.Minute || minTcd != -1 || maxTcd != 30*time.Minute || minAcc != 1 || maxAcc != -1 || - minTcc != 3 || maxTcc != -1 { - t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd, minTcd, maxTcd) + minTcc != 3 || maxTcc != -1 || + minDdc != -1 || maxDdc != 2 { + t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd, minTcd, maxTcd, minTcc, maxTcc, minDdc, maxDdc) } } func TestLcrGetQosLimitsNone(t *testing.T) { le := &LCREntry{ - StrategyParams: ";;;;;;;;;", + StrategyParams: ";;;;;;;;;;;", } - minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc := le.GetQOSLimits() + minAsr, maxAsr, minPdd, maxPdd, minAcd, maxAcd, minTcd, maxTcd, minAcc, maxAcc, minTcc, maxTcc, minDdc, maxDdc := le.GetQOSLimits() if minAsr != -1 || maxAsr != -1 || minPdd != -1 || maxPdd != -1 || minAcd != -1 || maxAcd != -1 || minTcd != -1 || maxTcd != -1 || minAcc != -1 || maxAcc != -1 || - minTcc != -1 || maxTcc != -1 { - t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd) + minTcc != -1 || maxTcc != -1 || + minDdc != -1 || maxDdc != -1 { + t.Error("Wrong qos limits parsed: ", minAsr, maxAsr, minAcd, maxAcd, minDdc, maxDdc) } } diff --git a/engine/responder_test.go b/engine/responder_test.go index 17e20d6fd..585886c36 100644 --- a/engine/responder_test.go +++ b/engine/responder_test.go @@ -284,7 +284,7 @@ func TestGetLCR(t *testing.T) { } } danStatsId := "dan12_stats" - rsponder.Stats.AddQueue(&CdrStats{Id: danStatsId, Supplier: []string{"dan12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC}}, nil) + rsponder.Stats.AddQueue(&CdrStats{Id: danStatsId, Supplier: []string{"dan12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, nil) danRpfl := &RatingProfile{Id: "*out:tenant12:call:dan12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), @@ -294,7 +294,7 @@ func TestGetLCR(t *testing.T) { }}, } rifStatsId := "rif12_stats" - rsponder.Stats.AddQueue(&CdrStats{Id: rifStatsId, Supplier: []string{"rif12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC}}, nil) + rsponder.Stats.AddQueue(&CdrStats{Id: rifStatsId, Supplier: []string{"rif12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, nil) rifRpfl := &RatingProfile{Id: "*out:tenant12:call:rif12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), @@ -304,7 +304,7 @@ func TestGetLCR(t *testing.T) { }}, } ivoStatsId := "ivo12_stats" - rsponder.Stats.AddQueue(&CdrStats{Id: ivoStatsId, Supplier: []string{"ivo12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC}}, nil) + rsponder.Stats.AddQueue(&CdrStats{Id: ivoStatsId, Supplier: []string{"ivo12"}, Metrics: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, nil) ivoRpfl := &RatingProfile{Id: "*out:tenant12:call:ivo12", RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{ ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC), @@ -341,7 +341,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}}, }, }, } @@ -459,11 +459,11 @@ 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}, SupplierCosts: []*LCRSupplierCost{ - &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1}, qosSortParams: []string{"35", "4m"}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1}, qosSortParams: []string{"35", "4m"}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1}, qosSortParams: []string{"35", "4m"}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, PDD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, }, } var lcrQT LCRCost @@ -480,10 +480,10 @@ func TestGetLCR(t *testing.T) { 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:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1}, qosSortParams: []string{"35", "4m"}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, ACD: 300, TCD: 300, ASR: 100, ACC: 2, TCC: 2}, qosSortParams: []string{"35", "4m"}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, TCD: -1, ACC: -1, TCC: -1, ASR: -1, ACD: -1, DDC: -1}, qosSortParams: []string{"35", "4m"}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{PDD: -1, ACD: 300, TCD: 300, ASR: 100, ACC: 2, TCC: 2, DDC: 2}, qosSortParams: []string{"35", "4m"}}, }, } if err := rsponder.GetLCR(cdQosThreshold, &lcrQT); err != nil { @@ -492,7 +492,7 @@ func TestGetLCR(t *testing.T) { t.Errorf("Expecting: %+v, received: %+v", eQTLcr.Entry, lcrQT.Entry) } else if !reflect.DeepEqual(eQTLcr.SupplierCosts, lcrQT.SupplierCosts) { - t.Errorf("Expecting: %+v, received: %+v", eQTLcr.SupplierCosts[0], lcrQT.SupplierCosts[0]) + t.Errorf("Expecting: %+v, received: %+v", eQTLcr.SupplierCosts[1], lcrQT.SupplierCosts[1]) } // Test *qos strategy here @@ -509,9 +509,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:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{ACD: -1, PDD: -1, TCD: -1, ASR: -1, ACC: -1, TCC: -1}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 300, PDD: -1, TCD: 300, ASR: 100, ACC: 2, TCC: 2}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC}}, - &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 180, PDD: -1, TCD: 180, ASR: 100, ACC: 1, TCC: 1}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:ivo12", Cost: 0, Duration: 60 * time.Second, QOS: map[string]float64{ACD: -1, PDD: -1, TCD: -1, ASR: -1, ACC: -1, TCC: -1, DDC: -1}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:dan12", Cost: 0.6, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 300, PDD: -1, TCD: 300, ASR: 100, ACC: 2, TCC: 2, DDC: 2}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, + &LCRSupplierCost{Supplier: "*out:tenant12:call:rif12", Cost: 0.4, Duration: 60 * time.Second, QOS: map[string]float64{ACD: 180, PDD: -1, TCD: 180, ASR: 100, ACC: 1, TCC: 1, DDC: 1}, qosSortParams: []string{ASR, PDD, ACD, TCD, ACC, TCC, DDC}}, }, } var lcrQ LCRCost diff --git a/engine/stats_metrics.go b/engine/stats_metrics.go index a2f923b88..a6549fda4 100644 --- a/engine/stats_metrics.go +++ b/engine/stats_metrics.go @@ -36,6 +36,7 @@ const TCD = "TCD" const ACC = "ACC" const TCC = "TCC" const PDD = "PDD" +const DDC = "DDC" const STATS_NA = -1 func CreateMetric(metric string) Metric { @@ -52,6 +53,8 @@ func CreateMetric(metric string) Metric { return &ACCMetric{} case TCC: return &TCCMetric{} + case DDC: + return NewDccMetric() } return nil } @@ -230,37 +233,37 @@ func (tcc *TCCMetric) GetValue() float64 { return utils.Round(tcc.sum, globalRoundingDecimals, utils.ROUNDING_MIDDLE) } -// DIC - Destination ID Counter +// DDC - Destination Distinct Count // -type DICMetric struct { +type DCCMetric struct { destinations map[string]int64 } -func (dic *DICMetric) AddCdr(cdr *QCdr) { - if dic.destinations == nil { - dic.destinations = make(map[string]int64) - } - if count, exists := dic.destinations[cdr.Dest]; exists { - dic.destinations[cdr.Dest] = count + 1 - } else { - dic.destinations[cdr.Dest] = 0 +func NewDccMetric() *DCCMetric { + return &DCCMetric{ + destinations: make(map[string]int64), } } -func (dic *DICMetric) RemoveCdr(cdr *QCdr) { - if dic.destinations == nil { - dic.destinations = make(map[string]int64) - } - if count, exists := dic.destinations[cdr.Dest]; exists && count > 1 { - dic.destinations[cdr.Dest] = count - 1 +func (dcc *DCCMetric) AddCdr(cdr *QCdr) { + if count, exists := dcc.destinations[cdr.Dest]; exists { + dcc.destinations[cdr.Dest] = count + 1 } else { - dic.destinations[cdr.Dest] = 0 + dcc.destinations[cdr.Dest] = 0 } } -func (dic *DICMetric) GetValue() float64 { - if len(dic.destinations) == 0 { +func (dcc *DCCMetric) RemoveCdr(cdr *QCdr) { + if count, exists := dcc.destinations[cdr.Dest]; exists && count > 1 { + dcc.destinations[cdr.Dest] = count - 1 + } else { + dcc.destinations[cdr.Dest] = 0 + } +} + +func (dcc *DCCMetric) GetValue() float64 { + if len(dcc.destinations) == 0 { return STATS_NA } - return float64(len(dic.destinations)) + return float64(len(dcc.destinations)) }