#include "TrafficControlThread.h" #include "Logger.h" #include "ProcessConfig.h" #include "RcdbInfo.h" #include "Database.h" #include "ReportLog.h" #include #include #include #include #include // 네트워크 통계 모듈의 sqlite SQLITE_BUSY 잦은 발생으로 INSERT 지연 발생 (#21440) // 이로 인해 Traffic 통계 합산이 정상적으로 처리되지 않을 수 있으므로 다음과 같이 조정 처리 // - Timestamp delay 15 sec -> 16 sec 로 변경 처리 ( 기존 3.4 rc_statd 는 15 sec 였으나.. 신규 fimngd 적용에 따라 조정 처리) // - sqlite lock timeout 값을 3 sec -> 1 sec 로 변경 처리 #define TIMESTAMP_DELAY_SEC 16 // 5분단위 timestamp 변경시... 변경 후.. 어느만큼 delay 를 허용할지 시간값. #define SQLITE_DB_LOCK_TIMEOUT 1000 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1 sec) #define TRAFFIC_CONTROL_LOW_LIMIT 25 // 트래픽 제한 관련 전송률의 최소값(%), 해당값 이하로는 떨어지지 않도록 처리. #define REPORT_TRAFFIC_CONTROL_LIMIT 50 // 트래픽 제한 관련 전송률이 해당 값(%) 이하로 떨어질 경우.. report 로그에 기록 처리한다. // 생성자.. CTrafficControlThread::CTrafficControlThread() { // Thread Handle 초기화 m_threadHandle = 0; } // 소멸자.. CTrafficControlThread::~CTrafficControlThread() { // Thread 가 동작 중인 경우 Thread 동작 정지 처리. if( m_threadHandle != 0 ) pthread_cancel(m_threadHandle); } // Thread 를 생성하여 작업을 시작한다... bool CTrafficControlThread::Start() { int nResult = pthread_create( &m_threadHandle, NULL, CTrafficControlThread::threadFunc, this ); if( nResult != 0 ) { // Thread 생성 실패시... int errorNum = errno; LOG( LERR, "TrafficControlThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) ); return false; } return true; } // Thread 함수... void * CTrafficControlThread::threadFunc( void * arg ) { CTrafficControlThread * pObject = reinterpret_cast(arg); pthread_detach( pthread_self() ); // 초기 기동시...통계 정보 및 timestamp 처리 관련 문제로 인해.... // 지정된 시간 sleep 했다가.. 작업을 시작한다. sleep( TIMESTAMP_DELAY_SEC ); // 본 Thread 에서는 // RCDB t_sms_sp_svc_product_band 테이블에 저장된 서비스별 트래픽 제어 정보를 조회하여.. // Local DB 에 저장된 서비스별 Network 통계 정보를 바탕으로... 현재 5분간 트래픽을 어느 수준으로 제어할 것인지 판단하여... // RCDB t_sms_sp_svc_product_band 테이블에 UPDATE 처리를 주기적으로 수행한다. // 실제 본 thread function 에서 loop 를 동작하는 것이 아니라.... // 객체 execute 함수 내에서 loop 로 동작하도록 한다. pthread_testcancel(); pObject->Execute(); pthread_testcancel(); // Thread 종료시 Handle 초기화 pObject->m_threadHandle = 0; return NULL; } // RCDB t_sms_sp_svc_product_band 테이블에 저장된 서비스별 트래픽 제어 정보를 조회하여.. // Local DB 에 저장된 Network 통계 정보와 계산을 통해.. 향후 5분간 서비스 트래픽을 어느수준으로 제어할 것인지 계산... // 해당 결과를 RCDB t_sms_sp_svc_product_band 테이블에 업데이트 처리한다. void CTrafficControlThread::Execute() { // 변수 선언 및 초기화.. time_t currentTime; // 현재 시간 정보를 저장하기 위한 변수.. unsigned long nowTimestamp = 0; // 현재 시간 기준으로 계산된 timestamp; unsigned long delayTimestamp = 0; // 지정된 시간동안 delay 를 통해 보정 처리된 timestamp; // RCDB 접속 정보 저장 관련 객체 생성 및 초기화... CRcdbInfo rcdbInfo; rcdbInfo.Load(); // Local DB 관련 변수 초기화. sqlite3 * db = NULL; sqlite3_stmt * stmt = NULL; int db_result; int nRetryCount; const char * select_sql = "SELECT sum(down_traffic)*8/300 FROM network_stat WHERE timestamp = ? AND user_seq = ? AND svc_seq = ?"; // RCDB 조회 결과를 저장하기 위한 임시 변수. struct traffic_control_info stTraffic; int nResult; // Logging _LOG( LINF, "TrafficControlThread: start..."); // 주기적으로 Network 통계 정보를 바탕으로 RCDB 트래픽 제어 정보를 업데이트 한다. while( 1 ) { // FHS 는 장비의 시간을 기준으로.. timestamp 값이 변경 후 20 sec 후에... 트래픽 제한 정보를 refresh 처리한다. // 기존 rc_statd(3.4 이전) 는 대략 timestamp 변경 후 12 sec 후에 RCDB 에 트래픽 제한 정보를 업데이트 처리한다. // 따라서.. 본 loop 에서는 timestamp 값 변경 후 약 TIMESTAMP_DELAY_SEC sec 후에 RCDB 에 트래픽 제한 정보를 업데이트토록 처리한다. // 1. 현재 시간 정보 추출.. currentTime = time(0); // 2. 현재 시간 정보를 기준으로 timestamp 값을 계산한다... // 계산시.. FHS 장비들이 시간 동기화 오차로... 통계 정보가 늦게 올라와 저장될 수 있으므로... // 위에서 언급한대로 TIMESTAMP_DELAY_SEC sec 보정 처리하여 5분 단위 timestamp 값을 추출한다. nowTimestamp = (currentTime + 299)/300; delayTimestamp = (currentTime - TIMESTAMP_DELAY_SEC + 299)/300; // 3. 현재 timestamp 와 보정 처리된 delayTimestamp 값이 동일한 경우.... if( nowTimestamp == delayTimestamp ) { // 1 sec 대기 후 timestamp 변경이 되었는지 다시 계산하도록 처리... sleep(1); continue; } else { // nowTimestamp 와 delayTimestamp 는 5분단위 정각에 값이 변경되어 서로 값이 달라지므로.... // 지정된 시간 동안 sleep 처리 후... 실제 작업을 수행한다. sleep( TIMESTAMP_DELAY_SEC ); } _LOG( LINF, "TrafficControlThread: Traffic Control update job start. timestamp[%lu]", nowTimestamp ); // 이제 실제 작업을 수행한다. // 1. RCDB 와 연결을 수행한다. // - RCDB 연결은 5분마다 발생하므로.. 세션을 유지하지 않고.. 필요할 경우에만 연결/연결해제 하도록 처리한다. DataBase rcdb; m_vecTraffic.clear(); // RCDB 와 연결을 시도한다. if( rcdb.PgOpenDB( rcdbInfo.m_strRcdbIp, rcdbInfo.m_nRcdbPort, rcdbInfo.m_strRcdbName, rcdbInfo.m_strRcdbAcct, rcdbInfo.m_strRcdbAcctPw) == NULL ) { // 연결 실패시... 5분 후 재시도하도록 한다. LOG( LERR, "TrafficControlThread: RCDB connection failed."); } else { // RCDB 와 연결 성공시.... // t_sms_sp_svc_product_band 테이블을 조회하여 트래픽 제어가 필요한 서비스 관련 정보를 추출, 멤버 변수에 저장한다. char szQuery[1024]; snprintf( szQuery, 1023, "SELECT A.sp_user_seq, A.sp_svc_tran_id, B.volume_bandwidth_limit, B.volume_bandwidth_current, B.volume_bandwidth_control_current " "FROM t_sms_sp_svc_product A, t_sms_sp_svc_product_band B " "WHERE A.sp_user_seq = B.sp_user_seq " " AND A.sp_svc_tran_id = B.sp_svc_tran_id " " AND A.bandwidth_ctrl_yn = 'Y'"); // 조회 Query 수행. rcdb.PgDoExec(szQuery); // 수행 결과 확인 if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 ) { // Query 수행 결과 오류 발생시... LOG( LERR, "TrafficControlThread: RCDB select query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery ); } else { // query 수행이 정상인 경우. nResult = rcdb.GetNoTuples(); // 조회 결과를 멤버변수 vector 에 저장한다. for( int i = 0; i < nResult; i++ ) { stTraffic.user_seq = atoi ( rcdb.GetValue( i, 0 ) ); stTraffic.svc_seq = atoi ( rcdb.GetValue( i, 1 ) ); stTraffic.limit = atoll( rcdb.GetValue( i, 2 ) ); stTraffic.previous_traffic = atoll( rcdb.GetValue( i, 3 ) ); stTraffic.previous_control_precent = atoi ( rcdb.GetValue( i, 4 ) ); // 나머지 변수도 초기화 처리 stTraffic.current_traffic = 0; // Local db 조회시 정보가 없을 수 있으므로 0 으로 설정. stTraffic.current_control_precent = 10000; // Local db 조회시 정보가 없을 수 있으므로... 100 % 의미의 값을 default 로 설정. // vector 에 push 처리 m_vecTraffic.push_back( stTraffic ); } } // RCDB 조회 결과 set 를 clear 처리한다. rcdb.PgClear(); // 트래픽 제어 설정된 서비스가 존재하는 경우.. if( m_vecTraffic.empty() == false ) { // Local DB 에서 해당 서비스 및 timestamp 에 맞는 트래픽 정보를 추출한다. std::vector::iterator it; // Local DB 와 연결을 수행한다. db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db); if( db_result != SQLITE_OK ) { LOG( LERR, "TrafficControlThread: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg(db)); sqlite3_close(db); db = NULL; } else { // DB File 이 open 된 경우... nRetryCount = 0; // bind 관련 Query 를 수행할 stmt 객체 생성. // - 만약 sqlite DB File 이 사전에 생성되어 있지 않은 경우... 위의 open 함수에서 0 파일을 생성되어 정상적으로 open 처리되지만.. // sqlite3_prepare 함수에서 table 이 생성되어 있지 않으므로 오류가 발생함. // 따라서 loop 중에 재시도하도록 한다. db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL ); // sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다. while( db_result == SQLITE_BUSY && nRetryCount < 30 ) { // sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정. sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT ); ++nRetryCount; LOG( LWAR, "TrafficControlThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", nRetryCount ); db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL ); } if( db_result != SQLITE_OK ) { LOG( LERR, "TrafficControlThread: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db)); sqlite3_close(db); db = NULL; stmt = NULL; } } // sqlite 접속이 정상적으로 된 경우... if( stmt != NULL ) { // vector 에 저장된 서비스 정보에 맞는 트래픽 정보를 추출한다. for( it = m_vecTraffic.begin(); it != m_vecTraffic.end(); ++it ) { /* _LOG( LDBG, "TrafficControlThread: RCDB result. svc[%d][%d] traffic[%llu/%llu] control[%u]" , it->user_seq, it->svc_seq, it->previous_traffic, it->limit, it->previous_control_precent ); */ sqlite3_bind_int64( stmt, 1, nowTimestamp-1 ); sqlite3_bind_int ( stmt, 2, it->user_seq ); sqlite3_bind_int ( stmt, 3, it->svc_seq ); // Query 수행 db_result = sqlite3_step(stmt); nRetryCount = 0; // 만약 BUSY 상태로 인해 오류가 발생하면..20번 재시도 while( db_result == SQLITE_BUSY && nRetryCount < 15 ) { sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT ); ++nRetryCount; LOG( LWAR, "TrafficControlThread: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount ); // query 재시도 db_result = sqlite3_step(stmt); } // select 조회 결과가 존재하는 경우... SQLITE_ROW (100 ) 을 반환... // select 조회 결과가 없는 경우.. SQLITE_DONE (101) 을 반환. // select query 수행시 오류가 발생한 경우... if( db_result != SQLITE_ROW && db_result != SQLITE_DONE ) { LOG( LERR, "TrafficControlThread: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db)); // 조회 loop 를 벗어나.. 마무리 작업을 한 후 다음 5분 후에 다시 시도하도록 한다. break; } else { // select 조회 결과가 존재하는 경우... 1개 Row 만 존재하므로... if( db_result == SQLITE_ROW ) { it->current_traffic = sqlite3_column_int64(stmt, 0); } } // Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다. sqlite3_reset(stmt); } // Local DB 와의 연결을 종료 처리한다. sqlite3_finalize(stmt); sqlite3_close(db); db = NULL; stmt = NULL; } // vector 에 수집된 정보를 바탕으로 // 계산을 통해 현재 5분간 트래픽을 어느 수준을 제어할지 결정한다. for( it = m_vecTraffic.begin(); it != m_vecTraffic.end(); ++it ) { // 기본 조건 // 1. 제한값이 0 인 경우.. 전송률은 0.01% (1) 로 설정한다. // 2. 트래픽 전송율은 서비스 품질을 위해 최대 25% 수준까지만 제한한다. // - 일반적으로 50% 이하로 제한되면...트래픽 증설을 권고할 수 있도록... report 로그에 기록 처리. // 3. 트래픽 전송율은 감소시킬 경우... Slow 하게... 원복시킬 경우에는 Fast 하게 처리될 수 있도록 한다. // 제한값이 0인 경우.. -> 운영자가 트래픽을 0으로 제한해 버린 경우... if( it->limit == 0 ) { it->current_control_precent = 1; // 0.01 % 로 설정 처리. } else { // limit 값이 0 을 초과하는 경우... // 현재 트래픽이 0 인 경우.. 전송율은 default 100% 로 설정처리. // - 해당 사항은 실제 traffic 전송한 것이 없거나.... // - local db 조회 결과가 존재하지 않는 경우에 발생한다. if( it->current_traffic == 0 ) { it->current_control_precent = 10000; } else { // 현재 트래픽이 존재하는 경우.... if( it->current_traffic > it->limit ) { // 현재 트래픽이... 제한 트래픽을 초과한 경우... // - 초과된 비율을 계산한 후.. // - 기존 비율에서 초과된 비율만큼 감소시키되.. 최대 20% 로 제한. unsigned int nOverPercent = (it->current_traffic - it->limit ) * 10000 / it->limit ; if( nOverPercent > 2000 ) it->current_control_precent = it->previous_control_precent - 2000; else it->current_control_precent = it->previous_control_precent - nOverPercent; } else if( it->current_traffic < it->limit ) { // 현재 트래픽이 제한 트래픽을 초과하지 않은 경우... // - 현재 트래픽이 제한 트래픽의 90% 초과 수준인 경우에는 현재 제한율 유지... // - 현재 트래픽이 제한 트래픽의 90% 이하인 경우...10 % 씩 증설 처리 ( 최대 100 % ) unsigned int nUsedPercent = it->current_traffic * 10000 / it->limit; if( nUsedPercent > 9000 ) it->current_control_precent = it->previous_control_precent; else it->current_control_precent = it->previous_control_precent + 1000; } else { // 제한 트래픽과 현재 트래픽이 동일한 경우... // 기존 전송율을 유지. it->current_control_precent = it->previous_control_precent; } } // 마지막으로 전송율 최소값 25%, 최대값 100% 이상을 초과하는 경우.. 보정 처리한다. if( it->current_control_precent < (TRAFFIC_CONTROL_LOW_LIMIT * 100) ) { it->current_control_precent = (TRAFFIC_CONTROL_LOW_LIMIT * 100); } else if( it->current_control_precent > 10000 ) { it->current_control_precent = 10000; } } // 전송율이 50% 미만으로 떨어질 경우.... report log 에 기록하여.. 운영자가 확인할 수 있도록 한다. if( it->limit > 0 && it->current_control_precent < (REPORT_TRAFFIC_CONTROL_LIMIT * 100) ) { _LOG( LWAR, "TrafficControlThread: service traffic control value set below %d%%. svc[%d][%d] limit[%llu] traffic[%llu->%llu] control[%5.2f->%5.2f%%]" , REPORT_TRAFFIC_CONTROL_LIMIT, it->user_seq, it->svc_seq, it->limit , it->previous_traffic, it->current_traffic, it->previous_control_precent/100.0, it->current_control_precent/100.0 ); // report 로그에 해당 내역 기록 처리... CReportLog reportLog; std::string strErrorMessage; strErrorMessage.clear(); if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false ) { reportLog.GetLastError( strErrorMessage ); LOG( LERR, "TrafficControlThread: report log init failed.[%s]", strErrorMessage.c_str() ); } else { reportLog.Write( "WAR", "[%s][service traffic control value set below %d%%. svc[%d][%d] limit[%llu] traffic[%llu->%llu] control[%5.2f->%5.2f%%]]" , PROG_NAME, REPORT_TRAFFIC_CONTROL_LIMIT , it->user_seq, it->svc_seq, it->limit , it->previous_traffic, it->current_traffic, it->previous_control_precent/100.0, it->current_control_precent/100.0 ); } } } // vector 에 저장된 최종 트래픽 제어 정보를 RCDB 에 UPDATE 처리한다. for( it = m_vecTraffic.begin(); it != m_vecTraffic.end(); ++it ) { snprintf( szQuery, 1023, "UPDATE t_sms_sp_svc_product_band " "SET " " volume_bandwidth_current = '%llu' " " , volume_bandwidth_before = '%llu' " " , volume_bandwidth_control_current = '%u' " " , volume_bandwidth_control_before = '%u' " " , volume_bandwidth_update_datetime = now() " "WHERE sp_user_seq = %d AND sp_svc_tran_id = %d " , it->current_traffic, it->previous_traffic, it->current_control_precent, it->previous_control_precent , it->user_seq, it->svc_seq ); // update query 수행. rcdb.PgDoExec(szQuery); // update 실패 발생시... if( rcdb.GetCmdTuples() <= 0 ) { LOG( LERR, "TrafficControlThread: RCDB update query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery); } else { _LOG( LINF, "TrafficControlThread: RCDB update OK..svc[%d][%d] limit[%llu] traffic[%llu->%llu] control[%5.2f->%5.2f%%]" , it->user_seq, it->svc_seq, it->limit , it->previous_traffic, it->current_traffic, it->previous_control_precent/100.0, it->current_control_precent/100.0 ); } // Update 처리 정상 수행시... RCDB 결과 Set 을 Clear 처리한다. rcdb.PgClear(); } }// RCDB 조회 결과가 존재하는 경우... // RCDB 관련 종료 처리 작업 수행. rcdb.PgCloseDB(); } // 작업 완료 후.. 불필요하게 loop 를 동작시킬 필요가 없고.. timestamp 체크 관련 루프 계산 문제로 인해서... // 5분 단위로 동작해야 하므로... 약 3분 정도 sleep 한다. sleep(180); } // 종료 처리 코드 return; }