/** * @file lltimer.cpp * @brief Cross-platform objects for doing timing * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "lltimer.h" #include "u64.h" #include <chrono> #include <thread> #if LL_WINDOWS # include "llwin32headerslean.h" #elif LL_LINUX || LL_DARWIN # include <errno.h> # include <sys/time.h> #else # error "architecture not supported" #endif // // Locally used constants // const U64 SEC_TO_MICROSEC_U64 = 1000000; //--------------------------------------------------------------------------- // Globals and statics //--------------------------------------------------------------------------- S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds LLTimer* LLTimer::sTimer = NULL; // // Forward declarations // //--------------------------------------------------------------------------- // Implementation //--------------------------------------------------------------------------- #if LL_WINDOWS #if 0 void ms_sleep(U32 ms) { LL_PROFILE_ZONE_SCOPED; using TimePoint = std::chrono::steady_clock::time_point; auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms); while (TimePoint::clock::now() < resume_time) { std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long } } U32 micro_sleep(U64 us, U32 max_yields) { // max_yields is unused; just fiddle with it to avoid warnings. max_yields = 0; ms_sleep((U32)(us / 1000)); return 0; } #else U32 micro_sleep(U64 us, U32 max_yields) { LL_PROFILE_ZONE_SCOPED #if 0 LARGE_INTEGER ft; ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); #else Sleep(us / 1000); #endif return 0; } void ms_sleep(U32 ms) { LL_PROFILE_ZONE_SCOPED micro_sleep(ms * 1000, 0); } #endif #elif LL_LINUX || LL_DARWIN static void _sleep_loop(struct timespec& thiswait) { struct timespec nextwait; bool sleep_more = false; do { int result = nanosleep(&thiswait, &nextwait); // check if sleep was interrupted by a signal; unslept // remainder was written back into 't' and we just nanosleep // again. sleep_more = (result == -1 && EINTR == errno); if (sleep_more) { if ( nextwait.tv_sec > thiswait.tv_sec || (nextwait.tv_sec == thiswait.tv_sec && nextwait.tv_nsec >= thiswait.tv_nsec) ) { // if the remaining time isn't actually going // down then we're being shafted by low clock // resolution - manually massage the sleep time // downward. if (nextwait.tv_nsec > 1000000) { // lose 1ms nextwait.tv_nsec -= 1000000; } else { if (nextwait.tv_sec == 0) { // already so close to finished sleep_more = false; } else { // lose up to 1ms nextwait.tv_nsec = 0; } } } thiswait = nextwait; } } while (sleep_more); } U32 micro_sleep(U64 us, U32 max_yields) { U64 start = get_clock_count(); // This is kernel dependent. Currently, our kernel generates software clock // interrupts at 250 Hz (every 4,000 microseconds). const S64 KERNEL_SLEEP_INTERVAL_US = 4000; // Use signed arithmetic to discover whether a sleep is even necessary. If // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler // promotes the difference to unsigned. If 'us' is less than half // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely // positive, resulting in a crazy long wait. auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US; if (num_sleep_intervals > 0) { U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1); struct timespec thiswait; thiswait.tv_sec = sleep_time / 1000000; thiswait.tv_nsec = (sleep_time % 1000000) * 1000l; _sleep_loop(thiswait); } U64 current_clock = get_clock_count(); U32 yields = 0; while ( (yields < max_yields) && (current_clock - start < us) ) { sched_yield(); ++yields; current_clock = get_clock_count(); } return yields; } void ms_sleep(U32 ms) { long mslong = ms; // tv_nsec is a long struct timespec thiswait; thiswait.tv_sec = ms / 1000; thiswait.tv_nsec = (mslong % 1000) * 1000000l; _sleep_loop(thiswait); } #else # error "architecture not supported" #endif // // CPU clock/other clock frequency and count functions // #if LL_WINDOWS U64 get_clock_count() { static bool firstTime = true; static U64 offset; // ensures that callers to this function never have to deal with wrap // QueryPerformanceCounter implementation LARGE_INTEGER clock_count; QueryPerformanceCounter(&clock_count); if (firstTime) { offset = clock_count.QuadPart; firstTime = false; } return clock_count.QuadPart - offset; } F64 calc_clock_frequency() { __int64 freq; QueryPerformanceFrequency((LARGE_INTEGER *) &freq); return (F64)freq; } #endif // LL_WINDOWS #if LL_LINUX || LL_DARWIN // Both Linux and Mac use gettimeofday for accurate time F64 calc_clock_frequency() { return 1000000.0; // microseconds, so 1 MHz. } U64 get_clock_count() { // Linux clocks are in microseconds struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec; } #endif TimerInfo::TimerInfo() : mClockFrequency(0.0), mTotalTimeClockCount(0), mLastTotalTimeClockCount(0) {} void TimerInfo::update() { mClockFrequency = calc_clock_frequency(); mClockFrequencyInv = 1.0/mClockFrequency; mClocksToMicroseconds = mClockFrequencyInv; } TimerInfo& get_timer_info() { static TimerInfo sTimerInfo; return sTimerInfo; } /////////////////////////////////////////////////////////////////////////////// // returns a U64 number that represents the number of // microseconds since the Unix epoch - Jan 1, 1970 U64MicrosecondsImplicit totalTime() { U64 current_clock_count = get_clock_count(); if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0) { get_timer_info().update(); get_timer_info().mTotalTimeClockCount = current_clock_count; #if LL_WINDOWS // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented) // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to // make in the future. get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency); #endif // Update the last clock count get_timer_info().mLastTotalTimeClockCount = current_clock_count; } else { if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount) { // No wrapping, we're all okay. get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount; } else { // We've wrapped. Compensate correctly get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count; } // Update the last clock count get_timer_info().mLastTotalTimeClockCount = current_clock_count; } // Return the total clock tick count in microseconds. U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds); return time; } /////////////////////////////////////////////////////////////////////////////// LLTimer::LLTimer() { if (!get_timer_info().mClockFrequency) { get_timer_info().update(); } mStarted = TRUE; reset(); } LLTimer::~LLTimer() {} // static void LLTimer::initClass() { if (!sTimer) sTimer = new LLTimer; } // static void LLTimer::cleanupClass() { delete sTimer; sTimer = NULL; } // static U64MicrosecondsImplicit LLTimer::getTotalTime() { // simply call into the implementation function. U64MicrosecondsImplicit total_time = totalTime(); return total_time; } // static F64SecondsImplicit LLTimer::getTotalSeconds() { return F64Microseconds(U64_to_F64(getTotalTime())); } void LLTimer::reset() { mLastClockCount = get_clock_count(); mExpirationTicks = 0; } /////////////////////////////////////////////////////////////////////////////// U64 LLTimer::getCurrentClockCount() { return get_clock_count(); } /////////////////////////////////////////////////////////////////////////////// void LLTimer::setLastClockCount(U64 current_count) { mLastClockCount = current_count; } /////////////////////////////////////////////////////////////////////////////// static U64 getElapsedTimeAndUpdate(U64& lastClockCount) { U64 current_clock_count = get_clock_count(); U64 result; if (current_clock_count >= lastClockCount) { result = current_clock_count - lastClockCount; } else { // time has gone backward result = 0; } lastClockCount = current_clock_count; return result; } F64SecondsImplicit LLTimer::getElapsedTimeF64() const { U64 last = mLastClockCount; return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv; } F32SecondsImplicit LLTimer::getElapsedTimeF32() const { return (F32)getElapsedTimeF64(); } F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64() { return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv; } F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32() { return (F32)getElapsedTimeAndResetF64(); } /////////////////////////////////////////////////////////////////////////////// void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) { mExpirationTicks = get_clock_count() + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value())); } F32SecondsImplicit LLTimer::getRemainingTimeF32() const { U64 cur_ticks = get_clock_count(); if (cur_ticks > mExpirationTicks) { return 0.0f; } return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv); } BOOL LLTimer::checkExpirationAndReset(F32 expiration) { U64 cur_ticks = get_clock_count(); if (cur_ticks < mExpirationTicks) { return FALSE; } mExpirationTicks = cur_ticks + (U64)((F32)(expiration * get_timer_info().mClockFrequency)); return TRUE; } BOOL LLTimer::hasExpired() const { return (get_clock_count() >= mExpirationTicks) ? TRUE : FALSE; } /////////////////////////////////////////////////////////////////////////////// BOOL LLTimer::knownBadTimer() { BOOL failed = FALSE; #if LL_WINDOWS WCHAR bad_pci_list[][10] = {L"1039:0530", L"1039:0620", L"10B9:0533", L"10B9:1533", L"1106:0596", L"1106:0686", L"1166:004F", L"1166:0050", L"8086:7110", L"\0" }; HKEY hKey = NULL; LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0, KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); WCHAR name[1024]; DWORD name_len = 1024; FILETIME scrap; S32 key_num = 0; WCHAR pci_id[10]; wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/ while (nResult == ERROR_SUCCESS) { nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap); if (nResult == ERROR_SUCCESS) { memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */ memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */ for (S32 check = 0; bad_pci_list[check][0]; check++) { if (!wcscmp(pci_id, bad_pci_list[check])) { // LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl; failed = TRUE; break; } } // llinfo << "PCI chipset found: " << pci_id << endl; name_len = 1024; } } #endif return(failed); } /////////////////////////////////////////////////////////////////////////////// // // NON-MEMBER FUNCTIONS // /////////////////////////////////////////////////////////////////////////////// time_t time_corrected() { return time(NULL) + gUTCOffset; } // Is the current computer (in its current time zone) // observing daylight savings time? BOOL is_daylight_savings() { time_t now = time(NULL); // Internal buffer to local server time struct tm* internal_time = localtime(&now); // tm_isdst > 0 => daylight savings // tm_isdst = 0 => not daylight savings // tm_isdst < 0 => can't tell return (internal_time->tm_isdst > 0); } struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time) { S32Hours pacific_offset_hours; if (pacific_daylight_time) { pacific_offset_hours = S32Hours(7); } else { pacific_offset_hours = S32Hours(8); } // We subtract off the PST/PDT offset _before_ getting // "UTC" time, because this will handle wrapping around // for 5 AM UTC -> 10 PM PDT of the previous day. utc_time -= S32SecondsImplicit(pacific_offset_hours); // Internal buffer to PST/PDT (see above) struct tm* internal_time = gmtime(&utc_time); /* // Don't do this, this won't correctly tell you if daylight savings is active in CA or not. if (pacific_daylight_time) { internal_time->tm_isdst = 1; } */ return internal_time; } void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring) { U64 hours; U64 minutes; U64 seconds; U64 frames; U64 subframes; hours = current_time / (U64)3600000000ul; minutes = current_time / (U64)60000000; minutes %= 60; seconds = current_time / (U64)1000000; seconds %= 60; frames = current_time / (U64)41667; frames %= 24; subframes = current_time / (U64)42; subframes %= 100; tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes); } void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring) { microsecondsToTimecodeString(current_time, tcstring); }