/* 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 ( "fmt" "math" "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" ) func NewQOSRouteSorter(cfg *config.CGRConfig, connMgr *ConnManager) *QOSRouteSorter { return &QOSRouteSorter{cfg: cfg, connMgr: connMgr} } // QOSSorter sorts route based on stats type QOSRouteSorter struct { cfg *config.CGRConfig connMgr *ConnManager } func (qos *QOSRouteSorter) SortRoutes(ctx *context.Context, prflID string, routes map[string]*RouteWithWeight, ev *utils.CGREvent, extraOpts *optsGetRoutes) (sortedRoutes *SortedRoutes, err error) { if len(qos.cfg.RouteSCfg().StatSConns) == 0 { return nil, utils.NewErrMandatoryIeMissing("connIDs") } sortedRoutes = &SortedRoutes{ ProfileID: prflID, Sorting: utils.MetaQOS, Routes: make([]*SortedRoute, 0, len(routes)), } for _, route := range routes { srtRoute := &SortedRoute{ RouteID: route.ID, SortingData: map[string]interface{}{ utils.Weight: route.Weight, }, sortingDataDecimal: map[string]*utils.Decimal{ utils.Weight: utils.NewDecimalFromFloat64(route.Weight), }, RouteParameters: route.RouteParameters, } if route.blocker { srtRoute.SortingData[utils.Blocker] = true } var metricSupp map[string]*utils.Decimal if metricSupp, err = populatStatsForQOSRoute(ctx, qos.cfg, qos.connMgr, route.StatIDs, ev.Tenant); err != nil { //create metric map for route if extraOpts.ignoreErrors { utils.Logger.Warning( fmt.Sprintf("<%s> ignoring route with ID: %s, err: %s", utils.RouteS, route.ID, err.Error())) err = nil continue } return } // add metrics from statIDs in SortingData for key, val := range metricSupp { srtRoute.SortingData[key] = val srtRoute.sortingDataDecimal[key] = val } // check if the route have the metric from sortingParameters // in case that the metric don't exist // we use 10000000 for *pdd and -1 for others for _, metric := range extraOpts.sortingParameters { if _, hasMetric := metricSupp[metric]; !hasMetric { if metric == utils.MetaPDD { srtRoute.SortingData[metric] = math.MaxFloat64 srtRoute.sortingDataDecimal[metric] = utils.NewDecimalFromFloat64(math.MaxFloat64) } else { srtRoute.SortingData[metric] = -1.0 srtRoute.sortingDataDecimal[metric] = utils.NewDecimalFromFloat64(-1.0) } } } var pass bool if pass, err = routeLazyPass(ctx, route.lazyCheckRules, ev, srtRoute.SortingData, qos.cfg.FilterSCfg().ResourceSConns, qos.cfg.FilterSCfg().StatSConns, qos.cfg.FilterSCfg().AccountSConns); err != nil { return } else if pass { sortedRoutes.Routes = append(sortedRoutes.Routes, srtRoute) } } sortedRoutes.SortQOS(extraOpts.sortingParameters) return } // populatStatsForQOSRoute will query a list of statIDs and return composed metric values // first metric found is always returned func populatStatsForQOSRoute(ctx *context.Context, cfg *config.CGRConfig, connMgr *ConnManager, statIDs []string, tenant string) (stsMetric map[string]*utils.Decimal, err error) { type metric struct { sum *utils.Decimal len int } stsMetric = make(map[string]*utils.Decimal) provStsMetrics := make(map[string]metric) for _, statID := range statIDs { var metrics map[string]*utils.Decimal if err = connMgr.Call(ctx, cfg.RouteSCfg().StatSConns, utils.StatSv1GetQueueDecimalMetrics, &utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: tenant, ID: statID}}, &metrics); err != nil && err.Error() != utils.ErrNotFound.Error() { utils.Logger.Warning( fmt.Sprintf("<%s> error: %s getting statMetrics for stat : %s", utils.RouteS, err.Error(), statID)) return } for key, val := range metrics { //add value of metric in a slice in case that we get the same metric from different stat provStsMetrics[key] = metric{ sum: utils.SumDecimal(provStsMetrics[key].sum, val), len: provStsMetrics[key].len + 1, } } } for metric, val := range provStsMetrics { stsMetric[metric] = utils.DivideDecimal(val.sum, utils.NewDecimal(int64(val.len), 0)) } return }