2026-02-16 09:02:48 +00:00
# define MODULE_LOG_PREFIX "stat"
# include "globals.h"
# ifdef WITH_LB
# include "cscrypt/md5.h"
# include "module-cacheex.h"
# include "module-cccam.h"
# include "oscam-array.h"
# include "oscam-cache.h"
# include "oscam-conf-chk.h"
# include "oscam-chk.h"
# include "oscam-client.h"
# include "oscam-ecm.h"
# include "oscam-files.h"
# include "oscam-lock.h"
# include "oscam-string.h"
# include "oscam-time.h"
# define UNDEF_AVG_TIME 99999 // NOT set here 0 or small value! Could cause there reader get selected
# define MAX_ECM_SEND_CACHE 16
# define LB_NONE 0
# define LB_FASTEST_READER_FIRST 1
# define LB_OLDEST_READER_FIRST 2
# define LB_LOWEST_USAGELEVEL 3
# define DEFAULT_LOCK_TIMEOUT 1000000
extern CS_MUTEX_LOCK ecmcache_lock ;
extern struct ecm_request_t * ecmcwcache ;
static int32_t stat_load_save ;
static struct timeb last_housekeeping ;
void init_stat ( void )
{
stat_load_save = - 100 ;
//checking config
if ( cfg . lb_nbest_readers < 2 )
{ cfg . lb_nbest_readers = DEFAULT_NBEST ; }
if ( cfg . lb_nfb_readers < 0 )
{ cfg . lb_nfb_readers = DEFAULT_NFB ; }
if ( cfg . lb_min_ecmcount < 2 )
{ cfg . lb_min_ecmcount = DEFAULT_MIN_ECM_COUNT ; }
if ( cfg . lb_max_ecmcount < 3 )
{ cfg . lb_max_ecmcount = DEFAULT_MAX_ECM_COUNT ; }
if ( cfg . lb_reopen_seconds < 10 )
{ cfg . lb_reopen_seconds = DEFAULT_REOPEN_SECONDS ; }
if ( cfg . lb_retrylimit < 0 )
{ cfg . lb_retrylimit = DEFAULT_RETRYLIMIT ; }
if ( cfg . lb_stat_cleanup < = 0 )
{ cfg . lb_stat_cleanup = DEFAULT_LB_STAT_CLEANUP ; }
}
# define LINESIZE 1024
static uint32_t get_prid ( uint16_t caid , uint32_t prid )
{
int32_t i ;
for ( i = 0 ; i < cfg . lb_noproviderforcaid . ctnum ; i + + )
{
CAIDTAB_DATA * d = & cfg . lb_noproviderforcaid . ctdata [ i ] ;
uint16_t tcaid = d - > caid ;
if ( ! tcaid ) { break ; }
if ( ( tcaid = = caid ) | | ( tcaid < 0x0100 & & ( caid > > 8 ) = = tcaid ) )
{
prid = 0 ;
break ;
}
}
return prid ;
}
static void get_stat_query ( ECM_REQUEST * er , STAT_QUERY * q )
{
memset ( q , 0 , sizeof ( STAT_QUERY ) ) ;
q - > caid = er - > caid ;
q - > prid = get_prid ( er - > caid , er - > prid ) ;
q - > srvid = er - > srvid ;
q - > chid = er - > chid ;
q - > ecmlen = er - > ecmlen ;
}
void load_stat_from_file ( void )
{
stat_load_save = 0 ;
char buf [ 256 ] ;
char * line ;
char * fname ;
FILE * file ;
if ( ! cfg . lb_savepath )
{
get_tmp_dir_filename ( buf , sizeof ( buf ) , " stat " ) ;
fname = buf ;
}
else
{ fname = cfg . lb_savepath ; }
file = fopen ( fname , " r " ) ;
if ( ! file )
{
cs_log_dbg ( D_LB , " loadbalancer: could not open %s for reading (errno=%d %s) " , fname , errno , strerror ( errno ) ) ;
return ;
}
if ( ! cs_malloc ( & line , LINESIZE ) )
{
fclose ( file ) ;
return ;
}
cs_log_dbg ( D_LB , " loadbalancer: load statistics from %s " , fname ) ;
struct timeb ts , te ;
cs_ftime ( & ts ) ;
struct s_reader * rdr = NULL ;
READER_STAT * s ;
int32_t i = 1 ;
int32_t valid = 0 ;
# ifdef WITH_DEBUG
int32_t count = 0 ;
# endif
int32_t type = 0 ;
char * ptr , * saveptr1 = NULL ;
char * split [ 12 ] ;
while ( fgets ( line , LINESIZE , file ) )
{
if ( ! line [ 0 ] | | line [ 0 ] = = ' # ' | | line [ 0 ] = = ' ; ' )
{ continue ; }
if ( ! cs_malloc ( & s , sizeof ( READER_STAT ) ) )
{ continue ; }
//get type by evaluating first line:
if ( type = = 0 )
{
if ( strstr ( line , " rc " ) ) { type = 2 ; }
else { type = 1 ; }
}
if ( type = = 1 ) // New format - faster parsing:
{
for ( i = 0 , ptr = strtok_r ( line , " , " , & saveptr1 ) ; ptr & & i < 12 ; ptr = strtok_r ( NULL , " , " , & saveptr1 ) , i + + )
{ split [ i ] = ptr ; }
valid = ( i = = 11 ) ;
if ( valid )
{
cs_strncpy ( buf , split [ 0 ] , sizeof ( buf ) ) ;
s - > rc = atoi ( split [ 1 ] ) ;
s - > caid = a2i ( split [ 2 ] , 4 ) ;
s - > prid = a2i ( split [ 3 ] , 6 ) ;
s - > srvid = a2i ( split [ 4 ] , 4 ) ;
s - > chid = a2i ( split [ 5 ] , 4 ) ;
s - > time_avg = atoi ( split [ 6 ] ) ;
s - > ecm_count = atoi ( split [ 7 ] ) ;
s - > last_received . time = atol ( split [ 8 ] ) ;
s - > fail_factor = atoi ( split [ 9 ] ) ;
s - > ecmlen = a2i ( split [ 10 ] , 2 ) ;
}
}
else // Old format - keep for compatibility:
{
i = sscanf ( line , " %255s rc %04d caid %04hX prid %06X srvid %04hX time avg %d ms ecms %d last % " SCNd64 " fail %d len %02hX \n " ,
buf , & s - > rc , & s - > caid , & s - > prid , & s - > srvid ,
& s - > time_avg , & s - > ecm_count , ( int64_t * ) & s - > last_received . millitm , & s - > fail_factor , & s - > ecmlen ) ;
valid = i > 5 ;
}
if ( valid & & s - > ecmlen > 0 )
{
if ( rdr = = NULL | | strcmp ( buf , rdr - > label ) ! = 0 )
{
LL_ITER itr = ll_iter_create ( configured_readers ) ;
while ( ( rdr = ll_iter_next ( & itr ) ) )
{
if ( strcmp ( rdr - > label , buf ) = = 0 )
{
break ;
}
}
}
if ( rdr ! = NULL & & strcmp ( buf , rdr - > label ) = = 0 )
{
if ( ! rdr - > lb_stat )
{
rdr - > lb_stat = ll_create ( " lb_stat " ) ;
cs_lock_create ( __func__ , & rdr - > lb_stat_lock , rdr - > label , DEFAULT_LOCK_TIMEOUT ) ;
}
ll_append ( rdr - > lb_stat , s ) ;
# ifdef WITH_DEBUG
count + + ;
# endif
}
else
{
cs_log ( " loadbalancer: statistics could not be loaded for %s " , buf ) ;
NULLFREE ( s ) ;
}
}
else
{
cs_log_dbg ( D_LB , " loadbalancer: statistics ERROR: %s rc=%d i=%d " , buf , s - > rc , i ) ;
NULLFREE ( s ) ;
}
}
fclose ( file ) ;
NULLFREE ( line ) ;
cs_ftime ( & te ) ;
# ifdef WITH_DEBUG
int64_t load_time = comp_timeb ( & te , & ts ) ;
cs_log_dbg ( D_LB , " loadbalancer: statistics loaded %d records in % " PRId64 " ms " , count , load_time ) ;
# endif
}
void lb_destroy_stats ( struct s_reader * rdr )
{
if ( ! rdr - > lb_stat )
return ;
cs_lock_destroy ( __func__ , & rdr - > lb_stat_lock ) ;
ll_destroy_data ( & rdr - > lb_stat ) ;
}
/**
* get statistic values for reader ridx and caid / prid / srvid / ecmlen
* */
static READER_STAT * get_stat_lock ( struct s_reader * rdr , STAT_QUERY * q , int8_t lock )
{
if ( ! rdr - > lb_stat )
{
rdr - > lb_stat = ll_create ( " lb_stat " ) ;
cs_lock_create ( __func__ , & rdr - > lb_stat_lock , rdr - > label , DEFAULT_LOCK_TIMEOUT ) ;
}
if ( lock ) { cs_readlock ( __func__ , & rdr - > lb_stat_lock ) ; }
LL_ITER it = ll_iter_create ( rdr - > lb_stat ) ;
READER_STAT * s ;
int32_t i = 0 ;
while ( ( s = ll_iter_next ( & it ) ) )
{
i + + ;
if ( s - > caid = = q - > caid & & s - > prid = = q - > prid & & s - > srvid = = q - > srvid & & s - > chid = = q - > chid )
{
if ( s - > ecmlen = = q - > ecmlen )
{ break ; }
if ( ! s - > ecmlen )
{
s - > ecmlen = q - > ecmlen ;
break ;
}
if ( ! q - > ecmlen ) // Query without ecmlen from dvbapi
{ break ; }
}
}
if ( lock ) { cs_readunlock ( __func__ , & rdr - > lb_stat_lock ) ; }
// Move stat to list start for faster access:
if ( i > 10 & & s & & ! rdr - > lb_stat_busy ) {
if ( lock ) cs_writelock ( __func__ , & rdr - > lb_stat_lock ) ;
ll_iter_move_first ( & it ) ;
if ( lock ) cs_writeunlock ( __func__ , & rdr - > lb_stat_lock ) ;
}
return s ;
}
/**
* get statistic values for reader ridx and caid / prid / srvid / ecmlen
* */
static READER_STAT * get_stat ( struct s_reader * rdr , STAT_QUERY * q )
{
return get_stat_lock ( rdr , q , 1 ) ;
}
/**
* Calculates average time
*/
static void calc_stat ( READER_STAT * s )
{
int32_t i , c = 0 , t = 0 ;
for ( i = 0 ; i < LB_MAX_STAT_TIME ; i + + )
{
if ( s - > time_stat [ i ] > 0 )
{
t + = ( int32_t ) s - > time_stat [ i ] ;
c + + ;
}
}
if ( ! c )
{ s - > time_avg = UNDEF_AVG_TIME ; }
else
{ s - > time_avg = t / c ; }
}
/**
* Saves statistik to / tmp / . oscam / stat . n where n is reader - index
*/
static void save_stat_to_file_thread ( void )
{
stat_load_save = 0 ;
char buf [ 256 ] ;
set_thread_name ( __func__ ) ;
char * fname ;
if ( ! cfg . lb_savepath )
{
get_tmp_dir_filename ( buf , sizeof ( buf ) , " stat " ) ;
fname = buf ;
}
else
{ fname = cfg . lb_savepath ; }
FILE * file = fopen ( fname , " w " ) ;
if ( ! file )
{
cs_log ( " can't write to file %s " , fname ) ;
return ;
}
struct timeb ts , te ;
cs_ftime ( & ts ) ;
int32_t cleanup_timeout = ( cfg . lb_stat_cleanup * 60 * 60 * 1000 ) ;
int32_t count = 0 ;
struct s_reader * rdr ;
LL_ITER itr = ll_iter_create ( configured_readers ) ;
while ( ( rdr = ll_iter_next ( & itr ) ) )
{
if ( rdr - > lb_stat )
{
rdr - > lb_stat_busy = 1 ;
cs_writelock ( __func__ , & rdr - > lb_stat_lock ) ;
LL_ITER it = ll_iter_create ( rdr - > lb_stat ) ;
READER_STAT * s ;
while ( ( s = ll_iter_next ( & it ) ) )
{
int64_t gone = comp_timeb ( & ts , & s - > last_received ) ;
if ( gone > cleanup_timeout | | ! s - > ecmlen ) // cleanup old stats
{
ll_iter_remove_data ( & it ) ;
continue ;
}
//Old version, too slow to parse:
//fprintf(file, "%s rc %d caid %04hX prid %06X srvid %04hX time avg %d ms ecms %d last %ld fail %d len %02hX\n",
// rdr->label, s->rc, s->caid, s->prid,
// s->srvid, s->time_avg, s->ecm_count, s->last_received, s->fail_factor, s->ecmlen);
//New version:
fprintf ( file , " %s,%d,%04hX,%06X,%04hX,%04hX,%d,%d,% " PRId64 " ,%d,%02hX \n " ,
rdr - > label , s - > rc , s - > caid , s - > prid ,
s - > srvid , ( uint16_t ) s - > chid , s - > time_avg , s - > ecm_count , ( int64_t ) s - > last_received . time , s - > fail_factor , s - > ecmlen ) ;
count + + ;
//if(count % 500 == 0) { // Saving stats is using too much cpu and causes high file load. so we need a break
// cs_readunlock(__func__, &rdr->lb_stat_lock);
// cs_sleepms(100);
// cs_readlock(__func__, &rdr->lb_stat_lock);
//}
}
cs_writeunlock ( __func__ , & rdr - > lb_stat_lock ) ;
rdr - > lb_stat_busy = 0 ;
}
}
fclose ( file ) ;
cs_ftime ( & te ) ;
int64_t load_time = comp_timeb ( & te , & ts ) ;
cs_log ( " loadbalancer: statistic saved %d records to %s in % " PRId64 " ms " , count , fname , load_time ) ;
}
void save_stat_to_file ( int32_t thread )
{
stat_load_save = 0 ;
if ( thread )
{ start_thread ( " save lb stats " , ( void * ) & save_stat_to_file_thread , NULL , NULL , 1 , 1 ) ; }
else
{ save_stat_to_file_thread ( ) ; }
}
/**
* fail_factor is multiplied to the reopen_time . This function increases the fail_factor
* */
static void inc_fail ( READER_STAT * s )
{
if ( s - > fail_factor < = 0 )
{ s - > fail_factor = 1 ; }
else
{ s - > fail_factor + + ; } // inc by one at the time
}
static READER_STAT * get_add_stat ( struct s_reader * rdr , STAT_QUERY * q )
{
if ( rdr - > lb_stat_busy )
return NULL ;
if ( ! rdr - > lb_stat )
{
rdr - > lb_stat = ll_create ( " lb_stat " ) ;
cs_lock_create ( __func__ , & rdr - > lb_stat_lock , rdr - > label , DEFAULT_LOCK_TIMEOUT ) ;
}
cs_writelock ( __func__ , & rdr - > lb_stat_lock ) ;
READER_STAT * s = get_stat_lock ( rdr , q , 0 ) ;
if ( ! s )
{
if ( cs_malloc ( & s , sizeof ( READER_STAT ) ) )
{
s - > caid = q - > caid ;
s - > prid = q - > prid ;
s - > srvid = q - > srvid ;
s - > chid = q - > chid ;
s - > ecmlen = q - > ecmlen ;
s - > time_avg = UNDEF_AVG_TIME ; // dummy placeholder
s - > rc = E_FOUND ; // set to found--> do not change!
cs_ftime ( & s - > last_received ) ;
s - > fail_factor = 0 ;
s - > ecm_count = 0 ;
ll_prepend ( rdr - > lb_stat , s ) ;
}
}
cs_writeunlock ( __func__ , & rdr - > lb_stat_lock ) ;
return s ;
}
static void housekeeping_stat ( int32_t force ) ;
void readerinfofix_get_stat_query ( ECM_REQUEST * er , STAT_QUERY * q )
{
get_stat_query ( er , q ) ;
}
void readerinfofix_inc_fail ( READER_STAT * s )
{
inc_fail ( s ) ;
}
READER_STAT * readerinfofix_get_add_stat ( struct s_reader * rdr , STAT_QUERY * q )
{
return get_add_stat ( rdr , q ) ;
}
static int32_t get_reopen_seconds ( READER_STAT * s )
{
int32_t max = ( INT_MAX / cfg . lb_reopen_seconds ) ;
if ( max > 9999 ) { max = 9999 ; }
if ( s - > fail_factor > max )
{ s - > fail_factor = max ; }
if ( ! s - > fail_factor )
{ return cfg . lb_reopen_seconds ; }
return s - > fail_factor * cfg . lb_reopen_seconds ;
}
/**
* Adds caid / prid / srvid / ecmlen to stat - list for reader ridx with time / rc
*/
static void add_stat ( struct s_reader * rdr , ECM_REQUEST * er , int32_t ecm_time , int32_t rc , uint8_t rcEx )
{
//inc ecm_count if found, drop to 0 if not found:
// rc codes:
// 0 = found +
// 1 = cache1 #
// 2 = cache2 #
// 3 = cacheex #
// 4 = not found -
// 5 = timeout -
// 6 = sleeping #
// 7 = fake -
// 8 = invalid -
// 9 = corrupt #
// 10= no card #
// 11= expdate #
// 12= disabled #
// 13= stopped #
// 100= unhandled #
// + = adds statistic values
// # = ignored because of duplicate values, temporary failures or softblocks
// - = causes loadbalancer to block this reader for this caid/prov/sid
if ( ! rdr | | ! er | | ! cfg . lb_mode | | ! er - > ecmlen | | ! er - > client | | rdr - > lb_stat_busy )
{ return ; }
struct s_client * cl = rdr - > client ;
if ( ! check_client ( cl ) )
{ return ; }
// IGNORE stats for fallback reader with lb_force_fallback parameter
if ( chk_is_fixed_fallback ( rdr , er ) & & rdr - > lb_force_fallback )
{ return ; }
// IGNORE fails for ratelimit check
if ( rc = = E_NOTFOUND & & rcEx = = E2_RATELIMIT )
{
# ifdef WITH_DEBUG
if ( ( D_LB & cs_dblevel ) )
{
char buf [ ECM_FMT_LEN ] ;
format_ecm ( er , buf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: NOT adding stat (blocking) for reader %s because fails ratelimit checks! " , rdr - > label ) ;
}
# endif
return ;
}
// IGNORE fails when reader has positive services defined in new lb_whitelist_services parameter! See ticket #3310,#3311
if ( rc > = E_NOTFOUND & & has_lb_srvid ( cl , er ) )
{
# ifdef WITH_DEBUG
if ( ( D_LB & cs_dblevel ) )
{
char buf [ ECM_FMT_LEN ] ;
format_ecm ( er , buf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: NOT adding stat (blocking) for reader %s because has positive srvid: rc %d %s time %d ms " ,
rdr - > label , rc , buf , ecm_time ) ;
}
# endif
return ;
}
// IGNORE fails for sleep CMD08
if ( rc = = E_NOTFOUND & & rdr - > client - > stopped = = 2 )
{
# ifdef WITH_DEBUG
if ( ( D_LB & cs_dblevel ) )
{
char buf [ ECM_FMT_LEN ] ;
format_ecm ( er , buf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: NOT adding stat (no block) for reader %s because CMD08 sleep command! " , rdr - > label ) ;
}
# endif
return ;
}
// IGNORE timeouts on local readers (they could be busy handling an emm or entitlement refresh)
if ( rc = = E_TIMEOUT & & ! is_network_reader ( rdr ) )
{
# ifdef WITH_DEBUG
if ( ( D_LB & cs_dblevel ) )
{
cs_log_dbg ( D_LB , " loadbalancer: NOT adding stat (no block) for reader %s because timeout on local reader " , rdr - > label ) ;
}
# endif
return ;
}
// IGNORE unhandled ecmresponses
if ( rc = = E_UNHANDLED )
{
# ifdef WITH_DEBUG
if ( ( D_LB & cs_dblevel ) )
{
cs_log_dbg ( D_LB , " loadbalancer: NOT adding stat (no block) for reader %s because unhandled reponse " , rdr - > label ) ;
}
# endif
return ;
}
// ignore too old ecms
if ( ( uint32_t ) ecm_time > = 3 * cfg . ctimeout )
{ return ; }
if ( ( uint32_t ) ecm_time > = cfg . ctimeout )
{ rc = E_TIMEOUT ; }
STAT_QUERY q ;
get_stat_query ( er , & q ) ;
READER_STAT * s ;
s = get_add_stat ( rdr , & q ) ;
if ( ! s ) return ;
struct timeb now ;
cs_ftime ( & now ) ;
cs_ftime ( & s - > last_received ) ;
if ( rc = = E_FOUND ) // found
{
s - > rc = E_FOUND ;
s - > ecm_count + + ;
s - > fail_factor = 0 ;
// FASTEST READER:
s - > time_idx + + ;
if ( s - > time_idx > = LB_MAX_STAT_TIME )
{ s - > time_idx = 0 ; }
s - > time_stat [ s - > time_idx ] = ecm_time ;
calc_stat ( s ) ;
// OLDEST READER now set by get best reader!
// USAGELEVEL:
/* Assign a value to rdr->lb_usagelevel_ecmcount,
because no determined value was assigned before . */
if ( rdr - > lb_usagelevel_ecmcount < 0 )
{ rdr - > lb_usagelevel_ecmcount = 0 ; }
rdr - > lb_usagelevel_ecmcount + + ; /* ecm is found so counter should increase */
if ( ( rdr - > lb_usagelevel_ecmcount % cfg . lb_min_ecmcount ) = = 0 ) //update every MIN_ECM_COUNT usagelevel:
{
int64_t t = comp_timeb ( & now , & rdr - > lb_usagelevel_time ) / 1000 ;
rdr - > lb_usagelevel = cfg . lb_min_ecmcount * 1000 / ( t < 1 ? 1 : t ) ;
/* Reset of usagelevel time and counter */
rdr - > lb_usagelevel_time = now ;
rdr - > lb_usagelevel_ecmcount = 0 ;
}
}
else if ( rc = = E_NOTFOUND | | rc = = E_TIMEOUT | | rc = = E_FAKE ) // not found / timeout /fake
{
inc_fail ( s ) ;
s - > rc = rc ;
}
else if ( rc = = E_INVALID ) // invalid
{
s - > rc = rc ;
}
else
{
# ifdef WITH_DEBUG
if ( rc > = E_FOUND & & ( D_LB & cs_dblevel ) )
{
char buf [ ECM_FMT_LEN ] ;
format_ecm ( er , buf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: not handled stat for reader %s: rc %d %s time %d ms " ,
rdr - > label , rc , buf , ecm_time ) ;
}
# endif
return ;
}
housekeeping_stat ( 0 ) ;
# ifdef WITH_DEBUG
if ( D_LB & cs_dblevel )
{
char buf [ ECM_FMT_LEN ] ;
format_ecm ( er , buf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: adding stat for reader %s: rc %d %s time %d ms fail %d " ,
rdr - > label , rc , buf , ecm_time , s - > fail_factor ) ;
}
# endif
if ( cfg . lb_save )
{
stat_load_save + + ;
if ( stat_load_save > cfg . lb_save )
{ save_stat_to_file ( 1 ) ; }
}
}
int32_t clean_stat_by_rc ( struct s_reader * rdr , int8_t rc , int8_t inverse )
{
int32_t count = 0 ;
if ( rdr & & rdr - > lb_stat )
{
if ( rdr - > lb_stat_busy ) return 0 ;
rdr - > lb_stat_busy = 1 ;
cs_writelock ( __func__ , & rdr - > lb_stat_lock ) ;
READER_STAT * s ;
LL_ITER itr = ll_iter_create ( rdr - > lb_stat ) ;
while ( ( s = ll_iter_next ( & itr ) ) )
{
if ( ( ! inverse & & s - > rc = = rc ) | | ( inverse & & s - > rc ! = rc ) )
{
ll_iter_remove_data ( & itr ) ;
count + + ;
}
}
cs_writeunlock ( __func__ , & rdr - > lb_stat_lock ) ;
rdr - > lb_stat_busy = 0 ;
}
return count ;
}
int32_t clean_all_stats_by_rc ( int8_t rc , int8_t inverse )
{
int32_t count = 0 ;
LL_ITER itr = ll_iter_create ( configured_readers ) ;
struct s_reader * rdr ;
while ( ( rdr = ll_iter_next ( & itr ) ) )
{
count + = clean_stat_by_rc ( rdr , rc , inverse ) ;
}
save_stat_to_file ( 0 ) ;
return count ;
}
int32_t clean_stat_by_id ( struct s_reader * rdr , uint16_t caid , uint32_t prid , uint16_t srvid , uint16_t chid , uint16_t ecmlen )
{
int32_t count = 0 ;
if ( rdr & & rdr - > lb_stat )
{
if ( rdr - > lb_stat_busy ) return 0 ;
rdr - > lb_stat_busy = 1 ;
cs_writelock ( __func__ , & rdr - > lb_stat_lock ) ;
READER_STAT * s ;
LL_ITER itr = ll_iter_create ( rdr - > lb_stat ) ;
while ( ( s = ll_iter_next ( & itr ) ) )
{
if ( s - > caid = = caid & &
s - > prid = = prid & &
s - > srvid = = srvid & &
s - > chid = = chid & &
s - > ecmlen = = ecmlen )
{
ll_iter_remove_data ( & itr ) ;
count + + ;
break ; // because the entry should unique we can left here
}
}
cs_writeunlock ( __func__ , & rdr - > lb_stat_lock ) ;
rdr - > lb_stat_busy = 0 ;
}
return count ;
}
/*
static int32_t has_ident ( FTAB * ftab , ECM_REQUEST * er ) {
if ( ! ftab | | ! ftab - > filts )
return 0 ;
int32_t j , k ;
for ( j = 0 ; j < ftab - > nfilts ; j + + ) {
if ( ftab - > filts [ j ] . caid ) {
if ( ftab - > filts [ j ] . caid = = er - > caid ) { //caid matches!
int32_t nprids = ftab - > filts [ j ] . nprids ;
if ( ! nprids ) // No Provider ->Ok
return 1 ;
for ( k = 0 ; k < nprids ; k + + ) {
uint32_t prid = ftab - > filts [ j ] . prids [ k ] ;
if ( prid = = er - > prid ) { //Provider matches
return 1 ;
}
}
}
}
}
return 0 ; //No match!
} */
static int32_t get_retrylimit ( ECM_REQUEST * er )
{
return caidvaluetab_get_value ( & cfg . lb_retrylimittab , er - > caid , cfg . lb_retrylimit ) ;
}
static int32_t get_nfb_readers ( ECM_REQUEST * er )
{
int32_t nfb_readers = er - > client - > account - > lb_nfb_readers = = - 1 ? cfg . lb_nfb_readers : er - > client - > account - > lb_nfb_readers ;
if ( nfb_readers < = 0 ) { nfb_readers = 0 ; }
return nfb_readers ;
}
static int32_t get_nbest_readers ( ECM_REQUEST * er )
{
int32_t nbest_readers = er - > client - > account - > lb_nbest_readers = = - 1 ? cfg . lb_nbest_readers : er - > client - > account - > lb_nbest_readers ;
CAIDVALUETAB * nbest_readers_tab = er - > client - > account - > lb_nbest_readers_tab . cvnum = = 0 ? & cfg . lb_nbest_readers_tab : & er - > client - > account - > lb_nbest_readers_tab ;
if ( nbest_readers < = 0 ) { nbest_readers = 1 ; }
return caidvaluetab_get_value ( nbest_readers_tab , er - > caid , nbest_readers ) ;
}
static void convert_to_beta_int ( ECM_REQUEST * er , uint16_t caid_to )
{
uint8_t md5tmp [ MD5_DIGEST_LENGTH ] ;
convert_to_beta ( er - > client , er , caid_to ) ;
// update ecmd5 for store ECM in cache
memcpy ( er - > ecmd5 , MD5 ( er - > ecm + 13 , er - > ecmlen - 13 , md5tmp ) , CS_ECMSTORESIZE ) ;
cacheex_update_hash ( er ) ;
er - > btun = 2 ; // marked as auto-betatunnel converted. Also for fixing recursive lock in get_cw
}
static void convert_to_nagra_int ( ECM_REQUEST * er , uint16_t caid_to )
{
uint8_t md5tmp [ MD5_DIGEST_LENGTH ] ;
convert_to_nagra ( er - > client , er , caid_to ) ;
// update ecmd5 for store ECM in cache
memcpy ( er - > ecmd5 , MD5 ( er - > ecm + 3 , er - > ecmlen - 3 , md5tmp ) , CS_ECMSTORESIZE ) ;
cacheex_update_hash ( er ) ;
er - > btun = 2 ; // marked as auto-betatunnel converted. Also for fixing recursive lock in get_cw
}
static int32_t lb_valid_btun ( ECM_REQUEST * er , uint16_t caidto )
{
STAT_QUERY q ;
READER_STAT * s ;
struct s_reader * rdr ;
get_stat_query ( er , & q ) ;
q . caid = caidto ;
cs_readlock ( __func__ , & readerlist_lock ) ;
for ( rdr = first_active_reader ; rdr ; rdr = rdr - > next )
{
if ( rdr - > lb_stat & & rdr - > client )
{
s = get_stat ( rdr , & q ) ;
if ( s & & s - > rc = = E_FOUND )
{
cs_readunlock ( __func__ , & readerlist_lock ) ;
return 1 ;
}
}
}
cs_readunlock ( __func__ , & readerlist_lock ) ;
return 0 ;
}
static uint16_t __lb_get_betatunnel_caid_to ( uint16_t caid )
{
int32_t lbbm = cfg . lb_auto_betatunnel_mode ;
if ( lbbm < = 3 )
{
if ( caid = = 0x1801 ) { return 0x1722 ; }
if ( caid = = 0x1833 ) { return 0x1702 ; }
if ( caid = = 0x1834 ) { return 0x1722 ; }
if ( caid = = 0x1835 ) { return 0x1722 ; }
}
if ( lbbm > = 1 )
{
if ( caid = = 0x1702 ) { return 0x1833 ; }
}
if ( lbbm = = 1 | | lbbm = = 4 )
{
if ( caid = = 0x1722 ) { return 0x1801 ; }
}
else if ( lbbm = = 2 | | lbbm = = 5 )
{
if ( caid = = 0x1722 ) { return 0x1834 ; }
}
else if ( lbbm = = 3 | | lbbm = = 6 )
{
if ( caid = = 0x1722 ) { return 0x1835 ; }
}
return 0 ;
}
uint16_t lb_get_betatunnel_caid_to ( ECM_REQUEST * er )
{
if ( ! cfg . lb_auto_betatunnel )
return 0 ;
uint16_t caidto = __lb_get_betatunnel_caid_to ( er - > caid ) ;
if ( lb_valid_btun ( er , caidto ) )
return caidto ;
return 0 ;
}
void check_lb_auto_betatunnel_mode ( ECM_REQUEST * er )
{
int32_t lbbm = cfg . lb_auto_betatunnel_mode ;
if ( lbbm = = 1 | | lbbm = = 4 )
{
er - > caid = 0x1801 ;
}
else if ( lbbm = = 2 | | lbbm = = 5 )
{
er - > caid = 0x1834 ;
}
else if ( lbbm = = 3 | | lbbm = = 6 )
{
er - > caid = 0x1835 ;
}
// no other way to autodetect 1801, 1834 or 1835
}
uint16_t get_rdr_caid ( struct s_reader * rdr )
{
2026-02-23 16:40:08 +00:00
if ( is_network_reader ( rdr ) )
2026-02-16 09:02:48 +00:00
{
return 0 ; // reader caid is not real caid
}
else
{
return rdr - > caid ;
}
}
static void reset_ecmcount_reader ( READER_STAT * s , struct s_reader * rdr )
{
cs_readlock ( __func__ , & rdr - > lb_stat_lock ) ;
if ( rdr - > lb_stat & & rdr - > client )
{
if ( s )
{
s - > ecm_count = 0 ;
}
}
cs_readunlock ( __func__ , & rdr - > lb_stat_lock ) ;
}
static void reset_avgtime_reader ( READER_STAT * s , struct s_reader * rdr )
{
cs_readlock ( __func__ , & rdr - > lb_stat_lock ) ;
if ( rdr - > lb_stat & & rdr - > client )
{
if ( ! s ) { return ; }
int32_t i ;
for ( i = 0 ; i < LB_MAX_STAT_TIME ; i + + )
{
if ( s - > time_stat [ i ] > 0 ) { s - > time_stat [ i ] = 0 ; }
}
s - > time_avg = UNDEF_AVG_TIME ;
}
cs_readunlock ( __func__ , & rdr - > lb_stat_lock ) ;
}
/* force_reopen=1 -> force opening of block readers
* force_reopen = 0 - > no force opening of block readers , use reopen_seconds
*/
static void try_open_blocked_readers ( ECM_REQUEST * er , STAT_QUERY * q , int32_t * max_reopen , int32_t force_reopen )
{
struct s_ecm_answer * ea ;
READER_STAT * s ;
struct s_reader * rdr ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
if ( ( ea - > status & READER_FALLBACK ) | | ( ea - > status & READER_ACTIVE ) ) { continue ; }
rdr = ea - > reader ;
s = get_stat ( rdr , q ) ;
if ( ! s ) { continue ; }
if ( ! cfg . lb_reopen_invalid & & s - > rc = = E_INVALID ) {
cs_log_dbg ( D_LB , " loadbalancer: reader %s blocked because INVALID sent! It will be blocked until stats cleaned! " , rdr - > label ) ;
continue ;
}
// if force_reopen we must active the "valid" reader
if ( s - > rc ! = E_FOUND & & force_reopen & & cfg . lb_force_reopen_always )
{
cs_log_dbg ( D_LB , " loadbalancer: force opening reader %s and reset fail_factor! --> ACTIVE " , rdr - > label ) ;
ea - > status | = READER_ACTIVE ;
s - > fail_factor = 0 ;
continue ;
}
//active readers reach get_reopen_seconds(s)
struct timeb now ;
cs_ftime ( & now ) ;
int64_t gone = comp_timeb ( & now , & s - > last_received ) ;
int32_t reopenseconds = get_reopen_seconds ( s ) ;
if ( s - > rc ! = E_FOUND & & gone > reopenseconds * 1000 )
{
if ( * max_reopen )
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s reaches %d seconds for reopening (fail_factor %d) --> ACTIVE " , rdr - > label , reopenseconds , s - > fail_factor ) ;
ea - > status | = READER_ACTIVE ;
( * max_reopen ) - - ;
}
else
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s reaches %d seconds for reopening (fail_factor %d), but max_reopen reached! " , rdr - > label , reopenseconds , s - > fail_factor ) ;
}
continue ;
}
if ( s - > rc ! = E_FOUND ) // for debug output
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s blocked for %d seconds (fail_factor %d), retrying in %d seconds " , rdr - > label , get_reopen_seconds ( s ) , s - > fail_factor , ( uint ) ( reopenseconds - ( gone / 1000 ) ) ) ;
continue ;
}
if ( s - > rc = = E_FOUND ) // for debug output
{ cs_log_dbg ( D_LB , " loadbalancer: reader %s \" e_found \" but not selected for lbvalue check " , rdr - > label ) ; }
}
}
/**
* Gets best reader for caid / prid / srvid / ecmlen .
* Best reader is evaluated by lowest avg time but only if ecm_count > cfg . lb_min_ecmcount ( 5 )
* Also the reader is asked if he is " available "
* returns ridx when found or - 1 when not found
*/
void stat_get_best_reader ( ECM_REQUEST * er )
{
if ( ! cfg . lb_mode | | cfg . lb_mode > 3 )
{ return ; }
if ( ! er - > reader_avail )
{ return ; }
struct s_reader * rdr ;
struct s_ecm_answer * ea ;
//preferred card forwarding (CCcam client):
if ( cccam_forward_origin_card ( er ) )
{ return ; }
STAT_QUERY q ;
get_stat_query ( er , & q ) ;
// auto-betatunnel: The trick is: "let the loadbalancer decide"!
if ( cfg . lb_auto_betatunnel & & caid_is_nagra ( er - > caid ) & & er - > ecmlen ) // nagra
{
uint16_t caid_to = __lb_get_betatunnel_caid_to ( er - > caid ) ;
if ( caid_to )
{
int8_t needs_stats_nagra = 1 , needs_stats_beta = 1 ;
// Clone query parameters for beta:
STAT_QUERY qbeta = q ;
qbeta . caid = caid_to ;
qbeta . prid = 0 ;
qbeta . ecmlen = er - > ecm [ 2 ] + 3 + 10 ;
int32_t time_nagra = 0 ;
int32_t time_beta = 0 ;
int32_t weight ;
int32_t ntime ;
READER_STAT * stat_nagra = NULL ;
READER_STAT * stat_beta = NULL ;
// What is faster? nagra or beta?
int8_t isn ;
int8_t isb ;
int8_t overall_valid = 0 ;
int8_t overall_nvalid = 0 ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
isn = 0 ;
isb = 0 ;
rdr = ea - > reader ;
weight = rdr - > lb_weight ;
if ( weight < = 0 ) { weight = 1 ; }
// Check if betatunnel is allowed on this reader:
int8_t valid = chk_ctab ( caid_to , & rdr - > ctab ) //Check caid
& & chk_rfilter2 ( caid_to , 0 , rdr ) //Ident
& & chk_srvid_by_caid_prov_rdr ( rdr , caid_to , 0 ) //Services
& & ( ! get_rdr_caid ( rdr ) | | chk_caid_rdr ( rdr , caid_to ) ) ; //rdr-caid
if ( valid )
{
stat_beta = get_stat ( rdr , & qbeta ) ;
overall_valid = 1 ;
}
//else
//stat_beta = NULL;
// Check if nagra is allowed on this reader:
int8_t nvalid = chk_ctab ( er - > caid , & rdr - > ctab ) //Check caid
& & chk_rfilter2 ( er - > caid , 0 , rdr ) //Ident
& & chk_srvid_by_caid_prov_rdr ( rdr , er - > caid , 0 ) //Services
& & ( ! get_rdr_caid ( rdr ) | | chk_caid_rdr ( rdr , er - > caid ) ) ; //rdr-caid
if ( nvalid )
{
stat_nagra = get_stat ( rdr , & q ) ;
overall_nvalid = 1 ;
}
// calculate nagra data:
if ( stat_nagra & & stat_nagra - > rc = = E_FOUND )
{
ntime = stat_nagra - > time_avg * 100 / weight ;
if ( ! time_nagra | | ntime < time_nagra )
{ time_nagra = ntime ; }
}
// calculate beta data:
if ( stat_beta & & stat_beta - > rc = = E_FOUND )
{
ntime = stat_beta - > time_avg * 100 / weight ;
if ( ! time_beta | | ntime < time_beta )
{ time_beta = ntime ; }
}
// Uncomplete reader evaluation, we need more stats!
if ( stat_nagra )
{
needs_stats_nagra = 0 ;
isn = 1 ;
}
if ( stat_beta )
{
needs_stats_beta = 0 ;
isb = 1 ;
}
cs_log_dbg ( D_LB , " loadbalancer-betatunnel valid %d, stat_nagra %d, stat_beta %d, (%04X,%04X) " , valid , isn , isb , get_rdr_caid ( rdr ) , caid_to ) ;
}
if ( ! overall_valid ) // we have no valid betatunnel reader also we don't needs stats (converted)
{ needs_stats_beta = 0 ; }
if ( ! overall_nvalid ) // we have no valid reader also we don't needs stats (unconverted)
{ needs_stats_nagra = 0 ; }
if ( cfg . lb_auto_betatunnel_prefer_beta & & time_beta )
{
time_beta = time_beta * cfg . lb_auto_betatunnel_prefer_beta / 100 ;
if ( time_beta < = 0 )
{ time_beta = 1 ; }
}
if ( needs_stats_nagra | | needs_stats_beta )
{
cs_log_dbg ( D_LB , " loadbalancer-betatunnel %04X:%04X (%d/%d) needs more statistics... " , er - > caid , caid_to ,
needs_stats_nagra , needs_stats_beta ) ;
if ( needs_stats_beta ) // try beta first
{
convert_to_beta_int ( er , caid_to ) ;
get_stat_query ( er , & q ) ;
}
}
else if ( time_beta & & ( ! time_nagra | | time_beta < = time_nagra ) )
{
cs_log_dbg ( D_LB , " loadbalancer-betatunnel %04X:%04X selected beta: n%d ms > b%d ms " , er - > caid , caid_to , time_nagra , time_beta ) ;
convert_to_beta_int ( er , caid_to ) ;
get_stat_query ( er , & q ) ;
}
else
{
cs_log_dbg ( D_LB , " loadbalancer-betatunnel %04X:%04X selected nagra: n%d ms < b%d ms " , er - > caid , caid_to , time_nagra , time_beta ) ;
}
// else nagra is faster or no beta, so continue unmodified
}
}
else
{
if ( cfg . lb_auto_betatunnel & & ( er - > caid = = 0x1702 | | er - > caid = = 0x1722 ) & & er - > ocaid = = 0x0000 & & er - > ecmlen ) // beta
{
uint16_t caid_to = __lb_get_betatunnel_caid_to ( er - > caid ) ;
if ( caid_to )
{
int8_t needs_stats_nagra = 1 , needs_stats_beta = 1 ;
// Clone query parameters for beta:
STAT_QUERY qnagra = q ;
qnagra . caid = caid_to ;
qnagra . prid = 0 ;
qnagra . ecmlen = er - > ecm [ 2 ] - 7 ;
int32_t time_nagra = 0 ;
int32_t time_beta = 0 ;
int32_t weight ;
int32_t avg_time ;
READER_STAT * stat_nagra = NULL ;
READER_STAT * stat_beta = NULL ;
//What is faster? nagra or beta?
int8_t isb ;
int8_t isn ;
int8_t overall_valid = 0 ;
int8_t overall_bvalid = 0 ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
isb = 0 ;
isn = 0 ;
rdr = ea - > reader ;
weight = rdr - > lb_weight ;
if ( weight < = 0 ) { weight = 1 ; }
//Check if reverse betatunnel is allowed on this reader:
int8_t valid = chk_ctab ( caid_to , & rdr - > ctab ) //, rdr->typ) //Check caid
& & chk_rfilter2 ( caid_to , 0 , rdr ) //Ident
& & chk_srvid_by_caid_prov_rdr ( rdr , caid_to , 0 ) //Services
& & ( ! get_rdr_caid ( rdr ) | | chk_caid_rdr ( rdr , caid_to ) ) ; //rdr-caid
if ( valid )
{
stat_nagra = get_stat ( rdr , & qnagra ) ;
overall_valid = 1 ;
}
//else
//stat_nagra = NULL;
// Check if beta is allowed on this reader:
int8_t bvalid = chk_ctab ( er - > caid , & rdr - > ctab ) //, rdr->typ) //Check caid
& & chk_rfilter2 ( er - > caid , 0 , rdr ) //Ident
& & chk_srvid_by_caid_prov_rdr ( rdr , er - > caid , 0 ) //Services
& & ( ! get_rdr_caid ( rdr ) | | chk_caid_rdr ( rdr , er - > caid ) ) ; //rdr-caid
if ( bvalid )
{
stat_beta = get_stat ( rdr , & q ) ;
overall_bvalid = 1 ;
}
// calculate nagra data:
if ( stat_nagra & & stat_nagra - > rc = = E_FOUND )
{
avg_time = stat_nagra - > time_avg * 100 / weight ;
if ( ! time_nagra | | avg_time < time_nagra )
{ time_nagra = avg_time ; }
}
// calculate beta data:
if ( stat_beta & & stat_beta - > rc = = E_FOUND )
{
avg_time = stat_beta - > time_avg * 100 / weight ;
if ( ! time_beta | | avg_time < time_beta )
{ time_beta = avg_time ; }
}
// Uncomplete reader evaluation, we need more stats!
if ( stat_beta )
{
needs_stats_beta = 0 ;
isb = 1 ;
}
if ( stat_nagra )
{
needs_stats_nagra = 0 ;
isn = 1 ;
}
cs_log_dbg ( D_LB , " loadbalancer-betatunnel valid %d, stat_beta %d, stat_nagra %d, (%04X,%04X) " , valid , isb , isn , get_rdr_caid ( rdr ) , caid_to ) ;
}
if ( ! overall_valid ) // we have no valid reverse betatunnel reader also we don't needs stats (converted)
{ needs_stats_nagra = 0 ; }
if ( ! overall_bvalid ) // we have no valid reader also we don't needs stats (unconverted)
{ needs_stats_beta = 0 ; }
if ( cfg . lb_auto_betatunnel_prefer_beta & & time_beta )
{
time_beta = time_beta * cfg . lb_auto_betatunnel_prefer_beta / 100 ;
if ( time_beta < 0 )
{ time_beta = 0 ; }
}
// if we needs stats, we send 2 ecm requests: 18xx and 17xx:
if ( needs_stats_nagra | | needs_stats_beta )
{
cs_log_dbg ( D_LB , " loadbalancer-betatunnel %04X:%04X (%d/%d) needs more statistics... " , er - > caid , caid_to ,
needs_stats_beta , needs_stats_nagra ) ;
if ( needs_stats_nagra ) // try nagra frist
{
convert_to_nagra_int ( er , caid_to ) ;
get_stat_query ( er , & q ) ;
}
}
else if ( time_nagra & & ( ! time_beta | | time_nagra < = time_beta ) )
{
cs_log_dbg ( D_LB , " loadbalancer-betatunnel %04X:%04X selected nagra: b%d ms > n%d ms " , er - > caid , caid_to , time_beta , time_nagra ) ;
convert_to_nagra_int ( er , caid_to ) ;
get_stat_query ( er , & q ) ;
}
else
{
cs_log_dbg ( D_LB , " loadbalancer-betatunnel %04X:%04X selected beta: b%d ms < n%d ms " , er - > caid , caid_to , time_beta , time_nagra ) ;
}
}
}
}
if ( cfg . lb_auto_betatunnel & & chk_is_betatunnel_caid ( er - > caid ) )
{
// check again is caid valied to reader
// with both caid on local readers or with proxy
// (both caid will setup to reader for make tunnel caid in share (ccc) visible)
// make sure dosn't send a beta ecm to nagra reader (or reverse)
struct s_ecm_answer * prv = NULL ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
rdr = ea - > reader ;
2026-02-23 16:40:08 +00:00
if ( is_network_reader ( rdr ) ) // reader caid is not real caid
2026-02-16 09:02:48 +00:00
{
prv = ea ;
continue ; // proxy can convert or reject
}
cs_log_dbg ( D_LB , " check again caid %04X on reader %s " , er - > caid , rdr - > label ) ;
if ( ! get_rdr_caid ( ea - > reader ) | | chk_caid_rdr ( ea - > reader , er - > caid ) )
{
prv = ea ;
}
else
{
if ( ! chk_is_fixed_fallback ( rdr , er ) ) { er - > reader_avail - - ; }
cs_log_dbg ( D_LB , " caid %04X not found in caidlist, reader %s removed from request reader list " , er - > caid , rdr - > label ) ;
if ( prv )
{
prv - > next = ea - > next ;
}
else
{ er - > matching_rdr = ea - > next ; }
}
}
if ( ! er - > reader_avail )
{ return ; }
}
struct timeb check_time ;
cs_ftime ( & check_time ) ;
int64_t current = - 1 ;
READER_STAT * s = NULL ;
int32_t retrylimit = get_retrylimit ( er ) ;
int32_t nlocal_readers = 0 ;
int32_t nbest_readers = get_nbest_readers ( er ) ; // Number of NON fallback readers ecm requests go (minimum 1)
int32_t nfb_readers = get_nfb_readers ( er ) ; // Number of fallback readers ecm requests go (minimum 1)
int32_t nreaders = cfg . lb_max_readers ; // lb_max_readers is limit lb uses while learning
if ( ! nreaders ) // if is configured zero -> replace it by -1 (default means unlimited!)
{ nreaders = - 1 ; }
else if ( nreaders < = nbest_readers )
{ nreaders = nbest_readers + 1 ; } // nreaders must cover nbest more 1 reader for try to unblock/add stats
int32_t reader_active = 0 ;
int32_t max_reopen = nreaders - nbest_readers ; // if nreaders=-1, we try to reopen all readers
# ifdef WITH_DEBUG
if ( cs_dblevel & D_LB )
{
//loadbalancer debug output:
int32_t nr = 0 ;
char buf [ 512 ] ;
int n , l = 512 ;
char * rptr = buf ;
* rptr = 0 ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
nr + + ;
if ( nr > 5 ) { continue ; }
if ( ! ( ea - > status & READER_FALLBACK ) )
{ n = snprintf ( rptr , l , " %s%s%s " , ea - > reader - > label , ( ea - > status & READER_CACHEEX ) ? " * " : " " , ( ea - > status & READER_LOCAL ) ? " L " : " " ) ; }
else
{ n = snprintf ( rptr , l , " [%s%s%s] " , ea - > reader - > label , ( ea - > status & READER_CACHEEX ) ? " * " : " " , ( ea - > status & READER_LOCAL ) ? " L " : " " ) ; }
rptr + = n ;
l - = n ;
}
if ( nr > 5 )
{ snprintf ( rptr , l , " ...(%d more) " , nr - 5 ) ; }
char ecmbuf [ ECM_FMT_LEN ] ;
format_ecm ( er , ecmbuf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: client %s for %s: n=%d valid readers: %s " ,
username ( er - > client ) , ecmbuf , nr , buf ) ;
}
# endif
//Deactive all matching readers and set ea->value = 0;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
ea - > status & = ~ ( READER_ACTIVE | READER_FALLBACK ) ;
ea - > value = 0 ;
}
cs_log_dbg ( D_LB , " loadbalancer: -------------------------------------------- " ) ;
if ( max_reopen < 1 ) { cs_log_dbg ( D_LB , " loadbalancer: mode %d, nbest %d, nfb %d, max_reopen ALL, retrylimit %d ms " , cfg . lb_mode , nbest_readers , nfb_readers , retrylimit ) ; }
else { cs_log_dbg ( D_LB , " loadbalancer: mode %d, nbest %d, nfb %d, max_reopen %d, retrylimit %d ms " , cfg . lb_mode , nbest_readers , nfb_readers , max_reopen , retrylimit ) ; }
// Here evaluate lbvalue for readers with valid statistics
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
rdr = ea - > reader ;
s = get_stat ( rdr , & q ) ;
int32_t weight = rdr - > lb_weight < = 0 ? 100 : rdr - > lb_weight ;
//struct s_client *cl = rdr->client;
if ( s & & s - > rc = = E_FOUND
& & s - > ecm_count > = cfg . lb_min_ecmcount
& & ( s - > ecm_count < = cfg . lb_max_ecmcount | | ( retrylimit & & s - > time_avg < = retrylimit ) ) )
{
// Reader can decode this service (rc==0) and has lb_min_ecmcount ecms:
if ( er - > preferlocalcards & & ( ea - > status & READER_LOCAL ) )
{ nlocal_readers + + ; } // Prefer local readers!
switch ( cfg . lb_mode )
{
case LB_FASTEST_READER_FIRST :
current = s - > time_avg * 100 / weight ;
break ;
case LB_OLDEST_READER_FIRST :
if ( ! rdr - > lb_last . time )
{ rdr - > lb_last = check_time ; }
//current is negative here!
current = comp_timeb ( & rdr - > lb_last , & check_time ) ;
current = current * weight / 100 ;
if ( ! current ) { current = - 1 ; }
//handle retrylimit
if ( retrylimit )
{
if ( s - > time_avg > retrylimit ) // set lowest value for reader with time-avg>retrylimit
{
current = s - > time_avg ; // in this way, it will choose best time-avg reader among the worst ones
}
else
{
current = current - 1 ; // so when all have same current, it prioritizes the one with s->time_avg<=retrylimit! This avoid a loop!
}
}
break ;
case LB_LOWEST_USAGELEVEL :
current = rdr - > lb_usagelevel * 100 / weight ;
//handle retrylimit
if ( retrylimit )
{
if ( s - > time_avg > retrylimit )
{ current = 1000 ; } //set lowest value for reader with time-avg>retrylimit
else
{ current = current - 1 ; } //so when all reaches retrylimit (all have lb_value=1000) or all have same current, it prioritizes the one with s->time_avg<=retrylimit! This avoid a loop!
}
break ;
}
if ( cfg . lb_mode ! = LB_OLDEST_READER_FIRST ) // Adjust selection to reader load:
{
/*if(rdr->ph.c_available && !rdr->ph.c_available(rdr, AVAIL_CHECK_LOADBALANCE, er))
{
current = current * 2 ;
}
if ( cl & & cl - > pending )
current = current * cl - > pending ;
*/
if ( current < 1 )
{ current = 1 ; }
}
cs_log_dbg ( D_LB , " loadbalancer: reader %s lbvalue = %d (time-avg %d) " , rdr - > label , ( int ) llabs ( current ) , s - > time_avg ) ;
# if defined(WEBIF) || defined(LCDSUPPORT)
rdr - > lbvalue = llabs ( current ) ;
# endif
ea - > value = current ;
ea - > time = s - > time_avg ;
}
}
// check for local readers
if ( nlocal_readers > nbest_readers ) // if we have local readers, we prefer them!
{
nlocal_readers = nbest_readers ;
nbest_readers = 0 ;
}
else
{
nbest_readers = nbest_readers - nlocal_readers ;
}
struct s_reader * best_rdr = NULL ;
struct s_reader * best_rdri = NULL ;
int32_t best_time = 0 ;
// Here choose nbest readers. We evaluate only readers with valid stats (they have ea->value>0, calculated above)
while ( 1 )
{
struct s_ecm_answer * best = NULL ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
if ( nlocal_readers & & ! ( ea - > status & READER_LOCAL ) )
{ continue ; }
if ( ea - > value & & ( ! best | | ea - > value < best - > value ) )
{ best = ea ; }
}
if ( ! best )
{ break ; }
best_rdri = best - > reader ;
if ( ! best_rdr )
{
best_rdr = best_rdri ;
best_time = best - > time ;
}
if ( nlocal_readers ) // primary readers, local
{
nlocal_readers - - ;
reader_active + + ;
best - > status | = READER_ACTIVE ;
best - > value = 0 ;
cs_log_dbg ( D_LB , " loadbalancer: reader %s --> ACTIVE " , best_rdri - > label ) ;
}
else if ( nbest_readers ) // primary readers, other
{
nbest_readers - - ;
reader_active + + ;
best - > status | = READER_ACTIVE ;
best - > value = 0 ;
cs_log_dbg ( D_LB , " loadbalancer: reader %s --> ACTIVE " , best_rdri - > label ) ;
}
else
{ break ; }
}
/* Here choose nfb_readers
* Select fallbacks reader until nfb_readers reached using this priority :
* 1. forced ( lb_force_fallback = 1 ) fixed fallback
* 2. " normal " fixed fallback
* 3. best ea - > value remaining reader ;
*/
//check for fixed fallbacks
int32_t n_fixed_fb = chk_has_fixed_fallback ( er ) ;
if ( n_fixed_fb )
{
//check before for lb_force_fallback=1 readers
for ( ea = er - > matching_rdr ; ea & & nfb_readers ; ea = ea - > next )
{
rdr = ea - > reader ;
if ( chk_is_fixed_fallback ( rdr , er ) & & rdr - > lb_force_fallback & & ! ( ea - > status & READER_ACTIVE ) ) {
nfb_readers - - ;
ea - > status | = ( READER_ACTIVE | READER_FALLBACK ) ;
cs_log_dbg ( D_LB , " loadbalancer: reader %s --> FALLBACK (FIXED with force) " , rdr - > label ) ;
}
}
//check for "normal" fixed fallback with valid stats
for ( ea = er - > matching_rdr ; ea & & nfb_readers ; ea = ea - > next )
{
rdr = ea - > reader ;
if ( chk_is_fixed_fallback ( rdr , er ) & & ! rdr - > lb_force_fallback & & ! ( ea - > status & READER_ACTIVE ) ) {
s = get_stat ( rdr , & q ) ;
if ( s & & s - > rc = = E_FOUND
& & s - > ecm_count > = cfg . lb_min_ecmcount
& & ( s - > ecm_count < = cfg . lb_max_ecmcount | | ( retrylimit & & s - > time_avg < = retrylimit ) ) )
{
nfb_readers - - ;
ea - > status | = ( READER_ACTIVE | READER_FALLBACK ) ;
cs_log_dbg ( D_LB , " loadbalancer: reader %s --> FALLBACK (FIXED) " , rdr - > label ) ;
}
}
}
}
//check for remaining best ea->value readers as fallbacks
while ( nfb_readers )
{
struct s_ecm_answer * best = NULL ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
if ( ( ea - > status & READER_ACTIVE ) )
{ continue ; }
if ( ea - > value & & ( ! best | | ea - > value < best - > value ) )
{ best = ea ; }
}
if ( ! best )
{ break ; }
nfb_readers - - ;
best - > status | = ( READER_ACTIVE | READER_FALLBACK ) ;
best - > value = 0 ;
cs_log_dbg ( D_LB , " loadbalancer: reader %s --> FALLBACK " , best - > reader - > label ) ;
}
// end fallback readers
// ACTIVE readers with no stats, or with no lb_min_ecmcount, or lb_max_ecmcount reached --> NO use max_reopen for these readers, always open!
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
rdr = ea - > reader ;
s = get_stat ( rdr , & q ) ;
# ifdef CS_CACHEEX
// if cacheex reader, always active and no stats
if ( rdr - > cacheex . mode = = 1 )
{
ea - > status | = READER_ACTIVE ;
continue ;
}
# endif
// ignore fixed fallback with lb_force_fallback=1: no need stats, always used as fallaback!
if ( chk_is_fixed_fallback ( rdr , er ) & & rdr - > lb_force_fallback )
continue ;
// active readers with no stats
if ( ! s )
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s need starting statistics --> ACTIVE " , rdr - > label ) ;
ea - > status | = READER_ACTIVE ;
reader_active + + ;
continue ;
}
// active readers with no lb_min_ecmcount reached
if ( s - > rc = = E_FOUND & & s - > ecm_count < cfg . lb_min_ecmcount )
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s needs to reach lb_min_ecmcount(%d), now %d --> ACTIVE " , rdr - > label , cfg . lb_min_ecmcount , s - > ecm_count ) ;
ea - > status | = READER_ACTIVE ;
reader_active + + ;
continue ;
}
// reset stats and active readers reach cfg.lb_max_ecmcount and time_avg > retrylimit.
if ( s - > rc = = E_FOUND & & s - > ecm_count > cfg . lb_max_ecmcount & & ( ! retrylimit | | s - > time_avg > retrylimit ) )
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s reaches max ecms (%d), resetting statistics --> ACTIVE " , rdr - > label , cfg . lb_max_ecmcount ) ;
reset_ecmcount_reader ( s , rdr ) ; // ecm_count=0
reset_avgtime_reader ( s , rdr ) ; // time_avg=0
ea - > status | = READER_ACTIVE ;
reader_active + + ;
continue ;
}
struct timeb now ;
cs_ftime ( & now ) ;
int64_t gone = comp_timeb ( & now , & s - > last_received ) ;
// reset avg-time and active reader with s->last_received older than 5 min and avg-time>retrylimit
if ( retrylimit & & s - > rc = = E_FOUND & & ( gone > = 300 * 1000 ) & & s - > time_avg > retrylimit )
{
cs_log_dbg ( D_LB , " loadbalancer: reader %s has time-avg>retrylimit and last received older than 5 minutes, resetting avg-time --> ACTIVE " , rdr - > label ) ;
reset_avgtime_reader ( s , rdr ) ; // time_avg=0
ea - > status & = ~ ( READER_ACTIVE | READER_FALLBACK ) ; // It could be activated as fallback above because has lb_vlaue>0, so remove fallback state!
ea - > status | = READER_ACTIVE ;
reader_active + + ;
continue ;
}
}
int32_t force_reopen = 0 ;
//no reader active --> force to reopen matching readers
if ( reader_active = = 0 )
{
cs_log_dbg ( D_LB , " loadbalancer: NO VALID MATCHING READER FOUND! " ) ;
force_reopen = 1 ;
}
else if ( reader_active = = 1 & & cfg . double_check & & is_double_check_caid ( er , & cfg . double_check_caid ) )
{
cs_log_dbg ( D_LB , " loadbalancer: ONLY 1 READER MATCHES AND DOUBLECHECK IS ENABLED! " ) ;
force_reopen = 1 ;
}
else if ( retrylimit )
{
/*
* check for lbretrylimit !
*
* if best_time > retrylimit we need to reset avg times of all computed above matching readers , so we can re - evaluated lbvalue !
* More , we force open blocked reader !
*/
int32_t retrylimit_reached = best_time & & best_time > retrylimit ;
if ( retrylimit_reached )
{
cs_log_dbg ( D_LB , " loadbalancer: best reader %s (avg_time %d ms) reaches RETRYLIMIT (%d ms), resetting avg times and ACTIVE all (valid and blocked) matching readers! " , best_rdr - > label , best_time , retrylimit ) ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
rdr = ea - > reader ;
# ifdef CS_CACHEEX
if ( rdr - > cacheex . mode = = 1 ) { continue ; }
# endif
s = get_stat ( rdr , & q ) ;
//reset avg time and ACTIVE all valid lbvalue readers
if ( s & & s - > rc = = E_FOUND
& & s - > ecm_count > = cfg . lb_min_ecmcount
& & ( s - > ecm_count < = cfg . lb_max_ecmcount | | s - > time_avg < = retrylimit ) )
{
if ( ( ea - > status & READER_FALLBACK ) ) { cs_log_dbg ( D_LB , " loadbalancer: reader %s selected as FALLBACK --> ACTIVE " , rdr - > label ) ; }
else if ( ! ( ea - > status & READER_ACTIVE ) ) { cs_log_dbg ( D_LB , " loadbalancer: reader %s --> ACTIVE " , rdr - > label ) ; }
ea - > status & = ~ ( READER_ACTIVE | READER_FALLBACK ) ; //remove active and fallback
ea - > status | = READER_ACTIVE ; //add active
reset_avgtime_reader ( s , rdr ) ;
}
//reset avg time all blocked "valid" readers. We active them by force_reopen=1
if ( s & & s - > rc ! = E_FOUND )
{
reset_avgtime_reader ( s , rdr ) ;
}
}
force_reopen = 1 ; //force reopen blocked readers
}
}
//try to reopen max_reopen blocked readers (readers with last ecm not "e_found"); if force_reopen=1, force reopen valid blocked readers!
try_open_blocked_readers ( er , & q , & max_reopen , force_reopen ) ;
cs_log_dbg ( D_LB , " loadbalancer: -------------------------------------------- " ) ;
# ifdef WITH_DEBUG
if ( cs_dblevel & D_LB )
{
//loadbalancer debug output:
int32_t nr = 0 ;
char buf [ 512 ] ;
int32_t l = 512 ;
char * rptr = buf ;
* rptr = 0 ;
int32_t n = 0 ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
if ( ! ( ea - > status & READER_ACTIVE ) )
{ continue ; }
nr + + ;
if ( nr > 5 ) { continue ; }
if ( ! ( ea - > status & READER_FALLBACK ) )
{ n = snprintf ( rptr , l , " %s%s%s " , ea - > reader - > label , ( ea - > status & READER_CACHEEX ) ? " * " : " " , ( ea - > status & READER_LOCAL ) ? " L " : " " ) ; }
else
{ n = snprintf ( rptr , l , " [%s%s%s] " , ea - > reader - > label , ( ea - > status & READER_CACHEEX ) ? " * " : " " , ( ea - > status & READER_LOCAL ) ? " L " : " " ) ; }
rptr + = n ;
l - = n ;
}
if ( nr > 5 )
{ snprintf ( rptr , l , " ...(%d more) " , nr - 5 ) ; }
if ( cs_dblevel & D_LB )
{
char ecmbuf [ ECM_FMT_LEN ] ;
format_ecm ( er , ecmbuf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_LB , " loadbalancer: client %s for %s: n=%d selected readers: %s " ,
username ( er - > client ) , ecmbuf , nr , buf ) ;
}
}
# endif
return ;
}
/**
* clears statistic of reader ridx .
* */
void clear_reader_stat ( struct s_reader * rdr )
{
if ( ! rdr - > lb_stat )
{ return ; }
ll_clear_data ( rdr - > lb_stat ) ;
}
void clear_all_stat ( void )
{
struct s_reader * rdr ;
LL_ITER itr = ll_iter_create ( configured_readers ) ;
while ( ( rdr = ll_iter_next ( & itr ) ) )
{
clear_reader_stat ( rdr ) ;
}
}
static void housekeeping_stat_thread ( void )
{
struct timeb now ;
cs_ftime ( & now ) ;
int32_t cleanup_timeout = cfg . lb_stat_cleanup * 60 * 60 * 1000 ;
int32_t cleaned = 0 ;
struct s_reader * rdr ;
set_thread_name ( __func__ ) ;
LL_ITER itr = ll_iter_create ( configured_readers ) ;
cs_readlock ( __func__ , & readerlist_lock ) ; // this avoids cleaning a reading during writing
while ( ( rdr = ll_iter_next ( & itr ) ) )
{
if ( rdr - > lb_stat )
{
rdr - > lb_stat_busy = 1 ;
cs_writelock ( __func__ , & rdr - > lb_stat_lock ) ;
LL_ITER it = ll_iter_create ( rdr - > lb_stat ) ;
READER_STAT * s ;
while ( ( s = ll_iter_next ( & it ) ) )
{
int64_t gone = comp_timeb ( & now , & s - > last_received ) ;
if ( gone > cleanup_timeout )
{
ll_iter_remove_data ( & it ) ;
cleaned + + ;
}
}
cs_writeunlock ( __func__ , & rdr - > lb_stat_lock ) ;
rdr - > lb_stat_busy = 0 ;
}
}
cs_readunlock ( __func__ , & readerlist_lock ) ;
cs_log_dbg ( D_LB , " loadbalancer cleanup: removed %d entries " , cleaned ) ;
}
static void housekeeping_stat ( int32_t force )
{
struct timeb now ;
cs_ftime ( & now ) ;
int64_t gone = comp_timeb ( & now , & last_housekeeping ) ;
if ( ! force & & ( gone < 60 * 60 * 1000 ) ) // only clean once in an hour
{ return ; }
last_housekeeping = now ;
start_thread ( " housekeeping lb stats " , ( void * ) & housekeeping_stat_thread , NULL , NULL , 1 , 1 ) ;
}
static int compare_stat ( READER_STAT * * ps1 , READER_STAT * * ps2 )
{
READER_STAT * s1 = ( * ps1 ) , * s2 = ( * ps2 ) ;
int64_t res = s1 - > rc - s2 - > rc ;
if ( res ) { return res ; }
res = s1 - > caid - s2 - > caid ;
if ( res ) { return res ; }
res = s1 - > prid - s2 - > prid ;
if ( res ) { return res ; }
res = s1 - > srvid - s2 - > srvid ;
if ( res ) { return res ; }
res = s1 - > chid - s2 - > chid ;
if ( res ) { return res ; }
res = s1 - > ecmlen - s2 - > ecmlen ;
if ( res ) { return res ; }
res = comp_timeb ( & s1 - > last_received , & s2 - > last_received ) ;
return res ;
}
static int compare_stat_r ( READER_STAT * * ps1 , READER_STAT * * ps2 )
{
return - compare_stat ( ps1 , ps2 ) ;
}
READER_STAT * * get_sorted_stat_copy ( struct s_reader * rdr , int32_t reverse , int32_t * size )
{
if ( reverse )
{ return ( READER_STAT * * ) ll_sort ( rdr - > lb_stat , compare_stat_r , size ) ; }
else
{ return ( READER_STAT * * ) ll_sort ( rdr - > lb_stat , compare_stat , size ) ; }
}
static int8_t stat_in_ecmlen ( struct s_reader * rdr , READER_STAT * s )
{
int32_t i ;
for ( i = 0 ; i < rdr - > ecm_whitelist . ewnum ; i + + )
{
ECM_WHITELIST_DATA * d = & rdr - > ecm_whitelist . ewdata [ i ] ;
if ( ( d - > caid = = 0 | | d - > caid = = s - > caid ) & & ( d - > ident = = 0 | | d - > ident = = s - > prid ) & & ( d - > len = = s - > ecmlen ) )
return 1 ;
}
return 0 ;
}
static int8_t add_to_ecmlen ( struct s_reader * rdr , READER_STAT * s )
{
int32_t i ;
for ( i = 0 ; i < rdr - > ecm_whitelist . ewnum ; i + + )
{
ECM_WHITELIST_DATA * d = & rdr - > ecm_whitelist . ewdata [ i ] ;
if ( ( d - > caid = = s - > caid ) & & ( d - > ident = = s - > prid ) & & ( d - > len = = s - > ecmlen ) )
return 1 ;
}
ECM_WHITELIST_DATA d = { . caid = s - > caid , . ident = s - > prid , . len = s - > ecmlen } ;
ecm_whitelist_add ( & rdr - > ecm_whitelist , & d ) ;
return 0 ;
}
void update_ecmlen_from_stat ( struct s_reader * rdr )
{
if ( ! rdr | | ! rdr - > lb_stat )
{ return ; }
cs_readlock ( __func__ , & rdr - > lb_stat_lock ) ;
LL_ITER it = ll_iter_create ( rdr - > lb_stat ) ;
READER_STAT * s ;
while ( ( s = ll_iter_next ( & it ) ) )
{
if ( s - > rc = = E_FOUND )
{
if ( ! stat_in_ecmlen ( rdr , s ) )
{ add_to_ecmlen ( rdr , s ) ; }
}
}
cs_readunlock ( __func__ , & rdr - > lb_stat_lock ) ;
}
/**
* mark as last reader after checked for cache requests :
* */
void lb_mark_last_reader ( ECM_REQUEST * er )
{
//OLDEST_READER: set lb_last
struct s_ecm_answer * ea ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
if ( ( ea - > status & ( READER_ACTIVE | READER_FALLBACK ) ) = = READER_ACTIVE )
{ cs_ftime ( & ea - > reader - > lb_last ) ; }
}
}
/**
* Automatic timeout feature depending on statistik values
* */
static uint32_t __lb_auto_timeout ( ECM_REQUEST * er , uint32_t ctimeout )
{
STAT_QUERY q ;
READER_STAT * s = NULL ;
struct s_reader * rdr = NULL ;
struct s_ecm_answer * ea ;
for ( ea = er - > matching_rdr ; ea ; ea = ea - > next )
{
if ( ( ea - > status & ( READER_ACTIVE | READER_FALLBACK ) ) = = READER_ACTIVE )
{
rdr = ea - > reader ;
get_stat_query ( er , & q ) ;
s = get_stat ( rdr , & q ) ;
if ( s ) { break ; }
}
}
if ( ! s ) { return ctimeout ; }
uint32_t t ;
if ( s - > rc = = E_TIMEOUT )
{ t = ctimeout / 2 ; } //timeout known, early timeout!
else
{
if ( s - > ecm_count < cfg . lb_min_ecmcount ) { return ctimeout ; }
t = s - > time_avg * ( 100 + cfg . lb_auto_timeout_p ) / 100 ;
if ( ( int32_t ) ( t - s - > time_avg ) < cfg . lb_auto_timeout_t ) { t = s - > time_avg + cfg . lb_auto_timeout_t ; }
}
if ( t > ctimeout ) { t = ctimeout ; }
# ifdef WITH_DEBUG
if ( D_TRACE & cs_dblevel )
{
char buf [ ECM_FMT_LEN ] ;
format_ecm ( er , buf , ECM_FMT_LEN ) ;
cs_log_dbg ( D_TRACE , " auto-timeout for %s %s set rdr %s to %d " , username ( er - > client ) , buf , rdr - > label , t ) ;
}
# endif
return t ;
}
uint32_t lb_auto_timeout ( ECM_REQUEST * er , uint32_t timeout )
{
if ( cfg . lb_auto_timeout )
return __lb_auto_timeout ( er , timeout ) ;
return timeout ;
}
bool lb_check_auto_betatunnel ( ECM_REQUEST * er , struct s_reader * rdr )
{
if ( ! cfg . lb_auto_betatunnel )
return 0 ;
bool match = 0 ;
uint16_t caid = __lb_get_betatunnel_caid_to ( er - > caid ) ;
if ( caid )
{
uint16_t save_caid = er - > caid ;
er - > caid = caid ;
match = matching_reader ( er , rdr ) ; // matching
er - > caid = save_caid ;
}
return match ;
}
/**
* search for same ecm hash with same readers
* */
static struct ecm_request_t * check_same_ecm ( ECM_REQUEST * er )
{
struct ecm_request_t * ecm ;
time_t timeout ;
struct s_ecm_answer * ea_ecm = NULL , * ea_er = NULL ;
uint8_t rdrs = 0 ;
cs_readlock ( __func__ , & ecmcache_lock ) ;
for ( ecm = ecmcwcache ; ecm ; ecm = ecm - > next )
{
timeout = time ( NULL ) - ( ( cfg . ctimeout + 500 ) / 1000 ) ;
if ( ecm - > tps . time < = timeout )
{ break ; }
if ( ecm = = er ) { continue ; }
if ( er - > caid ! = ecm - > caid | | memcmp ( ecm - > ecmd5 , er - > ecmd5 , CS_ECMSTORESIZE ) )
{ continue ; }
if ( ! er - > readers | | ! ecm - > readers | | er - > readers ! = ecm - > readers )
{ continue ; }
ea_ecm = ecm - > matching_rdr ;
ea_er = er - > matching_rdr ;
rdrs = er - > readers ;
while ( rdrs & & ea_ecm & & ea_er )
{
if ( ea_ecm - > reader ! = ea_er - > reader )
{ break ; }
ea_ecm = ea_ecm - > next ;
ea_er = ea_er - > next ;
rdrs - - ;
}
if ( ! rdrs )
{
cs_readunlock ( __func__ , & ecmcache_lock ) ;
return ecm ;
}
}
cs_readunlock ( __func__ , & ecmcache_lock ) ;
return NULL ; // nothing found so return null
}
static void use_same_readers ( ECM_REQUEST * er_new , ECM_REQUEST * er_cache )
{
struct s_ecm_answer * ea_new = er_new - > matching_rdr ;
struct s_ecm_answer * ea_cache = er_cache - > matching_rdr ;
uint8_t rdrs = er_new - > readers ;
while ( rdrs )
{
ea_new - > status & = ~ ( READER_ACTIVE | READER_FALLBACK ) ;
if ( ( ea_cache - > status & READER_ACTIVE ) )
{
if ( ! ( ea_cache - > status & READER_FALLBACK ) )
{
ea_new - > status | = READER_ACTIVE ;
}
else
{
ea_new - > status | = ( READER_ACTIVE | READER_FALLBACK ) ;
}
}
ea_new = ea_new - > next ;
ea_cache = ea_cache - > next ;
rdrs - - ;
}
}
void lb_set_best_reader ( ECM_REQUEST * er )
{
if ( ! cfg . lb_mode )
return ;
// cache2 is handled by readers queue, so, if a same ecm hash with same readers, use these same readers to get cache2 from them! Not ask other readers!
struct ecm_request_t * ecm_eq = NULL ;
ecm_eq = check_same_ecm ( er ) ;
if ( ecm_eq )
{
// set all readers used by ecm_eq, so we get cache2 from them!
use_same_readers ( er , ecm_eq ) ;
cs_log_dbg ( D_LB , " {client %s, caid %04X, prid %06X, srvid %04X} [get_cw] found same ecm with same readers from client %s, use them! " , ( check_client ( er - > client ) ? er - > client - > account - > usr : " - " ) , er - > caid , er - > prid , er - > srvid , ( check_client ( ecm_eq - > client ) ? ecm_eq - > client - > account - > usr : " - " ) ) ;
}
else
{
// FILTER readers by loadbalancing
stat_get_best_reader ( er ) ;
}
}
void lb_update_last ( struct s_ecm_answer * ea_er , struct s_reader * reader )
{
// for lb oldest reader mode - not use for fallback readers
if ( ! ( ea_er - > status & READER_FALLBACK ) )
cs_ftime ( & reader - > lb_last ) ;
}
2026-02-23 16:40:08 +00:00
void send_reader_stat ( struct s_reader * rdr , ECM_REQUEST * er , struct s_ecm_answer * ea , int8_t rc )
2026-02-16 09:02:48 +00:00
{
if ( rc > = E_99 | | cacheex_reader ( rdr ) )
{ return ; }
2026-02-23 16:40:08 +00:00
int32_t ecm_time = cfg . ctimeout ;
if ( ea - > ecm_time & & ea - > rc < = E_NOTFOUND )
{ ecm_time = ea - > ecm_time ; }
2026-02-16 09:02:48 +00:00
add_stat ( rdr , er , ecm_time , rc , ea - > rcEx ) ;
}
void stat_finish ( void )
{
if ( cfg . lb_mode & & cfg . lb_save )
{
save_stat_to_file ( 0 ) ;
if ( cfg . lb_savepath )
{ cs_log ( " stats saved to file %s " , cfg . lb_savepath ) ; }
cfg . lb_save = 0 ; //this is for avoiding duplicate saves
}
}
# endif