diff --git a/engine/resources.go b/engine/resources.go index 0784224f6..5789e8653 100644 --- a/engine/resources.go +++ b/engine/resources.go @@ -19,9 +19,10 @@ along with this program. If not, see package engine import ( + "cmp" "fmt" "runtime" - "sort" + "slices" "sync" "time" @@ -126,7 +127,6 @@ type Resource struct { tUsage *float64 // sum of all usages dirty *bool // the usages were modified, needs save, *bool so we only save if enabled in config rPrf *ResourceProfile // for ordering purposes - weight float64 } // resourceLockKey returns the ID used to lock a resource with guardian @@ -266,14 +266,9 @@ func (r *Resource) clearUsage(ruID string) (err error) { return } -// Resources is an orderable list of Resources based on Weight +// Resources is a collection of Resource objects. type Resources []*Resource -// Sort sorts based on Weight -func (rs Resources) Sort() { - sort.Slice(rs, func(i, j int) bool { return rs[i].weight > rs[j].weight }) -} - // unlock will unlock resources part of this slice func (rs Resources) unlock() { for _, r := range rs { @@ -592,6 +587,7 @@ func (rS *ResourceS) matchingResourcesForEvent(ctx *context.Context, tnt string, } } rs = make(Resources, 0, len(rIDs)) + weights := make(map[string]float64) // stores sorting weights by resource ID for resName := range rIDs { lkPrflID := guardian.Guardian.GuardIDs("", config.CgrConfig().GeneralCfg().LockingTimeout, @@ -639,17 +635,23 @@ func (rS *ResourceS) matchingResourcesForEvent(ctx *context.Context, tnt string, r.ttl = utils.DurationPointer(rPrf.UsageTTL) } r.rPrf = rPrf - if r.weight, err = WeightFromDynamics(ctx, rPrf.Weights, - rS.fltrS, tnt, evNm); err != nil { - return + weight, err := WeightFromDynamics(ctx, rPrf.Weights, rS.fltrS, tnt, evNm) + if err != nil { + return nil, err } + weights[r.ID] = weight rs = append(rs, r) } if len(rs) == 0 { return nil, utils.ErrNotFound } - rs.Sort() + + // Sort by weight (higher values first). + slices.SortFunc(rs, func(a, b *Resource) int { + return cmp.Compare(weights[b.ID], weights[a.ID]) + }) + for i, r := range rs { if r.rPrf.Blocker && i != len(rs)-1 { // blocker will stop processing and we are not at last index Resources(rs[i+1:]).unlock() diff --git a/engine/z_resources_test.go b/engine/z_resources_test.go index 1cd8f26e2..e5c93eb17 100644 --- a/engine/z_resources_test.go +++ b/engine/z_resources_test.go @@ -382,24 +382,21 @@ func TestResourceRemoveExpiredUnits(t *testing.T) { } } func TestResourceUsedUnits(t *testing.T) { - var r1 *Resource - var ru1 *ResourceUsage - var ru2 *ResourceUsage - ru1 = &ResourceUsage{ + ru1 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU1", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 1, } - ru2 = &ResourceUsage{ + ru2 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU2", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 2, } - r1 = &Resource{ + r1 := &Resource{ Tenant: "cgrates.org", ID: "RL1", rPrf: &ResourceProfile{ @@ -445,114 +442,22 @@ func TestResourceUsedUnits(t *testing.T) { } } -func TestResourceSort(t *testing.T) { - var r1 *Resource - var r2 *Resource - var ru1 *ResourceUsage - var ru2 *ResourceUsage - var rs Resources - ru1 = &ResourceUsage{ - Tenant: "cgrates.org", - ID: "RU1", - ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Units: 1, - } - - ru2 = &ResourceUsage{ - Tenant: "cgrates.org", - ID: "RU2", - ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), - Units: 2, - } - - r1 = &Resource{ - Tenant: "cgrates.org", - ID: "RL1", - rPrf: &ResourceProfile{ - Tenant: "cgrates.org", - ID: "RL1", - FilterIDs: []string{"FLTR_RES_RL1", "*ai:~*req.AnswerTime:2014-07-03T13:43:00Z|2014-07-03T13:44:00Z"}, - Weights: utils.DynamicWeights{ - { - Weight: 100, - }}, - Limit: 2, - ThresholdIDs: []string{"TEST_ACTIONS"}, - - UsageTTL: time.Millisecond, - AllocationMessage: "ALLOC", - }, - Usages: map[string]*ResourceUsage{ - ru1.ID: ru1, - }, - TTLIdx: []string{ru1.ID}, - tUsage: utils.Float64Pointer(2), - weight: 100, - } - - if err := r1.recordUsage(ru2); err != nil { - t.Error(err.Error()) - } else { - if err := r1.recordUsage(ru1); err == nil { - t.Error("duplicate ResourceUsage id should not be allowed") - } - if _, found := r1.Usages[ru2.ID]; !found { - t.Error("ResourceUsage was not recorded") - } - if *r1.tUsage != 4 { - t.Errorf("expecting: %+v, received: %+v", 4, r1.tUsage) - } - } - r2 = &Resource{ - Tenant: "cgrates.org", - ID: "RL2", - rPrf: &ResourceProfile{ - ID: "RL2", - FilterIDs: []string{"FLTR_RES_RL2", "*ai:~*req.AnswerTime:2014-07-03T13:43:00Z|2014-07-03T13:44:00Z"}, - Weights: utils.DynamicWeights{ - { - Weight: 50, - }}, - Limit: 2, - ThresholdIDs: []string{"TEST_ACTIONS"}, - UsageTTL: time.Millisecond, - }, - // AllocationMessage: "ALLOC2", - Usages: map[string]*ResourceUsage{ - ru2.ID: ru2, - }, - tUsage: utils.Float64Pointer(2), - weight: 50, - } - - rs = Resources{r2, r1} - rs.Sort() - - if rs[0].rPrf.ID != "RL1" { - t.Error("Sort failed") - } -} - func TestResourceClearUsage(t *testing.T) { - var r1 *Resource - var r2 *Resource - var ru1 *ResourceUsage - var ru2 *ResourceUsage - ru1 = &ResourceUsage{ + ru1 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU1", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 1, } - ru2 = &ResourceUsage{ + ru2 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU2", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 2, } - r1 = &Resource{ + r1 := &Resource{ Tenant: "cgrates.org", ID: "RL1", rPrf: &ResourceProfile{ @@ -574,7 +479,6 @@ func TestResourceClearUsage(t *testing.T) { }, TTLIdx: []string{ru1.ID}, tUsage: utils.Float64Pointer(2), - weight: 100, } if err := r1.recordUsage(ru2); err != nil { @@ -590,7 +494,7 @@ func TestResourceClearUsage(t *testing.T) { t.Errorf("expecting: %+v, received: %+v", 4, r1.tUsage) } } - r2 = &Resource{ + r2 := &Resource{ Tenant: "cgrates.org", ID: "RL2", rPrf: &ResourceProfile{ @@ -609,15 +513,8 @@ func TestResourceClearUsage(t *testing.T) { ru2.ID: ru2, }, tUsage: utils.Float64Pointer(2), - weight: 50, } - rs := Resources{r2, r1} - rs.Sort() - - if rs[0].rPrf.ID != "RL1" { - t.Error("Sort failed") - } r1.Usages = map[string]*ResourceUsage{ ru1.ID: ru1, } @@ -638,25 +535,21 @@ func TestResourceClearUsage(t *testing.T) { } } func TestResourceRecordUsages(t *testing.T) { - var r1 *Resource - var r2 *Resource - var ru1 *ResourceUsage - var ru2 *ResourceUsage - ru1 = &ResourceUsage{ + ru1 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU1", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 1, } - ru2 = &ResourceUsage{ + ru2 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU2", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 2, } - r1 = &Resource{ + r1 := &Resource{ Tenant: "cgrates.org", ID: "RL1", rPrf: &ResourceProfile{ @@ -678,7 +571,6 @@ func TestResourceRecordUsages(t *testing.T) { }, TTLIdx: []string{ru1.ID}, tUsage: utils.Float64Pointer(2), - weight: 100, } if err := r1.recordUsage(ru2); err != nil { @@ -694,7 +586,7 @@ func TestResourceRecordUsages(t *testing.T) { t.Errorf("expecting: %+v, received: %+v", 4, r1.tUsage) } } - r2 = &Resource{ + r2 := &Resource{ Tenant: "cgrates.org", ID: "RL2", rPrf: &ResourceProfile{ @@ -713,15 +605,9 @@ func TestResourceRecordUsages(t *testing.T) { ru2.ID: ru2, }, tUsage: utils.Float64Pointer(2), - weight: 50, } rs := Resources{r2, r1} - rs.Sort() - - if rs[0].rPrf.ID != "RL1" { - t.Error("Sort failed") - } r1.Usages = map[string]*ResourceUsage{ ru1.ID: ru1, } @@ -734,25 +620,21 @@ func TestResourceRecordUsages(t *testing.T) { } } func TestResourceAllocateResource(t *testing.T) { - var r1 *Resource - var r2 *Resource - var ru1 *ResourceUsage - var ru2 *ResourceUsage - ru1 = &ResourceUsage{ + ru1 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU1", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 1, } - ru2 = &ResourceUsage{ + ru2 := &ResourceUsage{ Tenant: "cgrates.org", ID: "RU2", ExpiryTime: time.Date(2014, 7, 3, 13, 43, 0, 1, time.UTC), Units: 2, } - r1 = &Resource{ + r1 := &Resource{ Tenant: "cgrates.org", ID: "RL1", rPrf: &ResourceProfile{ @@ -774,7 +656,6 @@ func TestResourceAllocateResource(t *testing.T) { }, TTLIdx: []string{ru1.ID}, tUsage: utils.Float64Pointer(2), - weight: 100, } if err := r1.recordUsage(ru2); err != nil { @@ -790,7 +671,7 @@ func TestResourceAllocateResource(t *testing.T) { t.Errorf("expecting: %+v, received: %+v", 4, r1.tUsage) } } - r2 = &Resource{ + r2 := &Resource{ Tenant: "cgrates.org", ID: "RL2", rPrf: &ResourceProfile{ @@ -809,15 +690,9 @@ func TestResourceAllocateResource(t *testing.T) { ru2.ID: ru2, }, tUsage: utils.Float64Pointer(2), - weight: 50, } - rs := Resources{r2, r1} - rs.Sort() - - if rs[0].rPrf.ID != "RL1" { - t.Error("Sort failed") - } + rs := Resources{r1, r2} r1.Usages = map[string]*ResourceUsage{ ru1.ID: ru1, } @@ -3449,7 +3324,6 @@ func TestResourcesV1ResourcesForEventOK(t *testing.T) { tUsage: utils.Float64Pointer(10), ttl: utils.DurationPointer(time.Minute), TTLIdx: []string{}, - weight: 10, } err := dm.SetResourceProfile(context.Background(), rsPrf, true) if err != nil { @@ -3489,7 +3363,6 @@ func TestResourcesV1ResourcesForEventOK(t *testing.T) { tUsage: utils.Float64Pointer(10), ttl: utils.DurationPointer(72 * time.Hour), TTLIdx: []string{}, - weight: 10, }, } var reply Resources @@ -3794,7 +3667,6 @@ func TestResourcesV1ResourcesForEventCacheReplySet(t *testing.T) { tUsage: utils.Float64Pointer(10), ttl: utils.DurationPointer(time.Minute), TTLIdx: []string{}, - weight: 10, } err := dm.SetResourceProfile(context.Background(), rsPrf, true) if err != nil { @@ -3834,7 +3706,6 @@ func TestResourcesV1ResourcesForEventCacheReplySet(t *testing.T) { tUsage: utils.Float64Pointer(10), ttl: utils.DurationPointer(72 * time.Hour), TTLIdx: []string{}, - weight: 10, }, } var reply Resources @@ -6552,7 +6423,6 @@ func TestResourcesMatchingResourcesForEventFinalCacheSetErr(t *testing.T) { Usages: make(map[string]*ResourceUsage), ttl: utils.DurationPointer(10 * time.Second), dirty: utils.BoolPointer(false), - weight: 10, } if rcv, err := rS.matchingResourcesForEvent(context.Background(), "cgrates.org", ev, ev.ID,