This commit is contained in:
biosvos
2026-08-07 17:38:18 +09:00
commit 873193a243
9613 changed files with 2755992 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
/***************************************************************************
Database Class
-----------------------------------------
begin : 2010/03/09
copyright : (C) 2010 SolutionBox Inc.
author : Service 1 Team
email : svc1@solbox.com
version : 1.0
CopyRight(C) 2010 SolutionBox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of SolutionBox Inc.
***************************************************************************/
#include <string.h>
#include <stdlib.h>
#include <sstream>
#include "Database.h"
#include "Logger.h"
DataBase::~DataBase()
{
if(m_PGconn != NULL) {
PgCloseDB();
}
}
PGconn *DataBase::PgOpenDB(string &strHost, int Port, string &strDBName, string &strAcct, string &strPasswd, int timeout)
{
ostringstream conninfo;
// CHG 2014-10-29 huibong
// application_name add to logging RCDB
conninfo << "host=" << strHost << " port=" << Port << " dbname=" << strDBName <<
" user=" << strAcct << " password=" << strPasswd << " connect_timeout=" << timeout <<
" application_name=" << PROG_NAME;
#ifdef _USE_LIBPQ_KEEPALIVE
// This option is supported by at least 9.1.2. and Currently supports Linux systems.
conninfo <<" keepalives=1";
#endif // _USE_LIBPQ_KEEPALIVE
//m_PGconn = PQsetdbLogin(strHost.c_str(), szPort, NULL, NULL, strDBName.c_str(), strAcct.c_str(), strPasswd.c_str());
m_PGconn = PQconnectdb(conninfo.str().c_str());
if(PQstatus(m_PGconn) == CONNECTION_BAD) {
return NULL;
}
return m_PGconn;
}
PGconn *DataBase::PgOpenDB(const char *pszDBName)
{
m_PGconn = PQsetdb(NULL, NULL, NULL, NULL, pszDBName);
if(PQstatus(m_PGconn) == CONNECTION_BAD) {
return NULL;
}
return m_PGconn;
}
void DataBase::PgCloseDB()
{
if(m_PGconn != NULL)
{
PQfinish(m_PGconn);
m_PGconn = NULL;
}
}
int DataBase::PgResult(CFLAG flag)
{
int fRet = 0;
int Result;
Result = PQresultStatus(m_pRes);
switch(Result) {
case PGRES_EMPTY_QUERY :
fRet = -1;
break;
case PGRES_BAD_RESPONSE :
fRet = -2;
break;
case PGRES_NONFATAL_ERROR :
fRet = -3;
break;
case PGRES_FATAL_ERROR :
fRet = -4;
break;
case PGRES_TUPLES_OK :
fRet = 1;
break;
case PGRES_COMMAND_OK :
fRet = 2;
break;
}
if(fRet < 0) {
m_ErrorMessage = PQresultErrorMessage(m_pRes);
}
m_ResultCode = Result;
if(flag == CLEAR && m_pRes) {
PQclear(m_pRes);
m_pRes = NULL;
}
return fRet;
}
string &DataBase::GetErrorMessage()
{
return m_ErrorMessage;
}
int DataBase::GetCmdTuples()
{
//fprintf(stderr, "DataBase::GetCmdTuples PQcmdTuples %s\n", PQcmdTuples(m_pRes));
return (int)atoi(PQcmdTuples(m_pRes));
}
int DataBase::GetNoTuples()
{
return PQntuples(m_pRes);
}
int DataBase::GetNoFields()
{
return PQnfields(m_pRes);
}
PGresult *DataBase::GetRes()
{
return m_pRes;
}
void DataBase::PgClear()
{
if(m_pRes)
{
PQclear(m_pRes);
m_pRes = NULL;
}
}
char *DataBase::GetValue(int tuple, int field)
{
return PQgetvalue(m_pRes, tuple, field);
}
int DataBase::PgDoExec(char *pszQuery)
{
return this->PgDoExec(pszQuery, NOT_CLEAR);
}
int DataBase::PgDoExec(string &strQuery)
{
return this->PgDoExec((char *)strQuery.c_str(), NOT_CLEAR);
}
int DataBase::PgDoExec(char *pszQuery, CFLAG flag)
{
if(m_PGconn == NULL) return -1;
if(PQstatus(m_PGconn) != CONNECTION_OK) return -2;
m_pRes = PQexec(m_PGconn, pszQuery);
int r = PgResult(flag);
return r;
}
int DataBase::PgDoExecParams(char *pszQuery, int nParamCnt, const char * const *paramValues ,CFLAG flag)
{
if(m_PGconn == NULL) return -1;
if(PQstatus(m_PGconn) != CONNECTION_OK) return -2;
m_pRes = PQexecParams(m_PGconn, pszQuery, nParamCnt, NULL, paramValues, NULL, NULL, 0);
int r = PgResult(flag);
return r;
}
int DataBase::PgEscapeString(char *to, const char *from, size_t length)
{
int retval = 0;
//PQescapeStringConn(m_PGconn, to, from, length, &retval);
PQescapeString(to, from, length);
return retval;
}
+56
View File
@@ -0,0 +1,56 @@
/***************************************************************************
Database Class
-----------------------------------------
begin : 2010/03/11
copyright : (C) 2010 SolutionBox Inc.
author : Service 1 Team
email : svc1@solbox.com
version : 1.0
CopyRight(C) 2010 SolutionBox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of SolutionBox Inc.
***************************************************************************/
#ifndef __DATABASE_H__
#define __DATABASE_H__
#include <iostream>
#include <string>
#include "libpq-fe.h"
#define MaxSizeOfDBQuery 1024*9
using namespace std;
class DataBase
{
public:
enum CFLAG { CLEAR = 1, NOT_CLEAR };
DataBase() { m_PGconn = NULL; m_pRes = NULL;}
~DataBase();
PGconn *PgOpenDB(string &strHost, int Port, string &strDBName, string &strAcct, string &strPasswd, int timeout = 10);
PGconn *PgOpenDB(const char *pszDBName);
void PgCloseDB();
int PgResult(CFLAG flag);
PGconn *GetPgConn(){ return m_PGconn;}
PGresult *GetRes();
void SetRes(PGresult * v) {m_pRes = v;}
int GetCmdTuples();
int GetNoTuples();
int GetNoFields();
int GetResultCode() { return m_ResultCode; }
char *GetValue(int tuple, int field);
void PgClear();
int PgDoExec(string &strQuery);
int PgDoExec(char *pszQuery);
int PgDoExec(char *pszQuery, CFLAG flag);
int PgDoExecParams(char *pszQuery, int nParamCnt, const char * const *paramValues ,CFLAG flag = NOT_CLEAR);
int PgEscapeString(char *to, const char *from, size_t length);
string &GetErrorMessage();
private:
int m_ResultCode;
PGconn *m_PGconn;
PGresult *m_pRes;
string m_ErrorMessage;
};
#endif // ~__DATABASE_H__
+489
View File
@@ -0,0 +1,489 @@
#include "GtsSendSocket.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include <string.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <stdio.h>
#define SQLITE_DB_LOCK_TIMEOUT 3000 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 3 sec )
CGtsSendSocket::CGtsSendSocket()
: CBaseSocket( SOCKET_NOT_VALID )
{
// GTS 서버 정보 설정.
m_strGtsServer = CProcessConfig::GetInstance()->GetGtsServer();
m_nGtsPort = CProcessConfig::GetInstance()->GetGtsPortStat();
m_strRcId = CProcessConfig::GetInstance()->GetRcId();
}
CGtsSendSocket::~CGtsSendSocket()
{
m_vecNotSendService.clear();
// 소멸자 Socket 명시적 Close 처리.
Close();
}
// 통계 정보 중 GTS 로 전송하지 않을 서비스 정보를 Local DB 에서 조회하여 저장하기 위한 함수.
bool CGtsSendSocket::InitNotSendService()
{
// Local DB 관련 변수 초기화.
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
// SELECT Query..
const char * select_sql = "SELECT user_seq, svc_seq "
"FROM stat_not_send_service "
"ORDER BY user_seq ASC, svc_seq ASC";
// Data 를 저장할 vector 객체 초기화.
m_vecNotSendService.clear();
// Local DB 와 연결을 수행한다.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "GtsSendSocket: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
sqlite3_close(db);
db = NULL;
return false;
}
else
{
// DB File 이 open 된 경우...
nRetryCount = 0;
db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < 20 )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "GtsSendSocket: 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, "GtsSendSocket: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
return false;
}
else
{
// select query 수행.
db_result = sqlite3_step(stmt);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..20번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 20 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "GtsSendSocket: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount );
// query 재시도
db_result = sqlite3_step(stmt);
}
// select query 수행시 오류가 발생한 경우...
if( db_result != SQLITE_ROW && db_result != SQLITE_DONE )
{
LOG( LERR, "GtsSendSocket: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db));
// Local DB 오류 발생시.. DB 에 문제가 있는 것으로 보고.. DB 객체를 초기화한다.
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
return false;
}
else
{
struct gts_not_send_service stService;
// select Query 가 정상적으로 수행된 경우....
while( db_result == SQLITE_ROW )
{
// 임시 저장변수 초기화.
memset( &stService, 0x00, sizeof(struct gts_not_send_service) );
stService.user_seq = sqlite3_column_int (stmt, 0);
stService.svc_seq = sqlite3_column_int (stmt, 1);
// vector 객체에 query 결과를 저장한다.
m_vecNotSendService.push_back( stService );
// Next 결과 처리..
db_result = sqlite3_step( stmt );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..20번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 20 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "GtsSendSocket: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount );
// query 재시도
db_result = sqlite3_step(stmt);
}
}
// Local DB 와의 연결을 종료 처리한다.
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
}
}
return true;
}
// 전달받은 서비스 정보가 GTS 로 통계을 전송하지 않을 서비스 목록에 포함되어 있는지 검사
// true : GTS 로 통계 전송.
// false : GTS 로 통계 전송하지 않을 서비스임.
bool CGtsSendSocket::IsSendStat(const int & user_seq, const int & svc_seq )
{
// 비전송 서비스 목록이 존재하지 않을 경우...
// GTS 로 서비스 정보를 전송하도록 true 반환.
if( m_vecNotSendService.empty() == true )
return true;
std::vector<struct gts_not_send_service>::iterator it;
for( it = m_vecNotSendService.begin(); it != m_vecNotSendService.end(); ++it )
{
if( it->svc_seq == svc_seq && it->user_seq == user_seq )
{
_LOG( LINF, "GtsSendSocket: GTS not send service.[%d][%d]", user_seq, svc_seq );
return false;
}
}
// 비전송 서비스 목록이 존재하지 않을 경우...
// GTS 로 서비스 정보를 전송하도록 true 반환.
return true;
}
bool CGtsSendSocket::SendStatNetwork( std::vector<struct gts_send_stat_network> &vecNetworkStat )
{
// 전달 받은 Vector 를 확인하여... Data 가 없는 경우는 정상 전송한 것으로 처리한다.
if( vecNetworkStat.empty() == true )
return true;
// GTS 로 비전송 서비스 목록을 Local DB 에서 조회하여 생성한다.
InitNotSendService();
// GTS cc_statd 로 서비스 Network 정보 전송을 위한 XML 조립 처리...
// - XML 라이브러리를 사용하여 조립할 경우.. 라이브러리 포함에 따른 초기화, 링킹 등의 복잡한 작업이 필요함.
// - 하지만. 전송 XML 형식은 단순하므로....
// - XML 라이브러리를 사용하지 않고.. 문자열을 이용하여 직접 조립하여 전송하기로 한다.
// 전송 XML Format
// <?xml version="1.0" encoding="UTF-8"?>
// <CCSTAT>
// <SERVICE RC="bd16" USERSEQ="47" SVCSEQ="346">
// <STAT>
// <NETWORK TIME="4614930">
// <UP_TRAFFIC>0</UP_TRAFFIC><DOWN_TRAFFIC>0</DOWN_TRAFFIC>
// <UP_SIZE>0</UP_SIZE><DOWN_SIZE>0</DOWN_SIZE>
// <UP_CONCURRENT_SESS>0</UP_CONCURRENT_SESS><DOWN_CONCURRENT_SESS>1</DOWN_CONCURRENT_SESS>
// </NETWORK>
// </STAT>
// </SERVICE>
// <SERVICE RC="bd16" USERSEQ="40" SVCSEQ="400">
// <STAT>
// <NETWORK TIME="4614938">
// <UP_TRAFFIC>0</UP_TRAFFIC><DOWN_TRAFFIC>0</DOWN_TRAFFIC>
// <UP_SIZE>0</UP_SIZE><DOWN_SIZE>0</DOWN_SIZE>
// <UP_CONCURRENT_SESS>100</UP_CONCURRENT_SESS><DOWN_CONCURRENT_SESS>1</DOWN_CONCURRENT_SESS>
// </NETWORK>
// </STAT>
// </SERVICE>
// </CCSTAT>
std::string sendXml;
char tempBuffer[1024];
unsigned int sendBodyLength;
// 실제 GTS 로 전송할 서비스 통계 갯수 정보.
int nSendServiceCount = 0;
std::vector<struct gts_send_stat_network>::iterator it;
// XML Header 부분 조립.
sendXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CCSTAT>";
// XML Body 부분 조립
for( it = vecNetworkStat.begin(); it != vecNetworkStat.end(); ++it )
{
// GTS 로 비전송 서비스인지 확인한다.
if( IsSendStat ( it->user_seq, it->svc_seq ) == false )
{
// GTS 비전송 서비스인 경우...
// send_flag 를 ignore 로 처리한다.
it->send_flag = 2;
}
else
{
// std::to_string 이 현재 gcc 버전에서 지원하지 않아.. sprintf 함수로 사용함.
// tempStr = "<SERVICE RC=\"" + m_strRcId + "\" USERSEQ=\"" + it->user_seq + "\" SVCSEQ=\"" + it->svc_seq + "\">";
// tempStr += "<STAT><NETWORK TIME=\"" + it->timestamp + "\">";
// tempStr += "<UP_TRAFFIC>" + it->up_traffic + "</UP_TRAFFIC><DOWN_TRAFFIC>" + it->down_traffic + "</DOWN_TRAFFIC>";
// tempStr += "<UP_SIZE>" + it->up_content_size + "</UP_SIZE><DOWN_SIZE>" + it->down_content_size + "</DOWN_SIZE>";
// tempStr += "<UP_CONCURRENT_SESS>" + it->up_max_concurrent_sess + "</UP_CONCURRENT_SESS><DOWN_CONCURRENT_SESS>" + it->down_max_concurrent_sess + "</DOWN_CONCURRENT_SESS>";
sprintf( tempBuffer, "<SERVICE RC=\"%s\" USERSEQ=\"%d\" SVCSEQ=\"%d\">", m_strRcId.c_str(), it->user_seq, it->svc_seq );
sendXml.append( tempBuffer );
sprintf( tempBuffer, "<STAT><NETWORK TIME=\"%u\">", it->timestamp);
sendXml.append( tempBuffer );
sprintf( tempBuffer, "<UP_TRAFFIC>%lu</UP_TRAFFIC><DOWN_TRAFFIC>%lu</DOWN_TRAFFIC>", it->up_traffic, it->down_traffic );
sendXml.append( tempBuffer );
sprintf( tempBuffer, "<UP_SIZE>%lu</UP_SIZE><DOWN_SIZE>%lu</DOWN_SIZE>", it->up_content_size, it->down_content_size );
sendXml.append( tempBuffer );
// 3.5 버전에서는 세션 정보를 수집 처리하지 않음.
// 하지만.. cc_statd 전송시에는 해당 항목 전달해 줘야 하므로.. 0 으로 설정 처리하여 전송함.
sprintf( tempBuffer, "<UP_CONCURRENT_SESS>0</UP_CONCURRENT_SESS><DOWN_CONCURRENT_SESS>0</DOWN_CONCURRENT_SESS>");
sendXml.append( tempBuffer );
sendXml += "</NETWORK></STAT></SERVICE>";
++nSendServiceCount;
}
}
// XML Tail 부분 조립
sendXml += "</CCSTAT>";
// GTS 로 전송할 서비스 정보가 존재하는 경우...
if( nSendServiceCount > 0 )
{
// GTS 장비와 TCP 연결.
if( Connect( m_strGtsServer, m_nGtsPort ) == false )
{
LOG( LERR, "GtsSendSocket: GTS connect fail. GTS[%s][%d]", m_strGtsServer.c_str(), m_nGtsPort );
Close();
return false;
}
// 전송 Size 값을 먼저 전송한 후..... ( 4Byte, Network Byte Order )
sendBodyLength = htonl( sendXml.size() );
if( WriteN( &sendBodyLength, 4) != 4 )
{
LOG( LERR, "GtsSendSocket: XML Network body length[%lu] send fail.", sendXml.size() );
Close();
return false;
}
if( WriteN( sendXml.c_str(), sendXml.size() ) != (ssize_t)(sendXml.size()) )
{
LOG( LERR, "GtsSendSocket: XML Network body send fail. xml size[%lu] xml[%s]", sendXml.size(), sendXml.c_str() );
Close();
return false;
}
//_LOG( LDBG, "GtsSendSocket: GTS send data [%lu][%s]", sendXml.size(), sendXml.c_str());
_LOG( LINF, "GtsSendSocket: Network stats send to GTS OK.. XML Size[%lu]", sendXml.size());
// 정상적으로 전송이 완료되었으며... send_flag 값을 변경한다.
for( it = vecNetworkStat.begin(); it != vecNetworkStat.end(); ++it )
{
if( it->send_flag == 0 )
it->send_flag = 1;
}
// 전송 완료 후 Connection 을 종료 처리한다. (GTS cc_statd 와의 통신 규칙)
Close();
}
else
{
_LOG( LINF, "GtsSendSocket: Network stat to send GTS is not exist.");
}
return true;
}
bool CGtsSendSocket::SendStatStorage( std::vector<struct gts_send_stat_storage> &vecStorageStat )
{
// 전달 받은 Vector 를 확인하여... Data 가 없는 경우는 정상 전송한 것으로 처리한다.
if( vecStorageStat.empty() == true )
return true;
// GTS 로 비전송 서비스 목록을 Local DB 에서 조회하여 생성한다.
InitNotSendService();
// GTS cc_statd 로 서비스 Storage 정보 전송을 위한 XML 조립 처리...
// - XML 라이브러리를 사용하여 조립할 경우.. 라이브러리 포함에 따른 초기화, 링킹 등의 복잡한 작업이 필요함.
// - 하지만. 전송 XML 형식은 단순하므로....
// - XML 라이브러리를 사용하지 않고.. 문자열을 이용하여 직접 조립하여 전송하기로 한다.
// 전송 XML Format
// <?xml version="1.0" encoding="UTF-8"?>
// <CCSTAT>
// <SERVICE RC="bd16" USERSEQ="47" SVCSEQ="346">
// <STAT>
// <STORAGE TIME="4614930">
// <STG_SIZE>85899345920</STG_SIZE>
// <USED_STG_SIZE>0</USED_STG_SIZE>
// </STORAGE>
// </STAT>
// </SERVICE>
// <SERVICE RC="bd16" USERSEQ="40" SVCSEQ="400">
// <STAT>
// <STORAGE TIME="4614938">
// <STG_SIZE>10995116277</STG_SIZE>
// <USED_STG_SIZE>2662913716</USED_STG_SIZE>
// </STORAGE>
// </STAT>
// </SERVICE>
// </CCSTAT>
std::string sendXml;
char tempBuffer[1024];
unsigned int sendBodyLength;
// 실제 GTS 로 전송할 서비스 통계 갯수 정보.
int nSendServiceCount = 0;
std::vector<struct gts_send_stat_storage>::iterator it;
// XML Header 부분 조립.
sendXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><CCSTAT>";
// XML Body 부분 조립
for( it = vecStorageStat.begin(); it != vecStorageStat.end(); ++it )
{
// GTS 로 비전송 서비스인지 확인한다.
if( IsSendStat ( it->user_seq, it->svc_seq ) == false )
{
// GTS 비전송 서비스인 경우...
// send_flag 를 ignore 로 처리한다.
it->send_flag = 2;
}
else
{
// std::to_string 이 현재 gcc 버전에서 지원하지 않아.. sprintf 함수로 사용함.
sprintf( tempBuffer, "<SERVICE RC=\"%s\" USERSEQ=\"%d\" SVCSEQ=\"%d\">", m_strRcId.c_str(), it->user_seq, it->svc_seq );
sendXml.append( tempBuffer );
sprintf( tempBuffer, "<STAT><STORAGE TIME=\"%u\">", it->timestamp);
sendXml.append( tempBuffer );
sprintf( tempBuffer, "<STG_SIZE>%lu</STG_SIZE>", it->quota);
sendXml.append( tempBuffer );
sprintf( tempBuffer, "<USED_STG_SIZE>%lu</USED_STG_SIZE>", it->used );
sendXml.append( tempBuffer );
sendXml += "</STORAGE></STAT></SERVICE>";
++nSendServiceCount;
}
}
// XML Tail 부분 조립
sendXml += "</CCSTAT>";
// GTS 로 전송할 서비스 정보가 존재하는 경우...
if( nSendServiceCount > 0 )
{
// GTS 장비와 TCP 연결.
if( Connect( m_strGtsServer, m_nGtsPort ) == false )
{
LOG( LERR, "GtsSendSocket: GTS connect fail.GTS[%s][%d]", m_strGtsServer.c_str(), m_nGtsPort );
Close();
return false;
}
// 전송 Size 값을 먼저 전송한 후..... ( 4Byte, Network Byte Order )
sendBodyLength = htonl( sendXml.size() );
if( WriteN( &sendBodyLength, 4) != 4 )
{
LOG( LERR, "GtsSendSocket: XML Storage body length[%lu] send fail.", sendXml.size() );
Close();
return false;
}
if( WriteN( sendXml.c_str(), sendXml.size() ) != (ssize_t)(sendXml.size()) )
{
LOG( LERR, "GtsSendSocket: XML Storage body send fail. xml size[%lu], xml[%s]", sendXml.size(), sendXml.c_str() );
Close();
return false;
}
//_LOG( LDBG, "GtsSendSocket: GTS send data [%lu][%s]", sendXml.size(), sendXml.c_str());
_LOG( LINF, "GtsSendSocket: Storage stats send to GTS OK.. XML Size[%lu]", sendXml.size());
// 정상적으로 전송이 완료되었으며... send_flag 값을 변경한다.
for( it = vecStorageStat.begin(); it != vecStorageStat.end(); ++it )
{
if( it->send_flag == 0 )
it->send_flag = 1;
}
// 전송 완료 후 Connection 을 종료 처리한다. (GTS cc_statd 와의 통신 규칙)
Close();
}
else
{
_LOG( LINF, "GtsSendSocket: Storage stat to send GTS is not exist.");
}
return true;
}
+121
View File
@@ -0,0 +1,121 @@
/****************************************************************************
Send Data to GTS cc_statd
-----------------------------------------
begin : 2015/09/01
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __GTS_SEND_SOCKET_H__
#define __GTS_SEND_SOCKET_H__
#include <unistd.h>
#include <stdint.h>
#include <vector>
#include <string>
#include "BaseSocket.h"
// GTS cc_statd 로 각 서비스별 네트워크 트래픽 정보 전송을 위해
// local db 의 조회결과를 저장하기 위한 구조체...
struct gts_send_stat_network {
unsigned int timestamp; // 5분 단위 timestamp 값 저장...
int user_seq; // 고객사 sequence 정보..
int svc_seq; // 서비스 sequence 정보.
uint64_t down_traffic; // 서비스에서 5분간 발생된 download Packet량.. Byte 단위
uint64_t up_traffic; // 서비스에서 5분간 발생된 upload Packet량.. Byte 단위
uint64_t down_content_size; // 서비스에서 5분간 발생된 download content size. Byte 단위
uint64_t up_content_size; // 서비스에서 5분간 발생된 upload content size. Byte 단위
int send_flag; // cc_statd 로의 전송 여부를 판단하여... local db 정보 update 에 사용하기 위한 flag 값..
// 0: not send(default), 1: sent ok, 2: ignore (통계 정보 전송하지 않도록 설정된 경우.)
};
// GTS cc_statd 로 각 서비스별 스토리지 사용량 정보 전송을 위해
// local db 의 조회결과를 저장하기 위한 구조체...
struct gts_send_stat_storage {
unsigned int timestamp; // 5분 단위 timestamp 값 저장...
int user_seq; // 고객사 sequence 정보..
int svc_seq; // 서비스 sequence 정보.
uint64_t quota; // Storage 할당량 (Byte)
uint64_t used; // Storage 사용량 (Byte)
int send_flag; // cc_statd 로의 전송 여부를 판단하여... local db 정보 update 에 사용하기 위한 flag 값..
// 0: not send(default), 1: sent ok, 2: ignore (통계 정보 전송하지 않도록 설정된 경우.)
};
// GTS cc_stat 로 통계 정보를 전송하지 않을 서비스 정보를 저장하기 위한 구조체.
// Local DB stat_not_send_service table 결과를 저장.
struct gts_not_send_service {
int user_seq; // 고객사 sequence 정보..
int svc_seq; // 서비스 sequence 정보.
};
/// @brief CGtsSendSocket
/// 본 클래스는 GMS 와 TCP 연결을 수행한 후...
/// 전달받은 정보를 전송 정보를 XML 규칙에 맞도록 조합한 후... GTS cc_statd 로 전송 처리하는 역활을 수행한다.
/// GTS cc_statd 와의 통신은 1개 Data 전송 후.. 자동으로 세션이 끊어지는 구조임.. (cc_statd 구조에 맞도록 처리 )
class CGtsSendSocket : public CBaseSocket
{
public:
CGtsSendSocket();
~CGtsSendSocket();
// GTS cc_statd 로 서비스 트래픽 관련 통계 정보를 전송한다.
bool SendStatNetwork( std::vector<struct gts_send_stat_network> &vecNetworkStat );
// GTS cc_statd 로 서비스 Storage 사용량 정보를 전송한다.
bool SendStatStorage( std::vector<struct gts_send_stat_storage> &vecStorageStat );
private:
// 통계 정보 중 GTS 로 전송하지 않을 서비스 정보를 Local DB 에서 조회하여 저장하기 위한 함수.
bool InitNotSendService();
// 전달받은 서비스 정보가 GTS 로 통계을 전송하지 않을 서비스 목록에 포함되어 있는지 검사
// true : GTS 로 통계 전송.
// false : GTS 로 통계 전송하지 않을 서비스임.
bool IsSendStat(const int & user_seq, const int & svc_seq );
private:
// GTS Server 정보.
std::string m_strGtsServer;
int m_nGtsPort;
// RC ID 정보
std::string m_strRcId;
// GTS 로 전송하지 않을 서비스 정보를 저장하기 위한 vector 객체.
std::vector<struct gts_not_send_service> m_vecNotSendService;
};
#endif /* __GTS_SEND_SOCKET_H__ */
+187
View File
@@ -0,0 +1,187 @@
#include "LocalDbClearThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sqlite3.h>
#define SQLITE_DB_LOCK_TIMEOUT 3000 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 3 sec )
// 생성자..
CLocalDbClearThread::CLocalDbClearThread()
{
// Thread Handle 초기화
m_threadHandle = 0;
}
// 소멸자..
CLocalDbClearThread::~CLocalDbClearThread()
{
// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
if( m_threadHandle != 0 )
pthread_cancel(m_threadHandle);
}
// Thread 에 정의된 작업을 시작한다...
bool CLocalDbClearThread::Start()
{
int nResult = pthread_create( &m_threadHandle, NULL, CLocalDbClearThread::threadFunc, this );
if( nResult != 0 )
{
// Thread 생성 실패시...
int errorNum = errno;
LOG( LERR, "LocalDbClearThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) );
return false;
}
return true;
}
// Thread 함수...
void * CLocalDbClearThread::threadFunc( void * arg )
{
CLocalDbClearThread * pObject = reinterpret_cast<CLocalDbClearThread *>(arg);
pthread_detach( pthread_self() );
// Local DB 정리 작업은 기동시 1번 수행하고...
sleep( 1 );
pthread_testcancel();
pObject->Execute();
pthread_testcancel();
// Thread 종료시 Handle 초기화
pObject->m_threadHandle = 0;
return NULL;
}
// Local DB 정리 작업은 중요도가 낮은 작업이므로... 1일 1회만 수행되도록 한다.
void CLocalDbClearThread::Execute()
{
// 변수 선언 및 초기화..
// Local DB 관련 변수 초기화.
sqlite3 * db = NULL;
int db_result;
int nRetryCount;
char szQuery[1024];
char * db_err;
// 주기적으로 Local DB 에 대해
// 보관 기간이 초과되었고....
// 전송 처리가 완료된 row 를 삭제 처리한다.
while( 1 )
{
// Local DB 와 연결을 수행한다.
// - Network, Storage 통계 정보 clear 처리
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "LocalDbClearThread: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
}
else
{
// DB File 이 open 된 경우...
// 1. network_stat 테이블을 정리한다.
// - 각 FHS 로 부터 5분단위로 수신된 정보이므로... FHS 수가 많을 수록 보관량이 많다.
// - 따라서 전송되지 않은 정보를 제외하고.. 7 day 지난 row 는 삭제처리한다. (보존기간 7 day, #21025)
_LOG( LINF, "LocalDbClearThread: Local DB network_stat table clear start." );
snprintf( szQuery, 1023,
"DELETE FROM network_stat WHERE send_flag != 0 AND insert_time < date('now', 'localtime', '-7 day') " );
db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..10번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 10 )
{
sqlite3_free( db_err );
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "LocalDbClearThread: local db delete sqlite3_exec SQLITE_BUSY. Retry [%d]", nRetryCount );
// Query 재수행
db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "LocalDbClearThread: Local DB network_stat table clear failed.[%s][%s]", db_err, szQuery );
sqlite3_free( db_err );
}
else
{
_LOG( LINF, "LocalDbClearThread: Local DB network_stat table clear end. [%d] rows deleted.", sqlite3_changes( db ) );
}
// 2. 서비스별 Storage 사용량 정보가 저장된 storage_stat 테이블을 정리한다.
// - 각 서비스별로 5분당 1개 Row 가 생성되므로... network_stat 테이블에 비해 row 수가 많지는 않다.
// - 따라서 전송되지 않은 정보를 제외하고.. 7 day 지난 row 는 삭제처리한다. (보존기간 7 day, #21025)
_LOG( LINF, "LocalDbClearThread: Local DB storage_stat table clear start." );
snprintf( szQuery, 1023,
"DELETE FROM storage_stat WHERE send_flag != 0 AND insert_time < date('now', 'localtime', '-7 day') " );
db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..10번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 10 )
{
sqlite3_free( db_err );
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "LocalDbClearThread: local db delete sqlite3_exec SQLITE_BUSY. Retry [%d]", nRetryCount );
// Query 재수행
db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "LocalDbClearThread: Local DB storage_stat table clear failed.[%s][%s]", db_err, szQuery );
sqlite3_free( db_err );
}
else
{
_LOG( LINF, "LocalDbClearThread: Local DB storage_stat table clear end. [%d] rows deleted.", sqlite3_changes( db ) );
}
// 3. 최종 Local DB 와의 연결을 해제한다.
sqlite3_close( db );
db = NULL;
}
// 본 루프는 1일 1회만 동작하도록 처리한다.
// 24시간 * 60분 * 60 sec = 86400
sleep( 86400 );
}
return;
}
+54
View File
@@ -0,0 +1,54 @@
/****************************************************************************
Local DB (sqlite3) Clear Thread Class
-----------------------------------------
begin : 2015/08/31
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __LOCAL_DB_CLEAR_THREAD_H__
#define __LOCAL_DB_CLEAR_THREAD_H__
#include <pthread.h>
#include <unistd.h>
/// @brief CLocalDbClearThread
/// 본 클래스는 Thread 클래스로서...
/// sqlite3 Local DB 에 저장된 통계 Data 중 보관 기간이 지난 Data 에 대해 삭제 처리를 수행한다.
class CLocalDbClearThread
{
public:
// 생성 및 소멸자.
CLocalDbClearThread();
~CLocalDbClearThread();
// Thread 기동 함수.
bool Start( void );
private:
// Thread function....
static void * threadFunc( void * arg );
// 실제 Local DB 삭제 관련 작업을 담당하는 함수...
void Execute( void );
private:
// 쓰레드 핸들
pthread_t m_threadHandle;
};
#endif /* __LOCAL_DB_CLEAR_THREAD_H__ */
+810
View File
@@ -0,0 +1,810 @@
#include "Main.h"
#include "ProcessRename.h"
#include "ProcessConfig.h"
#include "Logger.h"
#include "WorkerRecvServiceStat.h"
#include "WorkerControl.h"
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <iostream>
#include <fstream>
// TCP listen socket 생성 후 listen 처리시 accept 대기 Count 수
#define DEFAULT_ACCEPT_WAIT_COUNT 50
// sqlite local db 생성을 위한 script 파일 경로..
#define LOCAL_DB_CREATE_SCRIPT "/user/service/etc/rc_sscd.sql"
// FHS fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 위한 TCP Socket
// 시그널 처리 및 Worker 에서 처리를 단순화 하기 위해 전역 변수로 처리.
int g_sockServiceStatRecv = -1;
// Worker 프로세스 중 신규 fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 담당할 Worker PID 정보.
// 시그널 처리에 의해 재생성 처리 단순화를 위해 전역 변수로 처리
pid_t g_pidWorkerRecvServiceStat = -1;
// Worker 프로세스 중 각 서비스별 Storage 사용량 계산, GTS 로 통계 정보 전송 등의 작업을 수행할 Worker PID 정보.
// 시그널 처리에 의해 재생성 처리 단순화를 위해 전역 변수로 처리
pid_t g_pidWorkerControl = -1;
int main( int argc, char * argv[] )
{
// 설정 파일을 저장할 변수.
std::string strConfigFileName = DEFAULT_CONFIG_FILE;
// linux 에서 프로세스를 Titile 변경을 지원을 위해서 argv 메모리에 저장
argv = save_ps_display_args( argc, argv );
// 전달 받은 옵션 여부 확인 및 처리
if( argc >= 2 )
{
int opt;
while( ( opt = getopt( argc, argv, "hvc:" ) ) != -1 ) // 옵션 끝까지 파싱처리
{
switch( opt )
{
case 'h':
case '?': // 정의되지 않은 문자가 나타날 경우 getopt 에 자동반환. 도움말 표시 처리.
PrintUsage();
return EXIT_SUCCESS;
case 'v':
PrintVersion();
return EXIT_SUCCESS;
case 'c':
strConfigFileName = optarg;
fprintf( stderr, PROG_NAME " start with user define conf[%s]\n", strConfigFileName.c_str() );
break;
default: // 본 조건절은 getopt 특성상 동작하지 않지만...업무 Flow 이해(?)를 위해 유지한다.
fprintf( stderr, "Unkonwn options[%c] used. program terminated.\n", opt );
return EXIT_FAILURE;
}
}
}
// 프로세스 중복 실행 여부 검사
if( IsCurrentProcessRun() == true )
{
fprintf( stderr, "[warning] Process [" PROG_NAME "] is already running....\n" );
return EXIT_FAILURE;
}
std::string szErrorMessage;
// conf 로딩 및 검사
if( CProcessConfig::Init( PROG_NAME, strConfigFileName, szErrorMessage ) == false )
{
fprintf( stderr, "[ERR] %s\n\n", szErrorMessage.c_str() );
return EXIT_FAILURE;
}
// Log 객체 생성 및 초기화
if( CLogger::Init( PROG_NAME, CProcessConfig::GetInstance()->GetLogPath(), LINF ) == false )
{
fprintf( stderr, "[ERR] Log module initialize failed.[%s]\n\n", CProcessConfig::GetInstance()->GetLogPath() );
return EXIT_FAILURE;
}
// Daemonize...
if( daemon( 1, 0 ) == -1 ) // nochdir: true(작업 디렉토리 변경 안함), noclose: false (표준 입출력, 에러를 /dev/null 로 리디렉트 처리)
{
fprintf( stderr, "[ERR] Process daemonize failed.[%s]\n\n", strerror( errno ) );
return EXIT_FAILURE;
}
// Local DB 인 Sqlite DB 파일에 대해 생성 처리를 수행한다.
// - 만약 기존에 생성되어 있는 경우.. CREATE TABLE 오류가 발생할 수 있으니 무시할 것.
// - system 함수를 통해 외부 명령 수행.
// - 이로 인해 외부 출력 및 SIGCHLD 가 발생하므로.. signal 처리 설정 전에.. 본 함수를 위치시킨다.
// - 기존 table 존재시.. 오류 메시지가 화면에 표시. 이로 인한 불필요한 오해를 방지하기 위해.. daemon 처리 후 DB 생성 처리 수행
if( CheckLocalDB( CProcessConfig::GetInstance()->GetLocalDbFileName() ) == false )
{
//fprintf( stderr, "[ERR] sqlite db file initialize failed. [%s]\n\n", CProcessConfig::GetInstance()->GetLocalDbFileName() );
LOG( LERR, "sqlite db file[%s] initialize failed. => Process exit.", CProcessConfig::GetInstance()->GetLocalDbFileName() );
return EXIT_FAILURE;
}
// signal 처리 설정
SetSignalMain();
// Process 기동 관련 정보 기록 -> Log
_LOG( LINF, "***********************************************************" );
_LOG( LINF, " %s Start. Version: %s", PROG_NAME, PROG_VERSION );
_LOG( LINF, "***********************************************************" );
_LOG( LINF, "Config : %s", strConfigFileName.c_str() );
_LOG( LINF, "Log : %s/%s", CProcessConfig::GetInstance()->GetLogPath(), PROG_NAME );
_LOG( LINF, "Log level : %d", CProcessConfig::GetInstance()->GetLogLevel() );
_LOG( LINF, "RC ID : %s", CProcessConfig::GetInstance()->GetRcId() );
_LOG( LINF, "RCDB : %s %u %s %s", CProcessConfig::GetInstance()->GetRcdbIp()
, CProcessConfig::GetInstance()->GetRcdbPort()
, CProcessConfig::GetInstance()->GetRcdbName()
, CProcessConfig::GetInstance()->GetRcdbAcct() );
_LOG( LINF, "Open network port" );
_LOG( LINF, " Service stat listen port : TCP %u", CProcessConfig::GetInstance()->GetTcpServiceStatListenPort() );
_LOG( LINF, "GTS : %s:%u (TCP)", CProcessConfig::GetInstance()->GetGtsServer()
, CProcessConfig::GetInstance()->GetGtsPortStat() );
_LOG( LINF, "Local sqlite3 DB file : %s", CProcessConfig::GetInstance()->GetLocalDbFileName() );
_LOG( LINF, "Storage calc mode : %s", ( CProcessConfig::GetInstance()->IsStorageOnlyFullScanUse() == true ? "Only Full Scan" : "Mix Mode" ) );
_LOG( LINF, "***********************************************************" );
// Log Level 재설정. -> conf 설정대로 변경 처리.
#ifdef _DEBUG_
CLogger::GetInstance()->SetLogLevel( LDBG );
#else
CLogger::GetInstance()->SetLogLevel( CProcessConfig::GetInstance()->GetLogLevel() );
#endif
// 통신 관련 Listen 등의 처리가 필요한 Socket 생성 처리.
// worker 모듈에서 처리할 경우... 재기동 발생시.. 해당 port 점유로 인한 재사용 등의 오류가 발생하므로....
// FHS fimngd 가 전송하는 FHS 서비스별 Network 통계 정보 수신을 위한 TCP listen socket 생성
if( MakeServiceStatListenSocket() == false )
{
LOG( LERR, "service stat(network) recv tcp socket create failed. => Process exit." );
ReadyToExit();
return EXIT_FAILURE;
}
// Worker Process 생성
// Storage 통계 수집, GTS 로 통계 전송 등의 작업을 수행할 Control Worker 프로세스 생성.
if( MakeWorkerControl() == false )
{
LOG( LERR, "[Main] Control worker process create failed. => Process exit." );
ReadyToExit();
return EXIT_FAILURE;
}
// Cotrol 프로세스가 생성된 후 Control 의 DB 생성 및 초기화 등의 작업을 위해 약 2 sec 대기한다..
sleep( 2 );
// FHS fimngd 가 전송하는 서비스 network 통계 정보 수신을 담당할 Worker 프로세스 생성.
if( MakeWorkerRecvServiceStat() == false )
{
LOG( LERR, "[Main] RecvServiceStat worker process create failed. => Process exit." );
ReadyToExit();
return EXIT_FAILURE;
}
// Process Rename..
set_ps_display( PROG_NAME": Main", false );
// Main Process : 그냥 대기
while( 1 )
{
// Main Process 는 할 일이 없다.
// => Worker 프로세스 종료시 SIGCHLD 신호로 인해 신호처리기에서 Worker Process 재성성 수행함.
// => 따라서 그냥 시그널 대기
pause();
}
// 다음의 코드는 Daemon 으로 동작하기 때문에 수행되지 않는다.
ReadyToExit();
return EXIT_SUCCESS;
}
// 사용방법 표시
void PrintUsage()
{
fprintf( stderr, "\n" );
fprintf( stderr, "Usage: " PROG_NAME " [-h] [-v] [-c {file}] \n" );
fprintf( stderr, "Options: \n" );
fprintf( stderr, " -h Display help information \n" );
fprintf( stderr, " -v Display version \n" );
fprintf( stderr, " -c {file} Use {file} as config file \n" );
fprintf( stderr, "\n" );
fprintf( stderr, PROG_NAME " is Solbox Cloud Storage module.\n" );
fprintf( stderr, " - collect service network stat from FHS.\n" );
fprintf( stderr, " - calc service storage usage stat from RCDB.\n" );
fprintf( stderr, " - transfer periodically service stat(network, storage) to GTS.\n\n" );
return;
}
// 버전 정보 표시
void PrintVersion()
{
fprintf( stderr, "\n" );
fprintf( stderr, PROG_NAME " version: " PROG_VERSION "\n\n" );
return;
}
/// @brief std 상에 trim 함수가 없어서 직접 구현. 아니면 boost/algorithm/string.hpp 상의 boost::trim 함수 사용
/// @return void
void Trim( std::string & str )
{
if( str.length() == 0 )
return;
// 문자열 뒤의 공백, TAB, CR 등의 문자 제거처리.
std::string::size_type pos = str.find_last_not_of( " \a\b\f\n\r\t\v" );
if( pos != std::string::npos )
str.erase( pos + 1 );
// 문자열 앞의 공백, TAB, CR 등의 문자 제거처리.
pos = str.find_first_not_of( " \a\b\f\n\r\t\v" );
if( pos != std::string::npos )
str.erase( 0, pos );
}
/// @brief 현재 Process가 기동중인지 여부를 판단하기 위한 함수( 프로세스 중복 실행 체크)
/// @return 이미 해당 프로세스가 기동 중인 경우 true 반환, 그렇지 않으면 false 반환.
bool IsCurrentProcessRun()
{
char tempBuffer[512];
FILE * fd = NULL;
bool bRun = false;
snprintf( tempBuffer, sizeof( tempBuffer ), "pgrep -x %s | sort", PROG_NAME );
fd = popen( tempBuffer, "r" );
if( fd == NULL )
{
std::cerr << "[error] Process duplication check failed.[popen error][" << strerror( errno ) << "]" << std::endl;
// 오류 발생시 true 반환하여 프로세스 실행 방지처리
return true;
}
else
{
memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
{
std::string tempPid( tempBuffer );
Trim( tempPid );
if( atoi( tempPid.c_str() ) != getpid() )
{
std::cout << "[info] Process duplication found. pid[" << atoi( tempPid.c_str() ) << "]" << std::endl;
bRun = true;
break;
}
}
pclose( fd );
return bRun;
}
}
// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExit()
{
// open 된 socket 관련 Close 처리
if( g_sockServiceStatRecv != -1 )
close( g_sockServiceStatRecv );
// Logger 객체 종료 처리.
CLogger::Exit();
return;
}
// Worker 프로세스 종료 신호 수신시
static void SignalWorkerDead( int nSignalNumber )
{
pid_t deadPid;
int nDeadStatus;
while( ( deadPid = waitpid( -1, &nDeadStatus, WNOHANG ) ) > 0 )
{
// Worker 프로세스가 signal 에 의해 종료되었는지 검사.
// - 종료 사유 로깅 및
// - Worker 의 초기화 실패로 인한 종료시... Main 프로세스가 종료되도록 처리.
if( WIFSIGNALED( nDeadStatus ) )
{
// signal 에 의해 종료된 경우...
// - 관련 내역 로깅
if( deadPid == g_pidWorkerRecvServiceStat )
{
_LOG( LWAR, "[Main] RecvServiceStat worker process[%d] killed by signal[%d]", deadPid, WTERMSIG( nDeadStatus ) );
}
else if( deadPid == g_pidWorkerControl )
{
_LOG( LWAR, "[Main] Control worker process[%d] killed by signal[%d]", deadPid, WTERMSIG( nDeadStatus ) );
}
else
{
_LOG( LWAR, "[Main] Unknown child process[%d] killed by signal[%d]", deadPid, WTERMSIG( nDeadStatus ) );
}
}
else
{
// signal 에 종료된 상황이 아닌 경우...
// - 각 Worker 의 초기화 실패 발생시... return 구문에 의해 종료됨.
// - 기타 등등.
// 관련 내역 로깅.
if( deadPid == g_pidWorkerRecvServiceStat )
{
_LOG( LWAR, "[Main] RecvServiceStat worker process[%d] killed. Not signal.", deadPid );
}
else if( deadPid == g_pidWorkerControl )
{
_LOG( LWAR, "[Main] Control worker process[%d] killed. Not signal", deadPid );
}
else
{
_LOG( LWAR, "[Main] Unknown child process[%d] killed. Not signal", deadPid );
}
// 만약 Worker 프로세스들이 초기화 실패로 인해 EXIT_FAILURE 반환되는 상황이라면...
// 자식 프로세스를 재생성하지 않고.. 프로세스를 종료 처리한다.
if( WIFEXITED( nDeadStatus ) )
{
if( WEXITSTATUS( nDeadStatus ) == EXIT_FAILURE )
{
if( deadPid == g_pidWorkerRecvServiceStat )
{
_LOG( LWAR, "[Main] RecvServiceStat worker process[%d] initialize failed.", deadPid );
}
else if( deadPid == g_pidWorkerControl )
{
_LOG( LWAR, "[Main] Control worker process[%d] initialize failed.", deadPid );
}
else
{
_LOG( LWAR, "[Main] Unknown child process[%d] exit with fail.", deadPid );
}
_LOG( LERR, "[Main] Program abnormally exited because Worker initialize faild.." );
// Main 종료 시그널 발생
raise( SIGTERM );
continue;
}
}
}
// 위에서 로깅 및 초기화 실패로 인한 Main 종료 처리 작업을 수행
// 그 외의 경우에는 다음과 같이 한다.
// - Control 프로세스가 종료된 경우... 심각한 문제가 발생한 경우일 수 있으므로... 전체 프로세스를 종료 처리한다.
// - 그 외 Work : Worker 를 재생성 처리한다.
if( deadPid == g_pidWorkerControl )
{
_LOG( LERR, "[Main] Program abnormally exited because Worker::Control process dead" );
g_pidWorkerControl = -1;
raise( SIGTERM );
}
else if( deadPid == g_pidWorkerRecvServiceStat )
{
if( MakeWorkerRecvServiceStat() == false )
{
_LOG( LERR, "[Main] RecvServiceStat worker process recreate failed.=> Main process exit." );
raise( SIGTERM );
}
else
{
_LOG( LWAR, "[Main] RecvServiceStat worker process recreate success." );
}
}
}
// 오류 발생시 해당 내역 로깅
if( deadPid < 0 )
{
int errorNum = errno;
LOG( LERR, "[Main] SIG_CHLD receive but waitpid return error[%d][%s]", errorNum, strerror( errorNum ) );
}
return;
}
// Main 프로세스에 대한 종료 처리 수신시
static void SignalMainTerminate( int nSignalNumber )
{
// 자식 프로세스를 종료 처리한다.
if( g_pidWorkerControl != -1 )
{
kill( g_pidWorkerControl, SIGTERM );
}
if( g_pidWorkerRecvServiceStat != -1 )
{
kill( g_pidWorkerRecvServiceStat, SIGTERM );
}
// Signal Number 에 따른 로깅처리.
if( nSignalNumber == SIGTERM )
{
_LOG( LINF, "[Main:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
}
else
{
_LOG( LWAR, "[Main:%d] Process exit by abnormal signal[%d]. Good Bye..", getpid(), nSignalNumber );
}
// 기타 모듈들의 종료 처리 수행
ReadyToExit();
// 종료전 잠시 대기
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = 500000000; // 0.5 sec
nanosleep( &sleep, NULL );
exit( EXIT_SUCCESS );
}
// Main Process 의 signal 처리기...
// fork 된 worker 프로세스 역시 기본적으로 본 signal 처리 action 을 상속받는다.
void SetSignalMain()
{
sigset_t set;
struct sigaction act;
memset( &act, 0x00, sizeof( act ) );
sigfillset( &set );
sigprocmask( SIG_SETMASK, &set, NULL );
sigfillset( &act.sa_mask );
// 무시 처리 signal
act.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &act, NULL ); /* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
sigaction( SIGHUP, &act, NULL ); /* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
sigaction( SIGINT, &act, NULL ); /* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
sigaction( SIGQUIT, &act, NULL ); /* 키보드에 의한 Abort 신호 처리 => ? */
// Worker child process 종료에 대한 처리기 설정.
act.sa_handler = SignalWorkerDead;
sigaction( SIGCHLD, &act, NULL );
// 사용자의 종료 또는 에러 관련 신호 처리.
act.sa_handler = SignalMainTerminate;
sigaction( SIGTERM, &act, NULL ); /* kill -TERM 에 의한 프로세스 종료시 */
// 나머지 신호는 default 처리.
sigemptyset( &set ); /* 신호 처리기 처리 설정 위한 블록 해제 */
sigprocmask( SIG_SETMASK, &set, NULL );
}
// sqlite local db 생성 관련 처리를 수행한다.
bool CheckLocalDB( const char * dbPath )
{
// sqlite 버전 변경시 return 값이 변경되어.. 여러 문제가 발생됨. ( db 파일 생성 안되는 현상 등 )
// 이에 따라 다음과 같이 업무 flow 수정 처리한다.
// 1. DB 파일 존재시
// - 생성 스크립트 파일이 존재하는 경우 -> 스크립트 파일 실행( 오류 발생해도 로깅만 하고 그냥 넘어간다. )
// - 생성 스크립트 파일이 없는 경우 -> 해당 내역만 로깅하고 그냥 통과 처리.
// 2. DB 파일 미존재시
// - 생성 스크립트 파일이 존재하는 경우 -> 스크립트 파일 실행( 오류 발생하면.. 오류로 처리. )
// - 생성 스크립트 파일이 없는 경우 -> 오류로 처리.
// 1. DB 파일이 존재하는지 확인.
ifstream dbFile;
dbFile.open( dbPath );
if( dbFile.is_open() == false )
{
// DB 파일이 존재하지 않는 경우...
dbFile.close();
// Script 파일 존재 여부 확인.
ifstream scriptFile;
// 생성 원본 파일인 LOCAL_DB_CREATE_SCRIPT (/user/service/etc/rc_sscd.sql) 파일이 존재하는지 검사한다.
scriptFile.open( LOCAL_DB_CREATE_SCRIPT );
if( scriptFile.is_open() == false )
{
// DB 파일, Script 파일 모두 존재 안하는 경우..
// - DB 생성이 불가능하므로.. 오류로 처리한다.
scriptFile.close();
LOG( LERR, "Local db create script file not exist.[%s]", LOCAL_DB_CREATE_SCRIPT );
return false;
}
else
{
// DB 파일은 존재하지 않고.., Script 파일만 존재하는 경우..
// - 최초 설치 후 기동 처리시..
// - script 파일 수행하고.. 오류 발생시 오류로 처리한다.
scriptFile.close();
char tempBuffer[512];
int result;
snprintf( tempBuffer, sizeof( tempBuffer ), "sqlite3 %s < %s > /dev/null", dbPath, LOCAL_DB_CREATE_SCRIPT );
result = system( tempBuffer );
if( result != 0 ) // sqlite3 명령이 정상적으로 수행된 경우... 0 을 반환한다...
{
if( WEXITSTATUS( result ) == 127 )
{
// sqlite3 명령어가 존재하지 않는 경우....
LOG( LERR, "Local db create failed. sqlite3 command not found.[%s]", tempBuffer );
}
else
{
LOG( LERR, "Local db create failed.[%s][%d][%d]", tempBuffer, result, WEXITSTATUS( result ) );
}
return false;
}
else
{
// 생성 작업이 정상 수행된 경우...
_LOG( LINF, "Local db create success.[%s][%d][%d]", dbPath, result, WEXITSTATUS( result ) );
return true;
}
}
}
else
{
// DB 파일이 존재하는 경우...
dbFile.close();
// Script 파일 존재 여부 확인.
ifstream scriptFile;
// 생성 원본 파일인 LOCAL_DB_CREATE_SCRIPT (/user/service/etc/rc_sscd.sql ) 파일이 존재하는지 검사한다.
scriptFile.open( LOCAL_DB_CREATE_SCRIPT );
if( scriptFile.is_open() == false )
{
// DB 파일은 존재하나.. Script 파일이 존재하지 않는 경우...
// - 이런 경우는 정상적인 경우.. 발생하지 않음.
// - script 파일이 존재하지 않는다는 로깅 처리 후 정상 통과 처리.
scriptFile.close();
_LOG( LWAR, "Local db file[%s] exist. but db create script file[%s] not exist. check.", dbPath, LOCAL_DB_CREATE_SCRIPT );
return true;
}
else
{
// DB 파일 존재... Script 파일 존재
// - 스크립트 파일 실행하되.. 발생하는 오류는 무시 처리한다.
scriptFile.close();
char tempBuffer[512];
int result;
snprintf( tempBuffer, sizeof( tempBuffer ), "sqlite3 %s < %s > /dev/null", dbPath, LOCAL_DB_CREATE_SCRIPT );
result = system( tempBuffer );
if( result != 0 ) // sqlite3 명령이 정상적으로 수행된 경우... 0 을 반환한다...
{
if( WEXITSTATUS( result ) == 127 )
{
// sqlite3 명령어가 존재하지 않는 경우....
LOG( LERR, "Local db create failed. sqlite3 command not found.[%s]", tempBuffer );
return false;
}
else
{
// 기존 DB 가 존재하여.. 오류가 발생할 수 있으나.. 로깅만 하고.. 무시하여 진행
LOG( LDBG, "Local db create(update) error occured. but ignore.[%s][%d][%d]", tempBuffer, result, WEXITSTATUS( result ) );
return true;
}
}
else
{
// 생성 작업이 정상 수행된 경우...
_LOG( LINF, "Local db create(update) success.[%s][%d][%d]", dbPath, result, WEXITSTATUS( result ) );
return true;
}
}
}
}
// FHS fimngd 가 전송하는 서비스 network 통계 정보 수신을 담당할 Worker 프로세스 생성.
bool MakeWorkerRecvServiceStat()
{
// fork() 결과를 RecvSystem Worker 프로세스의 PID 정보 저장 목적 전역 변수에 저장.
g_pidWorkerRecvServiceStat = fork();
if( g_pidWorkerRecvServiceStat == 0 )
{
// 생성된 Worker 프로세스인 경우...
int iResult;
// Worker RecvServiceStat 프로세스의 Main 함수 호출
iResult = WorkerRecvServiceStatMain();
// Worker RecvSystem 프로세스의 Main 함수 종료시 종료 처리
_LOG( LINF, "[Main] RecvServiceStat worker process exit. PID[%d] return[%d]", getpid(), iResult );
ReadyToExit();
exit( iResult );
}
else if( g_pidWorkerRecvServiceStat < 0 )
{
// fork 실패시
int errorNum = errno;
LOG( LERR, "[Main] RecvServiceStat worker process create failed. [%d][%s]", errorNum, strerror( errorNum ) );
return false;
}
else
{
// 현재의 Main 프로세스
_LOG( LINF, "[Main] RecvServiceStat worker process created.. PID[%d]", g_pidWorkerRecvServiceStat );
// 잠시 대기
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = 200000000; // 0.2 sec
nanosleep( &sleep, NULL );
}
return true;
}
// Storage 통계 수집, GTS 로 통계 전송 등의 작업을 수행할 Worker 프로세스 생성.
bool MakeWorkerControl()
{
// fork() 결과를 Control Worker 프로세스의 PID 정보 저장 목적 전역변수에 저장.
g_pidWorkerControl = fork();
if( g_pidWorkerControl == 0 )
{
// 생성된 Worker 프로세스인 경우...
int iResult;
// Worker Control 프로세스의 Main 함수 호출
iResult = WorkerControlMain();
// Worker Control 프로세스의 Main 함수 종료시 종료 처리
_LOG( LINF, "[Main] Control worker process exit. PID[%d] return[%d]", getpid(), iResult );
ReadyToExit();
exit( iResult );
}
else if( g_pidWorkerControl < 0 )
{
// fork 실패시
int errorNum = errno;
LOG( LERR, "[Main] Control worker process create failed. [%d][%s]", errorNum, strerror( errorNum ) );
return false;
}
else
{
// 현재의 Main 프로세스
_LOG( LINF, "[Main] Control worker process created.. PID[%d]", g_pidWorkerControl );
// 잠시 대기
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = 200000000; // 0.2 sec
nanosleep( &sleep, NULL );
}
return true;
}
// FHS fimngd 가 전송하는 FHS 서비스별 Network 통계 정보 수신을 위한 TCP listen socket 생성
bool MakeServiceStatListenSocket()
{
// 기존 Socket close 처리.
if( g_sockServiceStatRecv != -1 )
{
close( g_sockServiceStatRecv );
g_sockServiceStatRecv = -1;
}
// TCP Socket 생성 처리.
g_sockServiceStatRecv = socket( AF_INET, SOCK_STREAM, 0 );
if( g_sockServiceStatRecv < 0 )
{
int errorNum = errno;
LOG( LERR, "service stat(network) recv tcp socket create failed.[%d][%s]", errorNum, strerror( errorNum ) );
return false;
}
// Socket option 설정 처리.
int result = 0;
int opt = 1;
// socket port reuse option set
opt = 1;
result = setsockopt( g_sockServiceStatRecv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof( opt ) );
if( result != 0 )
{
int errorNum = errno;
LOG( LERR, "service stat(network) recv tcp socket option[SO_REUSEADDR] set failed.[%d][%s]", errorNum, strerror( errorNum ) );
close( g_sockServiceStatRecv );
g_sockServiceStatRecv = -1;
return false;
}
// Keep Alive Option set
opt = 1;
result = setsockopt( g_sockServiceStatRecv, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) );
if( result != 0 )
{
int errorNum = errno;
LOG( LERR, "service stat(network) recv tcp socket option[SO_KEEPALIVE] set failed.[%d][%s]", errorNum, strerror( errorNum ) );
return false;
}
// bind 처리를 위한 설정
struct sockaddr_in listenSocketAddr;
socklen_t socketAddrLen = 0;
memset( &listenSocketAddr, 0x00, sizeof( listenSocketAddr ) );
socketAddrLen = sizeof( listenSocketAddr );
listenSocketAddr.sin_family = AF_INET;
listenSocketAddr.sin_port = htons( CProcessConfig::GetInstance()->GetTcpServiceStatListenPort() );
listenSocketAddr.sin_addr.s_addr = htonl( INADDR_ANY );
// bind 처리.
result = bind( g_sockServiceStatRecv, ( struct sockaddr * )&listenSocketAddr, socketAddrLen );
if( result != 0 )
{
int errorNum = errno;
LOG( LERR, "service stat(network) recv tcp socket bind[%u] failed.[%d][%s]"
, CProcessConfig::GetInstance()->GetTcpServiceStatListenPort(), errorNum, strerror( errorNum ) );
close( g_sockServiceStatRecv );
g_sockServiceStatRecv = -1;
return false;
}
// Socket Listen
result = listen( g_sockServiceStatRecv, DEFAULT_ACCEPT_WAIT_COUNT );
if( result != 0 )
{
int errorNum = errno;
LOG( LERR, "service stat(network) recv tcp socket listen failed.[%d][%s]"
, errorNum, strerror( errorNum ) );
close( g_sockServiceStatRecv );
g_sockServiceStatRecv = -1;
return false;
}
// logging 처리
_LOG( LINF, "service stat(network) recv tcp socket create success. tcp port[%u]", CProcessConfig::GetInstance()->GetTcpServiceStatListenPort() );
return true;
}
+75
View File
@@ -0,0 +1,75 @@
/****************************************************************************
Main.h for rc_sscd
-----------------------------------------
begin : 2015/08/24
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __RC_SSCD_MAIN_H__
#define __RC_SSCD_MAIN_H__
#include <unistd.h>
#include <stdlib.h>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif
/// @brief 사용 방법을 화면에 표시하기 위한 함수.
void PrintUsage( void );
/// @brief Version 정보를 화면에 표시하기 위한 함수
void PrintVersion( void );
/// @brief std 상에 trim 함수가 없어서 직접 구현 아니면 boost/algorithm/string.hpp 상의 boost::trim 함수 사용
/// @param str [in/out] Trim 할 문자열을 저장한 String 참조변수.
/// @return void
void Trim( std::string & str );
/// @brief 현재 Process가 기동중인지 여부를 판단하기 위한 함수( 프로세스 중복 실행 체크)
/// @return 이미 해당 프로세스가 기동 중인 경우 true 반환, 그렇지 않으면 false 반환.
bool IsCurrentProcessRun( void );
/// @brief 프로세스 종료에 따른 반복적인 종료 관련 작업을 수행하기 위한 함수.
void ReadyToExit( void );
/// @brief Main 프로세스 signal 처리 설정을 위한 함수
/// @return void
void SetSignalMain( void );
/// @brief sqlite local db 생성 처리 관련 함수..
/// @return 오류 발생시 false 반환, 그외에는 true 반환.
bool CheckLocalDB( const char * dbPath );
// FHS fimngd 가 전송하는 FHS 서비스별 Network 통계 정보 수신을 위한 TCP listen socket 생성
bool MakeServiceStatListenSocket( void );
/// @brief FHS fimngd 가 전송하는 서비스 network 통계 정보 수신을 담당할 Worker 프로세스 생성.
/// @return 생성 성공시 true, 실패시 false
bool MakeWorkerRecvServiceStat();
/// @brief Storage 통계 수집, GTS 로 통계 전송 등의 작업을 수행할 Worker 프로세스 생성.
/// @return 생성 성공시 true, 실패시 false
bool MakeWorkerControl();
#ifdef __cplusplus
}
#endif
#endif /* __RC_SSCD_MAIN_H__ */
+79
View File
@@ -0,0 +1,79 @@
#****************************************************************************
# Makefile for rc_sscd
# -----------------------------------------
#
# begin : 2015/08/24
# copyright : (C) 2005 Solbox Inc.
# author : Dev Storage Team
# email : huibong@solbox.com
# version : 3.5
#
# CopyRight(C) 2005 Solbox Inc. All Rights reserved.
# Redistribution and use in source and binary forms, with or with out
# modification, are not permitted in outside of Solbox Inc.
#****************************************************************************
# Program info
PROG_NAME = rc_sscd
REVISION = 1468
PROG_VERSION = 3.5.0.$(REVISION)-`date +%Y%m%d%H%M%S`
DEFAULT_CONFIG_FILE = /user/service/etc/rcts.conf
INSTALL_BIN = /user/service/bin
INSTALL_CONF = /user/service/etc
# Compiler info
CC = /usr/bin/g++
CFLAGS = -Wall -O3 -g -Wreturn-type -Wunused -Wuninitialized \
-Wparentheses -Wshadow -Wpointer-arith -Woverloaded-virtual \
-fno-rtti -D_REENTRANT -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
LFLAGS =
DFLAGS = -DPROG_NAME=\"$(PROG_NAME)\" -DPROG_VERSION=\"$(PROG_VERSION)\" -DDEFAULT_CONFIG_FILE=\"$(DEFAULT_CONFIG_FILE)\"
# Application Enviroment
APP = $(PROG_NAME)
DIR_INCLUDE = -I../lib -I/user/db/pgsql/include
DIR_LIB = -L../lib
OBJ = ProcessConfig.o SocketFimngd.o Database.o RcdbInfo.o \
LocalDbClearThread.o StorageUsedCalcThread.o StorageCorrectThread.o TrafficControlThread.o \
GtsSendSocket.o NetworkSendThread.o StorageSendThread.o \
WorkerControl.o WorkerRecvServiceStat.o \
Main.o
LIBS = ../lib/libInterCommon.a -lpthread -lsqlite3 /user/db/pgsql/lib/libpq.a
#---------------------------------------------------------------------#
all:$(APP)
sync
%.o: %.cpp
$(CC) $(CFLAGS) -o $@ -c $^ $(DFLAGS) $(DIR_INCLUDE)
$(PROG_NAME): $(OBJ)
$(CC) $(LFLAGS) -o $@ $^ $(DFLAGS) $(DIR_LIB) $(LIBS)
clean:
-rm -f *.o core.* *.out *.log
-rm -f $(APP)
sync
install : $(APP)
-cp $(APP) $(INSTALL_BIN)/$(APP)
-cp ../rc_sscd.sql $(INSTALL_CONF)/
sync
# End of Makefile
+456
View File
@@ -0,0 +1,456 @@
#include "NetworkSendThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sqlite3.h>
#define SQLITE_DB_LOCK_TIMEOUT 1500 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1.5 sec)
// 생성자..
CNetworkSendThread::CNetworkSendThread()
{
// Thread Handle 초기화
m_threadHandle = 0;
}
// 소멸자..
CNetworkSendThread::~CNetworkSendThread()
{
// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
if( m_threadHandle != 0 )
pthread_cancel(m_threadHandle);
}
// Thread 를 생성하여 작업을 시작한다...
bool CNetworkSendThread::Start()
{
int nResult = pthread_create( &m_threadHandle, NULL, CNetworkSendThread::threadFunc, this );
if( nResult != 0 )
{
// Thread 생성 실패시...
int errorNum = errno;
LOG( LERR, "NetworkSendThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) );
return false;
}
return true;
}
// Thread 함수...
void * CNetworkSendThread::threadFunc( void * arg )
{
CNetworkSendThread * pObject = reinterpret_cast<CNetworkSendThread *>(arg);
pthread_detach( pthread_self() );
// 초기 기동시...통계 정보가 바로 DB 에 저장되지 않은 상태이므로...
// 잠시 sleep 했다가.. 작업을 시작한다.
sleep( 10 );
// 본 Thread 에서는 Local DB 에 저장된 서비스별 Network 통계 정보를
// 주기적으로 select 하여... GTS cc_statd 모듈로 전송처리한다.
// 실제 본 thread function 에서 loop 를 동작하는 것이 아니라....
// 객체 execute 함수 내에서 loop 로 동작하도록 한다.
pthread_testcancel();
pObject->Execute();
pthread_testcancel();
// Thread 종료시 Handle 초기화
pObject->m_threadHandle = 0;
return NULL;
}
// Local DB 에 저장된 서비스 별 Network 통계 정보를 계산하여... 주기적으로 GTS cc_statd 에 전송 작업을 반복 수행한다...
void CNetworkSendThread::Execute()
{
// 변수 선언 및 초기화..
time_t currentTime; // 현재 시간 정보를 저장하기 위한 변수..
unsigned long nowTimestamp;
unsigned long lastSendTimestamp = 0; // cc_statd 로 전송 시도한 마지막 timestamp 값 (5분단위)
// Local DB 관련 변수 초기화.
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
// 기준 timestamp 값을 조건으로.. cc_statd 로 전송되지 않은 서비스별 통계 정보를 모두 조회하여... 전송 처리한다.
// cc_statd 로 전송 완료된 통계 정보는 send_flag 값이 1 로 설정됨.
// - 한번에 많은 양의 통계 정보를 GTS 로 전송할 경우.. GTS cc_statd 모듈의 메모리 사용량이 증가할 수 있으므로...
// - 한번에 1000 개 통계정보만 전송되도록 수정한다.
//
// - FHS 장비의 시간이 빠른 경우... 해당 FHS 는 다른 FHS 와 달리 큰 timestamp 값이 전송되어 Local DB 에 저장된다.
// - 본 Thread 에서 통계 정보 조회시...timestamp <= ? 조건으로 조회할 경우..
// - 현재 시간의 통계 정보까지 조회되며.. 이때.. 정상 FHS 통계는 아직 미수신 상태이지만.. 시간이 빠른 FHS 통계는 조회 처리됨.
// - 이로 인해 잘못된 Network 통계 정보가 GTS 로 전송되므로.. 이를 막기 위해 Query 조건을 timestamp < ? 으로 수정 처리한다.
const char * select_sql = "SELECT timestamp, user_seq, svc_seq, "
" sum(down_traffic), sum(up_traffic), sum(down_content_size), sum(up_content_size) "
"FROM network_stat "
"WHERE timestamp < ? AND send_flag = 0 "
"GROUP BY timestamp, user_seq, svc_seq "
"ORDER BY timestamp ASC "
"LIMIT 1000 ";
// Local DB 와 연결을 수행한다.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
if( db_result != SQLITE_OK )
{
LOG( LERR, "NetworkSendThread: 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, "NetworkSendThread: 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, "NetworkSendThread: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
}
// Local DB 조회 정보를 저장하기 위한 구조체...
struct gts_send_stat_network stNetworkStat;
// Logging
_LOG( LINF, "NetworkSendThread: start...");
// 루프를 돌면서... Local DB 의 Network 통계 정보를 조회하여... 주기적으로 cc_statd 에 전송한다.
while( 1 )
{
// cc_statd 로 전송을 수행할 것인지.. timestatmp 값을 계산하여... 확인한다.
// 1. 현재 시간 정보 추출..
currentTime = time(0);
// 2. 현재 timestamp 값을 계산한다...
// 계산시.. FHS 장비들이 시간 동기화 오차로... 통계 정보가 늦게 올라와 저장될 수 있으므로...
// 60 sec 보정 처리하여 5분 단위 timestamp 값을 추출한다.
// sqlite SQLITE_BUSY 잦은 발생으로 통계 수신 모듈에서 INSERT 지연 발생 가능, 이로 인해 CCDB 통계 누락 발생 (#21440)
// - 이를 해결하기 위해 CCDB 로 전송 시점을 기존 60 sec -> 120 sec 이후로 변경 처리한다.
nowTimestamp = (currentTime - 120 + 299)/300;
// 3. 최초 기동 또는 시간 동기화 이상으로 reset 처리된 경우... lastSendTimestamp = 0
// 트래픽 통계 정보를 합산하여 바로 전송하면... timestamp 관련 중복 등의 문제가 발생할 수 있으므로...
// 5분 후에 이전 통계 정보까지 전달될 수 있도록 한다.
if( lastSendTimestamp == 0 )
{
_LOG( LDBG, "NetworkSendThread: first try... send skip..now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
lastSendTimestamp = nowTimestamp;
// CHG 2015-02-03 huibong
// sqlite SQLITE_BUSY 잦은 발생으로 통계 수신 모듈에서 INSERT 지연 발생 가능, 이로 인해 CCDB 통계 누락 발생 (#21440)
// 보정 시간을 60 sec -> 120 sec 로 변경 처리한다.
//sleep(60);
sleep(120);
continue;
}
// 4. 마지막 timestamp 값과 현재 timestamp 계산 값이 같은 경우...
// 이미 전송한 것으로 판단하고.. 쉰다.
if( nowTimestamp == lastSendTimestamp )
{
//_LOG( LDBG, "NetworkSendThread: send check.. skip.. now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
// 3 sec 대기 후 timestamp 변경이 되었는지 다시 계산하도록 처리...
// - 원래 코드는 1 마다 검사였으나..
// - 이럴 경우.. 순간적으로 여러 rc_sscd 에서 cc_statd 로 통계 정보가 일괄 전송 되어
// - cc_statd 에 부하가 발생될 확률이 높으므로.. 다음과 같이 3 sec 단위로 체크토록 변경한다.
sleep(3);
continue;
}
else
{
// 만약 lastSendTimestamp > nowTimestamp 인 경우...
// - 시간 동기화 이상으로 이전에 계산된 timestamp 값이 잘못된 경우.... 보정처리..
if( nowTimestamp < lastSendTimestamp )
{
LOG( LWAR, "NetworkSendThread: Last timestamp not valid. skip and reset. now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
lastSendTimestamp = 0;
sleep(60);
continue;
}
}
_LOG( LDBG, "NetworkSendThread: stats info select & send start... now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
// 이제 전송작업을 수행할 것이지만.... 실제 전송 실패가 발생할 수도 있다...
// 따라서... lastSendTimestamp 값을 변경하지 않을 경우.... while 루프에 의해 금방 재시도를 하므로....
// 아예 5분 지난 timestamp 변경 후.. 다시 시도하도록 lastSendTimestamp 값을 현재 값으로 변경 처리한다.
lastSendTimestamp = nowTimestamp;
// Local DB 조회 정보를 저장할 vector 초기화.
m_vecNetworkStat.clear();
// 이제 실제 전송 관련 처리 작업을 수행한다.
// Local DB 연결이 되지 않은 경우...
if( db == NULL )
{
_LOG( LINF, "NetworkSendThread: sqlite3 open retry.[%s]", CProcessConfig::GetInstance()->GetLocalDbFileName() );
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "NetworkSendThread: sqlite3 open failed2.[%s][%d][%s]"
, CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
sqlite3_close(db);
db = NULL;
}
else
{
nRetryCount = 0;
// bind 관련 Query 를 수행할 stmt 객체 생성.
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, "NetworkSendThread: 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, "NetworkSendThread: sqlite3 prepare failed2.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
}
}
// sqlite 접속이 정상적으로 된 경우...
if( stmt != NULL )
{
// 매개변수 bind 처리.
sqlite3_bind_int64( stmt, 1, nowTimestamp ); // 64bit 에서 unsigned long 변수는 8Byte 이므로..
// select query 수행.
db_result = sqlite3_step(stmt);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..60번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 60 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "NetworkSendThread: 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, "NetworkSendThread: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db));
// Local DB 오류 발생시.. DB 에 문제가 있는 것으로 보고.. DB 객체를 초기화한다.
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
else
{
// select Query 가 정상적으로 수행된 경우....
while( db_result == SQLITE_ROW )
{
// 임시 저장변수 초기화.
memset( &stNetworkStat, 0x00, sizeof(struct gts_send_stat_network) );
// 조회 결과를 임시 변수에 저장..
stNetworkStat.timestamp = sqlite3_column_int (stmt, 0);
stNetworkStat.user_seq = sqlite3_column_int (stmt, 1);
stNetworkStat.svc_seq = sqlite3_column_int (stmt, 2);
stNetworkStat.down_traffic = sqlite3_column_int64(stmt, 3);
stNetworkStat.up_traffic = sqlite3_column_int64(stmt, 4);
stNetworkStat.down_content_size = sqlite3_column_int64(stmt, 5);
stNetworkStat.up_content_size = sqlite3_column_int64(stmt, 6);
stNetworkStat.send_flag = 0;
/*
_LOG( LDBG, "NetworkSendThread: select result [%u][%d][%d] [%lu][%lu] [%lu][%lu]"
, stNetworkStat.timestamp, stNetworkStat.user_seq, stNetworkStat.svc_seq
, stNetworkStat.down_traffic, stNetworkStat.up_traffic
, stNetworkStat.down_content_size, stNetworkStat.up_content_size );
*/
// vector 객체에 query 결과를 저장한다.
m_vecNetworkStat.push_back( stNetworkStat );
// Next 결과 처리..
db_result = sqlite3_step( stmt );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..30번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 60 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "NetworkSendThread: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount );
// query 재시도
db_result = sqlite3_step(stmt);
}
}
// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset(stmt);
}
}
// cc_statd 로 전송할 Data 가 존재하는 경우...
if( m_vecNetworkStat.empty() == false )
{
// GTS 로 Data 전송을 담당하는 Socket 객체에 Data 를 전달하여...
// 해당 Socket 객체에서 Data 를 전달하도록 처리한다.
m_socketGts.SendStatNetwork( m_vecNetworkStat );
// 전송 처리 작업 호출 후....
// 전송 결과를 확인하여... 전송이 완료된 정보에 대해 Local DB 의 send_flag, send_time 정보를 변경처리한다.
// - local DB 에서 조회 결과가 있어야만... m_vecNetworkStat 의 값이 존재하므로. local db 연결 확인은 따로 하지 않아도 됨.
char update_sql[1024];
char * db_err;
std::vector<struct gts_send_stat_network>::iterator it;
for( it = m_vecNetworkStat.begin(); it != m_vecNetworkStat.end(); ++it )
{
// GTS 로 전송 처리 또는 전송 무시 처리된 경우.. ( it->send_flag 가 1 or 2 인 경우.. )
if( it->send_flag != 0 )
{
// GTS 로 전송 완료된 Local DB 항목에 대해 전송 완료 flag 값을 변경하기 위한 Query.
snprintf( update_sql, 1023,
"UPDATE network_stat "
"SET send_flag = %d, send_time = datetime('now', 'localtime') "
"WHERE timestamp = %u AND user_seq = %d AND svc_seq = %d AND send_flag = 0 "
, it->send_flag, it->timestamp, it->user_seq, it->svc_seq );
// Query 수행...
db_result = sqlite3_exec(db, update_sql, NULL, NULL, &db_err);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..60번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 60 )
{
sqlite3_free(db_err);
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "NetworkSendThread: local db update sqlite3_exec SQLITE_BUSY. Retry [%d]",nRetryCount );
// Query 재수행
db_result = sqlite3_exec(db, update_sql, NULL, NULL, &db_err);
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "NetworkSendThread: Local db send_flag update fail.[%s][%s]", update_sql, db_err );
sqlite3_free(db_err);
}
_LOG( LINF, "Network stats send to GTS OK [%u] [%d][%d] [ut:%lu][dt:%lu]"
, it->timestamp, it->user_seq, it->svc_seq, it->up_traffic, it->down_traffic );
}
}
}
else
{
// 전송할 Data 가 없는 경우.. 로깅 처리..
LOG( LINF, "NetworkSendThread: Network stats data is empty. Not send to GTS ..");
}
// GTS cc_statd 로 통계 정보 전송 처리 후.. 불필요하게 loop 를 동작시킬 필요가 없으므로....
// 잠깐 sleep 한다.
sleep(120);
}
// 종료 처리 코드
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
m_vecNetworkStat.clear();
return;
}
+63
View File
@@ -0,0 +1,63 @@
/****************************************************************************
Thread class for Network stats send to cc_stat
-----------------------------------------
begin : 2015/09/01
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __NETWORK_SEND_THREAD_H__
#define __NETWORK_SEND_THREAD_H__
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include "GtsSendSocket.h"
/// @brief CNetworkSendThread
/// 본 클래스는 Thread 클래스로서...
/// 서비스별 Network 통계 정보를 합산하여 GTS 의 cc_statd 에 주기적으로 전송하는 기능을 수행한다.
class CNetworkSendThread
{
public:
// 생성 및 소멸자.
CNetworkSendThread();
~CNetworkSendThread();
// 전송 작업 시작
bool Start( void );
private:
// Thread function....
static void * threadFunc( void * arg );
// 실제 정보 전송을 담당하는 함수...
void Execute( void );
private:
// 쓰레드 핸들
pthread_t m_threadHandle;
// DB 조회 정보를 저장할 Vector 객체
std::vector<struct gts_send_stat_network> m_vecNetworkStat;
// cc_statd 로 Data 전송을 담당할 Socket 처리 Class.
CGtsSendSocket m_socketGts;
};
#endif /* __NETWORK_SEND_THREAD_H__ */
+293
View File
@@ -0,0 +1,293 @@
#include "ProcessConfig.h"
#include <stdlib.h>
#include "Logger.h"
// static 변수 초기화
CProcessConfig * CProcessConfig::m_pInstance = NULL;
// 생성자
CProcessConfig::CProcessConfig()
{
// default 값 설정.
// Network, Storage 통계 정보 저장 처리를 위한 Local Sqlite DB File name
m_strLocalDbFileStat = "/user/service/rc_sscd.db";
// Storage 사용량 계산시 Full Scan 모드만 사용할지 여부 ( default 는 false )
m_bStorageCalcOnlyFullScan = false;
}
// 소멸자
CProcessConfig::~CProcessConfig()
{
// 객체 소멸시...
// 만약 static GetInstance() 함수가 가르키는 객체가 자기 자신이라면...
// 소멸 처리에 의해 문제가 발생할 수 있으므로... 이에 대한 처리를 해 준다.
if( CProcessConfig::GetInstance() == this )
{
CProcessConfig::m_pInstance = NULL;
}
}
bool CProcessConfig::Init( const std::string strProgramName, const std::string & strConfFileName, std::string & strErrorMessage )
{
// 1. 임시 처리용 Instance 를 생성한다.
CProcessConfig * pTempInstance = new CProcessConfig();
// 2. 전달 받은 Config 정보를 임시 Instance 로 로딩한다.
// - 만약 로딩이 실패할 경우... 임시 Instance 객체를 삭제 처리한다.
if( pTempInstance->Load( strProgramName, strConfFileName, strErrorMessage ) == false )
{
delete pTempInstance;
pTempInstance = NULL;
return false; // 생성 실패 사유는 Error String 관련 함수를 사용하여 확인.
}
// 3. 전달받은 Conf 정보 로딩에 성공한 경우...
// - 기존 Instance 객체가 존재하는 경우.. 교체 처리..
// - 기존 Instance 객체가 없는 경우는 신규 Instance 사용토록 처리.
if( CProcessConfig::GetInstance() == NULL )
{
CProcessConfig::m_pInstance = pTempInstance;
}
else
{
// Instance replace...
CProcessConfig * pPreviosInstance = CProcessConfig::m_pInstance;
CProcessConfig::m_pInstance = pTempInstance;
delete pPreviosInstance;
}
return true;
}
CProcessConfig * CProcessConfig::GetInstance()
{
return CProcessConfig::m_pInstance;
}
bool CProcessConfig::Load( const std::string & strProgramName, const std::string & strConfFileName, std::string & strErrorMessage )
{
// 변수 유효성 확인
if( strProgramName.size() <= 0 )
{
strErrorMessage = "Config Program Name[" + strProgramName + "] not valid.";
return false;
}
// Config class (lib 하위) 를 이용하여 config 파일 상의 정보를 로딩 처리
Config conf;
if( conf.Open( strConfFileName ) == false )
{
strErrorMessage = "Config file[" + strConfFileName + "] open failed.";
return false;
}
// Config 모듈에 로딩된 정보 중 필요한 정보만 가져온다...
// - 만약 오류가 발생할 경우... 해당 내역은 Error String 에 저장 처리...
std::string strValue;
//std::vector< std::string > vecValue;
// DEFAULT_LOG_DIR
if( GetConfigValue( conf, strProgramName, "DEFAULT_LOG_DIR", strValue, strErrorMessage ) == false )
return false;
else
{
m_strLogPath = strValue;
strValue.clear();
}
// LOG_LEVEL
if( GetConfigValue( conf, strProgramName, "LOG_LEVEL", strValue, strErrorMessage ) == false )
return false;
else
{
m_nLogLevel = atoi(strValue.c_str());
if( m_nLogLevel < 0 || m_nLogLevel > MAX_LOG_LEVEL )
{
strErrorMessage = "Config [LOG_LEVEL] value[" + strValue + "] is not valid";
return false;
}
strValue.clear();
}
// RCID
if( GetConfigValue( conf, strProgramName, "RC_ID", strValue, strErrorMessage ) == false )
return false;
else
{
m_strRcId = strValue;
strValue.clear();
}
// RCDB IP
if( GetConfigValue( conf, strProgramName, "RCDB_IP", strValue, strErrorMessage ) == false )
return false;
else
{
m_strRcdbIp = strValue;
strValue.clear();
}
// RCDB PORT
if( GetConfigValue( conf, strProgramName, "RCDB_PORT", strValue, strErrorMessage ) == false )
return false;
else
{
m_nRcdbPort = atoi( strValue.c_str() );
if( m_nRcdbPort <= 0 || m_nRcdbPort > 65535 )
{
strErrorMessage = "Config [RCDB_PORT] value[" + strValue + "] is not valid";
return false;
}
strValue.clear();
}
// RCDB DB NAME
if( GetConfigValue( conf, strProgramName, "RCDB_DB_NAME", strValue, strErrorMessage ) == false )
return false;
else
{
m_strRcdbName = strValue;
strValue.clear();
}
// RCDB ACCOUNT
if( GetConfigValue( conf, strProgramName, "RCDB_ACCT", strValue, strErrorMessage ) == false )
return false;
else
{
m_strRcdbAcct = strValue;
strValue.clear();
}
// RCDB ACCOUNT PASSWORD
if( GetConfigValue( conf, strProgramName, "RCDB_ACCT_PW", strValue, strErrorMessage ) == false )
return false;
else
{
m_strRcdbAcctPw = strValue;
strValue.clear();
}
// PORT_TCP_LISTEN_SERVICE_STAT
if( GetConfigValue( conf, strProgramName, "PORT_TCP_LISTEN_SERVICE_STAT", strValue, strErrorMessage ) == false )
return false;
else
{
m_nTcpListenPortServiceStat = atoi( strValue.c_str() );
if( m_nTcpListenPortServiceStat <= 0 || m_nTcpListenPortServiceStat > 65535 )
{
strErrorMessage = "Config [PORT_TCP_LISTEN_SERVICE_STAT] value[" + strValue + "] is not valid";
return false;
}
strValue.clear();
}
// GTS_SERVER
if( GetConfigValue( conf, strProgramName, "GTS_SERVER", strValue, strErrorMessage ) == false )
return false;
else
{
m_strGtsServer = strValue;
strValue.clear();
}
// GTS_PORT_STAT -> 수집된 서비스별 Network, Storage 통계 정보를 GTS cc_statd 로 전송하기 위한 통신 port
if( GetConfigValue( conf, strProgramName, "GTS_PORT_STAT", strValue, strErrorMessage ) == false )
return false;
else
{
m_nGtsPortStat = atoi( strValue.c_str() );
if( m_nGtsPortStat <= 0 || m_nGtsPortStat > 65535 )
{
strErrorMessage = "Config [GTS_PORT_STAT] value[" + strValue + "] is not valid";
return false;
}
strValue.clear();
}
// LOCAL_DB_FILE
if( GetConfigValue( conf, strProgramName, "LOCAL_DB_FILE", strValue, strErrorMessage ) == false )
{
// 해당 값이 존재하지 않을 경우..
// 생성자에 의해 초기화된 default 값 사용.
}
else
{
m_strLocalDbFileStat = strValue;
strValue.clear();
}
// STORAGE_CALCULATE_ONLY_FULL_SCAN
// Storage 사용량 계산시... Only Full Sacn 모드만 사용할지 여부...
// 0 : full , 증감 scan 혼합 사용. (default )
// 1 : full scan 만 사용.
if( GetConfigValue( conf, strProgramName, "STORAGE_CALCULATE_ONLY_FULL_SCAN", strValue, strErrorMessage ) == false )
{
// 해당 값이 존재하지 않는 경우..
// 생성자에서 초기화 시킨 default(혼합 사용) 값을 사용한다.
}
else
{
if( atoi( strValue.c_str() ) != 0 ) // 0 값으로 설정되지 않은 경우......즉 1로 설정...
m_bStorageCalcOnlyFullScan = true;
else
m_bStorageCalcOnlyFullScan = false;
strValue.clear();
}
// conf 정보 로딩이 모두 정상적으로 완료된 경우... 마지막으로 전달받은 config 명, section 정보를 저장한다.
m_strConfigFileName = strConfFileName;
m_strProgramName = strProgramName;
return true;
}
bool CProcessConfig::GetConfigValue( Config & conf, const std::string & strProgramName, const std::string strKey, std::string & strValue, std::string & strErrorMessage )
{
// conf get value
if( conf.GetConfig( strProgramName, strKey, strValue) == false )
{
if( conf.GetConfig( "COMMON", strKey, strValue) == false )
{
strErrorMessage = "Config [" + strKey + "] value is not exist";
return false;
}
}
// value check.
if( strValue.empty() == true )
{
strErrorMessage = "Config [" + strKey + "] value is not valid";
return false;
}
return true;
}
+135
View File
@@ -0,0 +1,135 @@
/****************************************************************************
rc_sscd config processing module
-----------------------------------------
begin : 2015/08/24
copyright : (C) 2005 Solbox Inc.
author : Dev Storage team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __PROCESS_CONFIG_H__
#define __PROCESS_CONFIG_H__
#include <string>
#include "Config.h"
// 본 모듈은 conf 파일의 정보를 read 하여... 본 프로세스 관련 설정 정보를 저장한다.
// 본 모듈은 singleton 으로 동작시켜 Main 프로세스 및 Worker 프로세스에서도 접근이 가능토록 한다.
// conf 파일에 대한 파싱 및 설정 정보 추출은 lib 하위의 Config 클래스를 이용한다.
// 본 객체는 singleton 객체이므로 상속받아 사용하지 않도록 한다. ( 상속받아 사용할 수 없도록 private 처리함 )
class CProcessConfig
{
private:
// sigleton 객체 생성을 위해 생성자를 public 으로 처리하지 않음.
CProcessConfig();
// 소멸자
virtual ~CProcessConfig();
static CProcessConfig * m_pInstance;
public:
// 객체 초기화 처리용 Function.
// 본 함수 호출시 Instance 생성 후 입력 받은 Config 파일 load.
// 따라서 본 함수를 호출하지 않을 경우.. GetInstance() 함수는 NULL 을 반환
// 본 함수를 객체가 생성된 상태에서 재 호출할 경우
// - 주어진 Conf 파일 정보가 정확하다면.. conf 정보를 다시 load 함.
// - 주어진 conf 파일 정보가 부정확하다면... 이전 conf 를 계속 사용함.
static bool Init( const std::string strProgramName, const std::string & strConfFileName, std::string & strErrorMessage );
// Singleton 객체 접근 메소드
static CProcessConfig * GetInstance();
private:
// 전달받은 conf 파일에서 해당 config 정보를 로드하여 멤버 변수에 저장한다.
// 만약 반환값이 false 인 경우 해당 오류 관련 내역은 strErrorMessage 변수에 저장된다.
bool Load( const std::string & strProgramName, const std::string & strConfFileName, std::string & strErrorMessage );
// Config 객체로 부터 지정된 strKey 정보를 가져와 유효성 여부를 판단.
// 반환값이 false 인 경우 strErrorMessage 상에 오류 내역이 저장된다.
bool GetConfigValue( Config & conf, const std::string & strProgramName, const std::string strKey, std::string & strValue, std::string & strErrorMessage );
private:
// 객체 초기화시 전달받은 정보
std::string m_strConfigFileName; // 설정 파일 정보
std::string m_strProgramName; // 프로그램 명 (conf 상에서 해당 프로그램의 설정 정보를 가져올 때 사용)
// 기본 설정 정보.
std::string m_strLogPath; // 로그 저장 Path (/user/service/logs)
int m_nLogLevel; // 로그 기록 Level
std::string m_strRcId; // RC ID
std::string m_strRcdbIp; // RCDB IP
unsigned int m_nRcdbPort; // RCDB Port
std::string m_strRcdbName; // RCDB DB Name
std::string m_strRcdbAcct; // RCDB 접근 계정
std::string m_strRcdbAcctPw; // RCDB 접근 Password.
// 프로세스 설정 정보
// FHS 신규 fimngd 전송하는 FHS 별 Service 통계 정보 수신을 위한 TCP port = 14006
unsigned int m_nTcpListenPortServiceStat;
std::string m_strGtsServer; // GTS cc_statd server domain ( Failover 는 Neocast 에서)
unsigned int m_nGtsPortStat; // GTS cc_statd server tcp port
// 통계 정보 저장 처리를 위한 Local Sqlite DB File name ( default: /user/service/rc_sscd.db )
std::string m_strLocalDbFileStat;
// Storage 사용량 계산시 Full Scan 모드만 사용할지 여부 ( default 는 false )
// STORAGE_CALCULATE_ONLY_FULL_SCAN
bool m_bStorageCalcOnlyFullScan;
public: // 멤버 변수에 대한 Get 함수 선언 및 정의
const char * GetConfigFileName() { return m_strConfigFileName.c_str(); }
const char * GetProgramName() { return m_strProgramName.c_str(); }
const char * GetLogPath() { return m_strLogPath.c_str(); }
int GetLogLevel() { return m_nLogLevel; }
const char * GetRcId() { return m_strRcId.c_str(); }
const char * GetRcdbIp() { return m_strRcdbIp.c_str(); }
unsigned int GetRcdbPort() { return m_nRcdbPort; }
const char * GetRcdbName() { return m_strRcdbName.c_str(); }
const char * GetRcdbAcct() { return m_strRcdbAcct.c_str(); }
const char * GetRcdbAcctPw() { return m_strRcdbAcctPw.c_str(); }
// 신규 fimngd 로 부터 서비스 통계 정보를 수신하기 위한 TCP listen port 정보 반환.
unsigned int GetTcpServiceStatListenPort() { return m_nTcpListenPortServiceStat; }
const char * GetGtsServer() { return m_strGtsServer.c_str(); }
unsigned int GetGtsPortStat() { return m_nGtsPortStat; }
const char * GetLocalDbFileName() { return m_strLocalDbFileStat.c_str(); }
bool IsStorageOnlyFullScanUse() { return m_bStorageCalcOnlyFullScan; }
};
#endif /* __PROCESS_CONFIG_H__ */
+111
View File
@@ -0,0 +1,111 @@
/****************************************************************************
fimngd 의 서비스별 network 통계 정보 수신 관련 protocol 정의
-----------------------------------------
begin : 2015/08/27
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __PROTOCOL_FIMNGD_H__
#define __PROTOCOL_FIMNGD_H__
#include <unistd.h>
#include <sys/types.h>
#include <stdint.h>
// Packet Header stx 코드
#define HEADER_STX_CODE_FIMNGD 0x05
// Packet Header type 구분코드
#define HEADER_TYPE_REQUEST 0x00
#define HEADER_TYPE_RESPONSE 0x01 // 응답 전송시 (#27106)
// Packet Header command 구분 코드.
// 현재는 0th 미사용, 1th 항목만 사용.
#define COMMAND_SERVICE_NETWORK_STAT 0x02 // fimngd 의 서비스별 network 통계 전송 (0x00 0x02 0x00 0x00)
// Packet Header result 구분 코드.
// - type이 Reponse 인 경우에만 세팅됨.( 첫번째 Byte 만 사용시 )
#define HEADER_RESULT_SUCCESS 0x00
#define HEADER_RESULT_ERROR 0x01
// 기타 정보
//#define LENGTH_FIELD_SIZE 4 // 가변데이터 형식 사용시 Length 필드의 메모리 크기 (unsigned int)
// fimngd 와 통신시 사용할 packet header
// - 계산상 크기는 16Byte 이지만...
// - 통신상 sizeof() : 20Byte ( 64Bit OS )
struct PacketHeaderFimngd {
char stx; // Packet 유효성 관리 코드
char type; // Request or Response 여부 ( 0x00: Request, 0x01: Response )
char command[4]; // Command Code ( 4 Byte) : 0th 미사용, 1th fimngd-rc_mond 에서 사용)
char result[4]; // Result Code ( 4 Byte )
unsigned int data_length; // Packet Data 부분의 길이값 ( Network Byte Order 사용)
char protocol_version; // Packet Data 의 Protocol Version 정보. (1 Byte)
char extend_code; // 확장 및 Padding bits ( 1 Byte )
};
////////////////////////////////////////////
// Service network stat 통계 수신 처리 관련 Data 항목 및 구조체.
// RC ID 최대 길이.
#define FIMNGD_RC_ID_MAX_LENGTH 31
// hostname 최대 길이.
#define FIMNGD_HOST_NAME_MAX_LENGTH 63
// FHS fimngd 와 service network 통계 처리 통신 관련 protocol version 중 처리 가능한 최대 version 정보
// - 최초 버전: 0x00 으로 설정.
// - CHG 2016-05-25 huibong #27106 에 의해 fimngd 에 통계 수신 응답 전송을 위해 Protocol version 항목에 0x01 추가됨.
#define PARSE_MAX_PROTOCOL_VERSION_SERVICE_NETWORK 0x01
// FHS fimngd 에서 수신할 service network stat 관련 data 구조체
// 수신 packet 구조는 다음과 같다.
// PacketDataServiceNetworkStatHeader + N개의 PacketDataServiceNetworkStat_V00
struct PacketDataServiceNetworkStatHeader {
char rcid[FIMNGD_RC_ID_MAX_LENGTH + 1]; // RCID 정보, 최대 31 자까지만 허용.
char hostname[FIMNGD_HOST_NAME_MAX_LENGTH + 1]; // hostname, 최대 63자까지만 허용.
int service_count; // 아래 정의된 service network 구조체의 갯수, Network byte order
};
// CHG 2016-05-25 huibong (#27106)
// #27106 에 의해 fimngd 에 통계 수신 응답 전송을 위해 Protocol version 항목에 0x01 추가 상황 발생.
// 하지만 Data 구조는 0x00, 0x01 버전이 동일함.
struct PacketDataServiceNetworkStat_V00 {
int svc_seq; // service sequence
int user_seq; // user sequence
uint32_t timestamp; // timestamp
uint64_t down_content_size; // download content size
uint64_t up_content_size; // upload content size
uint64_t down_traffic; // download packet size
uint64_t up_traffic; // upload packet size
};
#endif /* __PROTOCOL_FIMNGD_H__ */
+105
View File
@@ -0,0 +1,105 @@
/****************************************************************************
opendav-listener service stats message header for rc_sscd
-----------------------------------------
begin : 2015/08/26
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __PROTOCOL_OPENDAV_LISTENER_H__
#define __PROTOCOL_OPENDAV_LISTENER_H__
#include <unistd.h>
#include <stdint.h>
// 본 Header 파일은 FHS 의 opendav listener가 UDP 13108 port로 전송하는 서비스 통계 관련 Protocol 을 정의하기 위한 header 이다.
// 본 Protocol 형식이 향후 변경될 수 있으므로.. 다음과 같이 특정하여 처리한다.
// sizeof( struct opendav_message_header ) => 352 (64Bit OS)
struct opendav_message_header {
char szTransactionID[52]; // 20131029180001-20f8b8d9f4-138303720-660114157 ( 세션 ID 정보 )
uint32_t ulMode; // 0x0110 (MODE 정보, network byte odrer )
char szIssuer[128]; // bd-16-fhs003.ktsh.co.kr (장비명)
char szServiceID[128]; // 348.0@207
char szRCID[32]; // bd16 (RC ID)
uint32_t ulResult; // 4096 (처리결과, network byte odrer)
uint32_t ulBodyLength; // 276 (int + Body Size , network byte odrer)
};
// opendav-listener 가 전송한 메시지 중....
// opendav_message_header 구조체에서 ntohl(ulMode) == MSG_MODE_OPENDAV_RCSTAT_NETSTAT 정보만 처리한다.
#define MSG_MODE_OPENDAV_RCSTAT_NETSTAT 0x0110
// 위의 Header 다음에 4 Byte int 값이 온다...
// 해당 값은 opendav-listener 가 보내는 서비스의 index 순서 정보를 기록하는 값으로써...
// n 번째 data 라는 의미임.
// - 해당 패킷에 n 개의 data 가 저장된다는 의미는 아니며...
// - 1개 패킷에는 무조건 1개 정보만 저장하여 전송 처리함.
// 해당 값은 Network byte order 아님.
// 이후 아래와 같이 body 가 나타난다. ( rc_statd(3.4 이전) 의 protocol_local.h 참고 )
// 아래 구조체의 각 항목 중 배열 첨자가 나타내는 것은. 다음을 의미 ( opendav 의 share.h 확인 내역 )
// #define OUTB 0 => FHS 에서 송신
// #define INB 1 => FHS 에서 수신
// 서비스 세션 통계 정보 저장 구조체...
// sizeof( struct service_stat ) => 56 (64Bit OS)
// 각 항목에 대한 정보는 opendav 상의 구현 코드를 바탕으로 분석한 내용임.
struct service_stat {
uint32_t success[2]; // 2nd: 각 요청에 대해 HTTP 200 ~ 399 응답이 발생한 횟수.
uint32_t fail_not_auth[2]; // 3rd: 각 요청에 대해 HTTP 401 응답 발생 횟수
uint32_t fail_illegal_request[2]; // 5th: HTTP 404 응답 외 요청에 대한 횟수.
uint32_t fail_timeout[2]; // 4th: keepalive 관련 timeout 발생 횟수.
uint32_t fail_disconnected[2]; // 1st: 각 요청에 대해 Connection 이 aborted 된 횟수..
uint32_t concurrent[2]; // 각 요청에 의해 발생된 최대 동시 세션 수
uint32_t saved_concurrent[2]; // 각 요청에 대한 현재 동시 세션 수
// 기존 cc_statd 확인 결과 본 값은 CCDB 에 저장하지 않음.
};
// 네트워크 통계 정보 저장 구조체...
// sizeof( struct network_stat ) => 48 (64Bit OS)
// 각 항목에 대한 정보는 opendav 상의 구현 코드를 바탕으로 분석한 내용임.
// #define CONTENT_TO_TRAFFIC(s) ((int)(s * 1.1))
struct network_stat {
uint64_t traffic[2]; // 아래 content 항목이 의미하는 실제 송수신된 Content Size 정보에..
// CONTENT_TO_TRAFFIC 매크로를 사용하여 1.1 을 곱해...
// 전송된 Hader 포함 패킷량을 Byte 단위로 나타냄.
uint64_t content[2]; // 송수신한 Content 의 Size 정보 (Byte 단위)
uint64_t tc_traffic[2]; // 실제 네트워크 전송량에 1.1 곱한 값의 의미로 사용하려 한 듯하나...
// 0으로 표시되며 실제 opendav 에서 무조건 0 으로 설정함.
// 따라서 해당 값은 미사용.
};
// 서비스 통계 정보 저장 구조체... Body 부분
// sizeof( opendav_message_body ) => 272 (64Bit OS)
struct opendav_message_body {
char szServiceID[128]; // 348.0@207
char szRCID[32]; // 사용 안함 (값 없음)
uint32_t timestamp; // 4610133
uint32_t __temp; // 의미 없는 dummy 코드임.
struct service_stat sst;
struct network_stat nst;
};
#endif /* __PROTOCOL_OPENDAV_LISTENER_H__ */
+40
View File
@@ -0,0 +1,40 @@
#include "RcdbInfo.h"
#include "ProcessConfig.h"
// 생성자..
CRcdbInfo::CRcdbInfo()
{
}
// 소멸자..
CRcdbInfo::~CRcdbInfo()
{
}
// CServiceConfig Class 에 저장된 RCDB 접속 정보를 가져와 멤버 변수에 저장한다.
void CRcdbInfo::Load()
{
// CProcessConfig Class 에서 기본적인 설정값에 대해 검사를 수행하므로...
// 본 Class 에서 다시 유효성 검사를 할 필요는 없다.. ( 어차피 유효성 검사 방법이 동일하므로.. )
// RCDB IP
m_strRcdbIp = CProcessConfig::GetInstance()->GetRcdbIp();
// RCDB Port
m_nRcdbPort = CProcessConfig::GetInstance()->GetRcdbPort();
// RCDB DB Name
m_strRcdbName = CProcessConfig::GetInstance()->GetRcdbName();
// RCDB 접근 계정
m_strRcdbAcct = CProcessConfig::GetInstance()->GetRcdbAcct();
// RCDB 접근 Password.
m_strRcdbAcctPw = CProcessConfig::GetInstance()->GetRcdbAcctPw();
return;
}
+52
View File
@@ -0,0 +1,52 @@
/****************************************************************************
RCDB 접속 정보 저장 처리 Class
-----------------------------------------
begin : 2013/11/13
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __RCDB_INFO_H__
#define __RCDB_INFO_H__
#include <unistd.h>
#include <string>
/// @brief CRcdbInfo
/// 본 클래스는 conf 파일에 저장된 RCDB 접속 관련 정보를 저장하기 위한 공용 Class...
/// 본 클래스를 사용하지 않고... CServiceConfig class 의 Get 함수를 이용하여 RCDB 접속 정보를 처리해도 된다..
/// 하지만... RCDB 접속 처리 Class 함수의 string type 변수 사용으로 인해... 여러 모듈에서 반복적인 동일 코드 처리 작업을 수행해야 하므로...
/// 본 Class 를 통해 코드를 단순화 시킬 목적으로 사용한다.
class CRcdbInfo
{
public:
// 생성 및 소멸자.
CRcdbInfo();
~CRcdbInfo();
// CServiceConfig class 에 저장된 RCDB 접속 정보를 멤버 변수에 저장처리한다.
void Load( void );
public:
// 멤버 변수들은 DB 접속을 처리하는 객체에서 직접 Access 해서 사용할 수 있도록 public 으로 구성. ( 편의성 제공 목적)
std::string m_strRcdbIp; // RCDB IP
unsigned int m_nRcdbPort; // RCDB Port
std::string m_strRcdbName; // RCDB DB Name
std::string m_strRcdbAcct; // RCDB 접근 계정
std::string m_strRcdbAcctPw; // RCDB 접근 Password.
};
#endif /* __RCDB_INFO_H__ */
+388
View File
@@ -0,0 +1,388 @@
#include "SocketFimngd.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include <arpa/inet.h>
#include <string.h>
#include <iterator>
// 생성자
// param [in] clientSocket client 와 통신 연결된 socket.
CSocketFimngd::CSocketFimngd( int clientSocket )
: CBaseSocket( clientSocket )
, m_iPacketHeaderLen( sizeof( m_packetHeader ) )
, m_uPacketDataLen( 0 )
{
}
// 소멸자.
CSocketFimngd::~CSocketFimngd()
{
// 소멸자 Socket 명시적 Close 처리.
Close();
}
// Packet Header 부분의 수신 처리를 위한 함수
// param command [out] 수신된 command code
// param timeout [in] 대기시간.
// retrun 0 : timeout
// 1 : 성공
// -1 : socket 오류 또는 정의된 Data 가 아닌 경우.
int CSocketFimngd::GetPacketHeader( char & command, int timeout )
{
if( IsValidSocket() == false )
return -1;
// Socket 으로 부터 Packet Header 부분 수신.
// 수신된 정보는 멤버변수에 저장처리.
/// read 된 데이터의 크기. 0: fd closed, -1: 오류, -2: Timeout
int nRead = ReadNTimeout( &m_packetHeader, m_iPacketHeaderLen, timeout );
if( nRead == -2 )
{
// timeout 발생시
return 0;
}
else if( nRead <= 0 )
{
// timeout 을 제외한 그 밖의 오류 발생시
return -1;
}
else
{
// 정상 Data 수신시.
// STX code 검사.
if( m_packetHeader.stx != HEADER_STX_CODE_FIMNGD )
{
LOG( LERR, "Not valid stx code." );
PrintHeaderToLog();
return -1;
}
// Type Code 검사
if( m_packetHeader.type != HEADER_TYPE_REQUEST )
{
LOG( LERR, "Not valid type code." );
PrintHeaderToLog();
return -1;
}
// Data Length 부분 값을 멤버 변수에 저장처리.
m_uPacketDataLen = ntohl( m_packetHeader.data_length );
// command code 정보를 출력 param 에 저장 처리.
// 통신 정의상 1th 항목만 사용.
command = m_packetHeader.command[1];
return 1;
}
}
// Packet Header 정보를 Log 파일에 Logging 처리 ( Debug 처리를 위한 함수)
void CSocketFimngd::PrintHeaderToLog()
{
_LOG( LINF, "------------------------------------" );
_LOG( LINF, "stx [%02x]", m_packetHeader.stx );
_LOG( LINF, "type [%02x]", m_packetHeader.type );
_LOG( LINF, "command [%02x][%02x][%02x][%02x]", m_packetHeader.command[0], m_packetHeader.command[1], m_packetHeader.command[2], m_packetHeader.command[3] );
_LOG( LINF, "result [%02x][%02x][%02x][%02x]", m_packetHeader.result[0], m_packetHeader.result[1], m_packetHeader.result[2], m_packetHeader.result[3] );
_LOG( LINF, "data_length [%u]", m_uPacketDataLen );
_LOG( LINF, "protocol_version [%02x]", m_packetHeader.protocol_version );
_LOG( LINF, "extend_code [%02x]", m_packetHeader.extend_code );
_LOG( LINF, "------------------------------------" );
}
// COMMAND_SERVICE_NETWORK_STAT 요청 관련 Body 수신 처리.
// - protocol version 에 따른 처리 필요.
// param vecNetworkStat [out] 수신된 각 서비스별 network 통계 정보 저장
// return false socket 오류, 해당 protocol version 미지원, RC ID 불일치 등 비정상 상황.
// 그외 true
bool CSocketFimngd::GetPacketBodyNetworkStat( std::vector< struct ServiceNetworkStat > & vecStat )
{
std::string strErrorMessage;
// 지원 가능한 protocol version 인지 검사.
if( m_packetHeader.protocol_version > PARSE_MAX_PROTOCOL_VERSION_SERVICE_NETWORK )
{
// 2016-05-27 (#27106)
// fimngd 와의 통신 Protocol 버전을 확인하여
// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "Not support protocol version.";
SendResultNetworkStat( false, strErrorMessage );
}
// 파싱 불가능한 버전인 경우...
LOG( LERR, "Not support protocol version. [COMMAND_SERVICE_NETWORK_STAT] support[%02x] request[%02x]."
, PARSE_MAX_PROTOCOL_VERSION_SERVICE_NETWORK, m_packetHeader.protocol_version );
PrintHeaderToLog();
return false;
}
int nRead = 0;
// body header 정보 수신 처리.
PacketDataServiceNetworkStatHeader stHeader;
memset( &stHeader, 0x00, sizeof( struct PacketDataServiceNetworkStatHeader ) );
nRead = ReadNTimeout( &stHeader, sizeof( struct PacketDataServiceNetworkStatHeader ), 2 );
if( nRead == -2 )
{
// timeout 발생시
// - 지정된 시간 동안 Body Header 읽지 못했으므로. 오류로 처리
LOG( LERR, "fimngd network stat body header[%lu] read timeout.", sizeof( struct PacketDataServiceNetworkStatHeader ) );
// 2016-05-27 (#27106)
// fimngd 와의 통신 Protocol 버전을 확인하여
// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "network stat data recv timeout.";
SendResultNetworkStat( false, strErrorMessage );
}
return false;
}
else if( nRead <= 0 )
{
// timeout 을 제외한 그 밖의 오류 발생시
LOG( LERR, "fimngd network stat body read error[%d]", nRead );
// 2016-05-27 (#27106)
// fimngd 와의 통신 Protocol 버전을 확인하여
// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "network stat data recv error.";
SendResultNetworkStat( false, strErrorMessage );
}
return false;
}
// body header 수신이 정상인 경우...
// 1. RC ID 정보 비교 (대소문자 무시)
if( strcasecmp( CProcessConfig::GetInstance()->GetRcId(), stHeader.rcid ) != 0 )
{
LOG( LERR, "fimngd network stat error. RC ID not match. request [%s][%s]", stHeader.rcid, stHeader.hostname );
// 2016-05-27 (#27106)
// fimngd 와의 통신 Protocol 버전을 확인하여
// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "network stat data RC ID mismatch.";
SendResultNetworkStat( false, strErrorMessage );
}
return false;
}
// 2. 서비스 count 정보 변환 처리.
unsigned int uServiceCount = ntohl( stHeader.service_count );
// protocol version 별 파싱 처리.
// 2016-05-27 (#27106)
// Protocol 버전 0x00, 0x01 의 Data 구조는 동일함
// 다만 Protocol 버전이 0x01인 경우.. fimngd로 오류 내역 및 메시지 전송 처리하도록 기능 추가함.
if( m_packetHeader.protocol_version == 0x01 || m_packetHeader.protocol_version == 0x00 )
{
// 0x00, 0x01 버전이 동일한 Data 구조체이므로 동시 지원.
// 서비스별 Network stat 정보 수신 처리를 담당할 구조체 정의 및 초기화.
struct PacketDataServiceNetworkStat_V00 stBody;
memset( &stBody, 0x00, sizeof( struct PacketDataServiceNetworkStat_V00 ) );
// Body 부분 크기가.. 수신 처리할 구조체 크기와 동일한지 체크...
// - 안 할 경우.... body 크기가 저장 구조체 크기보다 큰 경우..
// - 본 모듈이 memory overflow 로 죽으므로... 방어코드 적용.
if( m_uPacketDataLen != sizeof( stBody ) * uServiceCount + sizeof( struct PacketDataServiceNetworkStatHeader ) )
{
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "network stat body size invalid.";
SendResultNetworkStat( false, strErrorMessage );
}
LOG( LERR, "Invalid body size. [COMMAND_SERVICE_NETWORK_STAT] version[%02x] real[%zu] packet header[%u]"
, m_packetHeader.protocol_version
, sizeof( stBody ) * uServiceCount + sizeof( struct PacketDataServiceNetworkStatHeader )
, m_uPacketDataLen );
PrintHeaderToLog();
return false;
}
struct ServiceNetworkStat stStat;
// uServiceCount 만큼 루프를 돌면서.. 통계 정보를 수신처리한 후.. output 인자에 저장처리한다.
for( unsigned int i = 0; i < uServiceCount; ++i )
{
nRead = ReadNTimeout( &stBody, sizeof( struct PacketDataServiceNetworkStat_V00 ), 1 );
if( nRead == -2 )
{
// timeout 발생시
// - 지정된 시간 동안 Header 에 기록된 Body 크기를 읽지 못했으므로. 오류로 처리
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "network stat data body recv timeout.";
SendResultNetworkStat( false, strErrorMessage );
}
LOG( LERR, "fimngd network stat[%u/%u] body[%lu] read timeout."
, i + 1, uServiceCount, sizeof( struct PacketDataServiceNetworkStat_V00 ) );
return false;
}
else if( nRead <= 0 )
{
// timeout 을 제외한 그 밖의 오류 발생시
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "network stat data body recv error.";
SendResultNetworkStat( false, strErrorMessage );
}
LOG( LERR, "fimngd network stat[%u/%u] body read error[%d]", i + 1, uServiceCount, nRead );
return false;
}
else
{
// 정상적으로 수신처리한 경우....
// output 인자에 저장 처리.
memset( &stStat, 0x00, sizeof( struct ServiceNetworkStat ) );
// header 에서 수신된 hostname 정보 복사.
strncpy( stStat.hostname, stHeader.hostname, FIMNGD_HOST_NAME_MAX_LENGTH );
// 수신된 통계 정보 복사
// - 향후 구조가 변경될 수 있어.. 개별 copy 처리함.
stStat.timestamp = stBody.timestamp;
stStat.user_seq = stBody.user_seq;
stStat.svc_seq = stBody.svc_seq;
stStat.down_content_size = stBody.down_content_size;
stStat.up_content_size = stBody.up_content_size;
stStat.down_traffic = stBody.down_traffic;
stStat.up_traffic = stBody.up_traffic;
// output vector 에 저장 처리.
vecStat.push_back( stStat );
}
}
// 2016-05-27 (#27106)
// fimngd 와의 통신 Protocol 버전을 확인하여
// 0x01 이상인 경우.. fimngd로 Data 수신 처리 성공 메시지 전송.
// 만약 fimngd 로 성공 메시지 전송이 실패할 경우....
// - fimngd 는 다시 재전송 시도 하므로.. 실패로 처리해야 한다.
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage.clear();
// fimngd 로 성공 응답코드 전송 실패시...
if( SendResultNetworkStat( true, strErrorMessage ) == false )
{
// fimngd 가 재전송을 수행하므로.. 이번 수신 data 는 버린다.
_LOG( LINF, "fimngd network stat reponse send fail. recv network stat data throw.. protocol[%02x].", m_packetHeader.protocol_version );
return false;
}
}
// 수신된 service network 통계 정보가 존재하는 경우만 true 반환.
// - data 도 없는데.. 왜 header 를 보내 -> 이거는 false 로 처리.
if( uServiceCount > 0 )
return true;
else
return false;
}
else
{
// 해당 버전에 대한 파싱 코드가 아직 없는 경우...
// 2016-05-27 (#27106)
// fimngd 와의 통신 Protocol 버전을 확인하여
// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
if( m_packetHeader.protocol_version > 0x00 )
{
strErrorMessage = "Not support protocol version parsing.";
SendResultNetworkStat( false, strErrorMessage );
}
LOG( LERR, "Not support protocol version parsing. [COMMAND_SERVICE_NETWORK_STAT] request[%02x].", m_packetHeader.protocol_version );
return false;
}
}
// NEW 2016-05-27 huibong
// - #27106 에 의해
// - fimngd 로 부터 Network 통계 수신시 Protocol version 0x01 이상이면.. 처리 결과를 전송하도록 기능 추가
// - bSuccess = true : 성공인 경우.. Packet header 에 성공으로 표시하여 전달. ( 성공시 추가 메시지는 전달 안함)
// = false : 실패인 경우.. Packet header 에 실패로 표시하고.. 전달할 메시지가 있는 경우.. 해당 정보까지 전달.
bool CSocketFimngd::SendResultNetworkStat( bool bSuccess, std::string& strMessage )
{
if( IsValidSocket() == false )
return false;
// 정보 전달을 위한 Packet Header 생성
struct PacketHeaderFimngd stHeader;
memset( &stHeader, 0x00, sizeof( struct PacketHeaderFimngd ) );
stHeader.stx = HEADER_STX_CODE_FIMNGD;
stHeader.type = HEADER_TYPE_RESPONSE;
// command code 정보를 출력 param 에 저장 처리.
// 통신 정의상 1th 항목만 사용.
stHeader.command[1] = COMMAND_SERVICE_NETWORK_STAT;
// 성공, 실패 여부 및 전송할 Body Size 정보 설정
if( bSuccess == true )
{
stHeader.result[0] = HEADER_RESULT_SUCCESS;
// 성공시에는 메시지 전송 안함.
stHeader.data_length = htonl( 0 );
}
else
{
stHeader.result[0] = HEADER_RESULT_ERROR;
// 실패시에만 메시지 전송한다.
stHeader.data_length = htonl( strMessage.size() );
}
// Protocol version
// - 수신된 protocol version 과 같도록
stHeader.protocol_version = m_packetHeader.protocol_version;
// Packet Header 정보 전송
if( WriteN( &stHeader, m_iPacketHeaderLen ) != m_iPacketHeaderLen )
{
LOG( LERR, "reponse [fimngd network stat] send fail to client" );
return false;
}
else
{
// 오류 발생 상황이고.. message 부분이 존재하는 경우.. 전송 처리.
if( bSuccess == false && strMessage.empty() == false )
{
WriteN( strMessage.c_str(), strMessage.size() );
}
}
return true;
}
+100
View File
@@ -0,0 +1,100 @@
/****************************************************************************
fimngd 와 data 송수신을 담당할 socket class
-----------------------------------------
begin : 2015/08/27
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __SOCKET_FIMNGD_H__
#define __SOCKET_FIMNGD_H__
#include "BaseSocket.h"
#include "ProtocolFimngd.h"
#include <vector>
// fimngd 로 부터 수신된 각 서비스별 network 통계 정보를 저장하기 위한 구조체
struct ServiceNetworkStat {
char hostname[FIMNGD_HOST_NAME_MAX_LENGTH + 1]; // hostname, 최대 63자까지만 허용.
uint32_t timestamp; // timestamp
int user_seq; // user sequence
int svc_seq; // service sequence
uint64_t down_content_size; // download content size
uint64_t up_content_size; // upload content size
uint64_t down_traffic; // download packet size
uint64_t up_traffic; // upload packet size
};
// CSocketFimngd
// fimngd 와 TCP 통신 및 data 송수신을 담당할 class.
class CSocketFimngd : public CBaseSocket
{
public:
// 생성자
// param [in] clientSocket client 와 통신 연결된 socket.
CSocketFimngd( int clientSocket );
// 소멸자
~CSocketFimngd();
private:
// Packet Header 변수
struct PacketHeaderFimngd m_packetHeader;
// m_packetHeader 구조체의 크기를 저장하기 위한 상수
const int m_iPacketHeaderLen;
// Packet Header 에 저장된 Data 부분의 길이 정보값.
unsigned int m_uPacketDataLen;
public:
// Packet Header 부분의 수신 처리를 위한 함수
// param command [out] 수신된 command code
// param timeout [in] 대기시간.
// retrun 0 : timeout
// 1 : 성공
// -1 : socket 오류 또는 정의된 Data 가 아닌 경우.
int GetPacketHeader( char & command, int timeout = DEFAULT_DATA_RECEIVE_TIMEOUT );
// Packet Header 정보를 Log 파일에 Logging 처리 ( Debug 처리를 위한 함수)
void PrintHeaderToLog( void );
// COMMAND_SERVICE_NETWORK_STAT 요청 관련 Body 수신 처리.
// - protocol version 에 따른 처리 필요.
// param vecNetworkStat [out] 수신된 각 서비스별 network 통계 정보 저장
// return false socket 오류, 해당 protocol version 미지원, RC ID 불일치 등 비정상 상황.
// 그외 true
bool GetPacketBodyNetworkStat( std::vector< struct ServiceNetworkStat > & vecStat );
// NEW 2016-05-27 huibong
// - #27106 에 의해
// - fimngd 로 부터 Network 통계 수신시 Protocol version 0x01 이상이면.. 처리 결과를 전송하도록 기능 추가
// - bSuccess = true : 성공인 경우.. Packet header 에 성공으로 표시하여 전달. ( 성공시 추가 메시지는 전달 안함)
// = false : 실패인 경우.. Packet header 에 실패로 표시하고.. 전달할 메시지가 있는 경우.. 해당 정보까지 전달.
bool SendResultNetworkStat( bool bSuccess, std::string& strMessage );
};
#endif /* __SOCKET_FIMNGD_H__ */
+298
View File
@@ -0,0 +1,298 @@
#include "StorageCorrectThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#define CORRECT_DELAY_TIME 40 // 단위 Min. (5분단위 값)
// 현재 시간 기준으로 지정된 값만큼 이전의 Storage 통계가 존재하는지 확인하여 없을 경우 보정 처리 수행.
#define SQLITE_DB_LOCK_TIMEOUT 3000 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 3 sec )
// 생성자..
CStorageCorrectThread::CStorageCorrectThread()
{
// Thread Handle 초기화
m_threadHandle = 0;
}
// 소멸자..
CStorageCorrectThread::~CStorageCorrectThread()
{
// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
if( m_threadHandle != 0 )
pthread_cancel(m_threadHandle);
}
// Thread 에 정의된 작업을 시작한다...
bool CStorageCorrectThread::Start()
{
int nResult = pthread_create( &m_threadHandle, NULL, CStorageCorrectThread::threadFunc, this );
if( nResult != 0 )
{
// Thread 생성 실패시...
int errorNum = errno;
LOG( LERR, "StorageCorrectThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) );
return false;
}
return true;
}
// Thread 함수...
void * CStorageCorrectThread::threadFunc( void * arg )
{
CStorageCorrectThread * pObject = reinterpret_cast<CStorageCorrectThread *>(arg);
pthread_detach( pthread_self() );
// 본 보정 작업은 아주 급한 보정 작업이 아니므로.... 천천히 수행시킨다.
sleep( 60 );
pthread_testcancel();
pObject->Execute();
pthread_testcancel();
// Thread 종료시 Handle 초기화
pObject->m_threadHandle = 0;
return NULL;
}
// timestamp 가 변경될 때마다...
// 지정된 예전 timestamp 에 해당하는 통계가 존재하는지 확인한 후....
// 존재하지 않을 경우... 그 이전 timestamp 값을 예전 timestamp 값에 저장하여 보정 처리한다.
void CStorageCorrectThread::Execute()
{
// 변수 선언 및 초기화..
time_t currentTime; // 현재 시간 정보를 저장하기 위한 변수..
unsigned int nowTimestamp; // 현재 시간의 timestamp 정보를 저장하기 위한 변수.
unsigned int lastCheckTimestamp = 0; // 마지막 처리한 timestamp 값을 저장하기 위한 변수.
// Local DB 관련 변수 초기화.
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
char szQuery[2048];
char * db_err;
const char * select_sql = "SELECT count(*) FROM storage_stat WHERE timestamp = ?" ;
// Logging
_LOG( LINF, "StorageCorrectThread: start...");
// 주기적으로 Storage 통계 정보가 빠져있는지 확인하여... 보정 처리를 수행한다.
while( 1 )
{
// 1. 현재 시간 정보를 추출하여 5분단위 timesamp 를 계산
// timesatmp 변경시마다 수행할 경우 다른 모듈과 sqlite 경쟁으로 인해 SQLITE_BUSY 상황 발생 (#21440)
// 이를 회피하기 위해 150 sec delay 처리함.
currentTime = time(0);
nowTimestamp = ((currentTime - 150 + 299)/300);
// 2. timestamp 가 변경되지 않은 경우..
if( nowTimestamp == lastCheckTimestamp )
{
sleep(15);
continue;
}
else
{
// 만약 시간 동기화 이상으로 이전에 계산된 timestamp 값이 잘못된 경우.... 보정처리..
if( nowTimestamp < lastCheckTimestamp )
{
LOG( LWAR, "StorageCorrectThread: Last timestamp not valid. skip and reset. now[%u] last[%u]", nowTimestamp, lastCheckTimestamp );
lastCheckTimestamp = 0;
sleep(60);
continue;
}
// 3. timestamp 가 정상적으로 변경된 경우..
// - 보정 작업을 진행할 timestamp 값을 확인한다.
unsigned int correctTimestamp = nowTimestamp - (CORRECT_DELAY_TIME / 5);
_LOG( LINF, "StorageCorrectThread: correct job start. timestamp now[%u] correct[%u].", nowTimestamp, correctTimestamp );
// 4. Local DB 와의 연결을 수행한다.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
if( db_result != SQLITE_OK )
{
LOG( LERR, "StorageCorrectThread: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
}
else
{
nRetryCount = 0;
// 보정 대상 Timestamp 에 대한 Storage 서비스 정보가 존재하는지 확인한다.
// bind 관련 Query 를 수행할 stmt 객체 생성.
db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < 20 )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "StorageCorrectThread: 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, "StorageCorrectThread: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
else
{
// SELECT 함수에 대한 매개변수 bind 처리.
sqlite3_bind_int ( stmt, 1, correctTimestamp );
// select query 수행.
db_result = sqlite3_step(stmt);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..20번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 20 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "StorageCorrectThread: 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, "StorageCorrectThread: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db));
// Local DB 오류 발생시.. DB 에 문제가 있는 것으로 보고.. DB 객체를 초기화한다.
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
else
{
// select Query 가 정상적으로 수행된 경우....
// 1 row 만 존재하므로...
db_result = sqlite3_column_int(stmt, 0);
// Local DB stmt 관련 해제 처리.
sqlite3_finalize(stmt);
stmt = NULL;
// 해당 timestamp 에 대한 조회 결과가 0 인 경우....
if( db_result == 0 )
{
// 보정 대상 Timestamp 에 대한 Storage 사용량 정보가 존재하지 않는 경우...
// - 이전 timestmap 값을 이용하여 Storage 사용량 정보를 보정 처리한다. ( SELECT -> INSERT )
snprintf( szQuery, 2047,
"INSERT INTO storage_stat( timestamp, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, insert_time, send_flag) "
"SELECT %u, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, datetime('now', 'localtime'), 0 "
"FROM storage_stat "
"WHERE timestamp = (%u - 1) "
, correctTimestamp, correctTimestamp );
// sqlite 에.. Query 수행.
db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..30번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 30 )
{
sqlite3_free(db_err);
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "StorageCorrectThread: local db insert sqlite3_exec SQLITE_BUSY. Retry [%d]",nRetryCount );
// Query 재수행
db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
}
if( db_result != SQLITE_OK )
{
_LOG( LDBG, "StorageCorrectThread: correct job local db insert failed. [%u][%s]", correctTimestamp, db_err );
sqlite3_free(db_err);
}
else
{
_LOG( LINF, "StorageCorrectThread: correct job end. timestamp[%u], [%d]row insert.", correctTimestamp, sqlite3_changes(db) );
}
}
else
{
_LOG( LINF, "StorageCorrectThread: correct job timestamp[%u], [%d]row already exist..", correctTimestamp, db_result );
}
}
}
}
// Local DB 연결 해제.
if( stmt != NULL )
{
sqlite3_finalize(stmt);
stmt = NULL;
}
if( db != NULL )
{
sqlite3_close(db);
db = NULL;
}
//_LOG( LINF, "StorageCorrectThread: correct job end. timestamp now[%u] correct[%u].", nowTimestamp, correctTimestamp );
lastCheckTimestamp = nowTimestamp;
sleep(60);
}
}
return;
}
+56
View File
@@ -0,0 +1,56 @@
/****************************************************************************
Service storage used size correct Thread Class
-----------------------------------------
begin : 2015/08/31
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __STORAGE_CORRECT_THREAD_H__
#define __STORAGE_CORRECT_THREAD_H__
#include <pthread.h>
#include <unistd.h>
/// @brief CStorageCorrectThread
/// 본 클래스는 Thread 클래스로서...
/// Local DB 에 저장된 5분단위 Storage 사용량 정보 중....
/// rc_du 또는 증감 Scan 작업 시간이 오래 걸려 Storage 사용량 정보가 누락된 timestamp 를 찾아내어...
/// 이를 보정하는 역활을 담당한다.
class CStorageCorrectThread
{
public:
// 생성 및 소멸자.
CStorageCorrectThread();
~CStorageCorrectThread();
// Thread 기동 함수.
bool Start( void );
private:
// Thread function....
static void * threadFunc( void * arg );
// 실제 Local DB 에 저장된 Storage 사용량 정보를 확인하여 빠진 Data 를 보정 처리한다.
void Execute( void );
private:
// 쓰레드 핸들
pthread_t m_threadHandle;
};
#endif /* __STORAGE_CORRECT_THREAD_H__ */
+439
View File
@@ -0,0 +1,439 @@
#include "StorageSendThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#define SQLITE_DB_LOCK_TIMEOUT 1100 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1.1 sec)
// 생성자..
CStorageSendThread::CStorageSendThread()
{
// Thread Handle 초기화
m_threadHandle = 0;
}
// 소멸자..
CStorageSendThread::~CStorageSendThread()
{
// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
if( m_threadHandle != 0 )
pthread_cancel(m_threadHandle);
}
// Thread 를 생성하여 작업을 시작한다...
bool CStorageSendThread::Start()
{
int nResult = pthread_create( &m_threadHandle, NULL, CStorageSendThread::threadFunc, this );
if( nResult != 0 )
{
// Thread 생성 실패시...
int errorNum = errno;
LOG( LERR, "StorageSendThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) );
return false;
}
return true;
}
// Thread 함수...
void * CStorageSendThread::threadFunc( void * arg )
{
CStorageSendThread * pObject = reinterpret_cast<CStorageSendThread *>(arg);
pthread_detach( pthread_self() );
// 초기 기동시...통계 정보가 바로 DB 에 저장되지 않은 상태이므로...
// 잠시 sleep 했다가.. 작업을 시작한다.
sleep( 60 );
// 본 Thread 에서는 Local DB 에 저장된 서비스별 Storage 통계 정보를
// 주기적으로 select 하여... GTS cc_statd 모듈로 전송처리한다.
// 실제 본 thread function 에서 loop 를 동작하는 것이 아니라....
// 객체 execute 함수 내에서 loop 로 동작하도록 한다.
pthread_testcancel();
pObject->Execute();
pthread_testcancel();
// Thread 종료시 Handle 초기화
pObject->m_threadHandle = 0;
return NULL;
}
// Local DB 에 저장된 서비스 별 Storage 통계 정보를 계산하여... 주기적으로 GTS cc_statd 에 전송 작업을 반복 수행한다...
void CStorageSendThread::Execute()
{
// 변수 선언 및 초기화..
time_t currentTime; // 현재 시간 정보를 저장하기 위한 변수..
unsigned long nowTimestamp;
unsigned long lastSendTimestamp = 0; // cc_statd 로 전송 시도한 마지막 timestamp 값 (5분단위)
// Local DB 관련 변수 초기화.
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
// 기준 timestamp 값을 조건으로.. cc_statd 로 전송되지 않은 서비스별 통계 정보를 모두 조회하여... 전송 처리한다.
// cc_statd 로 전송 완료된 통계 정보는 send_flag 값이 1 로 설정, 전송 무시 처리인 경우 2로 설정됨.
// CHG 2014-02-17 huibong
// - 한번에 많은 양의 통계 정보를 GTS 로 전송할 경우.. GTS cc_statd 모듈의 메모리 사용량이 증가할 수 있으므로...
// - 한번에 1000 개 통계정보만 전송되도록 수정한다.
const char * select_sql = "SELECT timestamp, user_seq, svc_seq, quota, used "
"FROM storage_stat "
"WHERE timestamp <= ? AND send_flag = 0 "
"ORDER BY timestamp ASC, user_seq ASC, svc_seq ASC "
"LIMIT 1000 ";
// Local DB 와 연결을 수행한다.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "StorageSendThread: 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, "StorageSendThread: 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, "StorageSendThread: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
}
// Local DB 조회 정보를 저장하기 위한 구조체...
struct gts_send_stat_storage stStorageStat;
// Logging
_LOG( LINF, "StorageSendThread: start...");
// 루프를 돌면서... Local DB 의 Storage 통계 정보를 조회하여... 주기적으로 cc_statd 에 전송한다.
while( 1 )
{
// cc_statd 로 전송을 수행할 것인지.. timestatmp 값을 계산하여... 확인한다.
// 1. 현재 시간 정보 추출..
currentTime = time(0);
// 2. 현재 timestamp 값을 계산한다...
// 계산시.. 스토리지 사용량 계산 관련 시간이 오래 걸리수 있으므로....
// 240 sec 보정 처리하여 5분 단위 timestamp 값을 추출한다.
nowTimestamp = (currentTime - 240 + 299)/300;
// 3. 최초 기동 또는 시간 동기화 이상으로 reset 처리된 경우... lastSendTimestamp = 0
// 트래픽 통계 정보를 합산하여 바로 전송하면... timestamp 관련 중복 등의 문제가 발생할 수 있으므로...
// 5분 후에 이전 통계 정보까지 전달될 수 있도록 한다.
if( lastSendTimestamp == 0 )
{
_LOG( LDBG, "StorageSendThread: first try... send skip..now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
lastSendTimestamp = nowTimestamp;
sleep(30);
continue;
}
// 4. 마지막 timestamp 값과 현재 timestamp 계산 값이 같은 경우...
// 이미 전송한 것으로 판단하고.. 쉰다.
if( nowTimestamp == lastSendTimestamp )
{
//_LOG( LDBG, "NetworkSendThread: send check.. skip.. now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
// 10 sec 대기 후 timestamp 변경이 되었는지 다시 계산하도록 처리...
// - 원래 코드는 1 마다 검사였으나..
// - 이럴 경우.. 순간적으로 여러 rc_sscd 에서 cc_statd 로 통계 정보가 일괄 전송 되어
// - cc_statd 에 부하가 발생될 확률이 높으므로.. 다음과 같이 10 sec 단위로 체크토록 변경한다.
sleep(10);
continue;
}
else
{
// 만약 lastSendTimestamp > nowTimestamp 인 경우...
// - 시간 동기화 이상으로 이전에 계산된 timestamp 값이 잘못된 경우.... 보정처리..
if( nowTimestamp < lastSendTimestamp )
{
LOG( LWAR, "StorageSendThread: Last timestamp not valid. skip and reset. now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
lastSendTimestamp = 0;
sleep(30);
continue;
}
}
_LOG( LDBG, "StorageSendThread: stats info select & send start... now[%lu] last[%lu]", nowTimestamp, lastSendTimestamp );
// 이제 전송작업을 수행할 것이지만.... 실제 전송 실패가 발생할 수도 있다...
// 따라서... lastSendTimestamp 값을 변경하지 않을 경우.... while 루프에 의해 금방 재시도를 하므로....
// 아예 5분 지난 timestamp 변경 후.. 다시 시도하도록 lastSendTimestamp 값을 현재 값으로 변경 처리한다.
lastSendTimestamp = nowTimestamp;
// Local DB 조회 정보를 저장할 vector 초기화.
m_vecStorageStat.clear();
// 이제 실제 전송 관련 처리 작업을 수행한다.
// Local DB 연결이 되지 않은 경우...
if( db == NULL )
{
_LOG( LINF, "StorageSendThread: sqlite3 open retry.[%s]", CProcessConfig::GetInstance()->GetLocalDbFileName() );
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "StorageSendThread: sqlite3 open failed2.[%s][%d][%s]"
, CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
sqlite3_close(db);
db = NULL;
}
else
{
nRetryCount = 0;
// bind 관련 Query 를 수행할 stmt 객체 생성.
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, "StorageSendThread: 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, "StorageSendThread: sqlite3 prepare failed2.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
}
}
// sqlite 접속이 정상적으로 된 경우...
if( stmt != NULL )
{
// 매개변수 bind 처리.
sqlite3_bind_int64( stmt, 1, nowTimestamp ); // 64bit 에서 unsigned long 변수는 8Byte 이므로..
// select query 수행.
db_result = sqlite3_step(stmt);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..30번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 30 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "StorageSendThread: 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, "StorageSendThread: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db));
// Local DB 오류 발생시.. DB 에 문제가 있는 것으로 보고.. DB 객체를 초기화한다.
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
else
{
// select Query 가 정상적으로 수행된 경우....
while( db_result == SQLITE_ROW )
{
// 임시 저장변수 초기화.
memset( &stStorageStat, 0x00, sizeof(struct gts_send_stat_storage) );
// 조회 결과를 임시 변수에 저장..
stStorageStat.timestamp = sqlite3_column_int (stmt, 0);
stStorageStat.user_seq = sqlite3_column_int (stmt, 1);
stStorageStat.svc_seq = sqlite3_column_int (stmt, 2);
stStorageStat.quota = sqlite3_column_int64(stmt, 3);
stStorageStat.used = sqlite3_column_int64(stmt, 4);
stStorageStat.send_flag = 0;
_LOG( LDBG, "StorageSendThread: select result [%u][%d][%d] [%lu][%lu]"
, stStorageStat.timestamp, stStorageStat.user_seq, stStorageStat.svc_seq
, stStorageStat.quota, stStorageStat.used );
// vector 객체에 query 결과를 저장한다.
m_vecStorageStat.push_back( stStorageStat );
// Next 결과 처리..
db_result = sqlite3_step( stmt );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..60번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 60 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "StorageSendThread: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount );
// query 재시도
db_result = sqlite3_step(stmt);
}
}
// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset(stmt);
}
}
// cc_statd 로 전송할 Data 가 존재하는 경우...
if( m_vecStorageStat.empty() == false )
{
// GTS 로 Data 전송을 담당하는 Socket 객체에 Data 를 전달하여...
// 해당 Socket 객체에서 Data 를 전달하도록 처리한다.
m_socketGts.SendStatStorage( m_vecStorageStat );
// 전송 처리 작업 호출 후....
// 전송 결과를 확인하여... 전송이 완료된 정보에 대해 Local DB 의 send_flag, send_time 정보를 변경처리한다.
// - local DB 에서 조회 결과가 있어야만... m_vecStorageStat 의 값이 존재하므로. local db 연결 확인은 따로 하지 않아도 됨.
char update_sql[1024];
char * db_err;
std::vector<struct gts_send_stat_storage>::iterator it;
for( it = m_vecStorageStat.begin(); it != m_vecStorageStat.end(); ++it )
{
// GTS 로 전송 처리 또는 전송 무시 처리된 경우.. ( it->send_flag 가 1 or 2 인 경우.. )
if( it->send_flag != 0 )
{
// GTS 로 전송 완료된 Local DB 항목에 대해 전송 완료 flag 값을 변경하기 위한 Query.
snprintf( update_sql, 1023,
"UPDATE storage_stat "
"SET send_flag = %d, send_time = datetime('now', 'localtime') "
"WHERE timestamp = %u AND user_seq = %d AND svc_seq = %d AND send_flag = 0 "
, it->send_flag, it->timestamp, it->user_seq, it->svc_seq );
// Query 수행...
db_result = sqlite3_exec(db, update_sql, NULL, NULL, &db_err);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면.. 40번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 40 )
{
sqlite3_free(db_err);
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "StorageSendThread: local db update sqlite3_exec SQLITE_BUSY. Retry [%d]",nRetryCount );
// Query 재수행
db_result = sqlite3_exec(db, update_sql, NULL, NULL, &db_err);
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "StorageSendThread: local db send_flag update fail.[%s][%s]", update_sql, db_err );
sqlite3_free(db_err);
}
_LOG( LINF, "Storage stats send to GTS OK [%u] [%d][%d] [%lu / %lu]"
, it->timestamp, it->user_seq, it->svc_seq, it->used, it->quota );
}
}
}
else
{
// 전송할 Data 가 없는 경우.. 로깅 처리..
LOG( LINF, "StorageSendThread: Storage stats data is empty. Not send to GTS ..");
}
// GTS cc_statd 로 통계 정보 전송 처리 후.. 불필요하게 loop 를 동작시킬 필요가 없으므로....
// 잠깐 sleep 한다.
sleep(120);
}
// 종료 처리 코드
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
m_vecStorageStat.clear();
return;
}
+63
View File
@@ -0,0 +1,63 @@
/****************************************************************************
Thread class for Storage stats send to cc_stat
-----------------------------------------
begin : 2015/09/01
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __STORAGE_SEND_THREAD_H__
#define __STORAGE_SEND_THREAD_H__
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include "GtsSendSocket.h"
/// @brief CStorageSendThread
/// 본 클래스는 Thread 클래스로서...
/// 서비스별 Storage 통계 정보를 GTS 의 cc_statd 에 주기적으로 전송하는 기능을 수행한다.
class CStorageSendThread
{
public:
// 생성 및 소멸자.
CStorageSendThread();
~CStorageSendThread();
// 전송 작업 시작
bool Start( void );
private:
// Thread function....
static void * threadFunc( void * arg );
// 실제 정보 전송을 담당하는 함수...
void Execute( void );
private:
// 쓰레드 핸들
pthread_t m_threadHandle;
// DB 조회 정보를 저장할 Vector 객체
std::vector<struct gts_send_stat_storage> m_vecStorageStat;
// cc_statd 로 Data 전송을 담당할 Socket 처리 Class.
CGtsSendSocket m_socketGts;
};
#endif /* __STORAGE_SEND_THREAD_H__ */
File diff suppressed because it is too large Load Diff
+111
View File
@@ -0,0 +1,111 @@
/****************************************************************************
Thread class for storage used calcutator per service
-----------------------------------------
begin : 2015/08/31
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __STORAGE_USED_CALC_THREAD_H__
#define __STORAGE_USED_CALC_THREAD_H__
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include "RcdbInfo.h"
#include "Database.h"
// 각 서비스별 계산된 Storage 사용량 정보를 저장하기 위한 Struct....
struct storage_used_info {
unsigned int timestamp; // 5분 단위 timestamp 값 저장...
int user_seq; // 고객사 sequence 정보..
int svc_seq; // 서비스 sequence 정보.
unsigned long long quota; // 할당량 (Byte)
unsigned long long used; // 사용량 (Byte)
unsigned long long check_resource_id; // 조회 기준 resource_id 값.
unsigned long long check_get_lastmodified; // 조회 기준 get_lastmodifed 값.
};
/// @brief CStorageUsedCalcThread
/// 본 클래스는 Thread 클래스로서...
/// RCDB 정보를 기준으로 서비스별 스토리지 사용량 정보를 주기적으로 계산하여... RCDB 및 Local DB 에 저장하는 역활을 수행한다.
class CStorageUsedCalcThread
{
public:
// 생성 및 소멸자.
CStorageUsedCalcThread();
~CStorageUsedCalcThread();
// 업무 Flow 작업 시작
bool Start( void );
private:
// Thread function....
static void * threadFunc( void * arg );
// 실제 작업 수행을 담당하는 함수
void Execute( void );
// Full Scan 을 담당하는 rc_du 를 실행시킨다.
bool ExecuteFullScanProcess();
// RCDB 에 저장된 full scan 실행 결과 정보를 조회하여 내부 객체인 m_vecStorage 에 저장 처리한다.
bool GetFullScanResult( CRcdbInfo & rcdbInfo, unsigned int nowTimestamp );
// RCDB t_sms_sp_svc_product 테이블로 부터 서비스 정보를 추출하여.. 멤버변수 m_vecStorage 에 저장한다.
// return value : -1 : RCDB Query 수행 등의 오류 발생시
// 0 : 처리할 서비스 정보가 존재하지 않는 경우.
// 양수 : 처리할 서비스의 갯수
int GetServiceInfoFromRCDB( DataBase & rcdb );
// 멤버 변수 m_vecStorage 에 저장된 서비스에 대하여....
// Local db storage_stat 테이블에 저장된 마지막 스토리지 사용량, partial scan 관련 정보 값을 조회하여 저장 처리한다.
// return value : -1 : Local DB 접속 실패 등의 오류 발생시...
// 0 : 처리할 서비스 정보가 존재하지 않는 경우.
// 양수 : 처리할 서비스의 갯수
int GetLastStorageStatFromLocal();
// 멤버 변수 m_vecStorage 에 저장된 서비스별 Storage 사용량 정보를 Local DB 에 저장 처리한다.
bool SaveStorageStatToLocal( unsigned int nowTimestamp );
// 멤버 변수 m_vecStorage 에 저장된 서비스별 Storage 사용량 정보를 RCDB 에 저장 처리한다.
bool SaveStorageStatToRCDB( DataBase & rcdb, unsigned int nowTimestamp );
// 멤버 객체 m_vecStorage 에 저장된 서비스별 스트리지 사용량을 확인하여.. 지정된 임계치가 초과할 경우. report 로그에 기록 처리한다.
void CheckQuotaForReport( unsigned int nowTimestamp );
private:
// 쓰레드 핸들
pthread_t m_threadHandle;
// DB 조회 정보를 저장할 Vector 객체
std::vector<struct storage_used_info> m_vecStorage;
};
#endif /* __STORAGE_USED_CALC_THREAD_H__ */
+495
View File
@@ -0,0 +1,495 @@
#include "TrafficControlThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include "RcdbInfo.h"
#include "Database.h"
#include "ReportLog.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>
// 네트워크 통계 모듈의 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<CTrafficControlThread *>(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<struct traffic_control_info>::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;
}
+84
View File
@@ -0,0 +1,84 @@
/****************************************************************************
Thread class for Service Traffic Control
-----------------------------------------
begin : 2013/11/20
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __TRAFFIC_CONTROL_THREAD_H__
#define __TRAFFIC_CONTROL_THREAD_H__
#include <pthread.h>
#include <unistd.h>
#include <vector>
// 각 서비스별 트래픽 제어 관련 정보를 저장하기 위한 변수...
//
struct traffic_control_info {
int user_seq; // 고객사 sequence 정보..
int svc_seq; // 서비스 sequence 정보.
unsigned long long limit; // 제한 트래픽 정보 ( bps 단위 )
unsigned long long previous_traffic; // RCDB t_sms_sp_svc_product_band 테이블 volume_bandwidth_current 항목에
// 저장되었던 이전 traffic 정보. (bps 단위 )
unsigned long long current_traffic; // 각 FHS 로 부터 전달받은 합산된 traffic 정보. (bps 단위)
unsigned int previous_control_precent; // RCDB t_sms_sp_svc_product_band 테이블 volume_bandwidth_control_current 항목에 저장되었던...
// 트래픽 제어 Percent 정보. (정수형 처리를 위해 99.99 % -> 9999 로 형식으로 처리 )
unsigned int current_control_precent; // 현재 통계 정보를 바탕으로 계산된.. 트래픽 제어 percent 정보.
// (정수형 처리를 위해 99.99 % -> 9999 로 형식으로 처리)
};
/// @brief CTrafficControlThread
/// 본 클래스는 Thread 클래스로서...
/// 서비스별 5분단위 Network 통계 정보 바탕으로... RCDB t_sms_sp_svc_product_band 테이블에.. 서비스 Traffic 제어 정보를 갱신하여...
/// opendav-listener 모듈에서 해당 정보를 주기적으로 갱신하고... opendav 모듈에서 전송 처리시 제어 정보를 사용되도록 한다.
class CTrafficControlThread
{
public:
// 생성 및 소멸자.
CTrafficControlThread();
~CTrafficControlThread();
// 작업 시작
bool Start( void );
private:
// Thread function....
static void * threadFunc( void * arg );
// 실제 트래픽 제어 정보 갱신을 담당하는 함수...
void Execute( void );
private:
// 쓰레드 핸들
pthread_t m_threadHandle;
// DB 조회 정보를 저장할 Vector 객체
std::vector<struct traffic_control_info> m_vecTraffic;
};
#endif /* __TRAFFIC_CONTROL_THREAD_H__ */
+183
View File
@@ -0,0 +1,183 @@
#include "WorkerControl.h"
#include "Logger.h"
#include "ProcessRename.h"
#include "ProcessConfig.h"
#include "LocalDbClearThread.h"
#include "StorageUsedCalcThread.h"
#include "StorageCorrectThread.h"
#include "TrafficControlThread.h"
#include "NetworkSendThread.h"
#include "StorageSendThread.h"
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <time.h>
// FHS 의 opendav-listener 로 부터 서비스 관련 통계 (Network, 세션등의 통계 등)정보를 수신하기 위한 UDP socket...
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockDavListenerRecv;
// FHS fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockServiceStatRecv;
// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerControl()
{
// Logger 객체 종료 처리.
CLogger::Exit();
return;
}
// Worker Control 프로세스에 대한 대한 종료 처리 수신시.
static void SignalWorkerControlTerminate( int nSignalNumber )
{
// Signal Number 에 따른 로깅처리.
if( nSignalNumber == SIGTERM )
{
_LOG( LINF, "[Control:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
}
else
{
_LOG( LWAR, "[Control:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
}
ReadyToExitWorkerControl();
// 종료전 잠시 대기
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = 500000000; // 0.5 sec
nanosleep( &sleep, NULL );
exit( EXIT_SUCCESS );
}
// Worker::Control Process 의 signal 처리기...
void SetWorkerControlSignalHandler()
{
sigset_t set;
struct sigaction act;
memset( &act, 0x00, sizeof( act ) );
sigfillset( &set );
sigprocmask( SIG_SETMASK, &set, NULL );
sigfillset( &act.sa_mask );
// 무시 처리 signal
act.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &act, NULL ); /* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
sigaction( SIGHUP, &act, NULL ); /* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
sigaction( SIGINT, &act, NULL ); /* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
sigaction( SIGQUIT, &act, NULL ); /* 키보드에 의한 Abort 신호 처리 => ? */
// Worker Control 의 child process 종료에 대한 처리기 설정.
// -> Storage 사용량 계산 관련하여.. 외부 프로세스인 rc_du 를 system 명령으로 수행함.
// -> 이에 따라.. 시그널 처리기를 설정할 경우...
// 상황에 따라서.. 시그널 처리기에서 system() 함수에서 발생된 시그널을 먼저 Catch 하여... system() 함수 반환값이 -1이 되는 상황이 발생함.
// -> 따라서 다음과 같이 default 처리토록 변경한다.
act.sa_handler = SIG_DFL;
sigaction( SIGCHLD, &act, NULL );
/* 각종 에러나 사용자의 종료 신호 처리 */
act.sa_handler = SignalWorkerControlTerminate;
sigaction( SIGTERM, &act, NULL ); /* kill -TERM 에 의한 프로세스 종료시 */
// 나머지 신호는 default 처리.
sigemptyset( &set ); /* 신호 처리기 처리 설정 위한 블록 해제 */
sigprocmask( SIG_SETMASK, &set, NULL );
}
// Worker::Control 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerControlMain()
{
// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.
if( g_sockServiceStatRecv != -1 )
{
close( g_sockServiceStatRecv );
g_sockServiceStatRecv = -1;
}
// Process Rename..
set_ps_display( PROG_NAME": Control", false );
// Set Signal Handler
SetWorkerControlSignalHandler();
// 사용할 객체 초기화 처리
// 1. Local sqlite3 DB 를 주기적으로 정리하는 Thread 를 생성후 기동한다.
CLocalDbClearThread threadClearLocalDb;
if( threadClearLocalDb.Start() == false )
{
_LOG( LWAR, "[Control:%d] Local db clear thread create failed. => Process Exit", getpid() );
return EXIT_FAILURE; // EXIT_FAILURE 를 반환하여 재생성 처리 방지...
}
// 2. 서비스별 Storage 사용량 정보 계산 처리를 담당할 Thread 를 생성 후 기동한다.
CStorageUsedCalcThread threadStorageUsedCalc;
if( threadStorageUsedCalc.Start() == false )
{
_LOG( LWAR, "[Control:%d] Storage used calc thread create failed. => Process Exit", getpid() );
return EXIT_FAILURE; // EXIT_FAILURE 를 반환하여 재생성 처리 방지...
}
// 3. Storage 통계 Data 에 대한 보정 처리 Thread 를 기동시킨다.
CStorageCorrectThread threadCorrectStorage;
if( threadCorrectStorage.Start() == false )
{
_LOG( LWAR, "[Control:%d] Storage stat correct thread create failed. => Process Exit", getpid() );
return EXIT_FAILURE; // EXIT_FAILURE 를 반환하여 재생성 처리 방지...
}
// 4. 서비스별 Network 통계 정보를 바탕으로 트래픽 제어 정보를 주기적으로 재계산하여 RCDB 에 업데이트하는 Thread 를 생성 후 기동한다.
CTrafficControlThread threadTrafficControl;
if( threadTrafficControl.Start() == false )
{
_LOG( LWAR, "[Control:%d] Traffic control thread create failed. => Process Exit", getpid() );
return EXIT_FAILURE; // EXIT_FAILURE 를 반환하여 재생성 처리 방지...
}
// 5. 서비스별 Network 통계 정보를 주기적으로 GTS cc_statd 로 전송하기 위한 Thread 를 생성 후 기동한다.
CNetworkSendThread threadSendNetworkToGts;
if( threadSendNetworkToGts.Start() == false )
{
_LOG( LWAR, "[Control:%d] Network stats send to GTS thread create failed. => Process Exit", getpid() );
return EXIT_FAILURE; // EXIT_FAILURE 를 반환하여 재생성 처리 방지...
}
// 6. 서비스별 Storage 통계 정보를 GTS 로 전송.
CStorageSendThread threadSendStorageToGts;
if( threadSendStorageToGts.Start() == false )
{
_LOG( LWAR, "[Control:%d] Storage stats send to GTS thread create failed. = > Process Exit", getpid() );
return EXIT_FAILURE; // EXIT_FAILURE 를 반환하여 재생성 처리 방지...
}
// 대기
while( 1 )
{
// 할일 없어서.. 시그널 수신까지 대기 한다.
pause();
}
// 종료시 정리 작업 수행
ReadyToExitWorkerControl();
return EXIT_SUCCESS;
}
+36
View File
@@ -0,0 +1,36 @@
/****************************************************************************
Worker::Control process main
-----------------------------------------
begin : 2015/08/25
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __WORKER_CONTROL_H__
#define __WORKER_CONTROL_H__
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
// Worker::Control 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerControlMain( void );
#ifdef __cplusplus
}
#endif
#endif /* __WORKER_CONTROL_H__ */
+654
View File
@@ -0,0 +1,654 @@
#include "WorkerRecvDavListener.h"
#include "Logger.h"
#include "ProcessRename.h"
#include "ProcessConfig.h"
#include "FixedQueue.h"
#include "ProtocolOpendavListener.h"
#include "ReportLog.h"
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <sqlite3.h>
// 수신 처리를 담당할 Thread 의 갯수
#define MAX_THREAD_RECEIVER 10
// opendav-listener 로 부터 전달받은 Data 를 파싱 thread 에 전달시 사용할 Queue 의 최대 갯수
#define MAX_DATA_QUEUE_COUNT 3000
// opendav-listener 가 전송한 서비스 통계 정보를 수신하기 위한 buffer size
// header 352byte + int 4byte + body 272byte => 628 byte
// 따라서 향후 확장으로 고려하여 구형 rc_statd(3.4 이전) 9216 Byte -> 1024 Byte 로 변경함.
#define MAX_SOCKET_BUFFER_SIZE 1024
// sqlite SQLITE_BUSY 잦은 발생으로 INSERT 지연 발생, 이로 인해 CCDB 통계 누락 발생 (#21440)
// - 이를 해결하기 위해 lock timeout 값을 3 sec -> 0.6 sec 로 변경 처리
// - 이와 더불어 Lock 발생시 재시도 횟수를 30 -> 60 으로 변경 처리
#define SQLITE_DB_LOCK_TIMEOUT 600 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 0.6 sec )
#define SQLITE_DB_LOCK_MAX_RETRY 60 // SQLITE_BUSY 발생시 재시도 최대 횟수
// FHS 의 opendav-listener 로 부터 서비스 관련 통계 (Network, 세션등의 통계 등)정보를 수신하기 위한 UDP socket...
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockDavListenerRecv;
// FHS fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockServiceStatRecv;
// Data 수신부와 이를 파싱하여 처리할 Thread 객체간에 Data 전달을 위한 Queue 객체 생성
CFixedQueue * g_pRecvDataQueue = NULL;
// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerRecvDavListener()
{
// open 된 socket 관련 close 처리
if( g_sockDavListenerRecv != -1 )
close( g_sockDavListenerRecv );
// Queue 객체 해제
if( g_pRecvDataQueue != NULL )
{
CFixedQueue * temp = g_pRecvDataQueue;
g_pRecvDataQueue = NULL;
delete temp;
}
// Logger 객체 종료 처리.
CLogger::Exit();
return;
}
// Worker RecvDavListener 프로세스에 대한 대한 종료 처리 수신시.
static void SignalWorkerRecvDavListenerTerminate( int nSignalNumber )
{
// Signal Number 에 따른 로깅처리.
if( nSignalNumber == SIGTERM )
{
_LOG( LINF, "[RecvDavListener:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
}
else
{
_LOG( LWAR, "[RecvDavListener:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
}
ReadyToExitWorkerRecvDavListener();
// 종료전 잠시 대기
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = 500000000; // 0.5 sec
nanosleep( &sleep, NULL );
exit( EXIT_SUCCESS );
}
// Worker::RecvDavListener Process 의 signal 처리기...
void SetWorkerRecvDavListenerSignalHandler()
{
sigset_t set;
struct sigaction act;
memset( &act, 0x00, sizeof( act ) );
sigfillset( &set );
sigprocmask( SIG_SETMASK, &set, NULL );
sigfillset( &act.sa_mask );
// 무시 처리 signal
act.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &act, NULL ); /* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
sigaction( SIGHUP, &act, NULL ); /* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
sigaction( SIGINT, &act, NULL ); /* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
sigaction( SIGQUIT, &act, NULL ); /* 키보드에 의한 Abort 신호 처리 => ? */
// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
act.sa_handler = SIG_IGN;
sigaction( SIGCHLD, &act, NULL );
/* 각종 에러나 사용자의 종료 신호 처리 */
act.sa_handler = SignalWorkerRecvDavListenerTerminate;
sigaction( SIGTERM, &act, NULL ); /* kill -TERM 에 의한 프로세스 종료시 */
// 나머지 신호는 default 처리.
sigemptyset( &set ); /* 신호 처리기 처리 설정 위한 블록 해제 */
sigprocmask( SIG_SETMASK, &set, NULL );
}
// 340.0@278 형식의 서비스 ID 정보를 user_seq, svc_seq 정보를 파싱처리하기 위한 함수.
int ParseServiceId( const char * org, char * szUserSeq, char * szSvcSeq )
{
const char * p = org;
// 앞 부분인 svc_seq 부분 추출...
while( *p != '.' && *p != '@' && *p != '\0' )
p++;
if( *p == '\0' )
return -1;
strncpy( szSvcSeq, org, ( p - org ) );
*( szSvcSeq + ( p - org ) ) = '\0';
if( *p == '.' )
{
// 중간 부분인 class id 는 안 쓰므로 제외하고...
while( *p != '@' && *p != '\0' )
p++;
if( *p == '\0' )
return -1;
}
// @ 뒤에 있는 마지막 user_seq 부분을 추출한다.
p = p + 1;
if( *p == '\0' )
return -1;
strcpy( szUserSeq, p );
return 0;
}
// 전역 Queue 에 저장된 opendav listener network 통계 수신 data 를 파싱, 저장 처리를 담당하는 Thread
void * WorkerRecvDavListenerSaveThread( void * arg )
{
// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
pthread_detach( pthread_self() );
_LOG( LINF, "[RecvDavListener:%d] SaveThread: start...", getpid() );
// Queue pop data 저장 관련 변수 초기화.
int pop_result;
char szTempBuffer[MAX_SOCKET_BUFFER_SIZE];
struct opendav_message_header * stHeader = ( struct opendav_message_header * )szTempBuffer;
// Header 와 Body 사이 4Byte int 값 존재함.
struct opendav_message_body * stBody = ( struct opendav_message_body * )( szTempBuffer + sizeof( int ) + sizeof( struct opendav_message_header ) );
// sqlite 관련 변수 초기화
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
const char * insert_sql = "INSERT INTO network_stat( "
" timestamp, user_seq, svc_seq, fhs, insert_time, send_flag, down_traffic, up_traffic, "
" down_content_size, up_content_size ) "
"VALUES ( ?, ?, ?, ?, datetime('now', 'localtime'), 0, ?, ?, ?, ? ) ";
// 루프 내에서 파싱된 정보를 저장하기 위한 변수
char szUserSeq[128]; // 파싱된 user_seq 정보 저장 변수
char szSvcSeq[128]; // 파싱된 svc_seq 정보 저장 변수
// 본 Thread 는 main 에서 통계 정보를 수신한 후 동작해야 하므로... 약간 기다린 후 동작시킨다.
sleep( 3 );
// sqlite 는 centos 5.4 에 설치된 기본 버전을 사용한다. ( 3.3.6 version )
// 해당 버전에서는 최신 _v2 함수를 지원하지 않는다.
// 기동시 db 파일이 없는 경우... -> open 명령에 의해 db 파일이 생성됨..(open 성공).. 하지만 prepare 에서 table 이 없다는 오류가 발생함.
// 동작 중에 db 파일이 삭제되는 경우... -> Query 가 모두 정상적으로 수행됨... 따라서 감지가 안됨...
// 본 sqlite3_open 함수에서는 오류가 발생하지 않는다.. 왜냐하면.. 해당 파일이 없을 경우.. 해당 파일을 0 size 파일로 강제로 생성하기 때문임.
// 따라서 하위 오류 코드는 그냥 형식상 존재하는 코드임.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 open failed.[%s][%d][%s]", getpid(), 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, insert_sql, strlen( insert_sql ), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );
db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare failed.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
stmt = NULL;
}
}
// 루프를 돌면서... Queue 에 저장된 수신 정보를 POP -> 파싱하여.. sqlite DB 에 저장한다.
// g_pRecvDataQueue 값은 프로세스 종료 처리시 NULL 로 변경된다.. 따라서 본 Thread 가 종료될 수 있도록 처리한다.
while( g_pRecvDataQueue != NULL )
{
// Queue 에서 Data 를 Pop 한다..
// Queue 객체 자체에서 인자값을 전달한 버퍼를 memset 하므로.. 따로 수행하지 않는다.
pop_result = g_pRecvDataQueue->Pop( szTempBuffer, MAX_SOCKET_BUFFER_SIZE );
if( pop_result == 0 ) // Pop 성공시..
{
/*
_LOG( LDBG, "[RecvDavListener:%d] SaveThread: header [%s][0x%04x][%s][%s][%s][%d][%d]", getpid()
, stHeader->szTransactionID, ntohl(stHeader->ulMode), stHeader->szIssuer, stHeader->szServiceID, stHeader->szRCID
, ntohl(stHeader->ulResult), ntohl(stHeader->ulBodyLength) );
*/
// 해당 값은 opendav-listener 가 보내는 서비스의 index 순서 정보를 기록하는 값으로써... n 번째 data 라는 의미임.
// - 해당 패킷에 n 개의 data 가 저장된다는 의미는 아니며...
// - 1개 패킷에는 무조건 1개 정보만 저장하여 전송 처리함.
int num = *( (int *)( szTempBuffer + sizeof( struct opendav_message_header ) ) );
/*
_LOG( LDBG, "[RecvDavListener:%d] SaveThread: body [%d] [%s][%s][%d] [%lu][%lu][%lu][%lu][%lu][%lu] "
"[%u][%u][%u][%u] [%u][%u][%u][%u][%u][%u][%u][%u][%u][%u]"
, getpid()
, num
, stBody->szServiceID, stBody->szRCID, stBody->timestamp
, stBody->nst.traffic[0], stBody->nst.traffic[1]
, stBody->nst.content[0], stBody->nst.content[1]
, stBody->nst.tc_traffic[0], stBody->nst.tc_traffic[1]
, stBody->sst.concurrent[0], stBody->sst.concurrent[1]
, stBody->sst.saved_concurrent[0], stBody->sst.saved_concurrent[1]
, stBody->sst.success[0], stBody->sst.success[1]
, stBody->sst.fail_not_auth[0], stBody->sst.fail_not_auth[1]
, stBody->sst.fail_illegal_request[0], stBody->sst.fail_illegal_request[1]
, stBody->sst.fail_timeout[0], stBody->sst.fail_timeout[1]
, stBody->sst.fail_disconnected[0], stBody->sst.fail_disconnected[1]
);
*/
_LOG( LDBG, "[RecvDavListener:%d] SaveThread: num[%d]th stamp[%u] svc[%s] traffic[u:%lu][d:%lu] size[u:%lu][d:%lu]"
, getpid()
, num
, stBody->timestamp, stBody->szServiceID
, stBody->nst.traffic[1], stBody->nst.traffic[0]
, stBody->nst.content[1], stBody->nst.content[0]
);
// 통계 정보 중.. DB 에 저장할 값이 모두 0 인 통계 정보는 불필요하므로... DB 에 입력처리 하지 않는다.
// traffic = 0 (content size 도 자동으로 0이므로 ) 일 경우 불필요한 data 이므로 버린다.
if( stBody->nst.traffic[0] == 0 && stBody->nst.traffic[1] == 0 )
continue;
// sqlite open 이 되지 않은 경우.. 재시도 처리
if( db == NULL )
{
_LOG( LINF, "[RecvDavListener:%d] SaveThread: sqlite3 open retry.[%s]"
, getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName() );
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 open failed2.[%s][%d][%s]"
, getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
}
else
{
nRetryCount = 0;
// bind 관련 Query 를 수행할 stmt 객체 생성.
db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );
db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare failed2.[%d][%s]"
, getpid(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
stmt = NULL;
}
}
}
// sqlite 접속 불가시
// - 위에서 접속 불가 발생시.. 해당 Data 는 처리 방법이 없으므로.. 버린다.
// - 만약 잠시 대기 후 재시도할 경우... data 가 밀려... 최신 data 가 입력이 밀리고.. 이로 인해 traffic 제어 등에 문제가 발생할 수 있다.
// sqlite 접속이 정상적으로 된 경우...
if( stmt != NULL )
{
// pop 된 정보 중 stBody->szServiceID 정보는 348.0@207 형식으로 표시됨.
// 이 중 user_seq 는@ 다음에 오는 207 , svc_seq 는 348 이므로... 파싱 처리한다.
if( strlen( stBody->szServiceID ) == 0 )
{
// 서비스 정보가 없는 경우.. 에러 마킹하고 정보를 버린다.
LOG( LWAR, "[RecvDavListener:%d] SaveThread: service_id length 0. save failed.[%d][%s]", getpid(), stBody->timestamp, stHeader->szIssuer );
continue;
}
else
{
memset( szUserSeq, 0x00, sizeof( szUserSeq ) );
memset( szUserSeq, 0x00, sizeof( szSvcSeq ) );
// 전달 받은 stBody->szServiceID 정보(348.0@207 형식) 를 파싱 처리함.
if( ParseServiceId( stBody->szServiceID, szUserSeq, szSvcSeq ) != 0 )
{
LOG( LWAR, "[RecvDavListener:%d] SaveThread: service_id parse error. save failed.[%d][%s][%s]"
, getpid(), stBody->timestamp, stHeader->szIssuer, stBody->szServiceID );
continue;
}
}
// insert query 에 변수 bind 처리. ( bind 값을 1부터 시작 )
// text binding 에 사용되는 변수는 sqlite3_step() 함수 호출되기 전까지 변경이 없는 변수들이므로... SQLITE_TRANSIENT 대신 SQLITE_STATIC 을 사용한다.
sqlite3_bind_int( stmt, 1, stBody->timestamp );
sqlite3_bind_text( stmt, 2, szUserSeq, strlen( szUserSeq ), SQLITE_STATIC );
sqlite3_bind_text( stmt, 3, szSvcSeq, strlen( szSvcSeq ), SQLITE_STATIC );
sqlite3_bind_text( stmt, 4, stHeader->szIssuer, strlen( stHeader->szIssuer ), SQLITE_STATIC );
sqlite3_bind_int64( stmt, 5, stBody->nst.traffic[0] );
sqlite3_bind_int64( stmt, 6, stBody->nst.traffic[1] );
sqlite3_bind_int64( stmt, 7, stBody->nst.content[0] );
sqlite3_bind_int64( stmt, 8, stBody->nst.content[1] );
// insert query 수행.
db_result = sqlite3_step( stmt );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..지정된 횟수 만큼 재시도
while( db_result == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "[RecvDavListener:%d] SaveThread: sqlite3 step SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );
// query 재시도
db_result = sqlite3_step( stmt );
}
if( db_result != SQLITE_DONE )
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 step error.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );
// 만약 오류 코드가 SQLITE_ERROR, SQLITE_INTERNAL, SQLITE_EMPTY, SQLITE_SCHEMA 등이라면.... DB에 문제 있는 것으로 보고.. db 객체를 초기화한다.
if( db_result == SQLITE_ERROR || db_result == SQLITE_INTERNAL || db_result == SQLITE_EMPTY || db_result == SQLITE_SCHEMA )
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite db force closed", getpid() );
sqlite3_finalize( stmt );
sqlite3_close( db );
db = NULL;
stmt = NULL;
}
else
{
// Query 수행 오류가 발생된 경우... 해당 값은 포기하고... 다음 prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset( stmt );
}
}
else
{
// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset( stmt );
}
}
}
else if( pop_result == 1 ) // Queue 가 empty 인 경우..
{
// pop 할 data 가 없으므로.. 잠시 쉰다.. ( 1 sec 정도 )
//_LOG( LDBG, "[RecvDavListener:%d] SaveThread: queue empty. sleep", getpid());
// usleep 대신 sleep, nanosleep 으로 변경 처리.
//usleep( 1000000 );
sleep( 1 );
}
else // 나머지 상황은 오류..
{
LOG( LERR, "[RecvDavListener:%d] SaveThread: queue pop unknown error.[%d]", getpid(), pop_result );
}
} // while
// Thread 종료 관련 처리.
return NULL;
}
// Worker::RecvDavListener 에서 각 opendav-listener 가 전송한 network 통계 정보를 수신처리하기 위한 Thread.
void * WorkerRecvDavListenerClientThread( void * arg )
{
// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
pthread_detach( pthread_self() );
_LOG( LINF, "[RecvDavListener:%d:%lu] client recv thread start...", getpid(), pthread_self() );
// opendav-listener 로 부터 서비스 통계 정보 수신을 위한 각 변수 초기화
struct sockaddr_in clientSocketAddr;
socklen_t clientSocketAddrLen = sizeof( clientSocketAddr );
char szClientAddress[INET_ADDRSTRLEN];
memset( szClientAddress, 0x00, sizeof( szClientAddress ) );
char szSocketBuffer[MAX_SOCKET_BUFFER_SIZE];
int nRead;
int nResult;
// opendav-listener 로 부터 서비스 통계 관련 정보 수신 대기
while( 1 )
{
nRead = recvfrom( g_sockDavListenerRecv, &szSocketBuffer, MAX_SOCKET_BUFFER_SIZE,
0, ( struct sockaddr * )&clientSocketAddr, &clientSocketAddrLen );
// 수신 오류 발생시
if( nRead < 0 )
{
LOG( LWAR, "[RecvDavListener:%d] socket recvfrom error[%d][%s]", getpid(), errno, strerror( errno ) );
continue;
}
if( inet_ntop( AF_INET, &( clientSocketAddr.sin_addr ), szClientAddress, sizeof( szClientAddress ) ) == NULL )
{
LOG( LERR, "[RecvDavListener:%d] inet_ntop error[%d][%s]", getpid(), errno, strerror( errno ) );
}
// 로그 기록 목적으로 로깅처리
_LOG( LDBG, "[RecvDavListener:%d:%lu] opendav-listener msg recv. client[%s] len[%d]", getpid(), pthread_self(), szClientAddress, nRead );
// 1. 동일 RC 에서 전달된 메시지인지 확인한다.
struct opendav_message_header * stHeader = ( struct opendav_message_header * )szSocketBuffer;
if( strcasecmp( stHeader->szRCID, CProcessConfig::GetInstance()->GetRcId() ) != 0 )
{
// 다른 RC 로 부터 통계 정보를 수신한 경우... 수신 Data 는 그냥 버린다.
LOG( LWAR, "[RecvDavListener:%d] not match RCID[%s][%s]", getpid(), stHeader->szRCID, szClientAddress );
// 다른 RC 의 FHS opendav-listener 로부터 서비스 통계 정보를 수신받은 경우...
// 해당 FHS 의 opendav-listener 설정이 잘못된 상태이므로...
// report 로그 상에 해당 내역을 기록한다.
CReportLog reportLog;
std::string strErrorMessage;
strErrorMessage.clear();
if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
{
reportLog.GetLastError( strErrorMessage );
LOG( LERR, "[RecvDavListener:%d] report log init failed.[%s]", getpid(), strErrorMessage.c_str() );
}
else
{
reportLog.Write( "WAR", "[%s][opendav-listener stats RCID not match. check FHS setting. [%s][%s][recv: %s, local: %s]]"
, PROG_NAME, szClientAddress, stHeader->szIssuer, stHeader->szRCID, CProcessConfig::GetInstance()->GetRcId() );
}
continue;
}
// Debug 목적으로 Data 파싱 처리 후 출력...
_LOG( LDBG, "[RecvDavListener:%d:%lu] FHS [%s][0x%04x][%s][%s][%d][%d]", getpid(), pthread_self()
, stHeader->szIssuer, ntohl( stHeader->ulMode ), stHeader->szServiceID, stHeader->szRCID
, ntohl( stHeader->ulResult ), ntohl( stHeader->ulBodyLength ) );
// 2. 기존 rc_statd(3.4 이전) 소스 코드를 참조하여...
// 수신된 메시시 값 중 ntohl(stHeader->ulMode) == MSG_MODE_OPENDAV_RCSTAT_NETSTAT ( 0x0110 ) 인 경우에만 처리..
// 그외 메시지에 대해서는 Invalid Protocol 로 처리한다.
if( ntohl( stHeader->ulMode ) != MSG_MODE_OPENDAV_RCSTAT_NETSTAT ) // #define MSG_MODE_OPENDAV_RCSTAT_NETSTAT 0x0110
{
LOG( LWAR, "[RecvDavListener:%d] Invalid protocol received.[0x%04x][%s]", getpid(), ntohl( stHeader->ulMode ), szClientAddress );
}
else
{
// Data 를 Queue 에 저장.
if( g_pRecvDataQueue != NULL )
{
nResult = g_pRecvDataQueue->Push( szSocketBuffer, nRead );
// Push 처리시 반환값
// -1 : 전달 인자값에 NULL 존재
// -2 : Queue 생성시 설정한 Data 크기보다 더 큰 nRead 값을 전달한 경우.
// 1 : Queue 생성시 설정한 최대 Queue 갯수를 초과한 경우..
// 0 : 정상 처리
if( nResult != 0 )
{
LOG( LWAR, "[RecvDavListener:%d] recv queue push error[%d]", getpid(), nResult );
}
}
}
} // while(1)
// Thread 종료 관련 처리.
return NULL;
}
// Worker::RecvDavListener 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvDavListenerMain()
{
// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.
if( g_sockServiceStatRecv != -1 )
{
close( g_sockServiceStatRecv );
g_sockServiceStatRecv = -1;
}
// Process Rename..
set_ps_display( PROG_NAME": RecvDavListener", false );
// Set Signal Handler
SetWorkerRecvDavListenerSignalHandler();
// 사용할 객체 초기화 처리
// Data 전달을 담당할 Queue 객체 생성
g_pRecvDataQueue = new CFixedQueue( MAX_SOCKET_BUFFER_SIZE, MAX_DATA_QUEUE_COUNT );
if( g_pRecvDataQueue == NULL )
{
// 객체 생성 실패시
LOG( LERR, "[RecvDavListener:%d] data queue create failed. process exit", getpid() );
ReadyToExitWorkerRecvDavListener();
return EXIT_FAILURE;
}
// Queue 로 전달된 Data 를 파싱하여 local DB 에 저장 처리를 담당할 Thread 를 생성한다.
pthread_t workerSaveThreadId;
if( pthread_create( &workerSaveThreadId, NULL, WorkerRecvDavListenerSaveThread, NULL ) != 0 )
{
int errorNum = errno;
LOG( LERR, "[RecvDavListener:%d] save thread create failed[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );
ReadyToExitWorkerRecvDavListener();
return EXIT_FAILURE;
}
// opendav-listener 로 부터 UDP 통계 정보를 수신 처리할 Thread 생성 처리.
pthread_t workerReceiveThreadId;
for( int i = 0; i < MAX_THREAD_RECEIVER; i++ )
{
if( pthread_create( &workerReceiveThreadId, NULL, WorkerRecvDavListenerClientThread, NULL ) != 0 )
{
int errorNum = errno;
LOG( LERR, "[RecvDavListener:%d] receive thread create failed[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );
ReadyToExitWorkerRecvDavListener();
return EXIT_FAILURE;
}
}
sleep( 2 );
_LOG( LINF, "[RecvDavListener:%d] receive thread count[%d] create success.", getpid(), MAX_THREAD_RECEIVER );
// 대기
while( 1 )
{
// 할일 없어서.. 시그널 수신까지 대기 한다.
pause();
}
// 종료시 정리 작업 수행
ReadyToExitWorkerRecvDavListener();
return EXIT_SUCCESS;
}
+36
View File
@@ -0,0 +1,36 @@
/****************************************************************************
Worker::RecvDavListener process main
-----------------------------------------
begin : 2015/08/25
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __WORKER_RECV_DAV_LISTENER_H__
#define __WORKER_RECV_DAV_LISTENER_H__
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
// Worker::RecvDavListener 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvDavListenerMain( void );
#ifdef __cplusplus
}
#endif
#endif /* __WORKER_RECV_DAV_LISTENER_H__ */
+645
View File
@@ -0,0 +1,645 @@
#include "WorkerRecvServiceStat.h"
#include "Logger.h"
#include "ProcessRename.h"
#include "ProcessConfig.h"
#include "SocketFimngd.h"
#include "FixedQueue.h"
#include "ReportLog.h"
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <time.h>
#include <sqlite3.h>
// client ip 주소 저장을 위한 최대 크기값
// - NULL 포함 40자까지, IPv6 지원 목적
#define IP_ADDR_LENGTH 40
// fimngd 로 부터 전달받은 Data 를 저장 thread 에 전달시 사용할 Queue 의 최대 갯수
#define MAX_DATA_QUEUE_COUNT 3000
// 요청 수신 관련 최대 대기 시간. (sec)
// - fimngd 가 매 300 sec (5분) 마다 전송하므로... 최대 대기 시간은
#define MAX_REQUEST_TIMEOUT 360
// sqlite SQLITE_BUSY 잦은 발생으로 INSERT 지연 발생, 이로 인해 CCDB 통계 누락 발생 (#21440)
// - 이를 해결하기 위해 lock timeout 값을 3 sec -> 0.6 sec 로 변경 처리
// - 이와 더불어 Lock 발생시 재시도 횟수를 30 -> 60 으로 변경 처리
#define SQLITE_DB_LOCK_TIMEOUT 600 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 0.6 sec )
#define SQLITE_DB_LOCK_MAX_RETRY 60 // SQLITE_BUSY 발생시 재시도 최대 횟수
// FHS 의 opendav-listener 로 부터 서비스 관련 통계 (Network, 세션등의 통계 등)정보를 수신하기 위한 UDP socket...
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockDavListenerRecv;
// FHS fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockServiceStatRecv;
// Data 수신부와 이를 파싱하여 처리할 Thread 객체간에 Data 전달을 위한 Queue 객체 생성
CFixedQueue * g_pRecvNetworkStatQueue = NULL;
// 각 Client 를 처리할 Thread 에 전달할 구조체
struct stRecvServiceStatThreadArg {
// client socket descriptor
int clientSocketDesc;
// Client 의 IP 주소 정보를 저장한 변수
char szClientAddress[IP_ADDR_LENGTH];
};
// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerRecvServiceStat()
{
// open 된 socket 관련 Close 처리
if( g_sockServiceStatRecv != -1 )
close( g_sockServiceStatRecv );
// Queue 객체 해제
if( g_pRecvNetworkStatQueue != NULL )
{
CFixedQueue * temp = g_pRecvNetworkStatQueue;
g_pRecvNetworkStatQueue = NULL;
delete temp;
}
// Logger 객체 종료 처리.
CLogger::Exit();
return;
}
// Worker RecvServiceStat 프로세스에 대한 대한 종료 처리 수신시.
static void SignalWorkerRecvServiceStatTerminate( int nSignalNumber )
{
// Signal Number 에 따른 로깅처리.
if( nSignalNumber == SIGTERM )
{
_LOG( LINF, "[RecvServiceStat:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
}
else
{
_LOG( LWAR, "[RecvServiceStat:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
}
ReadyToExitWorkerRecvServiceStat();
// 종료전 잠시 대기
struct timespec sleep;
sleep.tv_sec = 0;
sleep.tv_nsec = 500000000; // 0.5 sec
nanosleep( &sleep, NULL );
exit( EXIT_SUCCESS );
}
// Worker::RecvServiceStat Process 의 signal 처리기...
void SetWorkerRecvServiceStatSignalHandler()
{
sigset_t set;
struct sigaction act;
memset( &act, 0x00, sizeof( act ) );
sigfillset( &set );
sigprocmask( SIG_SETMASK, &set, NULL );
sigfillset( &act.sa_mask );
// 무시 처리 signal
act.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &act, NULL ); /* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
sigaction( SIGHUP, &act, NULL ); /* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
sigaction( SIGINT, &act, NULL ); /* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
sigaction( SIGQUIT, &act, NULL ); /* 키보드에 의한 Abort 신호 처리 => ? */
// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
act.sa_handler = SIG_IGN;
sigaction( SIGCHLD, &act, NULL );
/* 각종 에러나 사용자의 종료 신호 처리 */
act.sa_handler = SignalWorkerRecvServiceStatTerminate;
sigaction( SIGTERM, &act, NULL ); /* kill -TERM 에 의한 프로세스 종료시 */
// 나머지 신호는 default 처리.
sigemptyset( &set ); /* 신호 처리기 처리 설정 위한 블록 해제 */
sigprocmask( SIG_SETMASK, &set, NULL );
}
// 전역 Queue 에 저장된 network 통계 수신 data 를 파싱, 저장 처리를 담당하는 Thread
void * WorkerRecvServiceStatSaveThread( void * arg )
{
// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
pthread_detach( pthread_self() );
_LOG( LINF, "[RecvServiceStat:%d] SaveThread: start...", getpid() );
// Queue pop data 저장 관련 변수 초기화.
int pop_result;
struct ServiceNetworkStat stNetworkStat;
// sqlite 관련 변수 초기화
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
const char * insert_sql = "INSERT INTO network_stat( "
" timestamp, user_seq, svc_seq, fhs, insert_time, send_flag, down_traffic, up_traffic, "
" down_content_size, up_content_size ) "
"VALUES ( ?, ?, ?, ?, datetime('now', 'localtime'), 0, ?, ?, ?, ? ) ";
// 본 Thread 는 main 에서 통계 정보를 수신한 후 동작해야 하므로... 약간 기다린 후 동작시킨다.
sleep( 3 );
// sqlite 는 centos 5.4 에 설치된 기본 버전을 사용한다. ( 3.3.6 version )
// 해당 버전에서는 최신 _v2 함수를 지원하지 않는다.
// 기동시 db 파일이 없는 경우... -> open 명령에 의해 db 파일이 생성됨..(open 성공).. 하지만 prepare 에서 table 이 없다는 오류가 발생함.
// 동작 중에 db 파일이 삭제되는 경우... -> Query 가 모두 정상적으로 수행됨... 따라서 감지가 안됨...
// 본 sqlite3_open 함수에서는 오류가 발생하지 않는다.. 왜냐하면.. 해당 파일이 없을 경우.. 해당 파일을 0 size 파일로 강제로 생성하기 때문임.
// 따라서 하위 오류 코드는 그냥 형식상 존재하는 코드임.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 open failed.[%s][%d][%s]", getpid(), 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, insert_sql, strlen( insert_sql ), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );
db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare failed.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
stmt = NULL;
}
}
// 루프를 돌면서... Queue 에 저장된 수신 정보를 POP -> 파싱하여.. sqlite DB 에 저장한다.
// g_pRecvNetworkStatQueue 값은 프로세스 종료 처리시 NULL 로 변경된다.. 따라서 본 Thread 가 종료될 수 있도록 처리한다.
while( g_pRecvNetworkStatQueue != NULL )
{
// Queue 에서 Data 를 Pop 한다..
// Queue 객체 자체에서 인자값을 전달한 버퍼를 memset 하므로.. 따로 수행하지 않는다.
pop_result = g_pRecvNetworkStatQueue->Pop( (char *)(&stNetworkStat), sizeof( struct ServiceNetworkStat ) );
if( pop_result == 0 ) // Pop 성공시..
{
_LOG( LDBG, "[RecvServiceStat:%d] SaveThread: stamp[%u] svc[%d][%d] traffic[u:%lu][d:%lu] size[u:%lu][d:%lu]"
, getpid()
, stNetworkStat.timestamp
, stNetworkStat.user_seq, stNetworkStat.svc_seq
, stNetworkStat.up_traffic, stNetworkStat.down_traffic
, stNetworkStat.up_content_size, stNetworkStat.down_content_size
);
// 통계 정보 중.. DB 에 저장할 값이 모두 0 인 통계 정보는 불필요하므로... DB 에 입력처리 하지 않는다.
// traffic = 0 (content size 도 자동으로 0이므로 ) 일 경우 불필요한 data 이므로 버린다.
if( stNetworkStat.down_traffic == 0 && stNetworkStat.up_traffic == 0 )
continue;
// sqlite open 이 되지 않은 경우.. 재시도 처리
if( db == NULL )
{
_LOG( LINF, "[RecvServiceStat:%d] SaveThread: sqlite3 open retry.[%s]"
, getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName() );
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 open failed2.[%s][%d][%s]"
, getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
}
else
{
nRetryCount = 0;
// bind 관련 Query 를 수행할 stmt 객체 생성.
db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );
db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 prepare failed2.[%d][%s]"
, getpid(), db_result, sqlite3_errmsg( db ) );
sqlite3_close( db );
db = NULL;
stmt = NULL;
}
}
}
// sqlite 접속 불가시
// - 위에서 접속 불가 발생시.. 해당 Data 는 처리 방법이 없으므로.. 버린다.
// - 만약 잠시 대기 후 재시도할 경우... data 가 밀려... 최신 data 가 입력이 밀리고.. 이로 인해 traffic 제어 등에 문제가 발생할 수 있다.
// sqlite 접속이 정상적으로 된 경우...
if( stmt != NULL )
{
// insert query 에 변수 bind 처리. ( bind 값을 1부터 시작 )
// text binding 에 사용되는 변수는 sqlite3_step() 함수 호출되기 전까지 변경이 없는 변수들이므로... SQLITE_TRANSIENT 대신 SQLITE_STATIC 을 사용한다.
sqlite3_bind_int( stmt, 1, stNetworkStat.timestamp );
sqlite3_bind_int( stmt, 2, stNetworkStat.user_seq );
sqlite3_bind_int( stmt, 3, stNetworkStat.svc_seq );
sqlite3_bind_text( stmt, 4, stNetworkStat.hostname, strlen( stNetworkStat.hostname ), SQLITE_STATIC );
sqlite3_bind_int64( stmt, 5, stNetworkStat.down_traffic );
sqlite3_bind_int64( stmt, 6, stNetworkStat.up_traffic );
sqlite3_bind_int64( stmt, 7, stNetworkStat.down_content_size );
sqlite3_bind_int64( stmt, 8, stNetworkStat.up_content_size );
// insert query 수행.
db_result = sqlite3_step( stmt );
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..지정된 횟수 만큼 재시도
while( db_result == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "[RecvServiceStat:%d] SaveThread: sqlite3 step SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );
// query 재시도
db_result = sqlite3_step( stmt );
}
if( db_result != SQLITE_DONE )
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite3 step error.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );
// 만약 오류 코드가 SQLITE_ERROR, SQLITE_INTERNAL, SQLITE_EMPTY, SQLITE_SCHEMA 등이라면.... DB에 문제 있는 것으로 보고.. db 객체를 초기화한다.
if( db_result == SQLITE_ERROR || db_result == SQLITE_INTERNAL || db_result == SQLITE_EMPTY || db_result == SQLITE_SCHEMA )
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: sqlite db force closed", getpid() );
sqlite3_finalize( stmt );
sqlite3_close( db );
db = NULL;
stmt = NULL;
}
else
{
// Query 수행 오류가 발생된 경우... 해당 값은 포기하고... 다음 prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset( stmt );
}
}
else
{
// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset( stmt );
}
}
}
else if( pop_result == 1 ) // Queue 가 empty 인 경우..
{
// pop 할 data 가 없으므로.. 잠시 쉰다.. ( 1 sec 정도 )
//_LOG( LDBG, "[RecvServiceStat:%d] SaveThread: queue empty. sleep", getpid());
// usleep 대신 sleep, nanosleep 으로 변경 처리.
//usleep( 1000000 );
sleep( 1 );
}
else // 나머지 상황은 오류..
{
LOG( LERR, "[RecvServiceStat:%d] SaveThread: queue pop unknown error.[%d]", getpid(), pop_result );
}
} // while
// Thread 종료 관련 처리.
return NULL;
}
// Worker::RecvServiceStat 에서 각 Client 에 대한 처리를 수행하기 위한 Thread.
void * WorkerRecvServiceStatClientThread( void * arg )
{
// 전달받은 변수를 내부 변수에 저장 처리.
// client socket 관련 처리 변수 생성
CSocketFimngd clientSocket( ( ( struct stRecvServiceStatThreadArg * ) arg )->clientSocketDesc );
// client 주소 처리 관련
char szClientAddress[IP_ADDR_LENGTH];
memcpy( szClientAddress, ( ( struct stRecvServiceStatThreadArg * ) arg )->szClientAddress, IP_ADDR_LENGTH );
// malloc 로 생성된 변수 해제 처리
free( arg );
// 본 thead 는 client 연결 상태에 따라 종료되므로.. process 에서 분리 처리
pthread_detach( pthread_self() );
_LOG( LINF, "[RecvServiceStat:%d] client thread start. [%s]", getpid(), szClientAddress );
// 송수신 처리 관련 변수
int nResult;
char command;
// 수신된 서비스별 network 통계 정보를 저장하기 위한 vector
// - ServiceNetworkStat 구조체는 SocketFimngd.h 에 정의
std::vector< struct ServiceNetworkStat > vecNetworkStat;
std::vector< struct ServiceNetworkStat >::iterator it;
// socket 수신 대기
while( 1 )
{
// 요청 수신 대기
nResult = clientSocket.GetPacketHeader( command, MAX_REQUEST_TIMEOUT );
if( nResult == 0 )
{
// timeout 발생시...
// - 지정된 시간동안 요청이 없는 경우 세션을 종료 처리한다.
_LOG( LINF, "[RecvServiceStat:%d] client[%s] request timeout[%d]. session close.", getpid(), szClientAddress, MAX_REQUEST_TIMEOUT );
break;
}
else if( nResult < 0 )
{
// Socket 종료, 오류 또는 정의되지 않은 형식의 패킷 header 수신시...
// 오류 내역은 socket 모듈에서 로깅하므로... 그냥 종료 처리.
break;
}
else
{
// 정상 header 수신시.
// - 수신된 command 에 따른 처리.
if( command == COMMAND_SERVICE_NETWORK_STAT )
{
// service network stat 전송인 경우..
// 수신된 통계 정보를 저장할 vector 초기화 후 Data 수신 처리.
vecNetworkStat.clear();
if( clientSocket.GetPacketBodyNetworkStat( vecNetworkStat ) == false )
{
// 수신 오류 발생시.
LOG( LERR, "[RecvServiceStat:%d] client[%s] service network stat recv error. session close."
, getpid(), szClientAddress );
// 세션 종료 처리
break;
}
else
{
// service network 통계 정보가 정상 수신된 경우...
// queue 에 저장 처리한다.
for( it = vecNetworkStat.begin(); it != vecNetworkStat.end(); ++it )
{
_LOG( LDBG, "[RecvServiceStat:%d] client[%s] [%s] stamp[%u] svc[%d][%d] traffic[u:%lu][d:%lu] size[u:%lu][d:%lu]"
, getpid(), szClientAddress
, it->hostname, it->timestamp, it->user_seq, it->svc_seq
, it->up_traffic, it->down_traffic, it->up_content_size, it->down_content_size );
// queue 에 저장 처리
if( g_pRecvNetworkStatQueue != NULL )
{
nResult = g_pRecvNetworkStatQueue->Push( it->hostname, sizeof( struct ServiceNetworkStat ) );
// Push 처리시 반환값
// -1 : 전달 인자값에 NULL 존재
// -2 : Queue 생성시 설정한 Data 크기보다 더 큰 nRead 값을 전달한 경우.
// 1 : Queue 생성시 설정한 최대 Queue 갯수를 초과한 경우..
// 0 : 정상 처리
if( nResult != 0 )
{
LOG( LWAR, "[RecvServiceStat:%d] recv queue push error[%d]", getpid(), nResult );
}
}
}
}
}
else
{
// 정의되지 않은 command 인 경우..
// - 오류 로깅 후 세션 종료 처리.
LOG( LERR, "[RecvServiceStat:%d] client[%s] request type not defined. session close.", getpid(), szClientAddress );
clientSocket.PrintHeaderToLog();
break;
}
}
} // while(1)
// client 와 연결 종료 처리.
clientSocket.Close();
_LOG( LINF, "[RecvServiceStat:%d] client[%s] connection closed.", getpid(), szClientAddress );
// Thread 종료 관련 처리.
return NULL;
}
// Worker::RecvServiceStat 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvServiceStatMain()
{
// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.
// Process Rename..
set_ps_display( PROG_NAME": RecvServiceStat", false );
// Set Signal Handler
SetWorkerRecvServiceStatSignalHandler();
// 사용할 객체 초기화 처리
// Data 전달을 담당할 Queue 객체 생성
g_pRecvNetworkStatQueue = new CFixedQueue( sizeof( struct ServiceNetworkStat ), MAX_DATA_QUEUE_COUNT );
if( g_pRecvNetworkStatQueue == NULL )
{
// 객체 생성 실패시
LOG( LERR, "[RecvServiceStat:%d] data queue create failed. process exit", getpid() );
ReadyToExitWorkerRecvServiceStat();
return EXIT_FAILURE;
}
// Queue 로 전달된 Data 를 파싱하여 local db에 저장 처리를 담당할 Thread 를 생성한다.
pthread_t workerSaveThreadId;
if( pthread_create( &workerSaveThreadId, NULL, WorkerRecvServiceStatSaveThread, NULL ) != 0 )
{
int errorNum = errno;
LOG( LERR, "[RecvDavListener:%d] save thread create failed[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );
ReadyToExitWorkerRecvServiceStat();
return EXIT_FAILURE;
}
// 수신 처리 관련 변수 정의 및 초기화.
int clientSock;
struct sockaddr_in clientSockAddr;
socklen_t clientSockLen = sizeof( clientSockAddr );
pthread_t clientThreadId;
struct stRecvServiceStatThreadArg * pThreadArg = NULL;
while( 1 )
{
clientSock = accept( g_sockServiceStatRecv, ( struct sockaddr * ) &clientSockAddr, &clientSockLen );
if( clientSock == -1 )
{
// 오류 발생시.... 해당 내역 로깅 처리.
int errorNum = errno;
LOG( LERR, "[RecvServiceStat:%d] client accept failed [%d][%s]", getpid(), errorNum, strerror( errorNum ) );
}
else
{
// 정상적인 Client 인 경우...
// Thread 로 전달할 변수를 malloc 로 생성 처리
pThreadArg = ( struct stRecvServiceStatThreadArg * ) malloc( sizeof( struct stRecvServiceStatThreadArg ) );
if( pThreadArg == NULL )
{
int errorNum = errno;
close( clientSock ); // client 접속 종료 처리.
char tempBuffer[IP_ADDR_LENGTH];
memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
{
LOG( LERR, "[RecvServiceStat:%d] client[%s] thread malloc failed [%d][%s]", getpid(), tempBuffer, errorNum, strerror( errorNum ) );
}
else
{
LOG( LERR, "[RecvServiceStat:%d] client thread malloc failed [%d][%s] and inet_ntop error[%d][%s]"
, getpid(), errorNum, strerror( errorNum ), errno, strerror( errno ) );
}
continue;
}
else
{
// 인자로 전달할 변수정보 설정.
pThreadArg->clientSocketDesc = clientSock;
char tempBuffer[IP_ADDR_LENGTH];
memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
{
memcpy( pThreadArg->szClientAddress, tempBuffer, sizeof( tempBuffer ) );
}
else
{
// inet_ntop 오류 발생시
// client ip 를 공유메모리 할당 관련 key 값으로 사용하므로.....
// ip 가 불분명할 경우.. 잘못된 공유메모리 할당으로 인해 문제 발생 가능.
// 따라서 관련 상세 내역 로깅 후.. 세션 종료 처리한다.
//snprintf( pThreadArg->szClientAddress, IP_ADDR_LENGTH - 1, "0.0.0.0" );
int errorNum = errno;
LOG( LERR, "[RecvServiceStat:%d] client ip not valid. connection closed. inet_ntop error[%d][%s]", getpid(), errorNum, strerror( errorNum ) );
close( clientSock );
free( pThreadArg );
continue;
}
// Client 처리를 담당할 Thread 생성 처리
if( pthread_create( &clientThreadId, NULL, WorkerRecvServiceStatClientThread, (void *)pThreadArg ) != 0 )
{
// 생성 실패시...
// - 로깅
// - client socket close
// - malloc 로 생성된 전달 변수 free 처리.
int errorNum = errno;
LOG( LERR, "[RecvServiceStat:%d] client[%s] thread create failed [%d][%s]"
, getpid(), pThreadArg->szClientAddress, errorNum, strerror( errorNum ) );
close( clientSock );
free( pThreadArg );
}
else
{
_LOG( LDBG, "[RecvServiceStat:%d] client[%s] accept & client thread create ok.", getpid(), pThreadArg->szClientAddress );
}
}
}
} // while(1)
// 종료시 정리 작업 수행
ReadyToExitWorkerRecvServiceStat();
return EXIT_SUCCESS;
}
+36
View File
@@ -0,0 +1,36 @@
/****************************************************************************
Worker::RecvServiceStat process main
-----------------------------------------
begin : 2015/08/25
copyright : (C) 2005 Solbox Inc.
author : Dev Storage Team
email : huibong@solbox.com
version : 3.5
CopyRight(C) 2005 Solbox Inc. All Rights reserved.
Redistribution and use in source and binary forms, with or with out
modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#ifndef __WORKER_RECV_SERVICE_STAT_H__
#define __WORKER_RECV_SERVICE_STAT_H__
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
// Worker::RecvServiceStat 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvServiceStatMain( void );
#ifdef __cplusplus
}
#endif
#endif /* __WORKER_RECV_SERVICE_STAT_H__ */