#include "ShmInfoThread.h"

#include "Logger.h"
#include "Common.h"
#include "ProcessConfig.h"
#include "../rc_mond/ShmDefine.h"
#include "../rc_mond/ShmControl.h"


#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>

#include <algorithm>    // vector sort 사용 목적


// 매 Loop 마다 Sleep 시간 정보 정의 ( sec 단위)
#define SLEEP_TIME_DEFAULT		20	
#define SLEEP_TIME_MAX			60

// rc_mond shared memory 접근을 위한 key 파일.
#define RC_MOND_SHARED_MEMORY_KEY_FILE		"/user/service/rc_mond.shm"

// Rebalance 처리 대상 FHS 관련 FHS 의 최소 Storage 사용률 제한값.
// 해당 percent(%) 이상인 장비만 선정되도록 하기 위함
#define REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT	50

// Rebalance 처리 대상 FHS 관련 FHS 의 최대 disk busy 제한값.
// 해당 percent(%) 이하인 장비만 선정되도록 하기 위함
#define REBALANCE_TARGET_DISK_MAX_BUSY_LIMIT	50.0

// Rebalance 대상 FHS 선정을 위한 score 계산시 disk_max_busy 항목에 부여할 가중치 정보.
// - 점수가 높은 FHS 장비가 선정되므로...
// - disk busy 상태인 장비는 선택되지 않도록 0.5 수준으로 설정
// - storage_max_usage : disk_max_busy = 2 : 1 로 계산
#define REBALANCE_TARGET_DISK_MAX_BUSY_WEIGHT	0.5

// Rebalance 대상 FHS 정보를 저장한 list 의 유효시간 (sec)
// - SLEEP_TIME_MAX 값보다 커야 함.
#define TIMEOUT_REBALANCE_LIST					120	

// Copy 수신 가능 FHS 의 Disk 여유용량 최소값
// - 1 TB
#define COPY_TARGET_DISK_MIN_AVAILABLE_LIMIT	(1099511627776ULL)

// COPY 수신 가능 FHS 선정을 위한 score 계산시 disk_max_busy 항목에 부여할 가중치 정보.
// - 참고로 rc_mond 의 Top FHS 선정은 storage_max_usage : disk_max_busy = 1 : 4 로 계산하여 처리
// - Copy Target 은 실제 copy 처리를 수행하는 thread 가 많지 않고...
// - 되도록 여유 용량이 많은 장비에 copy 하는 것이 효과적이므로... 
// - storage_max_usage : disk_max_busy = 1 : 2 로 계산
#define COPY_TARGET_DISK_MAX_BUSY_WEIGHT		2.0

// Copy 대상 FHS 정보를 저장한 list 의 유효시간 (sec)
// - default sleep time 값보다 커야 함
#define TIMEOUT_COPY_LIST					30	


// 공유메모리에 저장된 FHS 정보 중 적합한 FHS 정보를 찾기 위해
// FHS 와 각 점수 정보를 저장하기 위한 구조체
struct FhsScore {

	char     hostname[HOST_NAME_LENGTH + 1];       // hostname, NULL 포함 최대 64자까지만 허용됨.
	double   score;                                // 계산된 score, 작을수록 idle
};



CShmInfoThread::CShmInfoThread()
{
	// 멤버변수 초기화
	m_threadHandle = 0;

	m_uSleep = SLEEP_TIME_DEFAULT;

	m_listRebalanceFhs.clear();
	m_listCopyTargetFhs.clear();

	// 시간 정보 초기화.
	memset( &m_timeLastRefreshRebalanceFhs, 0x00, sizeof( struct timespec ) );
	memset( &m_timeLastRefreshCopyTargetFhs, 0x00, sizeof( struct timespec ) );

	pthread_mutex_init( &m_mutexRebalanceFhs, NULL );
	pthread_mutex_init( &m_mutexCopyTargetFhs, NULL );

}

CShmInfoThread::~CShmInfoThread()
{
	// thread 가 동작 중인 경우.. thread 중지 처리
	if( m_threadHandle != 0 )
	{
		pthread_cancel( m_threadHandle );
	}

	sleep( 1 );	// thread 종료 잠시 대기

	pthread_mutex_destroy( &m_mutexRebalanceFhs );
	pthread_mutex_destroy( &m_mutexCopyTargetFhs );
}

///////////////////////////////


bool CShmInfoThread::Start()
{
	// 작업 thread 생성
	int nResult = pthread_create( &m_threadHandle, NULL, CShmInfoThread::threadFunc, this );
	if( nResult != 0 )
	{
		// thread 생성 실패
		int errorNum = errno;
		LOG( LERR, "[SHM INF] thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

bool CShmInfoThread::GetRebalanceTargetFhs( std::string & szResultHost, std::list<std::string>& listIgnoreFHS )
{
	// Rebalance 대상 FHS 목록 중....
	// 전달받은 listIgnoreFHS 에 등록된 FHS 정보를 제외하고....
	// 남은 FHS 정보 중.. 맨 처음 나오는 정보를 전달한다.


	// 1. 저장된 list 정보가 갱신이 되지 않아 오래된 경우.... 버린다.
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );

	if( currentClock.tv_sec - m_timeLastRefreshRebalanceFhs.tv_sec > TIMEOUT_REBALANCE_LIST )
	{
		LOG( LWAR, "[SHM INF] rebalance list expired. %d sec.", TIMEOUT_REBALANCE_LIST );
		return false;
	}

	// 2. 저장된 list 정보가 존재하지 않는 경우...  false 반환 처리
	if( m_listRebalanceFhs.size() <= 0 )
	{
		LOG( LWAR, "[SHM INF] rebalance list empty.");
		return false;
	}

	std::list<std::string>::const_iterator it;
	std::list<std::string>::const_iterator itIgnore;
	std::string szHost;

	pthread_mutex_lock( &m_mutexRebalanceFhs );

	for( it = m_listRebalanceFhs.begin(); it != m_listRebalanceFhs.end(); ++it )
	{
		szHost = *it;

		// 추출된 FHS 가 ignore list 에 존재하는지 확인
		itIgnore = std::find( listIgnoreFHS.begin(), listIgnoreFHS.end(), szHost );
		if( itIgnore == listIgnoreFHS.end() )
		{
			// ignore list 에 존재하지 않는 경우...
			// 해당 장비를 선택하고.. loop 종료 처리
			szResultHost = szHost;
			pthread_mutex_unlock( &m_mutexRebalanceFhs );

			_LOG( LDBG, "[SHM INF] proper rebalance fhs [%s] selected.",szResultHost.c_str() );
			return true;
		}
	}

	// 적합한 장비를 찾지 못한 경우.
	pthread_mutex_unlock( &m_mutexRebalanceFhs );

	_LOG( LDBG, "[SHM INF] proper rebalance fhs not selectd. total[%lu], ignore[%lu]."
		, m_listRebalanceFhs.size(), listIgnoreFHS.size() );

	return false;
}

// copy content 를 저장할 target FHS 정보를 반환
// 2021-01-07 NEW huibong 이동 대상 content 선택 관련 mode 기능 추가 (#33306)
// - 복사 가능 FHS 가 존재하는지 검색 관련 오류 발생시 반환값 정보를 좀더 세분화 처리
// - 정상 처리시 :  return 1
// - 복사 가능 물리적 FHS 가 없는 경우 : return -1
// - 복사 가능 물리 FHS 는 존재하지만 ignore 목록에 포함되어.. 실제 복사 가능 FHS 가 없는 경우 : return 0
int CShmInfoThread::GetCopyTargetFhs( std::string & szResultHost, std::list<std::string>& listIgnoreFHS )
{
	// COPY 수신 가능 FHS list 에서
	// 전달받은 Ignore FHS 에 등록된 FHS 를 제외하고
	// 남은 FHS 중 맨 처음 나오는 정보를 전달한다.

	// 1. 저장된 list 정보가 갱신이 되지 않아 오래된 경우.... 버린다.
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );

	if( currentClock.tv_sec - m_timeLastRefreshCopyTargetFhs.tv_sec > TIMEOUT_COPY_LIST )
	{
		LOG( LWAR, "[SHM INF] copy target info list expired. %d sec.", TIMEOUT_COPY_LIST );

		// 복사 가능 물리 FHS 가 아예 없는 경우... -1 return
		return -1;
	}

	// 2. 저장된 list 정보가 존재하지 않는 경우
	if( m_listCopyTargetFhs.size() <= 0 )
	{
		LOG( LWAR, "[SHM INF] ] copy target info list empty." );
		
		// 복사 가능 물리 FHS 가 아예 없는 경우... -1 return
		return -1;
	}


	std::list<std::string>::const_iterator it;
	std::list<std::string>::const_iterator itIgnore;
	std::string szHost;

	pthread_mutex_lock( &m_mutexCopyTargetFhs );

	for( it = m_listCopyTargetFhs.begin(); it != m_listCopyTargetFhs.end(); ++it )
	{
		szHost = *it;

		// 추출된 FHS 가 ignore list 에 존재하는지 확인
		itIgnore = std::find( listIgnoreFHS.begin(), listIgnoreFHS.end(), szHost );
		if( itIgnore == listIgnoreFHS.end() )
		{
			// ignore list 에 존재하지 않는 경우...
			// 해당 장비를 선택하고.. loop 종료 처리
			szResultHost = szHost;
			pthread_mutex_unlock( &m_mutexCopyTargetFhs );

			_LOG( LDBG, "[SHM INF] proper copy target [%s] selected.", szResultHost.c_str() );
		
			// 복사 가능 물리 FHS 가 존재하는 경우 ... 1 return
			return 1;
		}
	}

	// 적합한 장비를 찾지 못한 경우.
	pthread_mutex_unlock( &m_mutexCopyTargetFhs );

	LOG( LWAR, "[SHM INF] proper copy target fhs not selectd. total[%lu], ignore[%lu]."
		, m_listCopyTargetFhs.size(), listIgnoreFHS.size() );

	// 복사 가능 물리 FHS 가 존재하지만.. ignore 에 포함되어.. 실제 가능 FHS 가 전혀 없는 경우 ... 0 return
	return 0;
}

bool CShmInfoThread::IsValidCopyTargetFhs( void )
{
	// 1. 저장된 list 정보가 갱신이 되지 않아 오래되었는지 check
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );

	bool bResult = true;

	pthread_mutex_lock( &m_mutexCopyTargetFhs );

	if( currentClock.tv_sec - m_timeLastRefreshCopyTargetFhs.tv_sec > TIMEOUT_COPY_LIST )
	{
		LOG( LWAR, "[SHM INF] copy target list expired. %d sec.", TIMEOUT_COPY_LIST );
		bResult = false;
	}
	else
	{
		if( m_listCopyTargetFhs.empty() == true )
		{
			LOG( LWAR, "[SHM INF] copy target list empty." );
			bResult = false;
		}
	}

	pthread_mutex_unlock( &m_mutexCopyTargetFhs );

	return bResult;
}


//////////////////////////////


void * CShmInfoThread::threadFunc( void * arg )
{
	CShmInfoThread * pObject = reinterpret_cast<CShmInfoThread *>(arg);
	pthread_detach( pthread_self() );

	while( 1 )
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		// 지정된 주기로 실행
		sleep( pObject->m_uSleep );
	}

	// thread 종료시 handle 정보 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}

void CShmInfoThread::Execute()
{
	// 다음 sleep time 은 default 로 처리
	// - 하위 flow 에서 문제 발생하면 sleep time 을 max 로 조정
	m_uSleep = SLEEP_TIME_DEFAULT;

	// rc_mond 가 기동 중인지 check
	// - 미기동 상태이면... 기동시까지 대기.
	if( IsRunRcMonD() == false )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LERR, "[SHM INF] %s process not running. max sleep %u sec.", PROCESS_NAME_RC_MONITORING_DAEMON, m_uSleep );
		return;
	}

	// 공유메모리 접근
	key_t iShmKey;

	// 공유메모리 key 생성을 위한 임시 파일 생성 처리.
	FILE * pShmFile = fopen( RC_MOND_SHARED_MEMORY_KEY_FILE, "r" );

	if( pShmFile == NULL )
	{
		// 공유메모리 접근 key file 존재하지 않는 경우....
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LERR, "[SHM INF] %s shared memory key file(%s) not exist. max sleep %u sec."
			, PROCESS_NAME_RC_MONITORING_DAEMON , RC_MOND_SHARED_MEMORY_KEY_FILE , m_uSleep );
		return;
	}
	else
	{
		// 공유메모리 접근 key file 존재하는 경우
		fclose( pShmFile );
		pShmFile = NULL;
	}

	// 공유메모리 접근 key 값 추출
	iShmKey = ftok( RC_MOND_SHARED_MEMORY_KEY_FILE, 1 );
	if( iShmKey == (key_t)-1 )
	{
		// 공유메모리 접근 key 획득 실패시
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LERR, "[SHM INF] %s shared memory key generate fail with file(%s). max sleep %u sec."
			, PROCESS_NAME_RC_MONITORING_DAEMON , RC_MOND_SHARED_MEMORY_KEY_FILE , m_uSleep );
		return;
	}

	// 공유메모리 attach 처리 
	CShmControl< struct RcInfo > shmCtl;
	if( shmCtl.Attach( iShmKey, sizeof( struct RcInfo ) ) == false )
	{
		// 공유메모리 접근 실패시
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LERR, "[SHM INF] %s shared memory attach fail with key(%d). max sleep %u sec."
			, PROCESS_NAME_RC_MONITORING_DAEMON, iShmKey, m_uSleep );
		return;
	}

	_LOG( LDBG, "[SHM INF] start." );

	// 공유메모리 최상위 접근 포인터 획득
	struct RcInfo * pShm = shmCtl.GetShmPtr();

	// copy 처리 수신 가능 FHS 추출 및 rebalance 대상 FHS 추출 목록 update
	if( RefreshFhsList( pShm ) == false )
	{
		// 실패 발생시 다음 sleep time 최대 설정.
		m_uSleep = SLEEP_TIME_MAX;
	}

	// 공유메모리 접근 해제
	shmCtl.Detach();
	pShm = NULL;

	return;
}


bool FhsScoreSortASC( const struct FhsScore & fhs1, const struct FhsScore & fhs2 )
{
	return (fhs1.score < fhs2.score);
}

bool FhsScoreSortDESC( const struct FhsScore & fhs1, const struct FhsScore & fhs2 )
{
	return (fhs1.score > fhs2.score);
}


bool CShmInfoThread::RefreshFhsList( const struct RcInfo * const pShm )
{
	// FHS 의 시스템 정보 업데이트가 오래된 경우.. 
	// - rc_mond 에서 지정된 시간( 20 sec ) 를 초과한 경우.. expire 처리하지만...
	// - 오류가 발생할 수 있고... FHS 상태가 이상할 경우.. 해당 장비는 선택하지 않는 것이 좋으므로...
	// - 업데이트가 비교적 최근에 발생한 FHS 정보만을 사용토록 한다.
	// - csagentd 는 4(service), 10(system) sec 마다 정보 전송...
	// - 따라서.. 8 sec 초과된 정보는 사용하지 않도록 한다.
	// - local time 은 시간 동기화 영향 받으므로... update_time_clock_sec 항목을 사용하여 판단한다.
	time_t    expireTime;
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );
	expireTime = currentClock.tv_sec - 8;
	

	// 1. Write 가능 FHS 의 Storage 사용량 정보를 조회하여 평균을 구한다.
	//    - 전체 FHS 의 평균을 구하면.. 용량이 충분한데 장비 문제로 강제 업로드 막은 장비까지 계산되어
	//    - 평균을 낮추 버리는 문제가 발생하여... 
	//    - reblance 수신 FHS 가 다시 rebalance 처리 대상 FHS 가 될 수 있는 문제가 생긴다.
	const struct ClientInfo * pClient = NULL;
	unsigned int uFhsCount = 0;
	unsigned int uFhsTotalUsage = 0;
	int index = 0;
	unsigned int uFhsAverageUsage = 0;
	
	for( index = 0; index <= pShm->client_max_index; index++ )
	{
		pClient = &(pShm->client[index]);

		// slot 할당되어 있고.. data 가 유효하고.. 
		// write 가능 정상 상태 이고
		// system 상태 정보가 정상인 경우...( system 정보가 늦게 수신되므로.. 이를 반영 )
		// service 상태 정보가 expire 시간보다 오래되지 않은 최근 업데이트된 FHS 인 경우..
		if( pClient->use == 1
			&& pClient->valid == 1
			&& pClient->service.service_stat == SERVICE_STAT_OK
			&& pClient->system.update_time != 0
			&& pClient->service.update_time_clock_sec >= expireTime
			)
		{
			// 평균값 계산을 위한 값 처리
			uFhsTotalUsage += pClient->system.storage_max_usage;
			uFhsCount += 1;
		}
	}

	// write 가능 유효한 FHS 가 없는 경우
	if( uFhsCount < 1 )
	{
		_LOG( LWAR, "[SHM INF] starge average usage calc fail: write available fhs[%u] not exist.", uFhsCount );
		return false;
	}

	// Storage 사용량 평균값 계산
	uFhsAverageUsage = (uFhsTotalUsage / uFhsCount) + 1; //  소수점 짤려서..  86.7% 여도 86 % 로 계산되므로.. 1 보정
	_LOG( LINF, "[SHM INF] starge average usage calc ok. write fhs count[%u], avg[%u %%]", uFhsCount, uFhsAverageUsage );

	// 2. 평균 Storage 사용률 미만인 FHS 를 Copy 수신 FHS 로 선정한다.
	//    - 만약 적합한 FHS 가 없는 경우 아래의 rebalance 대상 FHS 로 선정하지 않는다.
	if( RefreshCopyTargetFhsList( pShm, uFhsAverageUsage ) == false )
	{
		return false;
	}

	// 3. rebalance 대상 FHS 로 선정한다. 
	//    - 평균 Storage 사용률이 70% 미만인 경우... 평균 Storage 사용률보다 3% 이상 높은 FHS 를 rebalance 대상 FHS 로 선정
	//    - 평균 Storage 사용률이 70% 이상인 경우..  평균 Storage 사용률보다 1% 이상 높은 FHS 를 rebalance 대상 FHS 로 설정
	unsigned int uRebalanceTargetStorageUsage = 0;
	if( uFhsAverageUsage < 70 )
	{
		uRebalanceTargetStorageUsage = uFhsAverageUsage + 3;
	}
	else
	{
		uRebalanceTargetStorageUsage = uFhsAverageUsage + 1;
	}

	if( RefreshRebalanceFhsList( pShm, uRebalanceTargetStorageUsage ) == false )
	{
		return false;
	}

	return true;
}

bool CShmInfoThread::RefreshCopyTargetFhsList( const RcInfo * const pShm, unsigned int uStorageMaxUsage )
{
	// FHS 의 시스템 정보 업데이트가 오래된 경우.. 
	// - rc_mond 에서 지정된 시간( 20 sec ) 를 초과한 경우.. expire 처리하지만...
	// - 오류가 발생할 수 있고... FHS 상태가 이상할 경우.. 해당 장비는 선택하지 않는 것이 좋으므로...
	// - 업데이트가 비교적 최근에 발생한 FHS 정보만을 사용토록 한다.
	// - csagentd 는 4(service), 10(system) sec 마다 정보 전송...
	// - 따라서.. 8 sec 초과된 정보는 사용하지 않도록 한다.
	// - local time 은 시간 동기화 영향 받으므로... update_time_clock_sec 항목을 사용하여 판단한다.
	time_t    expireTime;
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );
	expireTime = currentClock.tv_sec - 8;

	const struct ClientInfo * pClient = NULL;
	struct FhsScore stFhsScore;
	std::vector<struct FhsScore> vecFhsRank;
	int nFhsCount = 0;

	vecFhsRank.clear();

	// 2021-01-19 NEW huibong move 대상 FHS 선정시 disk busy 허용 max 값 추가 (#33395)
	// - conf 처리 관련 변수 사용하도록 수정 처리
	double dTargetFhsMaxDiskBusyLimit = CProcessConfig::GetInstance()->GetTargetFhsMaxDiskBusyLimit();

	// write 가능 장비를 찾아.. score 계산.
	for( int i = 0; i <= pShm->client_max_index; i++ )
	{
		pClient = &(pShm->client[i]);

		// slot 할당되어 있고.. data 가 유효하고.. 
		// write 가능 상태
		// system 상태 정보가 정상인 경우...( system 정보가 늦게 수신되므로.. 이를 반영 )
		// service 상태 정보가 expire 시간보다 오래되지 않은 최근 업데이트된 FHS 인 경우..
		// disk 최대 사용율이 평균값 미만
		// disk busy 하지 않고.. 남은 유효 용량 역시 충분한 경우 
		if( pClient->use == 1
			&& pClient->valid == 1
			&& pClient->service.service_stat == SERVICE_STAT_OK
			&& pClient->system.update_time != 0
			&& pClient->service.update_time_clock_sec >= expireTime
			&& pClient->system.storage_max_usage < (int)uStorageMaxUsage
			&& pClient->system.disk_max_busy <= dTargetFhsMaxDiskBusyLimit
			&& pClient->system.storage_avail >= COPY_TARGET_DISK_MIN_AVAILABLE_LIMIT
			)
		{
			// 임시 객체 초기화.
			memset( &stFhsScore, 0x00, sizeof( struct FhsScore ) );

			// hostname 저장.
			strncpy( stFhsScore.hostname, pClient->hostname, HOST_NAME_LENGTH );

			// write score 계산 처리.
			// - storage_max_usage 항목과
			// - disk_max_busy * 전달받은 가중치 값의 합.
			stFhsScore.score = (double)(pClient->system.storage_max_usage)
				+ ((pClient->system.disk_max_busy) * COPY_TARGET_DISK_MAX_BUSY_WEIGHT);

			// vector 에 결과 저장
			vecFhsRank.push_back( stFhsScore );

			// count
			nFhsCount += 1;
		}
	}

	// copy write 가능 장비가 존재하면
	if( nFhsCount > 0 )
	{
		if( nFhsCount > 1 )
		{
			// 가장 점수가 낮아 이상적인 장비 순서로 정렬
			std::sort( vecFhsRank.begin(), vecFhsRank.end(), FhsScoreSortASC );
		}

		// lock 을 걸고... 
		// m_listCopyTargetFhs 정보 및 m_timeLastRefreshCopyTargetFhs 정보 업데이트.
		std::vector<struct FhsScore>::iterator it;
		std::string szRefresList;
		
		// 기존 list 에 저장된 정보 clear
		pthread_mutex_lock( &m_mutexCopyTargetFhs );
		m_listCopyTargetFhs.clear();

		for( it = vecFhsRank.begin(); it != vecFhsRank.end(); ++it )
		{
			// 정렬된 순서대로 list 에 push 처리
			m_listCopyTargetFhs.push_back( it->hostname );

			szRefresList.append( it->hostname );
			szRefresList.append( " " );

			_LOG( LDBG, "[SHM INF] copy target fhs list push_back.[%s].", it->hostname );
		}

		// refresh time 정보 업데이트
		clock_gettime( CLOCK_MONOTONIC, &m_timeLastRefreshCopyTargetFhs );
		pthread_mutex_unlock( &m_mutexCopyTargetFhs );

		_LOG( LINF, "[SHM INF] copy target fhs list refreshed. usage avg limit[%u %%]. [%d][%s]"
			, uStorageMaxUsage, nFhsCount, szRefresList.c_str() );
		return true;
	}
	else
	{
		// Copy 수신 가능 FHS 가 없는 경우...
		// - 관련 내역 로깅
		_LOG( LWAR, "[SHM INF] copy target fhs not exist. usage avg limit[%u %%], busy limit[%.1f %%]"
			, uStorageMaxUsage, dTargetFhsMaxDiskBusyLimit );
		return false;
	}
}


bool CShmInfoThread::RefreshRebalanceFhsList( const RcInfo * const pShm, unsigned int uStorageMinUsage )
{
	// FHS 의 시스템 정보 업데이트가 오래된 경우.. 
	// - rc_mond 에서 지정된 시간( 20 sec ) 를 초과한 경우.. expire 처리하지만...
	// - 오류가 발생할 수 있고... FHS 상태가 이상할 경우.. 해당 장비는 선택하지 않는 것이 좋으므로...
	// - 업데이트가 비교적 최근에 발생한 FHS 정보만을 사용토록 한다.
	// - csagentd 는 4(service), 10(system) sec 마다 정보 전송...
	// - 따라서.. 8 sec 초과된 정보는 사용하지 않도록 한다.
	// - local time 은 시간 동기화 영향 받으므로... update_time_clock_sec 항목을 사용하여 판단한다.
	time_t    expireTime;
	struct timespec currentClock;
	memset( &currentClock, 0x00, sizeof( struct timespec ) );
	clock_gettime( CLOCK_MONOTONIC, &currentClock );
	expireTime = currentClock.tv_sec - 8;

	const struct ClientInfo * pClient = NULL;
	struct FhsScore stFhsScore;
	std::vector<struct FhsScore> vecFhsRank;
	int nFhsCount = 0;

	vecFhsRank.clear();

	// rebalance 대상 장비를 찾아.. score 계산.
	for( int i = 0; i <= pShm->client_max_index; i++ )
	{
		pClient = &(pShm->client[i]);

		// slot 할당되어 있고.. data 가 유효하고.. 
		// read only 상태인 경우.. 
		// system 상태 정보가 정상인 경우...( system 정보가 늦게 수신되므로.. 이를 반영 )
		// service 상태 정보가 expire 시간보다 오래되지 않은 최근 업데이트된 FHS 인 경우..
		// disk 최대 사용율이 전달받은 값 이상이고
		// disk 최대 사용량이 REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT(%) 이상인 장비
		// disk busy 가 REBALANCE_TARGET_DISK_MAX_BUSY_LIMIT (%) 이하인 장비.
		if( pClient->use == 1
			&& pClient->valid == 1
			&& ( pClient->service.service_stat == SERVICE_STAT_RONLY || pClient->service.service_stat == SERVICE_STAT_OK )
			&& pClient->system.update_time != 0
			&& pClient->service.update_time_clock_sec >= expireTime
			&& pClient->system.storage_max_usage >= (int)uStorageMinUsage
			&& pClient->system.storage_max_usage >= REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT
			&& pClient->system.disk_max_busy <= REBALANCE_TARGET_DISK_MAX_BUSY_LIMIT )
		{
		
			// 임시 객체 초기화.
			memset( &stFhsScore, 0x00, sizeof( struct FhsScore ) );

			// hostname 저장.
			strncpy( stFhsScore.hostname, pClient->hostname, HOST_NAME_LENGTH );

			// score 계산 처리.
			// - storage_max_usage 항목에서 disk_max_busy 값을 빼서 점수 계산
			stFhsScore.score = (double)(pClient->system.storage_max_usage)
				- ((pClient->system.disk_max_busy) * REBALANCE_TARGET_DISK_MAX_BUSY_WEIGHT);

			// vector 에 결과 저장
			vecFhsRank.push_back( stFhsScore );

			// result count 증가.
			nFhsCount += 1;
			
		}
	}

	// Rebalance 대상 장비가 존재하면
	if( nFhsCount > 0 )
	{
		if( nFhsCount > 1 )
		{
			// 가장 점수가 높은 순으로 정렬
			std::sort( vecFhsRank.begin(), vecFhsRank.end(), FhsScoreSortDESC );
		}

		std::string szRefresList;

		// lock 을 걸고... 
		// m_listCopyTargetFhs 정보 및 m_timeLastRefreshCopyTargetFhs 정보 업데이트.
		std::vector<struct FhsScore>::iterator it;

		// 기존 list 에 저장된 정보 clear
		pthread_mutex_lock( &m_mutexRebalanceFhs );
		m_listRebalanceFhs.clear();

		for( it = vecFhsRank.begin(); it != vecFhsRank.end(); ++it )
		{
			// 정렬된 순서대로 list 에 push 처리
			m_listRebalanceFhs.push_back( it->hostname );

			szRefresList.append( it->hostname );
			szRefresList.append( " " );

			_LOG( LDBG, "[SHM INF] rebalance fhs list push_back.[%s]", it->hostname );
		}

		// refresh time 정보 업데이트
		clock_gettime( CLOCK_MONOTONIC, &m_timeLastRefreshRebalanceFhs );
		pthread_mutex_unlock( &m_mutexRebalanceFhs );

		_LOG( LINF, "[SHM INF] rebalance fhs list refreshed. usage limit[%u %%]. [%d][%s]"
			, (uStorageMinUsage > REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT ? uStorageMinUsage : REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT)
			, nFhsCount, szRefresList.c_str() );

		return true;
	}
	else
	{
		// Rebalance 처리 대상 FHS 가 없는 경우...
		// - 관련 내역 로깅
		_LOG( LINF, "[SHM INF] rebalance fhs not exist with usage limit [%u %%], busy limit [%.2f %%]"
			, (uStorageMinUsage > REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT ? uStorageMinUsage : REBALANCE_TARGET_DISK_MIN_USAGE_LIMIT)
			, REBALANCE_TARGET_DISK_MAX_BUSY_LIMIT );

		return false;
	}
}

