diff --git a/engine/librankings.go b/engine/librankings.go index ddb640d51..c87ea2d23 100644 --- a/engine/librankings.go +++ b/engine/librankings.go @@ -19,6 +19,7 @@ along with this program. If not, see package engine import ( + "sort" "sync" "github.com/cgrates/cgrates/utils" @@ -102,7 +103,8 @@ type rankingSorter interface { } // rankingSortStats will return the list of sorted statIDs out of the sortingData map -func rankingSortStats(sortingType string, sortingParams []string, statMetrics map[string]map[string]float64) (sortedStatIDs []string, err error) { +func rankingSortStats(sortingType string, sortingParams []string, + statMetrics map[string]map[string]float64) (sortedStatIDs []string, err error) { var rnkSrtr rankingSorter if rnkSrtr, err = newRankingSorter(sortingType, sortingParams, statMetrics); err != nil { return @@ -119,18 +121,59 @@ func newRankingSorter(sortingType string, sortingParams []string, default: err = utils.ErrPrefixNotErrNotImplemented(sortingType) return - case utils.MetaDescending: - return &rankingDescSorter{sortingParams, statMetrics}, nil - + case utils.MetaDesc: + return newRankingDescSorter(sortingParams, statMetrics), nil } + return +} + +// newRankingDescSorter is a constructor for rankingDescSorter +func newRankingDescSorter(sortingParams []string, + statMetrics map[string]map[string]float64) (rkDsrtr *rankingDescSorter) { + rkDsrtr = &rankingDescSorter{ + sortingParams, + statMetrics, + make([]string, 0, len(statMetrics))} + for statID := range rkDsrtr.statMetrics { + rkDsrtr.statIDs = append(rkDsrtr.statIDs, statID) + } + return } // rankingDescSorter will sort data descendently for metrics in sortingParams or randomly if all equal type rankingDescSorter struct { sortingParams []string statMetrics map[string]map[string]float64 + + statIDs []string // list of keys of the statMetrics } -func (rkDsrtr *rankingDescSorter) sortStatIDs() (statIDs []string) { - return +// sortStatIDs implements rankingSorter interface +func (rkDsrtr *rankingDescSorter) sortStatIDs() []string { + if len(rkDsrtr.statIDs) == 0 { + return rkDsrtr.statIDs + } + sort.Slice(rkDsrtr.statIDs, func(i, j int) bool { + for _, metricID := range rkDsrtr.sortingParams { + val1, hasMetric1 := rkDsrtr.statMetrics[rkDsrtr.statIDs[i]][metricID] + if !hasMetric1 { + return false + } + val2, hasMetric2 := rkDsrtr.statMetrics[rkDsrtr.statIDs[j]][metricID] + if !hasMetric2 { + return true + } + //in case we have the same value for the current metricID we skip to the next one + if val1 == val2 { + continue + } + if val1 > val2 { + return true + } + return false + } + //in case that we have the same value for all params we return randomly + return utils.BoolGenerator().RandomBool() + }) + return rkDsrtr.statIDs } diff --git a/engine/librankings_test.go b/engine/librankings_test.go new file mode 100644 index 000000000..3102cd963 --- /dev/null +++ b/engine/librankings_test.go @@ -0,0 +1,44 @@ +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package engine + +import ( + "reflect" + "testing" +) + +func TestRankingDescSorterSortStatIDs(t *testing.T) { + statMetrics := map[string]map[string]float64{ + "STATS1": {"*acc": 12.1, "*tcc": 24.2}, + "STATS2": {"*acc": 12.1, "*tcc": 24.3}, + "STATS3": {"*acc": 10.1, "*tcc": 25.3}, + } + sortMetrics := []string{"*acc", "*tcc"} + rdscSrtr := newRankingDescSorter(sortMetrics,statMetrics) + eStatIDs := []string{"STATS2", "STATS1", "STATS3"} + if statIDs := rdscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) { + t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs) + } + sortMetrics = []string{"*tcc", "*acc"} // changed the order of checks, stats3 should come first + rdscSrtr = newRankingDescSorter(sortMetrics,statMetrics) + eStatIDs = []string{"STATS3", "STATS2", "STATS1"} + if statIDs := rdscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) { + t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs) + } +} diff --git a/utils/consts.go b/utils/consts.go index db71e8be1..f34f62d6e 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -1019,6 +1019,8 @@ const ( MetaNodeID = "*node_id" MetaAscending = "*ascending" MetaDescending = "*descending" + MetaDesc = "*desc" + MetaAsc = "*asc" ) // MetaMetrics