diff --git a/engine/libroutes.go b/engine/libroutes.go index 216369911..8950d9283 100644 --- a/engine/libroutes.go +++ b/engine/libroutes.go @@ -65,6 +65,9 @@ func (sRoutes *SortedRoutes) RoutesWithParams() (sPs []string) { // SortWeight is part of sort interface, sort based on Weight func (sRoutes *SortedRoutes) SortWeight() { sort.Slice(sRoutes.SortedRoutes, func(i, j int) bool { + if sRoutes.SortedRoutes[i].SortingData[utils.Weight].(float64) == sRoutes.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sRoutes.SortedRoutes[i].SortingData[utils.Weight].(float64) > sRoutes.SortedRoutes[j].SortingData[utils.Weight].(float64) }) } @@ -74,6 +77,9 @@ func (sRoutes *SortedRoutes) SortWeight() { func (sSpls *SortedRoutes) SortLeastCost() { sort.Slice(sSpls.SortedRoutes, func(i, j int) bool { if sSpls.SortedRoutes[i].SortingData[utils.Cost].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Cost].(float64) { + if sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) } return sSpls.SortedRoutes[i].SortingData[utils.Cost].(float64) < sSpls.SortedRoutes[j].SortingData[utils.Cost].(float64) @@ -85,6 +91,9 @@ func (sSpls *SortedRoutes) SortLeastCost() { func (sSpls *SortedRoutes) SortHighestCost() { sort.Slice(sSpls.SortedRoutes, func(i, j int) bool { if sSpls.SortedRoutes[i].SortingData[utils.Cost].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Cost].(float64) { + if sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) } return sSpls.SortedRoutes[i].SortingData[utils.Cost].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Cost].(float64) @@ -116,6 +125,9 @@ func (sSpls *SortedRoutes) SortQOS(params []string) { } //in case that we have the same value for all params we sort base on weight + if sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) }) } @@ -125,6 +137,9 @@ func (sSpls *SortedRoutes) SortQOS(params []string) { func (sSpls *SortedRoutes) SortResourceAscendent() { sort.Slice(sSpls.SortedRoutes, func(i, j int) bool { if sSpls.SortedRoutes[i].SortingData[utils.ResourceUsage].(float64) == sSpls.SortedRoutes[j].SortingData[utils.ResourceUsage].(float64) { + if sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) } return sSpls.SortedRoutes[i].SortingData[utils.ResourceUsage].(float64) < sSpls.SortedRoutes[j].SortingData[utils.ResourceUsage].(float64) @@ -136,6 +151,9 @@ func (sSpls *SortedRoutes) SortResourceAscendent() { func (sSpls *SortedRoutes) SortResourceDescendent() { sort.Slice(sSpls.SortedRoutes, func(i, j int) bool { if sSpls.SortedRoutes[i].SortingData[utils.ResourceUsage].(float64) == sSpls.SortedRoutes[j].SortingData[utils.ResourceUsage].(float64) { + if sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) } return sSpls.SortedRoutes[i].SortingData[utils.ResourceUsage].(float64) > sSpls.SortedRoutes[j].SortingData[utils.ResourceUsage].(float64) @@ -149,6 +167,9 @@ func (sSpls *SortedRoutes) SortLoadDistribution() { splIVal := ((sSpls.SortedRoutes[i].SortingData[utils.Ratio].(float64)+sSpls.SortedRoutes[i].SortingData[utils.Load].(float64))/sSpls.SortedRoutes[i].SortingData[utils.Ratio].(float64) - 1.0) splJVal := ((sSpls.SortedRoutes[j].SortingData[utils.Ratio].(float64)+sSpls.SortedRoutes[j].SortingData[utils.Load].(float64))/sSpls.SortedRoutes[j].SortingData[utils.Ratio].(float64) - 1.0) if splIVal == splJVal { + if sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) == sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) { + return utils.BoolGenerator().RandomBool() + } return sSpls.SortedRoutes[i].SortingData[utils.Weight].(float64) > sSpls.SortedRoutes[j].SortingData[utils.Weight].(float64) } return splIVal < splJVal diff --git a/engine/libroutes_test.go b/engine/libroutes_test.go index 4f7632977..8e01f85e4 100644 --- a/engine/libroutes_test.go +++ b/engine/libroutes_test.go @@ -671,3 +671,39 @@ func TestLibRoutesSortLoadDistribution(t *testing.T) { eIds, rcv) } } + +func BenchmarkRouteSortCost(b *testing.B) { + sSpls := &SortedRoutes{ + SortedRoutes: []*SortedRoute{ + { + RouteID: "route1", + SortingData: map[string]interface{}{ + utils.Cost: 0.1, + utils.Weight: 10.0, + }, + RouteParameters: "param1", + }, + { + RouteID: "route2", + SortingData: map[string]interface{}{ + utils.Cost: 0.1, + utils.Weight: 10.0, + }, + RouteParameters: "param2", + }, + { + RouteID: "route3", + SortingData: map[string]interface{}{ + utils.Cost: 0.1, + utils.Weight: 10.0, + }, + RouteParameters: "param3", + }, + }, + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + sSpls.SortLeastCost() + } + +} diff --git a/packages/debian/changelog b/packages/debian/changelog index 13ceec4f0..2e25d3a78 100644 --- a/packages/debian/changelog +++ b/packages/debian/changelog @@ -124,6 +124,7 @@ cgrates (0.11.0~dev) UNRELEASED; urgency=medium * [General] For only *asap actions don't save AccountIDs withing ActionPlans * [AnalyzerS] Added AnalyzerSv1.StringQuery API to search over the recorded RPC calls * [CoreS] Moved the server implementation in the new cores package + * [RouteS] In case of same weight sort random -- DanB Wed, 19 Feb 2020 13:25:52 +0200 diff --git a/utils/coreutils.go b/utils/coreutils.go index e67f64e20..65f323639 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -49,6 +49,7 @@ import ( var ( startCGRateSTime time.Time + boolGenerator *boolGen rfc3339Rule = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.+$`) sqlRule = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`) @@ -69,6 +70,7 @@ var ( func init() { startCGRateSTime = time.Now() math_rand.Seed(startCGRateSTime.UnixNano()) + boolGenerator = newBoolGen() } // GetStartTime return the Start time of engine (in UNIX format) @@ -76,6 +78,11 @@ func GetStartTime() string { return startCGRateSTime.Format(time.UnixDate) } +// BoolGenerator return the boolean generator +func BoolGenerator() *boolGen { + return boolGenerator +} + func NewCounter(start, limit int64) *Counter { return &Counter{ value: start, @@ -1002,3 +1009,26 @@ func VerifyHash(hash string, dataKeys ...string) bool { []byte(ConcatenatedKey(dataKeys...))) return err == nil } + +//newBoolGen initialize an efficient boolean generator +func newBoolGen() *boolGen { + return &boolGen{src: math_rand.NewSource(time.Now().UnixNano())} +} + +//boolGen is an efficient boolean generator +type boolGen struct { + src math_rand.Source + cache int64 + remaining int +} + +//RandomBool generate a random boolean +func (b *boolGen) RandomBool() bool { + if b.remaining == 0 { + b.cache, b.remaining = b.src.Int63(), 63 + } + result := b.cache&0x01 == 1 + b.cache >>= 1 + b.remaining-- + return result +}