Use configured resolution for login/outgame/ingame
[ryzomcore.git] / ryzom / tools / connection_stats / connection_stats.cpp
blobb502b4bef495b8089efb6d7376cd08b8037127dc
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // connection_stats.cpp : Defines the entry point for the DLL application.
20 #include "stdafx.h"
22 #define LOG_ANALYSER_PLUGIN_EXPORTS
23 #include "connection_stats.h"
25 #include <windows.h>
28 BOOL APIENTRY DllMain( HANDLE hModule,
29 DWORD ul_reason_for_call,
30 LPVOID lpReserved
33 switch (ul_reason_for_call)
35 case DLL_PROCESS_ATTACH:
36 case DLL_THREAD_ATTACH:
37 case DLL_THREAD_DETACH:
38 case DLL_PROCESS_DETACH:
39 break;
41 return TRUE;
45 #include <nel/misc/debug.h>
46 using namespace NLMISC;
49 #include <time.h>
50 #include <map>
51 using namespace std;
54 time_t LogBeginTime = 0, LogEndTime = 0;
55 bool ContinuationOfStatInPreviousFile = true;
59 string timeToStr( const time_t& ts )
61 //return string(ctime( &ts )).substr( 0, 24 );
62 return IDisplayer::dateToHumanString( ts );
67 string toHourStr( uint totalSec )
69 uint hour = totalSec / 3600;
70 uint remMin = totalSec % 3600;
71 string res = toString( "%uh%02u'%02u\"", hour, remMin / 60, remMin % 60 );
72 return res;
79 struct TSession
81 uint ClientId;
82 time_t BeginTime;
83 time_t EndTime;
84 time_t Duration;
85 bool Closed;
92 struct TPlayerStat
94 uint UserId;
95 string Name;
96 vector<TSession> Sessions;
97 uint Average;
98 uint Sum;
99 uint Min;
100 uint Max;
103 TPlayerStat() : UserId(0), Name("?"), Average(0), Sum(0), Min(0), Max(0) {}
106 bool beginSession( const time_t& ts, uint clientId, uint userId, const string& name )
108 if ( Sessions.empty() || (Name == "?") )
110 init( userId, name );
113 if ( Sessions.empty() || (Sessions.back().EndTime != 0) )
115 // Open a new session
116 TSession s;
117 s.ClientId = clientId;
118 s.BeginTime = ts;
119 s.EndTime = 0;
120 s.Closed = false;
121 Sessions.push_back( s );
122 return true;
124 else
126 // Occurs if two clients have the same userId
127 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() );
128 //Sessions.back().ClientId = clientId; // cumulate times
129 //return false;
131 // Close the previous session
132 bool userMissing;
133 endSession( ts, Sessions.back().ClientId, userId, name, &userMissing, false );
135 // Open a new session
136 TSession s;
137 s.ClientId = clientId;
138 s.BeginTime = ts;
139 s.EndTime = 0;
140 s.Closed = false;
141 Sessions.push_back( s );
142 return false;
148 * Return true if the disconnection is valid, false if ignored. If true, set userMissing if connection of user not found in the log
149 * but was done before writing into this stat file.
151 bool endSession( const time_t& ts, uint clientId, uint userId, const string& name, bool *userMissing, bool closed=true )
153 if ( Sessions.empty() || (Name == "?") )
155 init( userId, name );
158 if ( Sessions.empty() )
160 // User was already connected at beginning of log
161 if ( ContinuationOfStatInPreviousFile )
163 nldebug( "User %u (%s): disconnection at %s: connection before beginning of stat detected", userId, name.c_str(), timeToStr( ts ).c_str() );
164 TSession s;
165 s.ClientId = clientId;
166 s.BeginTime = 0;
167 s.EndTime = ts;
168 s.Closed = closed;
169 Sessions.push_back( s );
170 *userMissing = true;
171 return true;
173 else
175 nldebug( "User %u (%s): ignoring disconnection at %s (could not be connected before stat)", userId, name.c_str(), timeToStr( ts ).c_str() );
176 return false;
179 else
181 // Close the current session
182 if ( clientId == Sessions.back().ClientId )
184 if ( Sessions.back().EndTime == 0 )
186 Sessions.back().EndTime = ts;
187 Sessions.back().Closed = closed;
188 *userMissing = false;
189 return true;
191 else
193 nlwarning( "Detected two successive disconnections of user %u (%s) without reconnection (second ignored) at %s", userId, name.c_str(), timeToStr( ts ).c_str() );
194 return false;
197 else
199 // Occurs if two clients have the same userId
200 nlwarning( "Closing a session for user %u (%s) with invalid client (ignored)", userId, name.c_str() );
201 return false;
207 sint calcSessionTime( uint numSession, const time_t& testEndTime )
209 if ( numSession < Sessions.size() )
211 if ( Sessions[numSession].BeginTime == 0 )
213 Sessions[numSession].BeginTime = LogBeginTime;
214 nlinfo( "User %u %s already connected at beginning of log (session end at %s)", UserId, Name.c_str(), timeToStr( Sessions[numSession].EndTime ).c_str() );
216 if ( Sessions[numSession].EndTime == 0 )
218 Sessions[numSession].EndTime = LogEndTime;
219 nlinfo( "User %u %s still connected at end of log (session begin at %s)", UserId, Name.c_str(), timeToStr( Sessions[numSession].BeginTime ).c_str() );
222 Sessions[numSession].Duration = (int)difftime( Sessions[numSession].EndTime, Sessions[numSession].BeginTime );
223 return Sessions[numSession].Duration;
225 else
226 return 0;
229 private:
232 void init( uint userId, const string& name )
234 UserId = userId;
235 Name = name;
244 struct TInstantNbPlayers
246 uint Nb;
247 time_t Timestamp;
248 uint UserId;
249 string Event;
256 typedef std::map< uint, TPlayerStat > TPlayerMap;
257 typedef std::deque< TInstantNbPlayers > TNbPlayersSeries;
258 TPlayerMap PlayerMap;
259 TNbPlayersSeries NbPlayersSeries;
260 uint NbPlayers;
261 string MainStats;
262 float TotalTimeInDays;
266 void resetAll()
268 LogBeginTime = 0;
269 LogEndTime = 0;
270 PlayerMap.clear();
271 NbPlayersSeries.clear();
272 NbPlayers = 0;
273 MainStats = "";
278 void addConnectionEvent( const time_t& ts, uint userId )
280 ++NbPlayers;
281 TInstantNbPlayers inp;
282 inp.UserId = userId;
283 inp.Event = "+";
284 if ( ts != 0 )
286 inp.Nb = NbPlayers;
287 inp.Timestamp = ts;
288 NbPlayersSeries.push_back( inp );
290 else
292 nldebug( "Inserting connection of user %u at beginning", userId );
293 // Insert at front and increment every other number
294 for ( TNbPlayersSeries::iterator iv=NbPlayersSeries.begin(); iv!=NbPlayersSeries.end(); ++iv )
295 ++(*iv).Nb;
296 inp.Nb = 1;
297 inp.Timestamp = LogBeginTime;
298 NbPlayersSeries.push_front( inp );
304 void addDisconnectionEvent( const time_t& ts, uint userId )
306 --NbPlayers;
307 TInstantNbPlayers inp;
308 inp.Nb = NbPlayers;
309 inp.Timestamp = ts;
310 inp.UserId = userId;
311 inp.Event = "-";
312 NbPlayersSeries.push_back( inp );
317 void addConnection( const time_t& ts, uint clientId, uint userId, const string& name )
319 if ( PlayerMap[userId].beginSession( ts, clientId, userId, name ) )
321 addConnectionEvent( ts, userId );
327 void addDisconnection( const time_t& ts, uint clientId, uint userId )
329 bool userMissing;
330 if ( PlayerMap[userId].endSession( ts, clientId, userId, "?", &userMissing ) )
332 if ( userMissing )
334 // Add connection at beginning if the server was started at a date anterior to the beginning of this stat file
335 // (otherwise, just discard the disconnection, it could be a stat file corruption transformed
336 // into server reset)
337 addConnectionEvent( 0, userId );
340 addDisconnectionEvent( ts, userId );
346 void resetConnections( const time_t& shutdownTs, const time_t& restartTs )
348 ContinuationOfStatInPreviousFile = false;
350 TPlayerMap::iterator ipm;
351 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
353 if ( ! (*ipm).second.Sessions.empty() )
355 if ( (*ipm).second.Sessions.back().EndTime == 0 )
357 addDisconnection( shutdownTs, (*ipm).second.Sessions.back().ClientId, (*ipm).second.UserId );
358 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() );
366 void fillUserNamesInEvents()
368 TNbPlayersSeries::iterator iv;
369 for ( iv=NbPlayersSeries.begin(); iv!=NbPlayersSeries.end(); ++iv )
371 (*iv).Event += PlayerMap[(*iv).UserId].Name;
377 void extractTime( const string& line, time_t& ts )
379 struct tm t;
380 t.tm_isdst = -1; // auto-detect Daylight Saving Time
381 t.tm_wday = 0;
382 t.tm_yday = 0;
383 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 );
384 t.tm_year -= 1900;
385 t.tm_mon -= 1; // 0..11
387 ts = mktime( &t );
388 if ( ts == (time_t)-1 )
390 /*CString s;
391 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 );
392 AfxMessageBox( s );*
393 exit(-1);*/
394 ts = 0;
400 void calcStats( string& res )
402 TPlayerMap::iterator ipm;
403 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
405 uint sum = 0, themax = 0, themin = ~0;
406 for ( uint i = 0; i!=(*ipm).second.Sessions.size(); ++i )
408 sum += (*ipm).second.Sessions[i].Duration;
409 if ( (uint)(*ipm).second.Sessions[i].Duration < themin )
410 themin = (*ipm).second.Sessions[i].Duration;
411 if ( (uint)(*ipm).second.Sessions[i].Duration > themax )
412 themax = (*ipm).second.Sessions[i].Duration;
414 (*ipm).second.Sum = sum;
415 (*ipm).second.Average = sum / (*ipm).second.Sessions.size();
416 (*ipm).second.Min = themin;
417 (*ipm).second.Max = themax;
422 // Return date for filename such as 2003-06-24
423 string extractDateFilename( time_t date )
425 string dateStr = timeToStr( date );
426 string::size_type pos;
427 for ( pos=0; pos!=dateStr.size(); ++pos )
429 if ( dateStr[pos] == '/' )
430 dateStr[pos] = '-';
431 if ( dateStr[pos] == ' ' )
433 dateStr = dateStr.substr( 0, pos );
434 break;
437 return dateStr;
439 /*// Revert date
440 string::size_type slashPos = dateStr.rfind( '/' );
441 if ( slashPos == string::npos )
442 return "";
443 string year = dateStr.substr( slashPos+1, slashPos+5 );
444 slashPos = dateStr.rfind( '/', slashPos-1 );
445 if ( slashPos == string::npos )
446 return "";
447 string month = dateStr.substr( slashPos+1, slashPos+3 );
448 string day = dateStr.substr( 0, 2 );
449 return year + "-" + month + "-" + day;*/
453 enum TMainStatEnum { MSNb, MSAverage, MSSum, MSMin, MSMax };
456 /// Return stats in float 'days'
457 void getValuesStatsAndClearValues( vector<float>& values, string& res, bool isTimeInMinute, TMainStatEnum msEnum )
459 float sum = 0.0f, themax = 0.0f, themin = 60.0f*24.0f*365.25f*100.0f; // 1 century should be enough
460 vector<float>::const_iterator iv;
461 for ( iv=values.begin(); iv!=values.end(); ++iv )
463 sum += (*iv);
464 if ( (*iv) < themin )
465 themin = (*iv);
466 if ( (*iv) > themax )
467 themax = (*iv);
469 if ( isTimeInMinute )
471 res += toString( "\t%g", sum / (float)values.size() / (24.0f*60.0f) ) +
472 toString( "\t%g", sum / (24.0f*60.0f) ) +
473 toString( "\t%g", themin / (24.0f*60.0f) ) +
474 toString( "\t%g", themax / (24.0f*60.0f) );
475 switch( msEnum )
477 case MSAverage:
478 break;
479 case MSSum:
480 MainStats += toString( "\t%g", sum / (float)values.size() / (24.0f*60.0f) / TotalTimeInDays ) +
481 toString( "\t%g", sum / (24.0f*60.0f) / TotalTimeInDays ) +
482 toString( "\t%g", themax / (24.0f*60.0f) / TotalTimeInDays );
483 break;
484 case MSMax:
485 break;
486 default:
487 break;
491 else
493 res += "\t" + toString( "%g", sum / (float)values.size() ) +
494 "\t" + toString( "%g", sum ) +
495 "\t" + toString( "%g", themin ) +
496 "\t" + toString( "%g", themax );
498 values.clear();
502 /// Return stats in float 'days'
503 void getValuesStatsAndClearValues( vector<float>& values, vector< vector<string> >& table, bool isTimeInMinute, TMainStatEnum msEnum )
505 float sum = 0.0f, themax = 0.0f, themin = 60.0f*24.0f*365.25f*100.0f; // 1 century should be enough
506 vector<float>::const_iterator iv;
507 for ( iv=values.begin(); iv!=values.end(); ++iv )
509 sum += (*iv);
510 if ( (*iv) < themin )
511 themin = (*iv);
512 if ( (*iv) > themax )
513 themax = (*iv);
515 if ( isTimeInMinute )
517 table.back().push_back( toString( "%g", sum / (float)values.size() / (24.0f*60.0f) ) );
518 table.back().push_back( toString( "%g", sum / (24.0f*60.0f) ) );
519 table.back().push_back( toString( "%g", themin / (24.0f*60.0f) ) );
520 table.back().push_back( toString( "%g", themax / (24.0f*60.0f) ) );
521 switch( msEnum )
523 case MSAverage:
524 break;
525 case MSSum:
526 MainStats += toString( "\t%g", sum / (float)values.size() / (24.0f*60.0f) / TotalTimeInDays ) +
527 toString( "\t%g", sum / (24.0f*60.0f) / TotalTimeInDays ) +
528 toString( "\t%g", themax / (24.0f*60.0f) / TotalTimeInDays );
529 break;
530 case MSMax:
531 break;
532 default:
533 break;
537 else
539 table.back().push_back( toString( "%g", sum / (float)values.size() ) );
540 table.back().push_back( toString( "%g", sum ) );
541 table.back().push_back( toString( "%g", themin ) );
542 table.back().push_back( toString( "%g", themax ) );
544 values.clear();
548 /// (Note: main stats only for minutes)
549 uint getSessionDurations( string& res, time_t endTime, bool convertToMinutes, bool inColumnsWithDetail )
551 uint sessionNum = 0;
552 if ( inColumnsWithDetail )
554 string s1, s2;
555 TPlayerMap::iterator ipm;
556 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
558 s1 += toString( "%u", (*ipm).second.UserId ) + "\t";
559 s2 += (*ipm).second.Name + "\t";
561 res += s1 + "\r\n" + s2 + "\r\n";
562 sint timeSum;
565 timeSum = 0;
566 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
568 sint duration = (*ipm).second.calcSessionTime( sessionNum, endTime );
569 if ( duration != 0 )
571 res += (convertToMinutes ? toString( "%.2f", (float)duration/60.0f ) : toString( "%d", duration )) + "\t";
573 else
575 res += string(convertToMinutes ? "0" : "") + "\t";
577 timeSum += duration;
579 res += "\r\n";
581 ++sessionNum;
583 while ( timeSum != 0 );
584 res += "\r\n";
586 calcStats( res );
588 if ( ! convertToMinutes)
590 // Stats
591 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
593 res += toString( "%u\t", (*ipm).second.Sessions.size() );
595 res += "\r\n";
596 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
598 res += toString( "%u\t", (*ipm).second.Average );
600 res += "\r\n";
601 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
603 res += toString( "%u\t", (*ipm).second.Sum );
605 res += "\r\n";
606 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
608 res += toString( "%u\t", (*ipm).second.Min );
610 res += "\r\n";
611 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
613 res += toString( "%u\t", (*ipm).second.Max );
615 res += "\r\n";
617 else
619 // Stats
620 vector<float> values;
621 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
623 values.push_back( (float)((*ipm).second.Sessions.size()) );
624 res += toString( "%u\t", (*ipm).second.Sessions.size() );
626 getValuesStatsAndClearValues( values, res, false, MSNb );
627 res += "\r\n";
628 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
630 values.push_back( (float)((*ipm).second.Average)/60.0f );
631 res += toString( "%.2f\t", values.back() );
633 getValuesStatsAndClearValues( values, res, true, MSAverage );
634 res += "\r\n";
635 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
637 values.push_back( (float)((*ipm).second.Sum)/60.0f );
638 res += toString( "%.2f\t", values.back() );
640 getValuesStatsAndClearValues( values, res, true, MSSum );
641 res += "\r\n";
642 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
644 values.push_back( (float)((*ipm).second.Min)/60.0f );
645 res += toString( "%.2f\t", values.back() );
647 getValuesStatsAndClearValues( values, res, true, MSMin );
648 res += "\r\n";
649 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
651 values.push_back( (float)((*ipm).second.Max)/60.0f );
652 res += toString( "%.2f\t", values.back() );
654 getValuesStatsAndClearValues( values, res, true, MSMax );
655 res += "\r\n";
658 else
660 vector< vector<string> > table;
662 string s1, s2;
663 table.push_back();
664 table.push_back();
665 TPlayerMap::iterator ipm;
666 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
668 table[0].push_back( toString( "%u", (*ipm).second.UserId ) ); //+ "\t";
669 table[1].push_back( (*ipm).second.Name ); //+ "\t";
671 //res += s1 + "\r\n" + s2 + "\r\n";
672 sint timeSum;
675 timeSum = 0;
676 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
678 sint duration = (*ipm).second.calcSessionTime( sessionNum, endTime );
679 timeSum += duration;
681 ++sessionNum;
683 while ( timeSum != 0 );
684 table.push_back();
685 //res += "\r\n";
687 calcStats( res );
689 if ( ! convertToMinutes)
691 // Stats
692 table.push_back();
693 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
695 table.back().push_back( toString( "%u", (*ipm).second.Sessions.size() ) );
697 //res += "\r\n";
698 table.push_back();
699 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
701 table.back().push_back( toString( "%u", (*ipm).second.Average ) );
703 //res += "\r\n";
704 table.push_back();
705 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
707 table.back().push_back( toString( "%u", (*ipm).second.Sum ) );
709 //res += "\r\n";
710 table.push_back();
711 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
713 table.back().push_back( toString( "%u", (*ipm).second.Min ) );
715 //res += "\r\n";
716 table.push_back();
717 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
719 table.back().push_back( toString( "%u", (*ipm).second.Max ) );
721 //res += "\r\n";
723 else
725 // Stats
726 vector<float> values;
727 table.push_back();
728 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
730 values.push_back( (float)((*ipm).second.Sessions.size()) );
731 table.back().push_back( toString( "%u", (*ipm).second.Sessions.size() ) );
733 getValuesStatsAndClearValues( values, table, false, MSNb );
734 //res += "\r\n";
735 table.push_back();
736 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
738 values.push_back( (float)((*ipm).second.Average)/60.0f );
739 table.back().push_back( toString( "%.2f", values.back() ) );
741 getValuesStatsAndClearValues( values, table, true, MSAverage );
742 //res += "\r\n";
743 table.push_back();
744 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
746 values.push_back( (float)((*ipm).second.Sum)/60.0f );
747 table.back().push_back( toString( "%.2f", values.back() ) );
749 getValuesStatsAndClearValues( values, table, true, MSSum );
750 //res += "\r\n";
751 table.push_back();
752 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
754 values.push_back( (float)((*ipm).second.Min)/60.0f );
755 table.back().push_back( toString( "%.2f", values.back() ) );
757 getValuesStatsAndClearValues( values, table, true, MSMin );
758 //res += "\r\n";
759 table.push_back();
760 for ( ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
762 values.push_back( (float)((*ipm).second.Max)/60.0f );
763 table.back().push_back( toString( "%.2f", values.back() ) );
765 getValuesStatsAndClearValues( values, table, true, MSMax );
766 //res += "\r\n";
769 // Print in column
770 /*for ( uint j=0; j!=table.size(); ++j )
772 for ( uint i=0; i!=table[j].size(); ++i )
774 res += table[j][i] + "\t";
776 res += "\r\n";
779 // Print in row
780 uint maxI = 0;
781 for ( uint j=0; j!=table.size(); ++j )
783 if ( table[j].size() > maxI )
784 maxI = table[j].size();
786 for ( uint i=0; i!=maxI; ++i )
788 for ( uint j=0; j!=table.size(); ++j )
790 if ( i < table[j].size() )
791 res += table[j][i] + "\t";
792 else
793 res += "\t";
795 res += "\r\n";
799 res += "\r\n";
801 return sessionNum;
808 LOG_ANALYSER_PLUGIN_API std::string getInfoString()
810 return "Input log: connection.stat or frontend_service.log.\n\nProduces tab-separated connection stats for Excel.";
817 LOG_ANALYSER_PLUGIN_API bool doAnalyse( const std::vector<const char *>& vec, std::string& res, std::string& log )
819 NLMISC::createDebug();
820 CMemDisplayer memdisp;
821 NLMISC::DebugLog->addDisplayer( &memdisp, true );
822 NLMISC::InfoLog->addDisplayer( &memdisp, true );
823 NLMISC::WarningLog->addDisplayer( &memdisp, true );
824 NLMISC::ErrorLog->addDisplayer( &memdisp, true );
825 NLMISC::AssertLog->addDisplayer( &memdisp, true );
827 resetAll();
829 // Begin and end time
830 if ( ! vec.empty() )
832 sint l = 0;
833 sint quicklines = min( vec.size(), 100 );
834 while ( (l < quicklines) && ((string(vec[l]).size()<5) || (vec[l][4] != '/')) )
835 ++l;
836 if ( l < quicklines )
837 extractTime( string(vec[l]), LogBeginTime );
839 l = ((sint)vec.size())-1;
840 quicklines = max( ((sint)vec.size())-100, 0 );
841 while ( (l >= quicklines) && ((string(vec[l]).size()<5) || (vec[l][4] != '/')) )
842 --l;
843 if ( l >= quicklines )
844 extractTime( string(vec[l]), LogEndTime );
846 res += "Log begin time\t\t" + timeToStr( LogBeginTime ) + "\r\n";
847 res += "Log end time\t\t" + timeToStr( LogEndTime ) + "\r\n";
848 MainStats += timeToStr( LogEndTime );
849 TotalTimeInDays = ((float)(LogEndTime-LogBeginTime)) / 3600.0f / 24.0f;
851 // Scan sessions
852 uint nbPossibleCorruptions = 0;
853 uint i;
854 for ( i=0; i!=vec.size(); ++i )
856 string line = string(vec[i]);
857 time_t ts;
858 uint clientId;
859 uint userId;
860 string::size_type p;
862 // Auto-detect file corruption (version for connections.stat)
863 if ( !line.empty() )
865 bool corrupted = false;
867 // Search for beginning not being a date or 'Log Starting"
868 if ( (line.size() < 20) || (line[10]!=' ') || (line[13]!=':')
869 || (line[16]!=':') || (line[19]!=' ') || (line.substr( 20 ).find( " : ") == string::npos ) )
871 if ( line.find( "Log Starting [" ) != 0 )
872 corrupted = true;
874 else
876 // Search for year not at beginning. Ex: "2003/" (it does not work when the year changes in the log!)
877 p = line.substr( 1 ).find( timeToStr( LogBeginTime ).substr( 0, 5 ) );
878 if ( p != string::npos )
880 ++p; // because searched from pos 1
882 // Search for date/time
883 if ( (line.size()>p+20) && (line[p+10]==' ') && (line[p+13]==':')
884 && (line[p+16]==':') && (line[p+19]==' ') )
886 // Search for the two next blank characters. The second is followed by ": ".
887 // (Date Time ThreadId Machine/Service : User-defined log line)
888 uint nbBlank = 0;
889 string::size_type sp;
890 for ( sp=p+20; sp!=line.size(); ++sp )
892 if ( line[sp]==' ')
893 ++nbBlank;
894 if ( nbBlank==2 )
895 break;
897 if ( (nbBlank==2) && (line[sp+1]==':') && (line[sp+2]==' ') )
899 corrupted = true;
904 if ( corrupted )
906 ++nbPossibleCorruptions;
907 nlwarning( "Found possible file corruption at line %u: %s", i, line.c_str() );
911 // Detect connections/disconnections
912 p = line.find( "Adding client" );
913 if ( p != string::npos )
915 extractTime( line, ts );
916 char name [200];
917 sscanf( line.substr( p ).c_str(), "Adding client %u (uid %u name %s", &clientId, &userId, &name );
918 string sname = name;
919 addConnection( ts, clientId, userId, sname /*sname.substr( 0, sname.size()-1 )*/ ); // now name format is "name priv ''".
920 continue;
922 p = line.find( "Sent CL_DISCONNECT for client" );
923 if ( p != string::npos )
925 extractTime( line, ts );
926 sscanf( line.substr( p ).c_str(), "Sent CL_DISCONNECT for client %u (uid %u)", &clientId, &userId );
927 addDisconnection( ts, clientId, userId );
928 continue;
930 p = line.find( "Log Starting [" );
931 if ( p != string::npos )
933 uint hs = string("Log Starting [").size();
934 line = line.substr( hs, line.size() - hs - 1 ); // remove ] to get the timestamp
935 time_t restartTs;
936 extractTime( line, restartTs );
937 // Go back to find the last time of log
938 sint quicklines = max( ((sint)i)-10, 0 );
939 sint l = ((sint)i)-1;
940 while ( (l >= quicklines) && ((string(vec[l]).size()<5) || (vec[l][4] != '/')) )
941 l--;
942 if ( l >= quicklines )
943 extractTime( vec[l], ts );
944 else
945 ts = restartTs;
946 resetConnections( ts, restartTs );
949 fillUserNamesInEvents();
951 // Session durations
952 string sd;
953 uint maxNbSession = getSessionDurations( sd, LogEndTime, true, false );
954 res += "Number of accounts\t" + toString( "%u", PlayerMap.size() ) + "\r\n";
955 res += "Max number of session\t" + toString( "%u", maxNbSession ) + "\r\n";
956 res += "\r\nTime of sessions\r\n";
957 res += sd;
958 res += "Connection events\r\n";
959 MainStats += toString( "\t%u", PlayerMap.size() );
961 // Timetable
962 time_t prevTimestamp = 0;
963 sint durationSum = 0;
964 int prevPlayerNb = -1;
965 uint maxPlayerNb = 0;
966 for ( i=0; i!=NbPlayersSeries.size(); ++i )
968 sint duration = (prevTimestamp!=0) ? (int)difftime( NbPlayersSeries[i].Timestamp, prevTimestamp ) : 0;
969 prevTimestamp = NbPlayersSeries[i].Timestamp;
970 durationSum += duration;
971 if ( prevPlayerNb != -1 )
972 res += "\t" + toString( "%d", durationSum ) + "\t" + timeToStr( NbPlayersSeries[i].Timestamp ) + "\t" + toString( "%u", prevPlayerNb ) + "\t" + "\r\n";
973 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";
974 prevPlayerNb = NbPlayersSeries[i].Nb;
975 if ( NbPlayersSeries[i].Nb > maxPlayerNb )
976 maxPlayerNb = NbPlayersSeries[i].Nb;
978 MainStats += toString( "\t%u", maxPlayerNb ) + toString( "\t\t(%g days)", TotalTimeInDays );
979 if ( nbPossibleCorruptions == 0 )
980 MainStats += toString( "\t\tStat file OK" );
981 else
982 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 );
984 // Stats per user
985 res += "\r\n\nStats per user (hrs)\r\n\nName\tUserId\tSessions\tCumulated\tAverage\tMin\tMax";
986 for ( TPlayerMap::const_iterator ipm=PlayerMap.begin(); ipm!=PlayerMap.end(); ++ipm )
988 res += "\r\n\n";
989 const TPlayerStat& playerStat = (*ipm).second;
990 res += toString( "%s\tUser %u\t%u\t%s\t%s\t%s\t%s",
991 playerStat.Name.c_str(), playerStat.UserId, playerStat.Sessions.size(),
992 toHourStr(playerStat.Sum).c_str(), toHourStr(playerStat.Average).c_str(), toHourStr(playerStat.Min).c_str(), toHourStr(playerStat.Max).c_str() );
993 for ( uint i=0; i!=playerStat.Sessions.size(); ++i )
995 res += "\r\n";
996 const TSession& sess = playerStat.Sessions[i];
997 string status = sess.Closed ? "OK" : "Not closed";
998 res += timeToStr( sess.BeginTime ) + "\t" + timeToStr( sess.EndTime ) + "\t" + status + "\t" + toHourStr( sess.Duration );
1002 string dateStr = " " + extractDateFilename( LogEndTime );
1003 res = dateStr + "\r\nDate\tAvg per player\tTotal time\tMax per player\tNb Players\tSimult. Pl.\r\n" + MainStats + "\r\n" + res;
1005 memdisp.write( log );
1006 return true;
1011 /*CString s;
1012 s.Format( "Found C=%u U=%u N=%s in %s", clientId, userId, name, line.substr( p ).c_str() );
1013 AfxMessageBox( s );*/