khanat-opennel-code/code/ryzom/tools/connection_stats/connection_stats.cpp

1013 lines
No EOL
27 KiB
C++

// 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\nProduces 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\nTime 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\tStat file OK" );
else
MainStats += toString( "\t\tFound %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\nStats per user (hrs)\r\n\nName\tUserId\tSessions\tCumulated\tAverage\tMin\tMax";
for ( TPlayerMap::const_iterator ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
{
res += "\r\n\n";
const TPlayerStat& playerStat = (*ipm).second;
res += toString( "%s\tUser %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\nDate\tAvg per player\tTotal time\tMax per player\tNb Players\tSimult. 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() );
AfxMessageBox( s );*/