2013-10-23 17:08:09 +00:00
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// connection_stats.cpp : Defines the entry point for the DLL application.
//
# include "stdafx.h"
# define LOG_ANALYSER_PLUGIN_EXPORTS
# include "connection_stats.h"
# include <windows.h>
BOOL APIENTRY DllMain ( HANDLE hModule ,
DWORD ul_reason_for_call ,
LPVOID lpReserved
)
{
switch ( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH :
case DLL_THREAD_ATTACH :
case DLL_THREAD_DETACH :
case DLL_PROCESS_DETACH :
break ;
}
return TRUE ;
}
# include <nel/misc/debug.h>
using namespace NLMISC ;
# include <time.h>
# include <map>
using namespace std ;
time_t LogBeginTime = 0 , LogEndTime = 0 ;
bool ContinuationOfStatInPreviousFile = true ;
//
string timeToStr ( const time_t & ts )
{
//return string(ctime( &ts )).substr( 0, 24 );
return IDisplayer : : dateToHumanString ( ts ) ;
}
//
string toHourStr ( uint totalSec )
{
uint hour = totalSec / 3600 ;
uint remMin = totalSec % 3600 ;
string res = toString ( " %uh%02u'%02u \" " , hour , remMin / 60 , remMin % 60 ) ;
return res ;
}
/*
*
*/
struct TSession
{
uint ClientId ;
time_t BeginTime ;
time_t EndTime ;
time_t Duration ;
bool Closed ;
} ;
/*
*
*/
struct TPlayerStat
{
uint UserId ;
string Name ;
vector < TSession > Sessions ;
uint Average ;
uint Sum ;
uint Min ;
uint Max ;
///
TPlayerStat ( ) : UserId ( 0 ) , Name ( " ? " ) , Average ( 0 ) , Sum ( 0 ) , Min ( 0 ) , Max ( 0 ) { }
///
bool beginSession ( const time_t & ts , uint clientId , uint userId , const string & name )
{
if ( Sessions . empty ( ) | | ( Name = = " ? " ) )
{
init ( userId , name ) ;
}
if ( Sessions . empty ( ) | | ( Sessions . back ( ) . EndTime ! = 0 ) )
{
// Open a new session
TSession s ;
s . ClientId = clientId ;
s . BeginTime = ts ;
s . EndTime = 0 ;
s . Closed = false ;
Sessions . push_back ( s ) ;
return true ;
}
else
{
// Occurs if two clients have the same userId
nlwarning ( " Opening a session for user %u (%s) at %s while previous session at %s not closed " , UserId , Name . c_str ( ) , timeToStr ( ts ) . c_str ( ) , timeToStr ( Sessions . back ( ) . BeginTime ) . c_str ( ) ) ;
//Sessions.back().ClientId = clientId; // cumulate times
//return false;
// Close the previous session
bool userMissing ;
endSession ( ts , Sessions . back ( ) . ClientId , userId , name , & userMissing , false ) ;
// Open a new session
TSession s ;
s . ClientId = clientId ;
s . BeginTime = ts ;
s . EndTime = 0 ;
s . Closed = false ;
Sessions . push_back ( s ) ;
return false ;
}
}
/**
* Return true if the disconnection is valid , false if ignored . If true , set userMissing if connection of user not found in the log
* but was done before writing into this stat file .
*/
bool endSession ( const time_t & ts , uint clientId , uint userId , const string & name , bool * userMissing , bool closed = true )
{
if ( Sessions . empty ( ) | | ( Name = = " ? " ) )
{
init ( userId , name ) ;
}
if ( Sessions . empty ( ) )
{
// User was already connected at beginning of log
if ( ContinuationOfStatInPreviousFile )
{
nldebug ( " User %u (%s): disconnection at %s: connection before beginning of stat detected " , userId , name . c_str ( ) , timeToStr ( ts ) . c_str ( ) ) ;
TSession s ;
s . ClientId = clientId ;
s . BeginTime = 0 ;
s . EndTime = ts ;
s . Closed = closed ;
Sessions . push_back ( s ) ;
* userMissing = true ;
return true ;
}
else
{
nldebug ( " User %u (%s): ignoring disconnection at %s (could not be connected before stat) " , userId , name . c_str ( ) , timeToStr ( ts ) . c_str ( ) ) ;
return false ;
}
}
else
{
// Close the current session
if ( clientId = = Sessions . back ( ) . ClientId )
{
if ( Sessions . back ( ) . EndTime = = 0 )
{
Sessions . back ( ) . EndTime = ts ;
Sessions . back ( ) . Closed = closed ;
* userMissing = false ;
return true ;
}
else
{
nlwarning ( " Detected two successive disconnections of user %u (%s) without reconnection (second ignored) at %s " , userId , name . c_str ( ) , timeToStr ( ts ) . c_str ( ) ) ;
return false ;
}
}
else
{
// Occurs if two clients have the same userId
nlwarning ( " Closing a session for user %u (%s) with invalid client (ignored) " , userId , name . c_str ( ) ) ;
return false ;
}
}
}
///
sint calcSessionTime ( uint numSession , const time_t & testEndTime )
{
if ( numSession < Sessions . size ( ) )
{
if ( Sessions [ numSession ] . BeginTime = = 0 )
{
Sessions [ numSession ] . BeginTime = LogBeginTime ;
nlinfo ( " User %u %s already connected at beginning of log (session end at %s) " , UserId , Name . c_str ( ) , timeToStr ( Sessions [ numSession ] . EndTime ) . c_str ( ) ) ;
}
if ( Sessions [ numSession ] . EndTime = = 0 )
{
Sessions [ numSession ] . EndTime = LogEndTime ;
nlinfo ( " User %u %s still connected at end of log (session begin at %s) " , UserId , Name . c_str ( ) , timeToStr ( Sessions [ numSession ] . BeginTime ) . c_str ( ) ) ;
}
Sessions [ numSession ] . Duration = ( int ) difftime ( Sessions [ numSession ] . EndTime , Sessions [ numSession ] . BeginTime ) ;
return Sessions [ numSession ] . Duration ;
}
else
return 0 ;
}
private :
///
void init ( uint userId , const string & name )
{
UserId = userId ;
Name = name ;
}
} ;
/*
*
*/
struct TInstantNbPlayers
{
uint Nb ;
time_t Timestamp ;
uint UserId ;
string Event ;
} ;
/*
*
*/
typedef std : : map < uint , TPlayerStat > TPlayerMap ;
typedef std : : deque < TInstantNbPlayers > TNbPlayersSeries ;
TPlayerMap PlayerMap ;
TNbPlayersSeries NbPlayersSeries ;
uint NbPlayers ;
string MainStats ;
float TotalTimeInDays ;
///
void resetAll ( )
{
LogBeginTime = 0 ;
LogEndTime = 0 ;
PlayerMap . clear ( ) ;
NbPlayersSeries . clear ( ) ;
NbPlayers = 0 ;
MainStats = " " ;
}
///
void addConnectionEvent ( const time_t & ts , uint userId )
{
+ + NbPlayers ;
TInstantNbPlayers inp ;
inp . UserId = userId ;
inp . Event = " + " ;
if ( ts ! = 0 )
{
inp . Nb = NbPlayers ;
inp . Timestamp = ts ;
NbPlayersSeries . push_back ( inp ) ;
}
else
{
nldebug ( " Inserting connection of user %u at beginning " , userId ) ;
// Insert at front and increment every other number
for ( TNbPlayersSeries : : iterator iv = NbPlayersSeries . begin ( ) ; iv ! = NbPlayersSeries . end ( ) ; + + iv )
+ + ( * iv ) . Nb ;
inp . Nb = 1 ;
inp . Timestamp = LogBeginTime ;
NbPlayersSeries . push_front ( inp ) ;
}
}
///
void addDisconnectionEvent ( const time_t & ts , uint userId )
{
- - NbPlayers ;
TInstantNbPlayers inp ;
inp . Nb = NbPlayers ;
inp . Timestamp = ts ;
inp . UserId = userId ;
inp . Event = " - " ;
NbPlayersSeries . push_back ( inp ) ;
}
///
void addConnection ( const time_t & ts , uint clientId , uint userId , const string & name )
{
if ( PlayerMap [ userId ] . beginSession ( ts , clientId , userId , name ) )
{
addConnectionEvent ( ts , userId ) ;
}
}
///
void addDisconnection ( const time_t & ts , uint clientId , uint userId )
{
bool userMissing ;
if ( PlayerMap [ userId ] . endSession ( ts , clientId , userId , " ? " , & userMissing ) )
{
if ( userMissing )
{
// Add connection at beginning if the server was started at a date anterior to the beginning of this stat file
// (otherwise, just discard the disconnection, it could be a stat file corruption transformed
// into server reset)
addConnectionEvent ( 0 , userId ) ;
}
addDisconnectionEvent ( ts , userId ) ;
}
}
///
void resetConnections ( const time_t & shutdownTs , const time_t & restartTs )
{
ContinuationOfStatInPreviousFile = false ;
TPlayerMap : : iterator ipm ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
if ( ! ( * ipm ) . second . Sessions . empty ( ) )
{
if ( ( * ipm ) . second . Sessions . back ( ) . EndTime = = 0 )
{
addDisconnection ( shutdownTs , ( * ipm ) . second . Sessions . back ( ) . ClientId , ( * ipm ) . second . UserId ) ;
nlwarning ( " Resetting connection of user %u because of server shutdown at %s (restart at %s) " , ( * ipm ) . second . UserId , timeToStr ( shutdownTs ) . c_str ( ) , timeToStr ( restartTs ) . c_str ( ) ) ;
}
}
}
}
///
void fillUserNamesInEvents ( )
{
TNbPlayersSeries : : iterator iv ;
for ( iv = NbPlayersSeries . begin ( ) ; iv ! = NbPlayersSeries . end ( ) ; + + iv )
{
( * iv ) . Event + = PlayerMap [ ( * iv ) . UserId ] . Name ;
}
}
///
void extractTime ( const string & line , time_t & ts )
{
struct tm t ;
t . tm_isdst = - 1 ; // auto-detect Daylight Saving Time
t . tm_wday = 0 ;
t . tm_yday = 0 ;
sscanf ( line . c_str ( ) , " %d/%d/%d %d:%d:%d " , & t . tm_year , & t . tm_mon , & t . tm_mday , & t . tm_hour , & t . tm_min , & t . tm_sec ) ;
t . tm_year - = 1900 ;
t . tm_mon - = 1 ; // 0..11
ts = mktime ( & t ) ;
if ( ts = = ( time_t ) - 1 )
{
/*CString s;
s . Format ( " %d/%d/%d %d:%d:%d (%d) " , t . tm_year , t . tm_mon , t . tm_mday , t . tm_hour , t . tm_min , t . tm_sec , ts ) ;
AfxMessageBox ( s ) ; *
exit ( - 1 ) ; */
ts = 0 ;
}
}
///
void calcStats ( string & res )
{
TPlayerMap : : iterator ipm ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
uint sum = 0 , themax = 0 , themin = ~ 0 ;
for ( uint i = 0 ; i ! = ( * ipm ) . second . Sessions . size ( ) ; + + i )
{
sum + = ( * ipm ) . second . Sessions [ i ] . Duration ;
if ( ( uint ) ( * ipm ) . second . Sessions [ i ] . Duration < themin )
themin = ( * ipm ) . second . Sessions [ i ] . Duration ;
if ( ( uint ) ( * ipm ) . second . Sessions [ i ] . Duration > themax )
themax = ( * ipm ) . second . Sessions [ i ] . Duration ;
}
( * ipm ) . second . Sum = sum ;
( * ipm ) . second . Average = sum / ( * ipm ) . second . Sessions . size ( ) ;
( * ipm ) . second . Min = themin ;
( * ipm ) . second . Max = themax ;
}
}
// Return date for filename such as 2003-06-24
string extractDateFilename ( time_t date )
{
string dateStr = timeToStr ( date ) ;
string : : size_type pos ;
for ( pos = 0 ; pos ! = dateStr . size ( ) ; + + pos )
{
if ( dateStr [ pos ] = = ' / ' )
dateStr [ pos ] = ' - ' ;
if ( dateStr [ pos ] = = ' ' )
{
dateStr = dateStr . substr ( 0 , pos ) ;
break ;
}
}
return dateStr ;
/*// Revert date
string : : size_type slashPos = dateStr . rfind ( ' / ' ) ;
if ( slashPos = = string : : npos )
return " " ;
string year = dateStr . substr ( slashPos + 1 , slashPos + 5 ) ;
slashPos = dateStr . rfind ( ' / ' , slashPos - 1 ) ;
if ( slashPos = = string : : npos )
return " " ;
string month = dateStr . substr ( slashPos + 1 , slashPos + 3 ) ;
string day = dateStr . substr ( 0 , 2 ) ;
return year + " - " + month + " - " + day ; */
}
enum TMainStatEnum { MSNb , MSAverage , MSSum , MSMin , MSMax } ;
/// Return stats in float 'days'
void getValuesStatsAndClearValues ( vector < float > & values , string & res , bool isTimeInMinute , TMainStatEnum msEnum )
{
float sum = 0.0f , themax = 0.0f , themin = 60.0f * 24.0f * 365.25f * 100.0f ; // 1 century should be enough
vector < float > : : const_iterator iv ;
for ( iv = values . begin ( ) ; iv ! = values . end ( ) ; + + iv )
{
sum + = ( * iv ) ;
if ( ( * iv ) < themin )
themin = ( * iv ) ;
if ( ( * iv ) > themax )
themax = ( * iv ) ;
}
if ( isTimeInMinute )
{
res + = toString ( " \t %g " , sum / ( float ) values . size ( ) / ( 24.0f * 60.0f ) ) +
toString ( " \t %g " , sum / ( 24.0f * 60.0f ) ) +
toString ( " \t %g " , themin / ( 24.0f * 60.0f ) ) +
toString ( " \t %g " , themax / ( 24.0f * 60.0f ) ) ;
switch ( msEnum )
{
case MSAverage :
break ;
case MSSum :
MainStats + = toString ( " \t %g " , sum / ( float ) values . size ( ) / ( 24.0f * 60.0f ) / TotalTimeInDays ) +
toString ( " \t %g " , sum / ( 24.0f * 60.0f ) / TotalTimeInDays ) +
toString ( " \t %g " , themax / ( 24.0f * 60.0f ) / TotalTimeInDays ) ;
break ;
case MSMax :
break ;
default :
break ;
}
}
else
{
res + = " \t " + toString ( " %g " , sum / ( float ) values . size ( ) ) +
" \t " + toString ( " %g " , sum ) +
" \t " + toString ( " %g " , themin ) +
" \t " + toString ( " %g " , themax ) ;
}
values . clear ( ) ;
}
/// Return stats in float 'days'
void getValuesStatsAndClearValues ( vector < float > & values , vector < vector < string > > & table , bool isTimeInMinute , TMainStatEnum msEnum )
{
float sum = 0.0f , themax = 0.0f , themin = 60.0f * 24.0f * 365.25f * 100.0f ; // 1 century should be enough
vector < float > : : const_iterator iv ;
for ( iv = values . begin ( ) ; iv ! = values . end ( ) ; + + iv )
{
sum + = ( * iv ) ;
if ( ( * iv ) < themin )
themin = ( * iv ) ;
if ( ( * iv ) > themax )
themax = ( * iv ) ;
}
if ( isTimeInMinute )
{
table . back ( ) . push_back ( toString ( " %g " , sum / ( float ) values . size ( ) / ( 24.0f * 60.0f ) ) ) ;
table . back ( ) . push_back ( toString ( " %g " , sum / ( 24.0f * 60.0f ) ) ) ;
table . back ( ) . push_back ( toString ( " %g " , themin / ( 24.0f * 60.0f ) ) ) ;
table . back ( ) . push_back ( toString ( " %g " , themax / ( 24.0f * 60.0f ) ) ) ;
switch ( msEnum )
{
case MSAverage :
break ;
case MSSum :
MainStats + = toString ( " \t %g " , sum / ( float ) values . size ( ) / ( 24.0f * 60.0f ) / TotalTimeInDays ) +
toString ( " \t %g " , sum / ( 24.0f * 60.0f ) / TotalTimeInDays ) +
toString ( " \t %g " , themax / ( 24.0f * 60.0f ) / TotalTimeInDays ) ;
break ;
case MSMax :
break ;
default :
break ;
}
}
else
{
table . back ( ) . push_back ( toString ( " %g " , sum / ( float ) values . size ( ) ) ) ;
table . back ( ) . push_back ( toString ( " %g " , sum ) ) ;
table . back ( ) . push_back ( toString ( " %g " , themin ) ) ;
table . back ( ) . push_back ( toString ( " %g " , themax ) ) ;
}
values . clear ( ) ;
}
/// (Note: main stats only for minutes)
uint getSessionDurations ( string & res , time_t endTime , bool convertToMinutes , bool inColumnsWithDetail )
{
uint sessionNum = 0 ;
if ( inColumnsWithDetail )
{
string s1 , s2 ;
TPlayerMap : : iterator ipm ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
s1 + = toString ( " %u " , ( * ipm ) . second . UserId ) + " \t " ;
s2 + = ( * ipm ) . second . Name + " \t " ;
}
res + = s1 + " \r \n " + s2 + " \r \n " ;
sint timeSum ;
do
{
timeSum = 0 ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
sint duration = ( * ipm ) . second . calcSessionTime ( sessionNum , endTime ) ;
if ( duration ! = 0 )
{
res + = ( convertToMinutes ? toString ( " %.2f " , ( float ) duration / 60.0f ) : toString ( " %d " , duration ) ) + " \t " ;
}
else
{
res + = string ( convertToMinutes ? " 0 " : " " ) + " \t " ;
}
timeSum + = duration ;
}
res + = " \r \n " ;
+ + sessionNum ;
}
while ( timeSum ! = 0 ) ;
res + = " \r \n " ;
calcStats ( res ) ;
if ( ! convertToMinutes )
{
// Stats
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
res + = toString ( " %u \t " , ( * ipm ) . second . Sessions . size ( ) ) ;
}
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
res + = toString ( " %u \t " , ( * ipm ) . second . Average ) ;
}
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
res + = toString ( " %u \t " , ( * ipm ) . second . Sum ) ;
}
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
res + = toString ( " %u \t " , ( * ipm ) . second . Min ) ;
}
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
res + = toString ( " %u \t " , ( * ipm ) . second . Max ) ;
}
res + = " \r \n " ;
}
else
{
// Stats
vector < float > values ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Sessions . size ( ) ) ) ;
res + = toString ( " %u \t " , ( * ipm ) . second . Sessions . size ( ) ) ;
}
getValuesStatsAndClearValues ( values , res , false , MSNb ) ;
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Average ) / 60.0f ) ;
res + = toString ( " %.2f \t " , values . back ( ) ) ;
}
getValuesStatsAndClearValues ( values , res , true , MSAverage ) ;
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Sum ) / 60.0f ) ;
res + = toString ( " %.2f \t " , values . back ( ) ) ;
}
getValuesStatsAndClearValues ( values , res , true , MSSum ) ;
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Min ) / 60.0f ) ;
res + = toString ( " %.2f \t " , values . back ( ) ) ;
}
getValuesStatsAndClearValues ( values , res , true , MSMin ) ;
res + = " \r \n " ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Max ) / 60.0f ) ;
res + = toString ( " %.2f \t " , values . back ( ) ) ;
}
getValuesStatsAndClearValues ( values , res , true , MSMax ) ;
res + = " \r \n " ;
}
}
else
{
vector < vector < string > > table ;
string s1 , s2 ;
table . push_back ( ) ;
table . push_back ( ) ;
TPlayerMap : : iterator ipm ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
table [ 0 ] . push_back ( toString ( " %u " , ( * ipm ) . second . UserId ) ) ; //+ "\t";
table [ 1 ] . push_back ( ( * ipm ) . second . Name ) ; //+ "\t";
}
//res += s1 + "\r\n" + s2 + "\r\n";
sint timeSum ;
do
{
timeSum = 0 ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
sint duration = ( * ipm ) . second . calcSessionTime ( sessionNum , endTime ) ;
timeSum + = duration ;
}
+ + sessionNum ;
}
while ( timeSum ! = 0 ) ;
table . push_back ( ) ;
//res += "\r\n";
calcStats ( res ) ;
if ( ! convertToMinutes )
{
// Stats
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
table . back ( ) . push_back ( toString ( " %u " , ( * ipm ) . second . Sessions . size ( ) ) ) ;
}
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
table . back ( ) . push_back ( toString ( " %u " , ( * ipm ) . second . Average ) ) ;
}
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
table . back ( ) . push_back ( toString ( " %u " , ( * ipm ) . second . Sum ) ) ;
}
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
table . back ( ) . push_back ( toString ( " %u " , ( * ipm ) . second . Min ) ) ;
}
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
table . back ( ) . push_back ( toString ( " %u " , ( * ipm ) . second . Max ) ) ;
}
//res += "\r\n";
}
else
{
// Stats
vector < float > values ;
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Sessions . size ( ) ) ) ;
table . back ( ) . push_back ( toString ( " %u " , ( * ipm ) . second . Sessions . size ( ) ) ) ;
}
getValuesStatsAndClearValues ( values , table , false , MSNb ) ;
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Average ) / 60.0f ) ;
table . back ( ) . push_back ( toString ( " %.2f " , values . back ( ) ) ) ;
}
getValuesStatsAndClearValues ( values , table , true , MSAverage ) ;
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Sum ) / 60.0f ) ;
table . back ( ) . push_back ( toString ( " %.2f " , values . back ( ) ) ) ;
}
getValuesStatsAndClearValues ( values , table , true , MSSum ) ;
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Min ) / 60.0f ) ;
table . back ( ) . push_back ( toString ( " %.2f " , values . back ( ) ) ) ;
}
getValuesStatsAndClearValues ( values , table , true , MSMin ) ;
//res += "\r\n";
table . push_back ( ) ;
for ( ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
values . push_back ( ( float ) ( ( * ipm ) . second . Max ) / 60.0f ) ;
table . back ( ) . push_back ( toString ( " %.2f " , values . back ( ) ) ) ;
}
getValuesStatsAndClearValues ( values , table , true , MSMax ) ;
//res += "\r\n";
}
// Print in column
/*for ( uint j=0; j!=table.size(); ++j )
{
for ( uint i = 0 ; i ! = table [ j ] . size ( ) ; + + i )
{
res + = table [ j ] [ i ] + " \t " ;
}
res + = " \r \n " ;
} */
// Print in row
uint maxI = 0 ;
for ( uint j = 0 ; j ! = table . size ( ) ; + + j )
{
if ( table [ j ] . size ( ) > maxI )
maxI = table [ j ] . size ( ) ;
}
for ( uint i = 0 ; i ! = maxI ; + + i )
{
for ( uint j = 0 ; j ! = table . size ( ) ; + + j )
{
if ( i < table [ j ] . size ( ) )
res + = table [ j ] [ i ] + " \t " ;
else
res + = " \t " ;
}
res + = " \r \n " ;
}
}
res + = " \r \n " ;
return sessionNum ;
}
/*
*
*/
LOG_ANALYSER_PLUGIN_API std : : string getInfoString ( )
{
return " Input log: connection.stat or frontend_service.log. \n \n Produces tab-separated connection stats for Excel. " ;
}
/*
*
*/
LOG_ANALYSER_PLUGIN_API bool doAnalyse ( const std : : vector < const char * > & vec , std : : string & res , std : : string & log )
{
NLMISC : : createDebug ( ) ;
CMemDisplayer memdisp ;
NLMISC : : DebugLog - > addDisplayer ( & memdisp , true ) ;
NLMISC : : InfoLog - > addDisplayer ( & memdisp , true ) ;
NLMISC : : WarningLog - > addDisplayer ( & memdisp , true ) ;
NLMISC : : ErrorLog - > addDisplayer ( & memdisp , true ) ;
NLMISC : : AssertLog - > addDisplayer ( & memdisp , true ) ;
resetAll ( ) ;
// Begin and end time
if ( ! vec . empty ( ) )
{
sint l = 0 ;
sint quicklines = min ( vec . size ( ) , 100 ) ;
while ( ( l < quicklines ) & & ( ( string ( vec [ l ] ) . size ( ) < 5 ) | | ( vec [ l ] [ 4 ] ! = ' / ' ) ) )
+ + l ;
if ( l < quicklines )
extractTime ( string ( vec [ l ] ) , LogBeginTime ) ;
l = ( ( sint ) vec . size ( ) ) - 1 ;
quicklines = max ( ( ( sint ) vec . size ( ) ) - 100 , 0 ) ;
while ( ( l > = quicklines ) & & ( ( string ( vec [ l ] ) . size ( ) < 5 ) | | ( vec [ l ] [ 4 ] ! = ' / ' ) ) )
- - l ;
if ( l > = quicklines )
extractTime ( string ( vec [ l ] ) , LogEndTime ) ;
}
res + = " Log begin time \t \t " + timeToStr ( LogBeginTime ) + " \r \n " ;
res + = " Log end time \t \t " + timeToStr ( LogEndTime ) + " \r \n " ;
MainStats + = timeToStr ( LogEndTime ) ;
TotalTimeInDays = ( ( float ) ( LogEndTime - LogBeginTime ) ) / 3600.0f / 24.0f ;
// Scan sessions
uint nbPossibleCorruptions = 0 ;
uint i ;
for ( i = 0 ; i ! = vec . size ( ) ; + + i )
{
string line = string ( vec [ i ] ) ;
time_t ts ;
uint clientId ;
uint userId ;
string : : size_type p ;
// Auto-detect file corruption (version for connections.stat)
if ( ! line . empty ( ) )
{
bool corrupted = false ;
// Search for beginning not being a date or 'Log Starting"
if ( ( line . size ( ) < 20 ) | | ( line [ 10 ] ! = ' ' ) | | ( line [ 13 ] ! = ' : ' )
| | ( line [ 16 ] ! = ' : ' ) | | ( line [ 19 ] ! = ' ' ) | | ( line . substr ( 20 ) . find ( " : " ) = = string : : npos ) )
{
if ( line . find ( " Log Starting [ " ) ! = 0 )
corrupted = true ;
}
else
{
// Search for year not at beginning. Ex: "2003/" (it does not work when the year changes in the log!)
p = line . substr ( 1 ) . find ( timeToStr ( LogBeginTime ) . substr ( 0 , 5 ) ) ;
if ( p ! = string : : npos )
{
+ + p ; // because searched from pos 1
// Search for date/time
if ( ( line . size ( ) > p + 20 ) & & ( line [ p + 10 ] = = ' ' ) & & ( line [ p + 13 ] = = ' : ' )
& & ( line [ p + 16 ] = = ' : ' ) & & ( line [ p + 19 ] = = ' ' ) )
{
// Search for the two next blank characters. The second is followed by ": ".
// (Date Time ThreadId Machine/Service : User-defined log line)
uint nbBlank = 0 ;
string : : size_type sp ;
for ( sp = p + 20 ; sp ! = line . size ( ) ; + + sp )
{
if ( line [ sp ] = = ' ' )
+ + nbBlank ;
if ( nbBlank = = 2 )
break ;
}
if ( ( nbBlank = = 2 ) & & ( line [ sp + 1 ] = = ' : ' ) & & ( line [ sp + 2 ] = = ' ' ) )
{
corrupted = true ;
}
}
}
}
if ( corrupted )
{
+ + nbPossibleCorruptions ;
nlwarning ( " Found possible file corruption at line %u: %s " , i , line . c_str ( ) ) ;
}
}
// Detect connections/disconnections
p = line . find ( " Adding client " ) ;
if ( p ! = string : : npos )
{
extractTime ( line , ts ) ;
char name [ 200 ] ;
sscanf ( line . substr ( p ) . c_str ( ) , " Adding client %u (uid %u name %s " , & clientId , & userId , & name ) ;
string sname = name ;
addConnection ( ts , clientId , userId , sname /*sname.substr( 0, sname.size()-1 )*/ ) ; // now name format is "name priv ''".
continue ;
}
p = line . find ( " Sent CL_DISCONNECT for client " ) ;
if ( p ! = string : : npos )
{
extractTime ( line , ts ) ;
sscanf ( line . substr ( p ) . c_str ( ) , " Sent CL_DISCONNECT for client %u (uid %u) " , & clientId , & userId ) ;
addDisconnection ( ts , clientId , userId ) ;
continue ;
}
p = line . find ( " Log Starting [ " ) ;
if ( p ! = string : : npos )
{
uint hs = string ( " Log Starting [ " ) . size ( ) ;
line = line . substr ( hs , line . size ( ) - hs - 1 ) ; // remove ] to get the timestamp
time_t restartTs ;
extractTime ( line , restartTs ) ;
// Go back to find the last time of log
sint quicklines = max ( ( ( sint ) i ) - 10 , 0 ) ;
sint l = ( ( sint ) i ) - 1 ;
while ( ( l > = quicklines ) & & ( ( string ( vec [ l ] ) . size ( ) < 5 ) | | ( vec [ l ] [ 4 ] ! = ' / ' ) ) )
l - - ;
if ( l > = quicklines )
extractTime ( vec [ l ] , ts ) ;
else
ts = restartTs ;
resetConnections ( ts , restartTs ) ;
}
}
fillUserNamesInEvents ( ) ;
// Session durations
string sd ;
uint maxNbSession = getSessionDurations ( sd , LogEndTime , true , false ) ;
res + = " Number of accounts \t " + toString ( " %u " , PlayerMap . size ( ) ) + " \r \n " ;
res + = " Max number of session \t " + toString ( " %u " , maxNbSession ) + " \r \n " ;
res + = " \r \n Time of sessions \r \n " ;
res + = sd ;
res + = " Connection events \r \n " ;
MainStats + = toString ( " \t %u " , PlayerMap . size ( ) ) ;
// Timetable
time_t prevTimestamp = 0 ;
sint durationSum = 0 ;
int prevPlayerNb = - 1 ;
uint maxPlayerNb = 0 ;
for ( i = 0 ; i ! = NbPlayersSeries . size ( ) ; + + i )
{
sint duration = ( prevTimestamp ! = 0 ) ? ( int ) difftime ( NbPlayersSeries [ i ] . Timestamp , prevTimestamp ) : 0 ;
prevTimestamp = NbPlayersSeries [ i ] . Timestamp ;
durationSum + = duration ;
if ( prevPlayerNb ! = - 1 )
res + = " \t " + toString ( " %d " , durationSum ) + " \t " + timeToStr ( NbPlayersSeries [ i ] . Timestamp ) + " \t " + toString ( " %u " , prevPlayerNb ) + " \t " + " \r \n " ;
res + = toString ( " %d " , duration ) + " \t " + toString ( " %d " , durationSum ) + " \t " + timeToStr ( NbPlayersSeries [ i ] . Timestamp ) + " \t " + toString ( " %u " , NbPlayersSeries [ i ] . Nb ) + " \t " + NbPlayersSeries [ i ] . Event + " \r \n " ;
prevPlayerNb = NbPlayersSeries [ i ] . Nb ;
if ( NbPlayersSeries [ i ] . Nb > maxPlayerNb )
maxPlayerNb = NbPlayersSeries [ i ] . Nb ;
}
MainStats + = toString ( " \t %u " , maxPlayerNb ) + toString ( " \t \t (%g days) " , TotalTimeInDays ) ;
if ( nbPossibleCorruptions = = 0 )
MainStats + = toString ( " \t \t Stat file OK " ) ;
else
MainStats + = toString ( " \t \t Found %u possible stat file corruptions (edit the stat file to replace them with server reset if time too long, see log.log) " , nbPossibleCorruptions ) ;
// Stats per user
res + = " \r \n \n Stats per user (hrs) \r \n \n Name \t UserId \t Sessions \t Cumulated \t Average \t Min \t Max " ;
for ( TPlayerMap : : const_iterator ipm = PlayerMap . begin ( ) ; ipm ! = PlayerMap . end ( ) ; + + ipm )
{
res + = " \r \n \n " ;
const TPlayerStat & playerStat = ( * ipm ) . second ;
res + = toString ( " %s \t User %u \t %u \t %s \t %s \t %s \t %s " ,
playerStat . Name . c_str ( ) , playerStat . UserId , playerStat . Sessions . size ( ) ,
toHourStr ( playerStat . Sum ) . c_str ( ) , toHourStr ( playerStat . Average ) . c_str ( ) , toHourStr ( playerStat . Min ) . c_str ( ) , toHourStr ( playerStat . Max ) . c_str ( ) ) ;
for ( uint i = 0 ; i ! = playerStat . Sessions . size ( ) ; + + i )
{
res + = " \r \n " ;
const TSession & sess = playerStat . Sessions [ i ] ;
string status = sess . Closed ? " OK " : " Not closed " ;
res + = timeToStr ( sess . BeginTime ) + " \t " + timeToStr ( sess . EndTime ) + " \t " + status + " \t " + toHourStr ( sess . Duration ) ;
}
}
string dateStr = " " + extractDateFilename ( LogEndTime ) ;
res = dateStr + " \r \n Date \t Avg per player \t Total time \t Max per player \t Nb Players \t Simult. Pl. \r \n " + MainStats + " \r \n " + res ;
memdisp . write ( log ) ;
return true ;
}
/*CString s;
s . Format ( " Found C=%u U=%u N=%s in %s " , clientId , userId , name , line . substr ( p ) . c_str ( ) ) ;
2011-05-30 18:37:36 +00:00
AfxMessageBox ( s ) ; */