diff --git a/code/nel/include/nel/misc/time_nl.h b/code/nel/include/nel/misc/time_nl.h index 2bd3edb72..9e44b4b36 100644 --- a/code/nel/include/nel/misc/time_nl.h +++ b/code/nel/include/nel/misc/time_nl.h @@ -48,6 +48,19 @@ typedef sint64 TTicks; class CTime { public: + struct CTimerInfo + { + /// Returns if there is a high precision timer that can be used. + bool IsHighPrecisionAvailable; + /// If a CPU specific timer is used and the values are not consistent accross threads. + bool RequiresSingleCore; + /// The resolution of the high resolution timer. + TTicks HighPrecisionResolution; + }; + + /** Get advanced information on the used timers. + */ + static void probeTimerInfo(CTimerInfo &result); /** Return the number of second since midnight (00:00:00), January 1, 1970, * coordinated universal time, according to the system clock. diff --git a/code/nel/src/misc/time_nl.cpp b/code/nel/src/misc/time_nl.cpp index f018208f9..4a3586baa 100644 --- a/code/nel/src/misc/time_nl.cpp +++ b/code/nel/src/misc/time_nl.cpp @@ -32,9 +32,145 @@ #include "nel/misc/time_nl.h" #include "nel/misc/sstring.h" +#include + namespace NLMISC { +void CTime::probeTimerInfo(CTime::CTimerInfo &result) +{ + breakable + { + #ifdef NL_OS_WINDOWS + LARGE_INTEGER winPerfFreq; + LARGE_INTEGER winPerfCount; + DWORD lowResTime; + if (!QueryPerformanceFrequency(&winPerfFreq)) + { + nldebug("Cannot query performance frequency"); + result.IsHighPrecisionAvailable = false; + } + else + { + result.HighPrecisionResolution = winPerfFreq.QuadPart; + } + if (winPerfFreq.QuadPart == 1000) + { + nldebug("Higher precision timer not available, OS defaulted to GetTickCount"); + result.IsHighPrecisionAvailable = false; + } + if (!QueryPerformanceCounter(&winPerfCount)) + { + nldebug("Cannot query performance counter"); + result.IsHighPrecisionAvailable = false; + result.HighPrecisionResolution = 1000; + } + if (!result.IsHighPrecisionAvailable) + { + lowResTime = timeGetTime(); + } + #else + // nldebug("Probe of timer info not implemented"); + result.IsHighPrecisionAvailable = false; + result.RequiresSingleCore = true; + break; + #endif + + uint64 cpuMask = IProcess::getCurrentProcess()->getCPUMask(); + uint64 threadMask = IThread::getCurrentThread()->getCPUMask(); + + #ifdef NL_OS_WINDOWS + + #else + TTicks timerFrequency = 0; + #endif + + uint identical = 0; // Identical stamps may indicate the os handling backwards glitches. + uint backwards = 0; // Happens when the timers are not always in sync and the implementation is faulty. + uint regular = 0; // How many times the number advanced normally. + uint skipping = 0; // Does not really mean anything necessarily. + uint frequencybug = 0; // Should never happen. + // uint badcore = 0; // Affinity does not work. + + // Cycle 32 times trough all cores, and verify if the timing remains consistent. + for (uint i = 32; i; --i) + { + uint64 currentBit = 1; + for (uint j = 64; j; --j) + { + if (cpuMask & currentBit) + { + IThread::getCurrentThread()->setCPUMask(currentBit); +#ifdef NL_OS_WINDOWS + // Make sure the thread is rescheduled. + SwitchToThread(); + Sleep(0); + // Verify the core + /* Can only verify on 2003, Vista and higher. + if (1 << GetCurrentProcessorNumber() != currentBit) + ++badcore; + */ + // Check if the timer is still sane. + if (result.IsHighPrecisionAvailable) + { + LARGE_INTEGER winPerfFreqN; + LARGE_INTEGER winPerfCountN; + QueryPerformanceFrequency(&winPerfFreqN); + if (winPerfFreqN.QuadPart != winPerfFreq.QuadPart) + ++frequencybug; + QueryPerformanceCounter(&winPerfCountN); + if (winPerfCountN.QuadPart == winPerfCount.QuadPart) + ++identical; + if (winPerfCountN.QuadPart < winPerfCount.QuadPart || winPerfCountN.QuadPart - winPerfCount.QuadPart < 0) + ++backwards; + if (winPerfCountN.QuadPart - winPerfCount.QuadPart > winPerfFreq.QuadPart / 20) // 50ms skipping check + ++skipping; + else if (winPerfCountN.QuadPart > winPerfCount.QuadPart) + ++regular; + winPerfCount.QuadPart = winPerfCountN.QuadPart; + } + else + { + DWORD lowResTimeN; + lowResTimeN = timeGetTime(); + if (lowResTimeN == lowResTime) + ++identical; + if (lowResTimeN < lowResTime || lowResTimeN - lowResTime < 0) + ++backwards; + if (lowResTimeN - lowResTime > 50) + ++skipping; + else if (lowResTimeN > lowResTime) + ++regular; + lowResTime = lowResTimeN; + } +#endif + } + currentBit <<= 1; + } + } + + IThread::getCurrentThread()->setCPUMask(threadMask); + + nldebug("Timer resolution: %i Hz", (int)(result.HighPrecisionResolution)); + nldebug("Time identical: %i, backwards: %i, regular: %i, skipping: %i, frequency bug: %i", identical, backwards, regular, skipping, frequencybug); + if (identical > regular) + nlwarning("The system timer is of relatively low resolution, you may experience issues"); + if (backwards > 0 || frequencybug > 0) + { + nlwarning("The current system timer is not reliable across multiple cpu cores"); + result.RequiresSingleCore = true; + } + else result.RequiresSingleCore = false; + + if (result.HighPrecisionResolution == 14318180) + nldebug("Detected known HPET era timer frequency"); + if (result.HighPrecisionResolution == 3579545) + nldebug("Detected known AHCI era timer frequency"); + if (result.HighPrecisionResolution == 1193182) + nldebug("Detected known i8253/i8254 era timer frequency"); + } +} + /* Return the number of second since midnight (00:00:00), January 1, 1970, * coordinated universal time, according to the system clock. * This values is the same on all computer if computers are synchronized (with NTP for example). diff --git a/code/nel/tools/misc/CMakeLists.txt b/code/nel/tools/misc/CMakeLists.txt index fcb259a64..5386cbbc6 100644 --- a/code/nel/tools/misc/CMakeLists.txt +++ b/code/nel/tools/misc/CMakeLists.txt @@ -18,3 +18,5 @@ IF(WIN32) ADD_SUBDIRECTORY(words_dic) ENDIF(MFC_FOUND) ENDIF(WIN32) + +ADD_SUBDIRECTORY(probe_timers) diff --git a/code/nel/tools/misc/probe_timers/CMakeLists.txt b/code/nel/tools/misc/probe_timers/CMakeLists.txt new file mode 100644 index 000000000..df0f34926 --- /dev/null +++ b/code/nel/tools/misc/probe_timers/CMakeLists.txt @@ -0,0 +1,9 @@ +FILE(GLOB SRC *.cpp) + +ADD_EXECUTABLE(nl_probe_timers ${SRC}) + +TARGET_LINK_LIBRARIES(nl_probe_timers nelmisc) +NL_DEFAULT_PROPS(nl_probe_timers "NeL, Tools, Misc: Probe Timers") +NL_ADD_RUNTIME_FLAGS(nl_probe_timers) + +INSTALL(TARGETS nl_probe_timers RUNTIME DESTINATION bin COMPONENT toolsmisc) diff --git a/code/nel/tools/misc/probe_timers/main.cpp b/code/nel/tools/misc/probe_timers/main.cpp new file mode 100644 index 000000000..de4c1f785 --- /dev/null +++ b/code/nel/tools/misc/probe_timers/main.cpp @@ -0,0 +1,38 @@ +// NeL - MMORPG Framework +// Copyright (C) 2012 by authors +// +// 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 . + +#include +#include +#include + +#include "nel/misc/types_nl.h" +#include "nel/misc/time_nl.h" + +using namespace NLMISC; + +int main (int argc, char **argv) +{ + for (uint i = 0; i < 8; ++i) + { + CTime::CTimerInfo timerInfo; + CTime::probeTimerInfo(timerInfo); + } + + printf ("\nPress to exit\n"); + getchar (); + + return EXIT_SUCCESS; +}