RankingSummary structure, LastUpdate timestamp inside Ranking struct, Ranking documentation

This commit is contained in:
DanB
2024-10-18 18:53:50 +02:00
parent 2e192dde78
commit 14292a77ea
6 changed files with 253 additions and 57 deletions

View File

@@ -22,6 +22,7 @@ import (
"sort"
"strings"
"sync"
"time"
"github.com/cgrates/cgrates/utils"
)
@@ -31,16 +32,17 @@ type RankingProfileWithAPIOpts struct {
APIOpts map[string]any
}
// RankingProfile represents one profile querying StatS and sorting them.
type RankingProfile struct {
Tenant string
ID string
Schedule string
StatIDs []string
MetricIDs []string
Sorting string
SortingParameters []string
Stored bool
ThresholdIDs []string
Tenant string // Tenant this profile belongs to
ID string // Profile identification
Schedule string // Cron schedule this profile should run at
StatIDs []string // List of stat instances to query
MetricIDs []string // Filter out only specific metrics in reply for sorting
Sorting string // Sorting strategy. Possible values: <*asc|*desc>
SortingParameters []string // Sorting parameters: depending on sorting type, list of metric ids for now with optional true or false in case of reverse logic is desired
Stored bool // Offline storage activation for this profile
ThresholdIDs []string // List of threshold IDs to limit this Ranking to. *none to disable threshold processing for it.
}
func (rkp *RankingProfile) TenantID() string {
@@ -73,10 +75,10 @@ func (rkP *RankingProfile) Clone() (cln *RankingProfile) {
// NewRankingFromProfile is a constructor for an empty ranking out of it's profile
func NewRankingFromProfile(rkP *RankingProfile) (rk *Ranking) {
rk = &Ranking{
Tenant: rkP.Tenant,
ID: rkP.ID,
Sorting: rkP.Sorting,
StatMetrics: make(map[string]map[string]float64),
Tenant: rkP.Tenant,
ID: rkP.ID,
Sorting: rkP.Sorting,
Metrics: make(map[string]map[string]float64),
rkPrfl: rkP,
metricIDs: utils.NewStringSet(rkP.MetricIDs),
@@ -98,7 +100,8 @@ type Ranking struct {
Tenant string
ID string
StatMetrics map[string]map[string]float64 // map[statID]map[metricID]metricValue
LastUpdate time.Time
Metrics map[string]map[string]float64 // map[statID]map[metricID]metricValue
Sorting string
SortingParameters []string
@@ -113,15 +116,26 @@ func (r *Ranking) TenantID() string {
return utils.ConcatenatedKey(r.Tenant, r.ID)
}
// asRankingSummary converts the Ranking instance into a RankingSummary one
func (rk *Ranking) asRankingSummary() (rkSm *RankingSummary) {
rkSm = &RankingSummary{
Tenant: rk.Tenant,
ID: rk.ID,
LastUpdate: rk.LastUpdate,
}
copy(rkSm.SortedStatIDs, rk.SortedStatIDs)
return
}
type rankingSorter interface {
sortStatIDs() []string // sortStatIDs returns the sorted list of statIDs
}
// 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) {
Metrics map[string]map[string]float64) (sortedStatIDs []string, err error) {
var rnkSrtr rankingSorter
if rnkSrtr, err = newRankingSorter(sortingType, sortingParams, statMetrics); err != nil {
if rnkSrtr, err = newRankingSorter(sortingType, sortingParams, Metrics); err != nil {
return
}
return rnkSrtr.sortStatIDs(), nil
@@ -131,21 +145,21 @@ func rankingSortStats(sortingType string, sortingParams []string,
//
// returns error if the sortingType is not implemented
func newRankingSorter(sortingType string, sortingParams []string,
statMetrics map[string]map[string]float64) (rkStr rankingSorter, err error) {
Metrics map[string]map[string]float64) (rkStr rankingSorter, err error) {
switch sortingType {
default:
err = utils.ErrPrefixNotErrNotImplemented(sortingType)
return
case utils.MetaDesc:
return newRankingDescSorter(sortingParams, statMetrics), nil
return newRankingDescSorter(sortingParams, Metrics), nil
case utils.MetaAsc:
return newRankingAscSorter(sortingParams, statMetrics), nil
return newRankingAscSorter(sortingParams, Metrics), nil
}
}
// newRankingDescSorter is a constructor for rankingDescSorter
func newRankingDescSorter(sortingParams []string,
statMetrics map[string]map[string]float64) (rkDsrtr *rankingDescSorter) {
Metrics map[string]map[string]float64) (rkDsrtr *rankingDescSorter) {
clnSp := make([]string, len(sortingParams))
sPReversed := make(utils.StringSet)
for i, sP := range sortingParams { // clean the sortingParams, out of param:false or param:true definitions
@@ -158,9 +172,9 @@ func newRankingDescSorter(sortingParams []string,
rkDsrtr = &rankingDescSorter{
clnSp,
sPReversed,
statMetrics,
make([]string, 0, len(statMetrics))}
for statID := range rkDsrtr.statMetrics {
Metrics,
make([]string, 0, len(Metrics))}
for statID := range rkDsrtr.Metrics {
rkDsrtr.statIDs = append(rkDsrtr.statIDs, statID)
}
return
@@ -168,11 +182,11 @@ func newRankingDescSorter(sortingParams []string,
// rankingDescSorter will sort data descendent for metrics in sortingParams or random if all equal
type rankingDescSorter struct {
sMetricIDs []string
sMetricRev utils.StringSet // list of exceptios for sortingParams, reverting the sorting logic
statMetrics map[string]map[string]float64
sMetricIDs []string
sMetricRev utils.StringSet // list of exceptios for sortingParams, reverting the sorting logic
Metrics map[string]map[string]float64
statIDs []string // list of keys of the statMetrics
statIDs []string // list of keys of the Metrics
}
// sortStatIDs implements rankingSorter interface
@@ -182,11 +196,11 @@ func (rkDsrtr *rankingDescSorter) sortStatIDs() []string {
}
sort.Slice(rkDsrtr.statIDs, func(i, j int) bool {
for _, metricID := range rkDsrtr.sMetricIDs {
val1, hasMetric1 := rkDsrtr.statMetrics[rkDsrtr.statIDs[i]][metricID]
val1, hasMetric1 := rkDsrtr.Metrics[rkDsrtr.statIDs[i]][metricID]
if !hasMetric1 {
return false
}
val2, hasMetric2 := rkDsrtr.statMetrics[rkDsrtr.statIDs[j]][metricID]
val2, hasMetric2 := rkDsrtr.Metrics[rkDsrtr.statIDs[j]][metricID]
if !hasMetric2 {
return true
}
@@ -208,7 +222,7 @@ func (rkDsrtr *rankingDescSorter) sortStatIDs() []string {
// newRankingAscSorter is a constructor for rankingAscSorter
func newRankingAscSorter(sortingParams []string,
statMetrics map[string]map[string]float64) (rkASrtr *rankingAscSorter) {
Metrics map[string]map[string]float64) (rkASrtr *rankingAscSorter) {
clnSp := make([]string, len(sortingParams))
sPReversed := make(utils.StringSet)
for i, sP := range sortingParams { // clean the sortingParams, out of param:false or param:true definitions
@@ -221,9 +235,9 @@ func newRankingAscSorter(sortingParams []string,
rkASrtr = &rankingAscSorter{
clnSp,
sPReversed,
statMetrics,
make([]string, 0, len(statMetrics))}
for statID := range rkASrtr.statMetrics {
Metrics,
make([]string, 0, len(Metrics))}
for statID := range rkASrtr.Metrics {
rkASrtr.statIDs = append(rkASrtr.statIDs, statID)
}
return
@@ -231,11 +245,11 @@ func newRankingAscSorter(sortingParams []string,
// rankingAscSorter will sort data ascendent for metrics in sortingParams or randomly if all equal
type rankingAscSorter struct {
sMetricIDs []string
sMetricRev utils.StringSet // list of exceptios for sortingParams, reverting the sorting logic
statMetrics map[string]map[string]float64
sMetricIDs []string
sMetricRev utils.StringSet // list of exceptios for sortingParams, reverting the sorting logic
Metrics map[string]map[string]float64
statIDs []string // list of keys of the statMetrics
statIDs []string // list of keys of the Metrics
}
// sortStatIDs implements rankingSorter interface
@@ -245,11 +259,11 @@ func (rkASrtr *rankingAscSorter) sortStatIDs() []string {
}
sort.Slice(rkASrtr.statIDs, func(i, j int) bool {
for _, metricID := range rkASrtr.sMetricIDs {
val1, hasMetric1 := rkASrtr.statMetrics[rkASrtr.statIDs[i]][metricID]
val1, hasMetric1 := rkASrtr.Metrics[rkASrtr.statIDs[i]][metricID]
if !hasMetric1 {
return false
}
val2, hasMetric2 := rkASrtr.statMetrics[rkASrtr.statIDs[j]][metricID]
val2, hasMetric2 := rkASrtr.Metrics[rkASrtr.statIDs[j]][metricID]
if !hasMetric2 {
return true
}
@@ -268,3 +282,11 @@ func (rkASrtr *rankingAscSorter) sortStatIDs() []string {
})
return rkASrtr.statIDs
}
// RankingSummary is the event sent to TrendS and EEs
type RankingSummary struct {
Tenant string
ID string
LastUpdate time.Time
SortedStatIDs []string
}

View File

@@ -24,32 +24,32 @@ import (
)
func TestRankingDescSorterSortStatIDs(t *testing.T) {
statMetrics := map[string]map[string]float64{
Metrics := 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},
"STATS4": {"*tcc": 26.3},
}
sortMetrics := []string{"*acc", "*tcc"}
rdscSrtr := newRankingDescSorter(sortMetrics, statMetrics)
rdscSrtr := newRankingDescSorter(sortMetrics, Metrics)
eStatIDs := []string{"STATS2", "STATS1", "STATS3", "STATS4"}
if statIDs := rdscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
}
sortMetrics = []string{"*acc:false", "*tcc"} // changed the order of checks, stats4 should come first
rdscSrtr = newRankingDescSorter(sortMetrics, statMetrics)
rdscSrtr = newRankingDescSorter(sortMetrics, Metrics)
eStatIDs = []string{"STATS3", "STATS2", "STATS1", "STATS4"}
if statIDs := rdscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
}
sortMetrics = []string{"*tcc", "*acc:true"} // changed the order of checks, stats4 should come first
rdscSrtr = newRankingDescSorter(sortMetrics, statMetrics)
rdscSrtr = newRankingDescSorter(sortMetrics, Metrics)
eStatIDs = []string{"STATS4", "STATS3", "STATS2", "STATS1"}
if statIDs := rdscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
}
sortMetrics = []string{"*tcc:false", "*acc"} // reversed *tcc which should consider ascendent instead of descendent
rdscSrtr = newRankingDescSorter(sortMetrics, statMetrics)
rdscSrtr = newRankingDescSorter(sortMetrics, Metrics)
eStatIDs = []string{"STATS1", "STATS2", "STATS3", "STATS4"}
if statIDs := rdscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
@@ -57,32 +57,32 @@ func TestRankingDescSorterSortStatIDs(t *testing.T) {
}
func TestRankingAscSorterSortStatIDs(t *testing.T) {
statMetrics := map[string]map[string]float64{
Metrics := 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},
"STATS4": {"*tcc": 26.3},
}
sortMetrics := []string{"*acc", "*tcc"}
rtAscSrtr := newRankingAscSorter(sortMetrics, statMetrics)
rtAscSrtr := newRankingAscSorter(sortMetrics, Metrics)
eStatIDs := []string{"STATS3", "STATS1", "STATS2", "STATS4"}
if statIDs := rtAscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
}
sortMetrics = []string{"*acc:false", "*tcc"}
rtAscSrtr = newRankingAscSorter(sortMetrics, statMetrics)
rtAscSrtr = newRankingAscSorter(sortMetrics, Metrics)
eStatIDs = []string{"STATS1", "STATS2", "STATS3", "STATS4"}
if statIDs := rtAscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
}
sortMetrics = []string{"*tcc", "*acc:true"}
rtAscSrtr = newRankingAscSorter(sortMetrics, statMetrics)
rtAscSrtr = newRankingAscSorter(sortMetrics, Metrics)
eStatIDs = []string{"STATS1", "STATS2", "STATS3", "STATS4"}
if statIDs := rtAscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)
}
sortMetrics = []string{"*tcc:false", "*acc"}
rtAscSrtr = newRankingAscSorter(sortMetrics, statMetrics)
rtAscSrtr = newRankingAscSorter(sortMetrics, Metrics)
eStatIDs = []string{"STATS4", "STATS3", "STATS2", "STATS1"}
if statIDs := rtAscSrtr.sortStatIDs(); !reflect.DeepEqual(eStatIDs, statIDs) {
t.Errorf("Expecting: %v, received %v", eStatIDs, statIDs)

View File

@@ -88,6 +88,9 @@ func (rkS *RankingS) computeRanking(rkP *RankingProfile) {
if rk.rkPrfl == nil {
rk.rkPrfl = rkP
}
rk.LastUpdate = time.Now()
rk.Metrics = make(map[string]map[string]float64) // reset previous values
rk.SortedStatIDs = make([]string, 0)
for _, statID := range rkP.StatIDs {
var floatMetrics map[string]float64
if err := rkS.connMgr.Call(context.Background(), rkS.cgrcfg.RankingSCfg().StatSConns,
@@ -107,15 +110,17 @@ func (rkS *RankingS) computeRanking(rkP *RankingProfile) {
}
}
}
if len(floatMetrics) != 0 {
rk.StatMetrics[statID] = make(map[string]float64)
rk.Metrics[statID] = make(map[string]float64)
}
for metricID, val := range floatMetrics {
rk.StatMetrics[statID][metricID] = val
rk.Metrics[statID][metricID] = val
}
}
if rk.SortedStatIDs, err = rankingSortStats(rkP.Sorting,
rkP.SortingParameters, rk.StatMetrics); err != nil {
rkP.SortingParameters, rk.Metrics); err != nil {
utils.Logger.Warning(
fmt.Sprintf(
"<%s> sorting stats for Ranking with ID: <%s:%s> error: <%s>",
@@ -170,6 +175,7 @@ func (rkS *RankingS) processThresholds(rk *Ranking) (err error) {
APIOpts: opts,
Event: map[string]any{
utils.RankingID: rk.ID,
utils.LastUpdate: rk.LastUpdate,
utils.SortedStatIDs: copy([]string{}, rk.SortedStatIDs),
},
}
@@ -205,6 +211,7 @@ func (rkS *RankingS) processEEs(rk *Ranking) (err error) {
APIOpts: opts,
Event: map[string]any{
utils.RankingID: rk.ID,
utils.LastUpdate: rk.LastUpdate,
utils.SortedStatIDs: copy([]string{}, rk.SortedStatIDs),
},
}
@@ -445,11 +452,11 @@ func (rkS *RankingS) V1GetRanking(ctx *context.Context, arg *utils.TenantIDWithA
defer rk.rMux.RUnlock()
retRanking.Tenant = rk.Tenant // avoid vet complaining for mutex copying
retRanking.ID = rk.ID
retRanking.StatMetrics = make(map[string]map[string]float64)
for statID, metrics := range rk.StatMetrics {
retRanking.StatMetrics[statID] = make(map[string]float64)
retRanking.Metrics = make(map[string]map[string]float64)
for statID, metrics := range rk.Metrics {
retRanking.Metrics[statID] = make(map[string]float64)
for metricID, val := range metrics {
retRanking.StatMetrics[statID][metricID] = val
retRanking.Metrics[statID][metricID] = val
}
}
retRanking.Sorting = rk.Sorting