Show bonus/malus timer text if available
[ryzomcore.git] / nel / src / misc / time_nl.cpp
blobcca6f14aabef410a051357dcee7b05765b0e4498
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012-2015 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdmisc.h"
22 #include "nel/misc/time_nl.h"
23 #include "nel/misc/sstring.h"
24 #include "nel/misc/thread.h"
26 #ifdef NL_OS_WINDOWS
27 # include <MMSystem.h>
28 #elif defined (NL_OS_UNIX)
29 # include <sys/time.h>
30 # include <unistd.h>
31 #endif
33 #ifdef NL_OS_MAC
34 #include <mach/mach.h>
35 #include <mach/mach_time.h>
36 #endif
38 #ifdef DEBUG_NEW
39 #define new DEBUG_NEW
40 #endif
42 namespace NLMISC
45 namespace {
46 #ifdef NL_OS_WINDOWS
47 bool a_HaveQueryPerformance = false;
48 LARGE_INTEGER a_QueryPerformanceFrequency;
49 #endif
50 #ifdef NL_OS_UNIX
51 # if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
52 # if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0)
53 # define NL_MONOTONIC_CLOCK
54 # endif
55 # endif
56 # ifdef NL_MONOTONIC_CLOCK
57 bool a_CheckedMonotonicClock = false;
58 bool a_HasMonotonicClock = false;
59 uint64 a_MonotonicClockFrequency = 0;
60 uint64 a_MonotonicClockResolutionNs = 0;
61 bool hasMonotonicClock()
63 if (!a_CheckedMonotonicClock)
65 /* Initialize the local time engine.
66 * On Unix, this method will find out if the Monotonic Clock is supported
67 * (seems supported by kernel 2.6, not by kernel 2.4). See getLocalTime().
69 struct timespec tv;
70 if ((clock_gettime( CLOCK_MONOTONIC, &tv ) == 0) &&
71 (clock_getres( CLOCK_MONOTONIC, &tv ) == 0))
73 // nldebug( "Monotonic local time supported (resolution %.6f ms)", ((float)tv.tv_sec)*1000.0f + ((float)tv.tv_nsec)/1000000.0f );
75 if (tv.tv_sec > 0)
77 nlwarning("Monotonic clock not ok, resolution > 1s");
78 a_HasMonotonicClock = false;
80 else
82 uint64 nsPerTick = tv.tv_nsec;
83 uint64 nsPerSec = 1000000000L;
84 uint64 tickPerSec = nsPerSec / nsPerTick;
85 a_MonotonicClockFrequency = tickPerSec;
86 a_MonotonicClockResolutionNs = nsPerTick;
87 a_HasMonotonicClock = true;
90 else
92 a_HasMonotonicClock = false;
94 a_CheckedMonotonicClock = true;
96 return a_HasMonotonicClock;
98 # endif
99 #endif
102 void CTime::probeTimerInfo(CTime::CTimerInfo &result)
104 breakable
106 #ifdef NL_OS_WINDOWS
107 LARGE_INTEGER winPerfFreq;
108 LARGE_INTEGER winPerfCount;
109 DWORD lowResTime;
110 if (!QueryPerformanceFrequency(&winPerfFreq))
112 nldebug("Cannot query performance frequency");
113 result.IsHighPrecisionAvailable = false;
115 else
117 result.HighPrecisionResolution = winPerfFreq.QuadPart;
119 if (winPerfFreq.QuadPart == 1000)
121 nldebug("Higher precision timer not available, OS defaulted to GetTickCount");
122 result.IsHighPrecisionAvailable = false;
124 if (!QueryPerformanceCounter(&winPerfCount))
126 nldebug("Cannot query performance counter");
127 result.IsHighPrecisionAvailable = false;
128 result.HighPrecisionResolution = 1000;
130 a_HaveQueryPerformance = result.IsHighPrecisionAvailable;
131 a_QueryPerformanceFrequency.QuadPart = winPerfFreq.QuadPart;
132 if (!result.IsHighPrecisionAvailable)
134 lowResTime = timeGetTime();
136 #else
138 // Other platforms are awesome. Generic implementation for now.
139 TTime localTime = getLocalTime();
140 result.IsHighPrecisionAvailable = true;
141 result.HighPrecisionResolution = 0;
143 # ifdef NL_MONOTONIC_CLOCK
144 timespec monoClock;
145 if (hasMonotonicClock())
147 clock_gettime(CLOCK_MONOTONIC, &monoClock);
148 result.HighPrecisionResolution = a_MonotonicClockFrequency;
150 else
152 nldebug("Monotonic clock not available");
154 # endif
156 #endif
158 uint64 cpuMask = IProcess::getCurrentProcess()->getCPUMask();
159 #ifdef NL_OS_WINDOWS
160 uint64 threadMask = IThread::getCurrentThread()->getCPUMask(); // broken on linux, don't expect it to work anywhere
161 #else
162 uint64 threadMask = cpuMask;
163 #endif
165 uint identical = 0; // Identical stamps may indicate the os handling backwards glitches.
166 uint backwards = 0; // Happens when the timers are not always in sync and the implementation is faulty.
167 uint regular = 0; // How many times the number advanced normally.
168 uint skipping = 0; // Does not really mean anything necessarily.
169 uint frequencybug = 0; // Should never happen.
170 // uint badcore = 0; // Affinity does not work.
172 // Cycle 32 times trough all cores, and verify if the timing remains consistent.
173 for (uint i = 32; i; --i)
175 uint64 currentBit = 1;
176 for (uint j = 64; j; --j)
178 if (cpuMask & currentBit)
180 #ifdef NL_OS_WINDOWS
181 if (!IThread::getCurrentThread()->setCPUMask(currentBit))
182 #else
183 if (!IProcess::getCurrentProcess()->setCPUMask(currentBit))
184 #endif
185 break; // Thread was set to last cpu.
186 #ifdef NL_OS_WINDOWS
187 // Make sure the thread is rescheduled.
188 SwitchToThread();
189 Sleep(0);
190 // Verify the core
191 /* Can only verify on 2003, Vista and higher.
192 if (1 << GetCurrentProcessorNumber() != currentBit)
193 ++badcore;
195 // Check if the timer is still sane.
196 if (result.IsHighPrecisionAvailable)
198 LARGE_INTEGER winPerfFreqN;
199 LARGE_INTEGER winPerfCountN;
200 QueryPerformanceFrequency(&winPerfFreqN);
201 if (winPerfFreqN.QuadPart != winPerfFreq.QuadPart)
202 ++frequencybug;
203 QueryPerformanceCounter(&winPerfCountN);
204 if (winPerfCountN.QuadPart == winPerfCount.QuadPart)
205 ++identical;
206 if (winPerfCountN.QuadPart < winPerfCount.QuadPart || winPerfCountN.QuadPart - winPerfCount.QuadPart < 0)
207 ++backwards;
208 if (winPerfCountN.QuadPart - winPerfCount.QuadPart > winPerfFreq.QuadPart / 20) // 50ms skipping check
209 ++skipping;
210 else if (winPerfCountN.QuadPart > winPerfCount.QuadPart)
211 ++regular;
212 winPerfCount.QuadPart = winPerfCountN.QuadPart;
214 else
216 DWORD lowResTimeN;
217 lowResTimeN = timeGetTime();
218 if (lowResTimeN == lowResTime)
219 ++identical;
220 if (lowResTimeN < lowResTime || lowResTimeN - lowResTime < 0)
221 ++backwards;
222 if (lowResTimeN - lowResTime > 50)
223 ++skipping;
224 else if (lowResTimeN > lowResTime)
225 ++regular;
226 lowResTime = lowResTimeN;
228 #else
229 #ifdef NL_OS_UNIX
230 sched_yield();
231 #else
232 nlSleep(0);
233 #endif
234 # ifdef NL_MONOTONIC_CLOCK
235 if (hasMonotonicClock())
237 timespec monoClockN;
238 clock_gettime(CLOCK_MONOTONIC, &monoClockN);
239 if (monoClock.tv_sec == monoClockN.tv_sec && monoClock.tv_nsec == monoClockN.tv_nsec)
240 ++identical;
241 if (monoClockN.tv_sec < monoClock.tv_sec || (monoClock.tv_sec == monoClockN.tv_sec && monoClockN.tv_nsec < monoClock.tv_nsec))
242 ++backwards;
243 if (monoClock.tv_sec == monoClockN.tv_sec && (monoClockN.tv_nsec - monoClock.tv_nsec > 50000000L))
244 ++skipping;
245 else if ((monoClock.tv_sec == monoClockN.tv_sec && monoClock.tv_nsec < monoClockN.tv_nsec) || monoClock.tv_sec < monoClockN.tv_sec)
246 ++regular;
247 monoClock.tv_sec = monoClockN.tv_sec;
248 monoClock.tv_nsec = monoClockN.tv_nsec;
250 else
251 # endif
253 TTime localTimeN = getLocalTime();
254 if (localTimeN == localTime)
255 ++identical;
256 if (localTimeN < localTime || localTimeN - localTime < 0)
257 ++backwards;
258 if (localTimeN - localTime > 50)
259 ++skipping;
260 else if (localTimeN > localTime)
261 ++regular;
262 localTime = localTimeN;
264 #endif
266 currentBit <<= 1;
270 #ifdef NL_OS_WINDOWS
271 IThread::getCurrentThread()->setCPUMask(threadMask);
272 #else
273 IProcess::getCurrentProcess()->setCPUMask(threadMask);
274 #endif
276 nldebug("Timer resolution: %i Hz", (int)(result.HighPrecisionResolution));
277 nldebug("Time identical: %i, backwards: %i, regular: %i, skipping: %i, frequency bug: %i", identical, backwards, regular, skipping, frequencybug);
278 if (identical > regular)
279 nlwarning("The system timer is of relatively low resolution, you may experience issues");
280 if (backwards > 0 || frequencybug > 0)
282 nlwarning("The current system timer is not reliable across multiple cpu cores");
283 result.RequiresSingleCore = true;
285 else result.RequiresSingleCore = false;
287 if (result.HighPrecisionResolution == 14318180)
289 nldebug("Detected known HPET era timer frequency");
291 if (result.HighPrecisionResolution == 3579545)
293 nldebug("Detected known AHCI era timer frequency");
295 if (result.HighPrecisionResolution == 1193182)
297 nldebug("Detected known i8253/i8254 era timer frequency");
302 /* Return the number of second since midnight (00:00:00), January 1, 1970,
303 * coordinated universal time, according to the system clock.
304 * This values is the same on all computer if computers are synchronized (with NTP for example).
306 uint32 CTime::getSecondsSince1970 ()
308 return uint32(time(NULL));
311 /** Return the number of second since midnight (00:00:00), January 1, 1970,
312 * coordinated universal time, according to the system clock.
313 * The time returned is UTC (aka GMT+0), ie it does not have the local time ajustement
314 * nor it have the daylight saving ajustement.
315 * This values is the same on all computer if computers are synchronized (with NTP for example).
317 //uint32 CTime::getSecondsSince1970UTC ()
319 // // get the local time
320 // time_t nowLocal = time(NULL);
321 // // convert it to GMT time (UTC)
322 // struct tm * timeinfo;
323 // timeinfo = gmtime(&nowLocal);
324 // return nl_mktime(timeinfo);
327 /* Return the local time in milliseconds.
328 * Use it only to measure time difference, the absolute value does not mean anything.
329 * On Unix, getLocalTime() will try to use the Monotonic Clock if available, otherwise
330 * the value can jump backwards if the system time is changed by a user or a NTP time sync process.
331 * The value is different on 2 different computers; use the CUniTime class to get a universal
332 * time that is the same on all computers.
333 * \warning On Win32, the value is on 32 bits only. It wraps around to 0 every about 49.71 days.
335 TTime CTime::getLocalTime ()
338 #ifdef NL_OS_WINDOWS
340 //static bool initdone = false;
341 //static bool byperfcounter;
342 // Initialization
343 //if ( ! initdone )
345 //byperfcounter = (getPerformanceTime() != 0);
346 //initdone = true;
349 /* Retrieve time is ms
350 * Why do we prefer getPerformanceTime() to timeGetTime() ? Because on one dual-processor Win2k
351 * PC, we have noticed that timeGetTime() slows down when the client is running !!!
353 /* Now we have noticed that on all WinNT4 PC the getPerformanceTime can give us value that
354 * are less than previous
357 //if ( byperfcounter )
359 // return (TTime)(ticksToSecond(getPerformanceTime()) * 1000.0f);
361 //else
363 // This is not affected by system time changes. But it cycles every 49 days.
364 // return timeGetTime(); // Only this was left active before it was commented.
368 * The above is no longer relevant.
371 if (a_HaveQueryPerformance)
373 // On a (fast) 15MHz timer this rolls over after 7000 days.
374 // If my calculations are right.
375 LARGE_INTEGER counter;
376 QueryPerformanceCounter(&counter);
377 counter.QuadPart *= (LONGLONG)1000L;
378 counter.QuadPart /= a_QueryPerformanceFrequency.QuadPart;
379 return counter.QuadPart;
381 else
383 // Use default reliable low resolution timer.
384 return timeGetTime();
387 #elif defined (NL_OS_UNIX)
389 #ifdef NL_MONOTONIC_CLOCK
391 if (hasMonotonicClock())
393 timespec tv;
394 // This is not affected by system time changes.
395 if ( clock_gettime( CLOCK_MONOTONIC, &tv ) != 0 )
396 nlerror ("Can't get clock time again");
397 return (TTime)tv.tv_sec * (TTime)1000 + (TTime)((tv.tv_nsec/*+500*/) / 1000000);
400 #endif
402 // This is affected by system time changes.
403 struct timeval tv;
404 if ( gettimeofday( &tv, NULL) != 0 )
405 nlerror ("Can't get time of day");
406 return (TTime)tv.tv_sec * (TTime)1000 + (TTime)tv.tv_usec / (TTime)1000;
408 #endif
411 /* Return the time in processor ticks. Use it for profile purpose.
412 * If the performance time is not supported on this hardware, it returns 0.
413 * \warning On a multiprocessor system, the value returned by each processor may
414 * be different. The only way to workaround this is to set a processor affinity
415 * to the measured thread.
416 * \warning The speed of tick increase can vary (especially on laptops or CPUs with
417 * power management), so profiling several times and computing the average could be
418 * a wise choice.
420 TTicks CTime::getPerformanceTime ()
422 #ifdef NL_OS_WINDOWS
423 LARGE_INTEGER ret;
424 if (QueryPerformanceCounter (&ret))
425 return ret.QuadPart;
426 else
427 return 0;
428 #elif defined(NL_OS_MAC)
429 return mach_absolute_time();
430 #else
431 #if defined(HAVE_X86_64)
432 uint64 hi, lo;
433 __asm__ volatile (".byte 0x0f, 0x31" : "=a" (lo), "=d" (hi));
434 return (hi << 32) | (lo & 0xffffffff);
435 #elif defined(HAVE_X86) and !defined(NL_OS_MAC)
436 uint64 x;
437 // RDTSC - Read time-stamp counter into EDX:EAX.
438 __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
439 return x;
440 #else // HAVE_X86
441 static bool firstWarn = true;
442 if (firstWarn)
444 nlwarning ("TTicks CTime::getPerformanceTime () is not implemented for your processor, returning 0");
445 firstWarn = false;
447 return 0;
448 #endif // HAVE_X86
450 #endif // NL_OS_WINDOWS
453 #define GETTICKS(t) asm volatile ("push %%esi\n\t" "mov %0, %%esi" : : "r" (t)); \
454 asm volatile ("push %eax\n\t" "push %edx"); \
455 asm volatile ("rdtsc"); \
456 asm volatile ("movl %eax, (%esi)\n\t" "movl %edx, 4(%esi)"); \
457 asm volatile ("pop %edx\n\t" "pop %eax\n\t" "pop %esi");
461 /* Convert a ticks count into second. If the performance time is not supported on this
462 * hardware, it returns 0.0.
464 double CTime::ticksToSecond (TTicks ticks)
466 #ifdef NL_OS_WINDOWS
467 LARGE_INTEGER ret;
468 if (QueryPerformanceFrequency(&ret))
470 return (double)(sint64)ticks/(double)ret.QuadPart;
472 else
473 #elif defined(NL_OS_MAC)
475 static double factor = 0.0;
476 if (factor == 0.0)
478 mach_timebase_info_data_t tbInfo;
479 mach_timebase_info(&tbInfo);
480 factor = 1000000000.0 * (double)tbInfo.numer / (double)tbInfo.denom;
482 return double(ticks / factor);
484 #endif // NL_OS_WINDOWS
486 static bool benchFrequency = true;
487 static sint64 freq = 0;
488 if (benchFrequency)
490 // try to have an estimation of the cpu frequency
492 TTicks tickBefore = getPerformanceTime ();
493 TTicks tickAfter = tickBefore;
494 TTime timeBefore = getLocalTime ();
495 TTime timeAfter = timeBefore;
496 for(;;)
498 if (timeAfter - timeBefore > 1000)
499 break;
500 timeAfter = getLocalTime ();
501 tickAfter = getPerformanceTime ();
504 TTime timeDelta = timeAfter - timeBefore;
505 TTicks tickDelta = tickAfter - tickBefore;
507 freq = 1000 * tickDelta / timeDelta;
508 benchFrequency = false;
511 return (double)(sint64)ticks/(double)freq;
516 std::string CTime::getHumanRelativeTime(sint32 nbSeconds)
518 sint32 delta = nbSeconds;
519 if (delta < 0)
520 delta = -delta;
522 // some constants of time duration in seconds
523 const sint32 oneMinute = 60;
524 const sint32 oneHour = oneMinute * 60;
525 const sint32 oneDay = oneHour * 24;
526 const sint32 oneWeek = oneDay * 7;
527 const sint32 oneMonth = oneDay * 30; // aprox, a more precise value is 30.416666... but no matter
528 const sint32 oneYear = oneDay * 365; // aprox, a more precise value is 365.26.. who care?
530 sint32 year, month, week, day, hour, minute;
531 year = month = week = day = hour = minute = 0;
533 /// compute the different parts
534 year = delta / oneYear;
535 delta %= oneYear;
537 month = delta / oneMonth;
538 delta %= oneMonth;
540 week = delta / oneWeek;
541 delta %= oneWeek;
543 day = delta / oneDay;
544 delta %= oneDay;
546 hour = delta / oneHour;
547 delta %= oneHour;
549 minute = delta / oneMinute;
550 delta %= oneMinute;
552 // compute the string
553 CSString ret;
555 if (year)
556 ret << year << " years ";
557 if (month)
558 ret << month << " months ";
559 if (week)
560 ret << week << " weeks ";
561 if (day)
562 ret << day << " days ";
563 if (hour)
564 ret << hour << " hours ";
565 if (minute)
566 ret << minute << " minutes ";
567 if (delta || ret.empty())
568 ret << delta << " seconds ";
570 return ret;
573 #ifdef NL_OS_WINDOWS
574 /** Return the offset in 10th of micro sec between the windows base time (
575 * 01-01-1601 0:0:0 UTC) and the unix base time (01-01-1970 0:0:0 UTC).
576 * This value is used to convert windows system and file time back and
577 * forth to unix time (aka epoch)
579 uint64 CTime::getWindowsToUnixBaseTimeOffset()
581 static bool init = false;
583 static uint64 offset = 0;
585 if (! init)
587 // compute the offset to convert windows base time into unix time (aka epoch)
588 // build a WIN32 system time for jan 1, 1970
589 SYSTEMTIME baseTime;
590 baseTime.wYear = 1970;
591 baseTime.wMonth = 1;
592 baseTime.wDayOfWeek = 0;
593 baseTime.wDay = 1;
594 baseTime.wHour = 0;
595 baseTime.wMinute = 0;
596 baseTime.wSecond = 0;
597 baseTime.wMilliseconds = 0;
599 FILETIME baseFileTime = {0,0};
600 // convert it into a FILETIME value
601 SystemTimeToFileTime(&baseTime, &baseFileTime);
602 offset = baseFileTime.dwLowDateTime | (uint64(baseFileTime.dwHighDateTime)<<32);
604 init = true;
607 return offset;
609 #endif
612 } // NLMISC