diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mozglue/misc | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mozglue/misc')
-rw-r--r-- | mozglue/misc/StackWalk.cpp | 1204 | ||||
-rw-r--r-- | mozglue/misc/StackWalk.h | 163 | ||||
-rw-r--r-- | mozglue/misc/StackWalk_windows.h | 21 | ||||
-rw-r--r-- | mozglue/misc/TimeStamp.cpp | 92 | ||||
-rw-r--r-- | mozglue/misc/TimeStamp.h | 609 | ||||
-rw-r--r-- | mozglue/misc/TimeStamp_darwin.cpp | 206 | ||||
-rw-r--r-- | mozglue/misc/TimeStamp_posix.cpp | 362 | ||||
-rw-r--r-- | mozglue/misc/TimeStamp_windows.cpp | 576 | ||||
-rw-r--r-- | mozglue/misc/TimeStamp_windows.h | 83 | ||||
-rw-r--r-- | mozglue/misc/moz.build | 37 |
10 files changed, 3353 insertions, 0 deletions
diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp new file mode 100644 index 000000000..bb23b922a --- /dev/null +++ b/mozglue/misc/StackWalk.cpp @@ -0,0 +1,1204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* API for getting a stack trace of the C/C++ stack on the current thread */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/StackWalk.h" + +#include <string.h> + +using namespace mozilla; + +// The presence of this address is the stack must stop the stack walk. If +// there is no such address, the structure will be {nullptr, true}. +struct CriticalAddress +{ + void* mAddr; + bool mInit; +}; +static CriticalAddress gCriticalAddress; + +// for _Unwind_Backtrace from libcxxrt or libunwind +// cxxabi.h from libcxxrt implicitly includes unwind.h first +#if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#if defined(HAVE_DLOPEN) || defined(XP_DARWIN) +#include <dlfcn.h> +#endif + +#if (defined(XP_DARWIN) && \ + (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) +#define MOZ_STACKWALK_SUPPORTS_MACOSX 1 +#else +#define MOZ_STACKWALK_SUPPORTS_MACOSX 0 +#endif + +#if (defined(linux) && \ + ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ + defined(HAVE__UNWIND_BACKTRACE))) +#define MOZ_STACKWALK_SUPPORTS_LINUX 1 +#else +#define MOZ_STACKWALK_SUPPORTS_LINUX 0 +#endif + +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) +#define HAVE___LIBC_STACK_END 1 +#else +#define HAVE___LIBC_STACK_END 0 +#endif + +#if HAVE___LIBC_STACK_END +extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so +#endif + +#ifdef ANDROID +#include <algorithm> +#include <unistd.h> +#include <pthread.h> +#endif + +#if MOZ_STACKWALK_SUPPORTS_MACOSX +#include <pthread.h> +#include <sys/errno.h> +#ifdef MOZ_WIDGET_COCOA +#include <CoreServices/CoreServices.h> +#endif + +typedef void +malloc_logger_t(uint32_t aType, + uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, + uintptr_t aResult, uint32_t aNumHotFramesToSkip); +extern malloc_logger_t* malloc_logger; + +static void +stack_callback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure) +{ + const char* name = static_cast<char*>(aClosure); + Dl_info info; + + // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The + // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The + // correct one is the first that we find on our way up, so the + // following check for gCriticalAddress.mAddr is critical. + if (gCriticalAddress.mAddr || dladdr(aPc, &info) == 0 || + !info.dli_sname || strcmp(info.dli_sname, name) != 0) { + return; + } + gCriticalAddress.mAddr = aPc; +} + +static void +my_malloc_logger(uint32_t aType, + uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, + uintptr_t aResult, uint32_t aNumHotFramesToSkip) +{ + static bool once = false; + if (once) { + return; + } + once = true; + + // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The + // stack shows up as having two pthread_cond_wait$UNIX2003 frames. + const char* name = "new_sem_from_pool"; + MozStackWalk(stack_callback, /* skipFrames */ 0, /* maxFrames */ 0, + const_cast<char*>(name), 0, nullptr); +} + +// This is called from NS_LogInit() and from the stack walking functions, but +// only the first call has any effect. We need to call this function from both +// places because it must run before any mutexes are created, and also before +// any objects whose refcounts we're logging are created. Running this +// function during NS_LogInit() ensures that we meet the first criterion, and +// running this function during the stack walking functions ensures we meet the +// second criterion. +MFBT_API void +StackWalkInitCriticalAddress() +{ + if (gCriticalAddress.mInit) { + return; + } + gCriticalAddress.mInit = true; + // We must not do work when 'new_sem_from_pool' calls realloc, since + // it holds a non-reentrant spin-lock and we will quickly deadlock. + // new_sem_from_pool is not directly accessible using dlsym, so + // we force a situation where new_sem_from_pool is on the stack and + // use dladdr to check the addresses. + + // malloc_logger can be set by external tools like 'Instruments' or 'leaks' + malloc_logger_t* old_malloc_logger = malloc_logger; + malloc_logger = my_malloc_logger; + + pthread_cond_t cond; + int r = pthread_cond_init(&cond, 0); + MOZ_ASSERT(r == 0); + pthread_mutex_t mutex; + r = pthread_mutex_init(&mutex, 0); + MOZ_ASSERT(r == 0); + r = pthread_mutex_lock(&mutex); + MOZ_ASSERT(r == 0); + struct timespec abstime = { 0, 1 }; + r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime); + + // restore the previous malloc logger + malloc_logger = old_malloc_logger; + + MOZ_ASSERT(r == ETIMEDOUT); + r = pthread_mutex_unlock(&mutex); + MOZ_ASSERT(r == 0); + r = pthread_mutex_destroy(&mutex); + MOZ_ASSERT(r == 0); + r = pthread_cond_destroy(&cond); + MOZ_ASSERT(r == 0); +} + +static bool +IsCriticalAddress(void* aPC) +{ + return gCriticalAddress.mAddr == aPC; +} +#else +static bool +IsCriticalAddress(void* aPC) +{ + return false; +} +// We still initialize gCriticalAddress.mInit so that this code behaves +// the same on all platforms. Otherwise a failure to init would be visible +// only on OS X. +MFBT_API void +StackWalkInitCriticalAddress() +{ + gCriticalAddress.mInit = true; +} +#endif + +#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code + +#include <windows.h> +#include <process.h> +#include <stdio.h> +#include <malloc.h> +#include "mozilla/ArrayUtils.h" +#include "mozilla/StackWalk_windows.h" + +#include <imagehlp.h> +// We need a way to know if we are building for WXP (or later), as if we are, we +// need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. +// A value of 9 indicates we want to use the new APIs. +#if API_VERSION_NUMBER < 9 +#error Too old imagehlp.h +#endif + +struct WalkStackData +{ + // Are we walking the stack of the calling thread? Note that we need to avoid + // calling fprintf and friends if this is false, in order to avoid deadlocks. + bool walkCallingThread; + uint32_t skipFrames; + HANDLE thread; + HANDLE process; + HANDLE eventStart; + HANDLE eventEnd; + void** pcs; + uint32_t pc_size; + uint32_t pc_count; + uint32_t pc_max; + void** sps; + uint32_t sp_size; + uint32_t sp_count; + void* platformData; +}; + +DWORD gStackWalkThread; +CRITICAL_SECTION gDbgHelpCS; + +// Routine to print an error message to standard error. +static void +PrintError(const char* aPrefix) +{ + LPSTR lpMsgBuf; + DWORD lastErr = GetLastError(); + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + lastErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPSTR)&lpMsgBuf, + 0, + nullptr + ); + fprintf(stderr, "### ERROR: %s: %s", + aPrefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); + fflush(stderr); + LocalFree(lpMsgBuf); +} + +static unsigned int WINAPI WalkStackThread(void* aData); + +static bool +EnsureWalkThreadReady() +{ + static bool walkThreadReady = false; + static HANDLE stackWalkThread = nullptr; + static HANDLE readyEvent = nullptr; + + if (walkThreadReady) { + return walkThreadReady; + } + + if (!stackWalkThread) { + readyEvent = ::CreateEvent(nullptr, FALSE /* auto-reset*/, + FALSE /* initially non-signaled */, + nullptr); + if (!readyEvent) { + PrintError("CreateEvent"); + return false; + } + + unsigned int threadID; + stackWalkThread = (HANDLE)_beginthreadex(nullptr, 0, WalkStackThread, + (void*)readyEvent, 0, &threadID); + if (!stackWalkThread) { + PrintError("CreateThread"); + ::CloseHandle(readyEvent); + readyEvent = nullptr; + return false; + } + gStackWalkThread = threadID; + ::CloseHandle(stackWalkThread); + } + + MOZ_ASSERT((stackWalkThread && readyEvent) || + (!stackWalkThread && !readyEvent)); + + // The thread was created. Try to wait an arbitrary amount of time (1 second + // should be enough) for its event loop to start before posting events to it. + DWORD waitRet = ::WaitForSingleObject(readyEvent, 1000); + if (waitRet == WAIT_TIMEOUT) { + // We get a timeout if we're called during static initialization because + // the thread will only start executing after we return so it couldn't + // have signalled the event. If that is the case, give up for now and + // try again next time we're called. + return false; + } + ::CloseHandle(readyEvent); + stackWalkThread = nullptr; + readyEvent = nullptr; + + + ::InitializeCriticalSection(&gDbgHelpCS); + + return walkThreadReady = true; +} + +static void +WalkStackMain64(struct WalkStackData* aData) +{ + // Get a context for the specified thread. + CONTEXT context; + if (!aData->platformData) { + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + if (!GetThreadContext(aData->thread, &context)) { + if (aData->walkCallingThread) { + PrintError("GetThreadContext"); + } + return; + } + } else { + context = *static_cast<CONTEXT*>(aData->platformData); + } + +#if defined(_M_IX86) || defined(_M_IA64) + // Setup initial stack frame to walk from. + STACKFRAME64 frame64; + memset(&frame64, 0, sizeof(frame64)); +#ifdef _M_IX86 + frame64.AddrPC.Offset = context.Eip; + frame64.AddrStack.Offset = context.Esp; + frame64.AddrFrame.Offset = context.Ebp; +#elif defined _M_IA64 + frame64.AddrPC.Offset = context.StIIP; + frame64.AddrStack.Offset = context.SP; + frame64.AddrFrame.Offset = context.RsBSP; +#endif + frame64.AddrPC.Mode = AddrModeFlat; + frame64.AddrStack.Mode = AddrModeFlat; + frame64.AddrFrame.Mode = AddrModeFlat; + frame64.AddrReturn.Mode = AddrModeFlat; +#endif + + // Skip our own stack walking frames. + int skip = (aData->walkCallingThread ? 3 : 0) + aData->skipFrames; + + // Now walk the stack. + while (true) { + DWORD64 addr; + DWORD64 spaddr; + +#if defined(_M_IX86) || defined(_M_IA64) + // 32-bit frame unwinding. + // Debug routines are not threadsafe, so grab the lock. + EnterCriticalSection(&gDbgHelpCS); + BOOL ok = StackWalk64( +#if defined _M_IA64 + IMAGE_FILE_MACHINE_IA64, +#elif defined _M_IX86 + IMAGE_FILE_MACHINE_I386, +#endif + aData->process, + aData->thread, + &frame64, + &context, + nullptr, + SymFunctionTableAccess64, // function table access routine + SymGetModuleBase64, // module base routine + 0 + ); + LeaveCriticalSection(&gDbgHelpCS); + + if (ok) { + addr = frame64.AddrPC.Offset; + spaddr = frame64.AddrStack.Offset; + } else { + addr = 0; + spaddr = 0; + if (aData->walkCallingThread) { + PrintError("WalkStack64"); + } + } + + if (!ok) { + break; + } + +#elif defined(_M_AMD64) + // 64-bit frame unwinding. + // Try to look up unwind metadata for the current function. + ULONG64 imageBase; + PRUNTIME_FUNCTION runtimeFunction = + RtlLookupFunctionEntry(context.Rip, &imageBase, NULL); + + if (!runtimeFunction) { + // Alas, this is probably a JIT frame, for which we don't generate unwind + // info and so we have to give up. + break; + } + + PVOID dummyHandlerData; + ULONG64 dummyEstablisherFrame; + RtlVirtualUnwind(UNW_FLAG_NHANDLER, + imageBase, + context.Rip, + runtimeFunction, + &context, + &dummyHandlerData, + &dummyEstablisherFrame, + nullptr); + + addr = context.Rip; + spaddr = context.Rsp; + +#else +#error "unknown platform" +#endif + + if (addr == 0) { + break; + } + + if (skip-- > 0) { + continue; + } + + if (aData->pc_count < aData->pc_size) { + aData->pcs[aData->pc_count] = (void*)addr; + } + ++aData->pc_count; + + if (aData->sp_count < aData->sp_size) { + aData->sps[aData->sp_count] = (void*)spaddr; + } + ++aData->sp_count; + + if (aData->pc_max != 0 && aData->pc_count == aData->pc_max) { + break; + } + +#if defined(_M_IX86) || defined(_M_IA64) + if (frame64.AddrReturn.Offset == 0) { + break; + } +#endif + } +} + +// The JIT needs to allocate executable memory. Because of the inanity of +// the win64 APIs, this requires locks that stalk walkers also need. Provide +// another lock to allow synchronization around these resources. +#ifdef _M_AMD64 + +struct CriticalSectionAutoInitializer { + CRITICAL_SECTION lock; + + CriticalSectionAutoInitializer() { + InitializeCriticalSection(&lock); + } +}; + +static CriticalSectionAutoInitializer gWorkaroundLock; + +#endif // _M_AMD64 + +MFBT_API void +AcquireStackWalkWorkaroundLock() +{ +#ifdef _M_AMD64 + EnterCriticalSection(&gWorkaroundLock.lock); +#endif +} + +MFBT_API bool +TryAcquireStackWalkWorkaroundLock() +{ +#ifdef _M_AMD64 + return TryEnterCriticalSection(&gWorkaroundLock.lock); +#else + return true; +#endif +} + +MFBT_API void +ReleaseStackWalkWorkaroundLock() +{ +#ifdef _M_AMD64 + LeaveCriticalSection(&gWorkaroundLock.lock); +#endif +} + +static unsigned int WINAPI +WalkStackThread(void* aData) +{ + BOOL msgRet; + MSG msg; + + // Call PeekMessage to force creation of a message queue so that + // other threads can safely post events to us. + ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + // and tell the thread that created us that we're ready. + HANDLE readyEvent = (HANDLE)aData; + ::SetEvent(readyEvent); + + while ((msgRet = ::GetMessage(&msg, (HWND)-1, 0, 0)) != 0) { + if (msgRet == -1) { + PrintError("GetMessage"); + } else { + DWORD ret; + + struct WalkStackData* data = (WalkStackData*)msg.lParam; + if (!data) { + continue; + } + + // Don't suspend the calling thread until it's waiting for + // us; otherwise the number of frames on the stack could vary. + ret = ::WaitForSingleObject(data->eventStart, INFINITE); + if (ret != WAIT_OBJECT_0) { + PrintError("WaitForSingleObject"); + } + + // Suspend the calling thread, dump his stack, and then resume him. + // He's currently waiting for us to finish so now should be a good time. + ret = ::SuspendThread(data->thread); + if (ret == -1) { + PrintError("ThreadSuspend"); + } else { + WalkStackMain64(data); + + ret = ::ResumeThread(data->thread); + if (ret == -1) { + PrintError("ThreadResume"); + } + } + + ::SetEvent(data->eventEnd); + } + } + + return 0; +} + +/** + * Walk the stack, translating PC's found into strings and recording the + * chain in aBuffer. For this to work properly, the DLLs must be rebased + * so that the address in the file agrees with the address in memory. + * Otherwise StackWalk will return FALSE when it hits a frame in a DLL + * whose in memory address doesn't match its in-file address. + */ + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + StackWalkInitCriticalAddress(); + static HANDLE myProcess = nullptr; + HANDLE myThread; + DWORD walkerReturn; + struct WalkStackData data; + + if (!EnsureWalkThreadReady()) { + return false; + } + + HANDLE currentThread = ::GetCurrentThread(); + HANDLE targetThread = + aThread ? reinterpret_cast<HANDLE>(aThread) : currentThread; + data.walkCallingThread = (targetThread == currentThread); + + // Have to duplicate handle to get a real handle. + if (!myProcess) { + if (!::DuplicateHandle(::GetCurrentProcess(), + ::GetCurrentProcess(), + ::GetCurrentProcess(), + &myProcess, + PROCESS_ALL_ACCESS, FALSE, 0)) { + if (data.walkCallingThread) { + PrintError("DuplicateHandle (process)"); + } + return false; + } + } + if (!::DuplicateHandle(::GetCurrentProcess(), + targetThread, + ::GetCurrentProcess(), + &myThread, + THREAD_ALL_ACCESS, FALSE, 0)) { + if (data.walkCallingThread) { + PrintError("DuplicateHandle (thread)"); + } + return false; + } + + data.skipFrames = aSkipFrames; + data.thread = myThread; + data.process = myProcess; + void* local_pcs[1024]; + data.pcs = local_pcs; + data.pc_count = 0; + data.pc_size = ArrayLength(local_pcs); + data.pc_max = aMaxFrames; + void* local_sps[1024]; + data.sps = local_sps; + data.sp_count = 0; + data.sp_size = ArrayLength(local_sps); + data.platformData = aPlatformData; + + if (aThread) { + // If we're walking the stack of another thread, we don't need to + // use a separate walker thread. + WalkStackMain64(&data); + + if (data.pc_count > data.pc_size) { + data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); + data.pc_size = data.pc_count; + data.pc_count = 0; + data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); + data.sp_size = data.sp_count; + data.sp_count = 0; + WalkStackMain64(&data); + } + } else { + data.eventStart = ::CreateEvent(nullptr, FALSE /* auto-reset*/, + FALSE /* initially non-signaled */, nullptr); + data.eventEnd = ::CreateEvent(nullptr, FALSE /* auto-reset*/, + FALSE /* initially non-signaled */, nullptr); + + ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); + + walkerReturn = ::SignalObjectAndWait(data.eventStart, + data.eventEnd, INFINITE, FALSE); + if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) { + PrintError("SignalObjectAndWait (1)"); + } + if (data.pc_count > data.pc_size) { + data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); + data.pc_size = data.pc_count; + data.pc_count = 0; + data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); + data.sp_size = data.sp_count; + data.sp_count = 0; + ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); + walkerReturn = ::SignalObjectAndWait(data.eventStart, + data.eventEnd, INFINITE, FALSE); + if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) { + PrintError("SignalObjectAndWait (2)"); + } + } + + ::CloseHandle(data.eventStart); + ::CloseHandle(data.eventEnd); + } + + ::CloseHandle(myThread); + + for (uint32_t i = 0; i < data.pc_count; ++i) { + (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure); + } + + return data.pc_count != 0; +} + + +static BOOL CALLBACK +callbackEspecial64( + PCSTR aModuleName, + DWORD64 aModuleBase, + ULONG aModuleSize, + PVOID aUserContext) +{ + BOOL retval = TRUE; + DWORD64 addr = *(DWORD64*)aUserContext; + + /* + * You'll want to control this if we are running on an + * architecture where the addresses go the other direction. + * Not sure this is even a realistic consideration. + */ + const BOOL addressIncreases = TRUE; + + /* + * If it falls in side the known range, load the symbols. + */ + if (addressIncreases + ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize)) + : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize)) + ) { + retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, + (PSTR)aModuleName, nullptr, + aModuleBase, aModuleSize); + if (!retval) { + PrintError("SymLoadModule64"); + } + } + + return retval; +} + +/* + * SymGetModuleInfoEspecial + * + * Attempt to determine the module information. + * Bug 112196 says this DLL may not have been loaded at the time + * SymInitialize was called, and thus the module information + * and symbol information is not available. + * This code rectifies that problem. + */ + +// New members were added to IMAGEHLP_MODULE64 (that show up in the +// Platform SDK that ships with VC8, but not the Platform SDK that ships +// with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to +// use them, and it's useful to be able to function correctly with the +// older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll +// version 5.1.) Since Platform SDK version need not correspond to +// compiler version, and the version number in debughlp.h was NOT bumped +// when these changes were made, ifdef based on a constant that was +// added between these versions. +#ifdef SSRVOPT_SETCONTEXT +#define NS_IMAGEHLP_MODULE64_SIZE (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / sizeof(DWORD64)) * sizeof(DWORD64)) +#else +#define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) +#endif + +BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, + PIMAGEHLP_MODULE64 aModuleInfo, + PIMAGEHLP_LINE64 aLineInfo) +{ + BOOL retval = FALSE; + + /* + * Init the vars if we have em. + */ + aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; + if (aLineInfo) { + aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + } + + /* + * Give it a go. + * It may already be loaded. + */ + retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); + if (retval == FALSE) { + /* + * Not loaded, here's the magic. + * Go through all the modules. + */ + // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the + // constness of the first parameter of + // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from + // non-const to const over time). See bug 391848 and bug + // 415426. + BOOL enumRes = EnumerateLoadedModules64( + aProcess, + (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, + (PVOID)&aAddr); + if (enumRes != FALSE) { + /* + * One final go. + * If it fails, then well, we have other problems. + */ + retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); + } + } + + /* + * If we got module info, we may attempt line info as well. + * We will not report failure if this does not work. + */ + if (retval != FALSE && aLineInfo) { + DWORD displacement = 0; + BOOL lineRes = FALSE; + lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo); + if (!lineRes) { + // Clear out aLineInfo to indicate that it's not valid + memset(aLineInfo, 0, sizeof(*aLineInfo)); + } + } + + return retval; +} + +static bool +EnsureSymInitialized() +{ + static bool gInitialized = false; + bool retStat; + + if (gInitialized) { + return gInitialized; + } + + if (!EnsureWalkThreadReady()) { + return false; + } + + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE); + if (!retStat) { + PrintError("SymInitialize"); + } + + gInitialized = retStat; + /* XXX At some point we need to arrange to call SymCleanup */ + + return retStat; +} + + +MFBT_API bool +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) +{ + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + + if (!EnsureSymInitialized()) { + return false; + } + + HANDLE myProcess = ::GetCurrentProcess(); + BOOL ok; + + // debug routines are not threadsafe, so grab the lock. + EnterCriticalSection(&gDbgHelpCS); + + // + // Attempt to load module info before we attempt to resolve the symbol. + // This just makes sure we get good info if available. + // + + DWORD64 addr = (DWORD64)aPC; + IMAGEHLP_MODULE64 modInfo; + IMAGEHLP_LINE64 lineInfo; + BOOL modInfoRes; + modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); + + if (modInfoRes) { + strncpy(aDetails->library, modInfo.LoadedImageName, + sizeof(aDetails->library)); + aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; + aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage; + + if (lineInfo.FileName) { + strncpy(aDetails->filename, lineInfo.FileName, + sizeof(aDetails->filename)); + aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0'; + aDetails->lineno = lineInfo.LineNumber; + } + } + + ULONG64 buffer[(sizeof(SYMBOL_INFO) + + MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement; + ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); + + if (ok) { + strncpy(aDetails->function, pSymbol->Name, + sizeof(aDetails->function)); + aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; + aDetails->foffset = static_cast<ptrdiff_t>(displacement); + } + + LeaveCriticalSection(&gDbgHelpCS); // release our lock + return true; +} + +// i386 or PPC Linux stackwalking code +#elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || MOZ_STACKWALK_SUPPORTS_MACOSX) + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed +// if __USE_GNU is defined. I suppose its some kind of standards +// adherence thing. +// +#if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) +#define __USE_GNU +#endif + +// This thing is exported by libstdc++ +// Yes, this is a gcc only hack +#if defined(MOZ_DEMANGLE_SYMBOLS) +#include <cxxabi.h> +#endif // MOZ_DEMANGLE_SYMBOLS + +void DemangleSymbol(const char* aSymbol, + char* aBuffer, + int aBufLen) +{ + aBuffer[0] = '\0'; + +#if defined(MOZ_DEMANGLE_SYMBOLS) + /* See demangle.h in the gcc source for the voodoo */ + char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0); + + if (demangled) { + strncpy(aBuffer, demangled, aBufLen); + aBuffer[aBufLen - 1] = '\0'; + free(demangled); + } +#endif // MOZ_DEMANGLE_SYMBOLS +} + +// {x86, ppc} x {Linux, Mac} stackwalking code. +#if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \ + (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX)) + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + MOZ_ASSERT(!aThread); + MOZ_ASSERT(!aPlatformData); + StackWalkInitCriticalAddress(); + + // Get the frame pointer + void** bp = (void**)__builtin_frame_address(0); + + void* stackEnd; +#if HAVE___LIBC_STACK_END + stackEnd = __libc_stack_end; +#elif defined(XP_DARWIN) + stackEnd = pthread_get_stackaddr_np(pthread_self()); +#elif defined(ANDROID) + pthread_attr_t sattr; + pthread_attr_init(&sattr); + pthread_getattr_np(pthread_self(), &sattr); + void* stackBase = stackEnd = nullptr; + size_t stackSize = 0; + if (gettid() != getpid()) { + // bionic's pthread_attr_getstack doesn't tell the truth for the main + // thread (see bug 846670). So don't use it for the main thread. + if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) { + stackEnd = static_cast<char*>(stackBase) + stackSize; + } else { + stackEnd = nullptr; + } + } + if (!stackEnd) { + // So consider the current frame pointer + an arbitrary size of 8MB + // (modulo overflow ; not really arbitrary as it's the default stack + // size for the main thread) if pthread_attr_getstack failed for + // some reason (or was skipped). + static const uintptr_t kMaxStackSize = 8 * 1024 * 1024; + uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize; + uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp)); + stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize); + } +#else +# error Unsupported configuration +#endif + return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, + aClosure, bp, stackEnd); +} + +#elif defined(HAVE__UNWIND_BACKTRACE) + +// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 +#include <unwind.h> + +struct unwind_info +{ + MozWalkStackCallback callback; + int skip; + int maxFrames; + int numFrames; + bool isCriticalAbort; + void* closure; +}; + +static _Unwind_Reason_Code +unwind_callback(struct _Unwind_Context* context, void* closure) +{ + unwind_info* info = static_cast<unwind_info*>(closure); + void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context)); + // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. + if (IsCriticalAddress(pc)) { + info->isCriticalAbort = true; + // We just want to stop the walk, so any error code will do. Using + // _URC_NORMAL_STOP would probably be the most accurate, but it is not + // defined on Android for ARM. + return _URC_FOREIGN_EXCEPTION_CAUGHT; + } + if (--info->skip < 0) { + info->numFrames++; + (*info->callback)(info->numFrames, pc, nullptr, info->closure); + if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { + // Again, any error code that stops the walk will do. + return _URC_FOREIGN_EXCEPTION_CAUGHT; + } + } + return _URC_NO_REASON; +} + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + MOZ_ASSERT(!aThread); + MOZ_ASSERT(!aPlatformData); + StackWalkInitCriticalAddress(); + unwind_info info; + info.callback = aCallback; + info.skip = aSkipFrames + 1; + info.maxFrames = aMaxFrames; + info.numFrames = 0; + info.isCriticalAbort = false; + info.closure = aClosure; + + (void)_Unwind_Backtrace(unwind_callback, &info); + + // We ignore the return value from _Unwind_Backtrace and instead determine + // the outcome from |info|. There are two main reasons for this: + // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns + // _URC_FAILURE. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. + // - If aMaxFrames != 0, we want to stop early, and the only way to do that + // is to make unwind_callback return something other than _URC_NO_REASON, + // which causes _Unwind_Backtrace to return a non-success code. + if (info.isCriticalAbort) { + return false; + } + return info.numFrames != 0; +} + +#endif + +bool MFBT_API +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) +{ + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + + Dl_info info; + int ok = dladdr(aPC, &info); + if (!ok) { + return true; + } + + strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library)); + aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; + aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; + + const char* symbol = info.dli_sname; + if (!symbol || symbol[0] == '\0') { + return true; + } + + DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); + + if (aDetails->function[0] == '\0') { + // Just use the mangled symbol if demangling failed. + strncpy(aDetails->function, symbol, sizeof(aDetails->function)); + aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; + } + + aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; + return true; +} + +#else // unsupported platform. + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + MOZ_ASSERT(!aThread); + MOZ_ASSERT(!aPlatformData); + return false; +} + +MFBT_API bool +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) +{ + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + return false; +} + +#endif + +#if defined(XP_WIN) || defined (XP_MACOSX) || defined (XP_LINUX) +namespace mozilla { +bool +FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, void** bp, + void* aStackEnd) +{ + // Stack walking code courtesy Kipp's "leaky". + + int32_t skip = aSkipFrames; + uint32_t numFrames = 0; + while (bp) { + void** next = (void**)*bp; + // bp may not be a frame pointer on i386 if code was compiled with + // -fomit-frame-pointer, so do some sanity checks. + // (bp should be a frame pointer on ppc(64) but checking anyway may help + // a little if the stack has been corrupted.) + // We don't need to check against the begining of the stack because + // we can assume that bp > sp + if (next <= bp || + next > aStackEnd || + (uintptr_t(next) & 3)) { + break; + } +#if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) + // ppc mac or powerpc64 linux + void* pc = *(bp + 2); + bp += 3; +#else // i386 or powerpc32 linux + void* pc = *(bp + 1); + bp += 2; +#endif + if (IsCriticalAddress(pc)) { + return false; + } + if (--skip < 0) { + // Assume that the SP points to the BP of the function + // it called. We can't know the exact location of the SP + // but this should be sufficient for our use the SP + // to order elements on the stack. + numFrames++; + (*aCallback)(numFrames, pc, bp, aClosure); + if (aMaxFrames != 0 && numFrames == aMaxFrames) { + break; + } + } + bp = next; + } + return numFrames != 0; +} +} // namespace mozilla + +#else + +namespace mozilla { +MFBT_API bool +FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + void* aClosure, void** aBp) +{ + return false; +} +} + +#endif + +MFBT_API void +MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, void* aPC, + const MozCodeAddressDetails* aDetails) +{ + MozFormatCodeAddress(aBuffer, aBufferSize, + aFrameNumber, aPC, aDetails->function, + aDetails->library, aDetails->loffset, + aDetails->filename, aDetails->lineno); +} + +MFBT_API void +MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, + const void* aPC, const char* aFunction, + const char* aLibrary, ptrdiff_t aLOffset, + const char* aFileName, uint32_t aLineNo) +{ + const char* function = aFunction && aFunction[0] ? aFunction : "???"; + if (aFileName && aFileName[0]) { + // We have a filename and (presumably) a line number. Use them. + snprintf(aBuffer, aBufferSize, + "#%02u: %s (%s:%u)", + aFrameNumber, function, aFileName, aLineNo); + } else if (aLibrary && aLibrary[0]) { + // We have no filename, but we do have a library name. Use it and the + // library offset, and print them in a way that scripts like + // fix_{linux,macosx}_stacks.py can easily post-process. + snprintf(aBuffer, aBufferSize, + "#%02u: %s[%s +0x%" PRIxPTR "]", + aFrameNumber, function, aLibrary, static_cast<uintptr_t>(aLOffset)); + } else { + // We have nothing useful to go on. (The format string is split because + // '??)' is a trigraph and causes a warning, sigh.) + snprintf(aBuffer, aBufferSize, + "#%02u: ??? (???:???" ")", + aFrameNumber); + } +} diff --git a/mozglue/misc/StackWalk.h b/mozglue/misc/StackWalk.h new file mode 100644 index 000000000..534c0bd82 --- /dev/null +++ b/mozglue/misc/StackWalk.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* API for getting a stack trace of the C/C++ stack on the current thread */ + +#ifndef mozilla_StackWalk_h +#define mozilla_StackWalk_h + +/* WARNING: This file is intended to be included from C or C++ files. */ + +#include "mozilla/Types.h" +#include <stdint.h> + +/** + * The callback for MozStackWalk. + * + * @param aFrameNumber The frame number (starts at 1, not 0). + * @param aPC The program counter value. + * @param aSP The best approximation possible of what the stack + * pointer will be pointing to when the execution returns + * to executing that at aPC. If no approximation can + * be made it will be nullptr. + * @param aClosure Extra data passed in via MozStackWalk(). + */ +typedef void +(*MozWalkStackCallback)(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure); + +/** + * Call aCallback for the C/C++ stack frames on the current thread, from + * the caller of MozStackWalk to main (or above). + * + * @param aCallback Callback function, called once per frame. + * @param aSkipFrames Number of initial frames to skip. 0 means that + * the first callback will be for the caller of + * MozStackWalk. + * @param aMaxFrames Maximum number of frames to trace. 0 means no limit. + * @param aClosure Caller-supplied data passed through to aCallback. + * @param aThread The thread for which the stack is to be retrieved. + * Passing null causes us to walk the stack of the + * current thread. On Windows, this is a thread HANDLE. + * It is currently not supported on any other platform. + * @param aPlatformData Platform specific data that can help in walking the + * stack, this should be nullptr unless you really know + * what you're doing! This needs to be a pointer to a + * CONTEXT on Windows and should not be passed on other + * platforms. + * + * May skip some stack frames due to compiler optimizations or code + * generation. + * + */ +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData); + +typedef struct +{ + /* + * The name of the shared library or executable containing an + * address and the address's offset within that library, or empty + * string and zero if unknown. + */ + char library[256]; + ptrdiff_t loffset; + /* + * The name of the file name and line number of the code + * corresponding to the address, or empty string and zero if + * unknown. + */ + char filename[256]; + unsigned long lineno; + /* + * The name of the function containing an address and the address's + * offset within that function, or empty string and zero if unknown. + */ + char function[256]; + ptrdiff_t foffset; +} MozCodeAddressDetails; + +/** + * For a given pointer to code, fill in the pieces of information used + * when printing a stack trace. + * + * @param aPC The code address. + * @param aDetails A structure to be filled in with the result. + */ +MFBT_API bool +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails); + +/** + * Format the information about a code address in a format suitable for + * stack traces on the current platform. When available, this string + * should contain the function name, source file, and line number. When + * these are not available, library and offset should be reported, if + * possible. + * + * Note that this output is parsed by several scripts including the fix*.py and + * make-tree.pl scripts in tools/rb/. It should only be change with care, and + * in conjunction with those scripts. + * + * @param aBuffer A string to be filled in with the description. + * The string will always be null-terminated. + * @param aBufferSize The size, in bytes, of aBuffer, including + * room for the terminating null. If the information + * to be printed would be larger than aBuffer, it + * will be truncated so that aBuffer[aBufferSize-1] + * is the terminating null. + * @param aFrameNumber The frame number. + * @param aPC The code address. + * @param aFunction The function name. Possibly null or the empty string. + * @param aLibrary The library name. Possibly null or the empty string. + * @param aLOffset The library offset. + * @param aFileName The filename. Possibly null or the empty string. + * @param aLineNo The line number. Possibly zero. + */ +MFBT_API void +MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, + const void* aPC, const char* aFunction, + const char* aLibrary, ptrdiff_t aLOffset, + const char* aFileName, uint32_t aLineNo); + +/** + * Format the information about a code address in the same fashion as + * MozFormatCodeAddress. + * + * @param aBuffer A string to be filled in with the description. + * The string will always be null-terminated. + * @param aBufferSize The size, in bytes, of aBuffer, including + * room for the terminating null. If the information + * to be printed would be larger than aBuffer, it + * will be truncated so that aBuffer[aBufferSize-1] + * is the terminating null. + * @param aFrameNumber The frame number. + * @param aPC The code address. + * @param aDetails The value filled in by MozDescribeCodeAddress(aPC). + */ +MFBT_API void +MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, void* aPC, + const MozCodeAddressDetails* aDetails); + +namespace mozilla { + +MFBT_API bool +FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, void** aBp, + void* aStackEnd); + +} // namespace mozilla + +/** + * Initialize the critical sections for this platform so that we can + * abort stack walks when needed. + */ +MFBT_API void +StackWalkInitCriticalAddress(void); + +#endif diff --git a/mozglue/misc/StackWalk_windows.h b/mozglue/misc/StackWalk_windows.h new file mode 100644 index 000000000..5ffdd2068 --- /dev/null +++ b/mozglue/misc/StackWalk_windows.h @@ -0,0 +1,21 @@ +#ifndef mozilla_StackWalk_windows_h +#define mozilla_StackWalk_windows_h + +#include "mozilla/Types.h" + +/** + * Allow stack walkers to work around the egregious win64 dynamic lookup table + * list API by locking around SuspendThread to avoid deadlock. + * + * See comment in StackWalk.cpp + */ +MFBT_API void +AcquireStackWalkWorkaroundLock(); + +MFBT_API bool +TryAcquireStackWalkWorkaroundLock(); + +MFBT_API void +ReleaseStackWalkWorkaroundLock(); + +#endif // mozilla_StackWalk_windows_h diff --git a/mozglue/misc/TimeStamp.cpp b/mozglue/misc/TimeStamp.cpp new file mode 100644 index 000000000..932b75c99 --- /dev/null +++ b/mozglue/misc/TimeStamp.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Implementation of the OS-independent methods of the TimeStamp class + */ + +#include "mozilla/TimeStamp.h" +#include <stdio.h> +#include <string.h> + +namespace mozilla { + +/** + * Wrapper class used to initialize static data used by the TimeStamp class + */ +struct TimeStampInitialization +{ + /** + * First timestamp taken when the class static initializers are run. This + * timestamp is used to sanitize timestamps coming from different sources. + */ + TimeStamp mFirstTimeStamp; + + /** + * Timestamp representing the time when the process was created. This field + * is populated lazily the first time this information is required and is + * replaced every time the process is restarted. + */ + TimeStamp mProcessCreation; + + TimeStampInitialization() + { + TimeStamp::Startup(); + mFirstTimeStamp = TimeStamp::Now(); + }; + + ~TimeStampInitialization() + { + TimeStamp::Shutdown(); + }; +}; + +static TimeStampInitialization sInitOnce; + +MFBT_API TimeStamp +TimeStamp::ProcessCreation(bool& aIsInconsistent) +{ + aIsInconsistent = false; + + if (sInitOnce.mProcessCreation.IsNull()) { + char* mozAppRestart = getenv("MOZ_APP_RESTART"); + TimeStamp ts; + + /* When calling PR_SetEnv() with an empty value the existing variable may + * be unset or set to the empty string depending on the underlying platform + * thus we have to check if the variable is present and not empty. */ + if (mozAppRestart && (strcmp(mozAppRestart, "") != 0)) { + /* Firefox was restarted, use the first time-stamp we've taken as the new + * process startup time. */ + ts = sInitOnce.mFirstTimeStamp; + } else { + TimeStamp now = Now(); + uint64_t uptime = ComputeProcessUptime(); + + ts = now - TimeDuration::FromMicroseconds(uptime); + + if ((ts > sInitOnce.mFirstTimeStamp) || (uptime == 0)) { + /* If the process creation timestamp was inconsistent replace it with + * the first one instead and notify that a telemetry error was + * detected. */ + aIsInconsistent = true; + ts = sInitOnce.mFirstTimeStamp; + } + } + + sInitOnce.mProcessCreation = ts; + } + + return sInitOnce.mProcessCreation; +} + +void +TimeStamp::RecordProcessRestart() +{ + sInitOnce.mProcessCreation = TimeStamp(); +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h new file mode 100644 index 000000000..a1a0eb360 --- /dev/null +++ b/mozglue/misc/TimeStamp.h @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimeStamp_h +#define mozilla_TimeStamp_h + +#include <stdint.h> +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/Types.h" + +namespace IPC { +template<typename T> struct ParamTraits; +} // namespace IPC + +#ifdef XP_WIN +// defines TimeStampValue as a complex value keeping both +// GetTickCount and QueryPerformanceCounter values +#include "TimeStamp_windows.h" +#endif + +namespace mozilla { + +#ifndef XP_WIN +typedef uint64_t TimeStampValue; +#endif + +class TimeStamp; + +/** + * Platform-specific implementation details of BaseTimeDuration. + */ +class BaseTimeDurationPlatformUtils +{ +public: + static MFBT_API double ToSeconds(int64_t aTicks); + static MFBT_API double ToSecondsSigDigits(int64_t aTicks); + static MFBT_API int64_t TicksFromMilliseconds(double aMilliseconds); + static MFBT_API int64_t ResolutionInTicks(); +}; + +/** + * Instances of this class represent the length of an interval of time. + * Negative durations are allowed, meaning the end is before the start. + * + * Internally the duration is stored as a int64_t in units of + * PR_TicksPerSecond() when building with NSPR interval timers, or a + * system-dependent unit when building with system clocks. The + * system-dependent unit must be constant, otherwise the semantics of + * this class would be broken. + * + * The ValueCalculator template parameter determines how arithmetic + * operations are performed on the integer count of ticks (mValue). + */ +template <typename ValueCalculator> +class BaseTimeDuration +{ +public: + // The default duration is 0. + constexpr BaseTimeDuration() : mValue(0) {} + // Allow construction using '0' as the initial value, for readability, + // but no other numbers (so we don't have any implicit unit conversions). + struct _SomethingVeryRandomHere; + MOZ_IMPLICIT BaseTimeDuration(_SomethingVeryRandomHere* aZero) : mValue(0) + { + MOZ_ASSERT(!aZero, "Who's playing funny games here?"); + } + // Default copy-constructor and assignment are OK + + // Converting copy-constructor and assignment operator + template <typename E> + explicit BaseTimeDuration(const BaseTimeDuration<E>& aOther) + : mValue(aOther.mValue) + { } + + template <typename E> + BaseTimeDuration& operator=(const BaseTimeDuration<E>& aOther) + { + mValue = aOther.mValue; + return *this; + } + + double ToSeconds() const + { + if (mValue == INT64_MAX) { + return PositiveInfinity<double>(); + } + if (mValue == INT64_MIN) { + return NegativeInfinity<double>(); + } + return BaseTimeDurationPlatformUtils::ToSeconds(mValue); + } + // Return a duration value that includes digits of time we think to + // be significant. This method should be used when displaying a + // time to humans. + double ToSecondsSigDigits() const + { + if (mValue == INT64_MAX) { + return PositiveInfinity<double>(); + } + if (mValue == INT64_MIN) { + return NegativeInfinity<double>(); + } + return BaseTimeDurationPlatformUtils::ToSecondsSigDigits(mValue); + } + double ToMilliseconds() const { return ToSeconds() * 1000.0; } + double ToMicroseconds() const { return ToMilliseconds() * 1000.0; } + + // Using a double here is safe enough; with 53 bits we can represent + // durations up to over 280,000 years exactly. If the units of + // mValue do not allow us to represent durations of that length, + // long durations are clamped to the max/min representable value + // instead of overflowing. + static inline BaseTimeDuration FromSeconds(double aSeconds) + { + return FromMilliseconds(aSeconds * 1000.0); + } + static BaseTimeDuration FromMilliseconds(double aMilliseconds) + { + if (aMilliseconds == PositiveInfinity<double>()) { + return Forever(); + } + if (aMilliseconds == NegativeInfinity<double>()) { + return FromTicks(INT64_MIN); + } + return FromTicks( + BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aMilliseconds)); + } + static inline BaseTimeDuration FromMicroseconds(double aMicroseconds) + { + return FromMilliseconds(aMicroseconds / 1000.0); + } + + static BaseTimeDuration Forever() + { + return FromTicks(INT64_MAX); + } + + BaseTimeDuration operator+(const BaseTimeDuration& aOther) const + { + return FromTicks(ValueCalculator::Add(mValue, aOther.mValue)); + } + BaseTimeDuration operator-(const BaseTimeDuration& aOther) const + { + return FromTicks(ValueCalculator::Subtract(mValue, aOther.mValue)); + } + BaseTimeDuration& operator+=(const BaseTimeDuration& aOther) + { + mValue = ValueCalculator::Add(mValue, aOther.mValue); + return *this; + } + BaseTimeDuration& operator-=(const BaseTimeDuration& aOther) + { + mValue = ValueCalculator::Subtract(mValue, aOther.mValue); + return *this; + } + BaseTimeDuration operator-() const + { + // We don't just use FromTicks(ValueCalculator::Subtract(0, mValue)) + // since that won't give the correct result for -TimeDuration::Forever(). + int64_t ticks; + if (mValue == INT64_MAX) { + ticks = INT64_MIN; + } else if (mValue == INT64_MIN) { + ticks = INT64_MAX; + } else { + ticks = -mValue; + } + + return FromTicks(ticks); + } + +private: + // Block double multiplier (slower, imprecise if long duration) - Bug 853398. + // If required, use MultDouble explicitly and with care. + BaseTimeDuration operator*(const double aMultiplier) const = delete; + + // Block double divisor (for the same reason, and because dividing by + // fractional values would otherwise invoke the int64_t variant, and rounding + // the passed argument can then cause divide-by-zero) - Bug 1147491. + BaseTimeDuration operator/(const double aDivisor) const = delete; + +public: + BaseTimeDuration MultDouble(double aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const int32_t aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const uint32_t aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const int64_t aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const uint64_t aMultiplier) const + { + if (aMultiplier > INT64_MAX) { + return Forever(); + } + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator/(const int64_t aDivisor) const + { + MOZ_ASSERT(aDivisor != 0, "Division by zero"); + return FromTicks(ValueCalculator::Divide(mValue, aDivisor)); + } + double operator/(const BaseTimeDuration& aOther) const + { +#ifndef MOZ_B2G + // Bug 1066388 - This fails on B2G ICS Emulator + MOZ_ASSERT(aOther.mValue != 0, "Division by zero"); +#endif + return ValueCalculator::DivideDouble(mValue, aOther.mValue); + } + BaseTimeDuration operator%(const BaseTimeDuration& aOther) const + { + MOZ_ASSERT(aOther.mValue != 0, "Division by zero"); + return FromTicks(ValueCalculator::Modulo(mValue, aOther.mValue)); + } + + template<typename E> + bool operator<(const BaseTimeDuration<E>& aOther) const + { + return mValue < aOther.mValue; + } + template<typename E> + bool operator<=(const BaseTimeDuration<E>& aOther) const + { + return mValue <= aOther.mValue; + } + template<typename E> + bool operator>=(const BaseTimeDuration<E>& aOther) const + { + return mValue >= aOther.mValue; + } + template<typename E> + bool operator>(const BaseTimeDuration<E>& aOther) const + { + return mValue > aOther.mValue; + } + template<typename E> + bool operator==(const BaseTimeDuration<E>& aOther) const + { + return mValue == aOther.mValue; + } + template<typename E> + bool operator!=(const BaseTimeDuration<E>& aOther) const + { + return mValue != aOther.mValue; + } + bool IsZero() const + { + return mValue == 0; + } + explicit operator bool() const + { + return mValue != 0; + } + + // Return a best guess at the system's current timing resolution, + // which might be variable. BaseTimeDurations below this order of + // magnitude are meaningless, and those at the same order of + // magnitude or just above are suspect. + static BaseTimeDuration Resolution() { + return FromTicks(BaseTimeDurationPlatformUtils::ResolutionInTicks()); + } + + // We could define additional operators here: + // -- convert to/from other time units + // -- scale duration by a float + // but let's do that on demand. + // Comparing durations for equality will only lead to bugs on + // platforms with high-resolution timers. + +private: + friend class TimeStamp; + friend struct IPC::ParamTraits<mozilla::BaseTimeDuration<ValueCalculator>>; + template <typename> + friend class BaseTimeDuration; + + static BaseTimeDuration FromTicks(int64_t aTicks) + { + BaseTimeDuration t; + t.mValue = aTicks; + return t; + } + + static BaseTimeDuration FromTicks(double aTicks) + { + // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX)) + // overflows and gives INT64_MIN. + if (aTicks >= double(INT64_MAX)) { + return FromTicks(INT64_MAX); + } + + // This MUST be a <= test. + if (aTicks <= double(INT64_MIN)) { + return FromTicks(INT64_MIN); + } + + return FromTicks(int64_t(aTicks)); + } + + // Duration, result is implementation-specific difference of two TimeStamps + int64_t mValue; +}; + +/** + * Perform arithmetic operations on the value of a BaseTimeDuration without + * doing strict checks on the range of values. + */ +class TimeDurationValueCalculator +{ +public: + static int64_t Add(int64_t aA, int64_t aB) { return aA + aB; } + static int64_t Subtract(int64_t aA, int64_t aB) { return aA - aB; } + + template <typename T> + static int64_t Multiply(int64_t aA, T aB) + { + static_assert(IsIntegral<T>::value, + "Using integer multiplication routine with non-integer type." + " Further specialization required"); + return aA * static_cast<int64_t>(aB); + } + + static int64_t Divide(int64_t aA, int64_t aB) { return aA / aB; } + static double DivideDouble(int64_t aA, int64_t aB) + { + return static_cast<double>(aA) / aB; + } + static int64_t Modulo(int64_t aA, int64_t aB) { return aA % aB; } +}; + +template <> +inline int64_t +TimeDurationValueCalculator::Multiply<double>(int64_t aA, double aB) +{ + return static_cast<int64_t>(aA * aB); +} + +/** + * Specialization of BaseTimeDuration that uses TimeDurationValueCalculator for + * arithmetic on the mValue member. + * + * Use this class for time durations that are *not* expected to hold values of + * Forever (or the negative equivalent) or when such time duration are *not* + * expected to be used in arithmetic operations. + */ +typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; + +/** + * Instances of this class represent moments in time, or a special + * "null" moment. We do not use the non-monotonic system clock or + * local time, since they can be reset, causing apparent backward + * travel in time, which can confuse algorithms. Instead we measure + * elapsed time according to the system. This time can never go + * backwards (i.e. it never wraps around, at least not in less than + * five million years of system elapsed time). It might not advance + * while the system is sleeping. If TimeStamp::SetNow() is not called + * at all for hours or days, we might not notice the passage of some + * of that time. + * + * We deliberately do not expose a way to convert TimeStamps to some + * particular unit. All you can do is compute a difference between two + * TimeStamps to get a TimeDuration. You can also add a TimeDuration + * to a TimeStamp to get a new TimeStamp. You can't do something + * meaningless like add two TimeStamps. + * + * Internally this is implemented as either a wrapper around + * - high-resolution, monotonic, system clocks if they exist on this + * platform + * - PRIntervalTime otherwise. We detect wraparounds of + * PRIntervalTime and work around them. + * + * This class is similar to C++11's time_point, however it is + * explicitly nullable and provides an IsNull() method. time_point + * is initialized to the clock's epoch and provides a + * time_since_epoch() method that functions similiarly. i.e. + * t.IsNull() is equivalent to t.time_since_epoch() == decltype(t)::duration::zero(); + */ +class TimeStamp +{ +public: + /** + * Initialize to the "null" moment + */ + constexpr TimeStamp() : mValue(0) {} + // Default copy-constructor and assignment are OK + + /** + * The system timestamps are the same as the TimeStamp + * retrieved by mozilla::TimeStamp. Since we need this for + * vsync timestamps, we enable the creation of mozilla::TimeStamps + * on platforms that support vsync aligned refresh drivers / compositors + * Verified true as of Jan 31, 2015: B2G and OS X + * False on Windows 7 + * UNTESTED ON OTHER PLATFORMS + */ +#if defined(MOZ_WIDGET_GONK) || defined(XP_DARWIN) + static TimeStamp FromSystemTime(int64_t aSystemTime) + { + static_assert(sizeof(aSystemTime) == sizeof(TimeStampValue), + "System timestamp should be same units as TimeStampValue"); + return TimeStamp(aSystemTime); + } +#endif + + /** + * Return true if this is the "null" moment + */ + bool IsNull() const { return mValue == 0; } + + /** + * Return true if this is not the "null" moment, may be used in tests, e.g.: + * |if (timestamp) { ... }| + */ + explicit operator bool() const + { + return mValue != 0; + } + + /** + * Return a timestamp reflecting the current elapsed system time. This + * is monotonically increasing (i.e., does not decrease) over the + * lifetime of this process' XPCOM session. + * + * Now() is trying to ensure the best possible precision on each platform, + * at least one millisecond. + * + * NowLoRes() has been introduced to workaround performance problems of + * QueryPerformanceCounter on the Windows platform. NowLoRes() is giving + * lower precision, usually 15.6 ms, but with very good performance benefit. + * Use it for measurements of longer times, like >200ms timeouts. + */ + static TimeStamp Now() { return Now(true); } + static TimeStamp NowLoRes() { return Now(false); } + + /** + * Return a timestamp representing the time when the current process was + * created which will be comparable with other timestamps taken with this + * class. If the actual process creation time is detected to be inconsistent + * the @a aIsInconsistent parameter will be set to true, the returned + * timestamp however will still be valid though inaccurate. + * + * @param aIsInconsistent Set to true if an inconsistency was detected in the + * process creation time + * @returns A timestamp representing the time when the process was created, + * this timestamp is always valid even when errors are reported + */ + static MFBT_API TimeStamp ProcessCreation(bool& aIsInconsistent); + + /** + * Records a process restart. After this call ProcessCreation() will return + * the time when the browser was restarted instead of the actual time when + * the process was created. + */ + static MFBT_API void RecordProcessRestart(); + + /** + * Compute the difference between two timestamps. Both must be non-null. + */ + TimeDuration operator-(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + static_assert(-INT64_MAX > INT64_MIN, "int64_t sanity check"); + int64_t ticks = int64_t(mValue - aOther.mValue); + // Check for overflow. + if (mValue > aOther.mValue) { + if (ticks < 0) { + ticks = INT64_MAX; + } + } else { + if (ticks > 0) { + ticks = INT64_MIN; + } + } + return TimeDuration::FromTicks(ticks); + } + + TimeStamp operator+(const TimeDuration& aOther) const + { + TimeStamp result = *this; + result += aOther; + return result; + } + TimeStamp operator-(const TimeDuration& aOther) const + { + TimeStamp result = *this; + result -= aOther; + return result; + } + TimeStamp& operator+=(const TimeDuration& aOther) + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + TimeStampValue value = mValue + aOther.mValue; + // Check for underflow. + // (We don't check for overflow because it's not obvious what the error + // behavior should be in that case.) + if (aOther.mValue < 0 && value > mValue) { + value = 0; + } + mValue = value; + return *this; + } + TimeStamp& operator-=(const TimeDuration& aOther) + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + TimeStampValue value = mValue - aOther.mValue; + // Check for underflow. + // (We don't check for overflow because it's not obvious what the error + // behavior should be in that case.) + if (aOther.mValue > 0 && value > mValue) { + value = 0; + } + mValue = value; + return *this; + } + + bool operator<(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue < aOther.mValue; + } + bool operator<=(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue <= aOther.mValue; + } + bool operator>=(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue >= aOther.mValue; + } + bool operator>(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue > aOther.mValue; + } + bool operator==(const TimeStamp& aOther) const + { + return IsNull() + ? aOther.IsNull() + : !aOther.IsNull() && mValue == aOther.mValue; + } + bool operator!=(const TimeStamp& aOther) const + { + return !(*this == aOther); + } + + // Comparing TimeStamps for equality should be discouraged. Adding + // two TimeStamps, or scaling TimeStamps, is nonsense and must never + // be allowed. + + static MFBT_API void Startup(); + static MFBT_API void Shutdown(); + +private: + friend struct IPC::ParamTraits<mozilla::TimeStamp>; + friend void StartupTimelineRecordExternal(int, uint64_t); + + MOZ_IMPLICIT TimeStamp(TimeStampValue aValue) : mValue(aValue) {} + + static MFBT_API TimeStamp Now(bool aHighResolution); + + /** + * Computes the uptime of the current process in microseconds. The result + * is platform-dependent and needs to be checked against existing timestamps + * for consistency. + * + * @returns The number of microseconds since the calling process was started + * or 0 if an error was encountered while computing the uptime + */ + static MFBT_API uint64_t ComputeProcessUptime(); + + /** + * When built with PRIntervalTime, a value of 0 means this instance + * is "null". Otherwise, the low 32 bits represent a PRIntervalTime, + * and the high 32 bits represent a counter of the number of + * rollovers of PRIntervalTime that we've seen. This counter starts + * at 1 to avoid a real time colliding with the "null" value. + * + * PR_INTERVAL_MAX is set at 100,000 ticks per second. So the minimum + * time to wrap around is about 2^64/100000 seconds, i.e. about + * 5,849,424 years. + * + * When using a system clock, a value is system dependent. + */ + TimeStampValue mValue; +}; + +} // namespace mozilla + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/TimeStamp_darwin.cpp b/mozglue/misc/TimeStamp_darwin.cpp new file mode 100644 index 000000000..f30bc9846 --- /dev/null +++ b/mozglue/misc/TimeStamp_darwin.cpp @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Implement TimeStamp::Now() with mach_absolute_time +// +// The "tick" unit for mach_absolute_time is defined using mach_timebase_info() which +// gives a conversion ratio to nanoseconds. For more information see Apple's QA1398. +// +// This code is inspired by Chromium's time_mac.cc. The biggest +// differences are that we explicitly initialize using +// TimeStamp::Initialize() instead of lazily in Now() and that +// we store the time value in ticks and convert when needed instead +// of storing the time value in nanoseconds. + +#include <mach/mach_time.h> +#include <sys/time.h> +#include <sys/sysctl.h> +#include <time.h> +#include <unistd.h> + +#include "mozilla/TimeStamp.h" + +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; + +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kUsPerSec = 1000000; +static const double kNsPerMsd = 1000000.0; +static const double kNsPerSecd = 1000000000.0; + +static bool gInitialized = false; +static double sNsPerTick; + +static uint64_t +ClockTime() +{ + // mach_absolute_time is it when it comes to ticks on the Mac. Other calls + // with less precision (such as TickCount) just call through to + // mach_absolute_time. + // + // At the time of writing mach_absolute_time returns the number of nanoseconds + // since boot. This won't overflow 64bits for 500+ years so we aren't going + // to worry about that possiblity + return mach_absolute_time(); +} + +static uint64_t +ClockResolutionNs() +{ + uint64_t start = ClockTime(); + uint64_t end = ClockTime(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTime(); + end = ClockTime(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on NSPR's resolution assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + +namespace mozilla { + +double +BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + return (aTicks * sNsPerTick) / kNsPerSecd; +} + +double +BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return (valueSigDigs * sNsPerTick) / kNsPerSecd; +} + +int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + double result = (aMilliseconds * kNsPerMsd) / sNsPerTick; + if (result > INT64_MAX) { + return INT64_MAX; + } else if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +int64_t +BaseTimeDurationPlatformUtils::ResolutionInTicks() +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + return static_cast<int64_t>(sResolution); +} + +void +TimeStamp::Startup() +{ + if (gInitialized) { + return; + } + + mach_timebase_info_data_t timebaseInfo; + // Apple's QA1398 suggests that the output from mach_timebase_info + // will not change while a program is running, so it should be safe + // to cache the result. + kern_return_t kr = mach_timebase_info(&timebaseInfo); + if (kr != KERN_SUCCESS) { + MOZ_RELEASE_ASSERT(false, "mach_timebase_info failed"); + } + + sNsPerTick = double(timebaseInfo.numer) / timebaseInfo.denom; + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; + !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10); + + gInitialized = true; + + return; +} + +void +TimeStamp::Shutdown() +{ +} + +TimeStamp +TimeStamp::Now(bool aHighResolution) +{ + return TimeStamp(ClockTime()); +} + +// Computes and returns the process uptime in microseconds. +// Returns 0 if an error was encountered. + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + struct timeval tv; + int rv = gettimeofday(&tv, nullptr); + + if (rv == -1) { + return 0; + } + + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), + }; + u_int mibLen = sizeof(mib) / sizeof(mib[0]); + + struct kinfo_proc proc; + size_t bufferSize = sizeof(proc); + rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); + + if (rv == -1) { + return 0; + } + + uint64_t startTime = + ((uint64_t)proc.kp_proc.p_un.__p_starttime.tv_sec * kUsPerSec) + + proc.kp_proc.p_un.__p_starttime.tv_usec; + uint64_t now = (tv.tv_sec * kUsPerSec) + tv.tv_usec; + + if (startTime > now) { + return 0; + } + + return now - startTime; +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_posix.cpp b/mozglue/misc/TimeStamp_posix.cpp new file mode 100644 index 000000000..05deddea4 --- /dev/null +++ b/mozglue/misc/TimeStamp_posix.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Implement TimeStamp::Now() with POSIX clocks. +// +// The "tick" unit for POSIX clocks is simply a nanosecond, as this is +// the smallest unit of time representable by struct timespec. That +// doesn't mean that a nanosecond is the resolution of TimeDurations +// obtained with this API; see TimeDuration::Resolution; +// + +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> +#include <string.h> + +#if defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/param.h> +#include <sys/sysctl.h> +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include <sys/user.h> +#endif + +#if defined(__NetBSD__) +#undef KERN_PROC +#define KERN_PROC KERN_PROC2 +#define KINFO_PROC struct kinfo_proc2 +#else +#define KINFO_PROC struct kinfo_proc +#endif + +#if defined(__DragonFly__) +#define KP_START_SEC kp_start.tv_sec +#define KP_START_USEC kp_start.tv_usec +#elif defined(__FreeBSD__) +#define KP_START_SEC ki_start.tv_sec +#define KP_START_USEC ki_start.tv_usec +#else +#define KP_START_SEC p_ustart_sec +#define KP_START_USEC p_ustart_usec +#endif + +#include "mozilla/Sprintf.h" +#include "mozilla/TimeStamp.h" +#include <pthread.h> + +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; + +static const uint16_t kNsPerUs = 1000; +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kNsPerSec = 1000000000; +static const double kNsPerMsd = 1000000.0; +static const double kNsPerSecd = 1000000000.0; + +static uint64_t +TimespecToNs(const struct timespec& aTs) +{ + uint64_t baseNs = uint64_t(aTs.tv_sec) * kNsPerSec; + return baseNs + uint64_t(aTs.tv_nsec); +} + +static uint64_t +ClockTimeNs() +{ + struct timespec ts; + // this can't fail: we know &ts is valid, and TimeStamp::Startup() + // checks that CLOCK_MONOTONIC is supported (and aborts if not) + clock_gettime(CLOCK_MONOTONIC, &ts); + + // tv_sec is defined to be relative to an arbitrary point in time, + // but it would be madness for that point in time to be earlier than + // the Epoch. So we can safely assume that even if time_t is 32 + // bits, tv_sec won't overflow while the browser is open. Revisit + // this argument if we're still building with 32-bit time_t around + // the year 2037. + return TimespecToNs(ts); +} + +static uint64_t +ClockResolutionNs() +{ + // NB: why not rely on clock_getres()? Two reasons: (i) it might + // lie, and (ii) it might return an "ideal" resolution that while + // theoretically true, could never be measured in practice. Since + // clock_gettime() likely involves a system call on your platform, + // the "actual" timing resolution shouldn't be lower than syscall + // overhead. + + uint64_t start = ClockTimeNs(); + uint64_t end = ClockTimeNs(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTimeNs(); + end = ClockTimeNs(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on clock_getres() + struct timespec ts; + if (0 == clock_getres(CLOCK_MONOTONIC, &ts)) { + minres = TimespecToNs(ts); + } + } + + if (0 == minres) { + // clock_getres probably failed. fall back on NSPR's resolution + // assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + +namespace mozilla { + +double +BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) +{ + return double(aTicks) / kNsPerSecd; +} + +double +BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) +{ + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; +} + +int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) +{ + double result = aMilliseconds * kNsPerMsd; + if (result > INT64_MAX) { + return INT64_MAX; + } else if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +int64_t +BaseTimeDurationPlatformUtils::ResolutionInTicks() +{ + return static_cast<int64_t>(sResolution); +} + +static bool gInitialized = false; + +void +TimeStamp::Startup() +{ + if (gInitialized) { + return; + } + + struct timespec dummy; + if (clock_gettime(CLOCK_MONOTONIC, &dummy) != 0) { + MOZ_CRASH("CLOCK_MONOTONIC is absent!"); + } + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; + !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10); + + gInitialized = true; + + return; +} + +void +TimeStamp::Shutdown() +{ +} + +TimeStamp +TimeStamp::Now(bool aHighResolution) +{ + return TimeStamp(ClockTimeNs()); +} + +#if defined(XP_LINUX) || defined(ANDROID) + +// Calculates the amount of jiffies that have elapsed since boot and up to the +// starttime value of a specific process as found in its /proc/*/stat file. +// Returns 0 if an error occurred. + +static uint64_t +JiffiesSinceBoot(const char* aFile) +{ + char stat[512]; + + FILE* f = fopen(aFile, "r"); + if (!f) { + return 0; + } + + int n = fread(&stat, 1, sizeof(stat) - 1, f); + + fclose(f); + + if (n <= 0) { + return 0; + } + + stat[n] = 0; + + long long unsigned startTime = 0; // instead of uint64_t to keep GCC quiet + char* s = strrchr(stat, ')'); + + if (!s) { + return 0; + } + + int rv = sscanf(s + 2, + "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u " + "%*u %*u %*u %*d %*d %*d %*d %*d %*d %llu", + &startTime); + + if (rv != 1 || !startTime) { + return 0; + } + + return startTime; +} + +// Computes the interval that has elapsed between the thread creation and the +// process creation by comparing the starttime fields in the respective +// /proc/*/stat files. The resulting value will be a good approximation of the +// process uptime. This value will be stored at the address pointed by aTime; +// if an error occurred 0 will be stored instead. + +static void* +ComputeProcessUptimeThread(void* aTime) +{ + uint64_t* uptime = static_cast<uint64_t*>(aTime); + long hz = sysconf(_SC_CLK_TCK); + + *uptime = 0; + + if (!hz) { + return nullptr; + } + + char threadStat[40]; + SprintfLiteral(threadStat, "/proc/self/task/%d/stat", (pid_t)syscall(__NR_gettid)); + + uint64_t threadJiffies = JiffiesSinceBoot(threadStat); + uint64_t selfJiffies = JiffiesSinceBoot("/proc/self/stat"); + + if (!threadJiffies || !selfJiffies) { + return nullptr; + } + + *uptime = ((threadJiffies - selfJiffies) * kNsPerSec) / hz; + return nullptr; +} + +// Computes and returns the process uptime in us on Linux & its derivatives. +// Returns 0 if an error was encountered. + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + uint64_t uptime = 0; + pthread_t uptime_pthread; + + if (pthread_create(&uptime_pthread, nullptr, ComputeProcessUptimeThread, &uptime)) { + MOZ_CRASH("Failed to create process uptime thread."); + return 0; + } + + pthread_join(uptime_pthread, NULL); + + return uptime / kNsPerUs; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) + +// Computes and returns the process uptime in us on various BSD flavors. +// Returns 0 if an error was encountered. + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + struct timespec ts; + int rv = clock_gettime(CLOCK_REALTIME, &ts); + + if (rv == -1) { + return 0; + } + + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +#if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +#endif + }; + u_int mibLen = sizeof(mib) / sizeof(mib[0]); + + KINFO_PROC proc; + size_t bufferSize = sizeof(proc); + rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); + + if (rv == -1) { + return 0; + } + + uint64_t startTime = ((uint64_t)proc.KP_START_SEC * kNsPerSec) + + (proc.KP_START_USEC * kNsPerUs); + uint64_t now = ((uint64_t)ts.tv_sec * kNsPerSec) + ts.tv_nsec; + + if (startTime > now) { + return 0; + } + + return (now - startTime) / kNsPerUs; +} + +#else + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + return 0; +} + +#endif + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp new file mode 100644 index 000000000..cd519affd --- /dev/null +++ b/mozglue/misc/TimeStamp_windows.cpp @@ -0,0 +1,576 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with +// values of GetTickCount(). + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/TimeStamp.h" + +#include <stdio.h> +#include <intrin.h> +#include <windows.h> + +// To enable logging define to your favorite logging API +#define LOG(x) + +class AutoCriticalSection +{ +public: + AutoCriticalSection(LPCRITICAL_SECTION aSection) + : mSection(aSection) + { + ::EnterCriticalSection(mSection); + } + ~AutoCriticalSection() + { + ::LeaveCriticalSection(mSection); + } +private: + LPCRITICAL_SECTION mSection; +}; + +// Estimate of the smallest duration of time we can measure. +static volatile ULONGLONG sResolution; +static volatile ULONGLONG sResolutionSigDigs; +static const double kNsPerSecd = 1000000000.0; +static const LONGLONG kNsPerMillisec = 1000000; + +// ---------------------------------------------------------------------------- +// Global constants +// ---------------------------------------------------------------------------- + +// Tolerance to failures settings. +// +// What is the interval we want to have failure free. +// in [ms] +static const uint32_t kFailureFreeInterval = 5000; +// How many failures we are willing to tolerate in the interval. +static const uint32_t kMaxFailuresPerInterval = 4; +// What is the threshold to treat fluctuations as actual failures. +// in [ms] +static const uint32_t kFailureThreshold = 50; + +// If we are not able to get the value of GTC time increment, use this value +// which is the most usual increment. +static const DWORD kDefaultTimeIncrement = 156001; + +// ---------------------------------------------------------------------------- +// Global variables, not changing at runtime +// ---------------------------------------------------------------------------- + +/** + * The [mt] unit: + * + * Many values are kept in ticks of the Performance Coutner x 1000, + * further just referred as [mt], meaning milli-ticks. + * + * This is needed to preserve maximum precision of the performance frequency + * representation. GetTickCount values in milliseconds are multiplied with + * frequency per second. Therefor we need to multiply QPC value by 1000 to + * have the same units to allow simple arithmentic with both QPC and GTC. + */ + +#define ms2mt(x) ((x) * sFrequencyPerSec) +#define mt2ms(x) ((x) / sFrequencyPerSec) +#define mt2ms_f(x) (double(x) / sFrequencyPerSec) + +// Result of QueryPerformanceFrequency +static LONGLONG sFrequencyPerSec = 0; + +// How much we are tolerant to GTC occasional loose of resoltion. +// This number says how many multiples of the minimal GTC resolution +// detected on the system are acceptable. This number is empirical. +static const LONGLONG kGTCTickLeapTolerance = 4; + +// Base tolerance (more: "inability of detection" range) threshold is calculated +// dynamically, and kept in sGTCResulutionThreshold. +// +// Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - +// (QPC_now - QPC_epoch)) was in [-sGTCResulutionThreshold, sGTCResulutionThreshold] +// interval every time we'd compared two time stamps. +// If not, then we check the overflow behind this basic threshold +// is in kFailureThreshold. If not, we condider it as a QPC failure. If too many +// failures in short time are detected, QPC is considered faulty and disabled. +// +// Kept in [mt] +static LONGLONG sGTCResulutionThreshold; + +// If QPC is found faulty for two stamps in this interval, we engage +// the fault detection algorithm. For duration larger then this limit +// we bypass using durations calculated from QPC when jitter is detected, +// but don't touch the sUseQPC flag. +// +// Value is in [ms]. +static const uint32_t kHardFailureLimit = 2000; +// Conversion to [mt] +static LONGLONG sHardFailureLimit; + +// Conversion of kFailureFreeInterval and kFailureThreshold to [mt] +static LONGLONG sFailureFreeInterval; +static LONGLONG sFailureThreshold; + +// ---------------------------------------------------------------------------- +// Systemm status flags +// ---------------------------------------------------------------------------- + +// Flag for stable TSC that indicates platform where QPC is stable. +static bool sHasStableTSC = false; + +// ---------------------------------------------------------------------------- +// Global state variables, changing at runtime +// ---------------------------------------------------------------------------- + +// Initially true, set to false when QPC is found unstable and never +// returns back to true since that time. +static bool volatile sUseQPC = true; + +// ---------------------------------------------------------------------------- +// Global lock +// ---------------------------------------------------------------------------- + +// Thread spin count before entering the full wait state for sTimeStampLock. +// Inspired by Rob Arnold's work on PRMJ_Now(). +static const DWORD kLockSpinCount = 4096; + +// Common mutex (thanks the relative complexity of the logic, this is better +// then using CMPXCHG8B.) +// It is protecting the globals bellow. +static CRITICAL_SECTION sTimeStampLock; + +// ---------------------------------------------------------------------------- +// Global lock protected variables +// ---------------------------------------------------------------------------- + +// Timestamp in future until QPC must behave correctly. +// Set to now + kFailureFreeInterval on first QPC failure detection. +// Set to now + E * kFailureFreeInterval on following errors, +// where E is number of errors detected during last kFailureFreeInterval +// milliseconds, calculated simply as: +// E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1. +// When E > kMaxFailuresPerInterval -> disable QPC. +// +// Kept in [mt] +static ULONGLONG sFaultIntoleranceCheckpoint = 0; + +// Used only when GetTickCount64 is not available on the platform. +// Last result of GetTickCount call. +// +// Kept in [ms] +static DWORD sLastGTCResult = 0; + +// Higher part of the 64-bit value of MozGetTickCount64, +// incremented atomically. +static DWORD sLastGTCRollover = 0; + +namespace mozilla { + +typedef ULONGLONG (WINAPI* GetTickCount64_t)(); +static GetTickCount64_t sGetTickCount64 = nullptr; + +// Function protecting GetTickCount result from rolling over, +// result is in [ms] +static ULONGLONG WINAPI +MozGetTickCount64() +{ + DWORD GTC = ::GetTickCount(); + + // Cheaper then CMPXCHG8B + AutoCriticalSection lock(&sTimeStampLock); + + // Pull the rollover counter forward only if new value of GTC goes way + // down under the last saved result + if ((sLastGTCResult > GTC) && ((sLastGTCResult - GTC) > (1UL << 30))) { + ++sLastGTCRollover; + } + + sLastGTCResult = GTC; + return ULONGLONG(sLastGTCRollover) << 32 | sLastGTCResult; +} + +// Result is in [mt] +static inline ULONGLONG +PerformanceCounter() +{ + LARGE_INTEGER pc; + ::QueryPerformanceCounter(&pc); + return pc.QuadPart * 1000ULL; +} + +static void +InitThresholds() +{ + DWORD timeAdjustment = 0, timeIncrement = 0; + BOOL timeAdjustmentDisabled; + GetSystemTimeAdjustment(&timeAdjustment, + &timeIncrement, + &timeAdjustmentDisabled); + + LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement)); + + if (!timeIncrement) { + timeIncrement = kDefaultTimeIncrement; + } + + // Ceiling to a millisecond + // Example values: 156001, 210000 + DWORD timeIncrementCeil = timeIncrement; + // Don't want to round up if already rounded, values will be: 156000, 209999 + timeIncrementCeil -= 1; + // Convert to ms, values will be: 15, 20 + timeIncrementCeil /= 10000; + // Round up, values will be: 16, 21 + timeIncrementCeil += 1; + // Convert back to 100ns, values will be: 160000, 210000 + timeIncrementCeil *= 10000; + + // How many milli-ticks has the interval rounded up + LONGLONG ticksPerGetTickCountResolutionCeiling = + (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL; + + // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value. + sGTCResulutionThreshold = + LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling); + + sHardFailureLimit = ms2mt(kHardFailureLimit); + sFailureFreeInterval = ms2mt(kFailureFreeInterval); + sFailureThreshold = ms2mt(kFailureThreshold); +} + +static void +InitResolution() +{ + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + + ULONGLONG minres = ~0ULL; + int loops = 10; + do { + ULONGLONG start = PerformanceCounter(); + ULONGLONG end = PerformanceCounter(); + + ULONGLONG candidate = (end - start); + if (candidate < minres) { + minres = candidate; + } + } while (--loops && minres); + + if (0 == minres) { + minres = 1; + } + + // Converting minres that is in [mt] to nanosecods, multiplicating + // the argument to preserve resolution. + ULONGLONG result = mt2ms(minres * kNsPerMillisec); + if (0 == result) { + result = 1; + } + + sResolution = result; + + // find the number of significant digits in mResolution, for the + // sake of ToSecondsSigDigits() + ULONGLONG sigDigs; + for (sigDigs = 1; + !(sigDigs == result || 10 * sigDigs > result); + sigDigs *= 10); + + sResolutionSigDigs = sigDigs; +} + +// ---------------------------------------------------------------------------- +// TimeStampValue implementation +// ---------------------------------------------------------------------------- +MFBT_API +TimeStampValue::TimeStampValue(ULONGLONG aGTC, ULONGLONG aQPC, bool aHasQPC) + : mGTC(aGTC) + , mQPC(aQPC) + , mHasQPC(aHasQPC) + , mIsNull(false) +{ +} + +MFBT_API TimeStampValue& +TimeStampValue::operator+=(const int64_t aOther) +{ + mGTC += aOther; + mQPC += aOther; + return *this; +} + +MFBT_API TimeStampValue& +TimeStampValue::operator-=(const int64_t aOther) +{ + mGTC -= aOther; + mQPC -= aOther; + return *this; +} + +// If the duration is less then two seconds, perform check of QPC stability +// by comparing both GTC and QPC calculated durations of this and aOther. +MFBT_API uint64_t +TimeStampValue::CheckQPC(const TimeStampValue& aOther) const +{ + uint64_t deltaGTC = mGTC - aOther.mGTC; + + if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC + return deltaGTC; + } + + uint64_t deltaQPC = mQPC - aOther.mQPC; + + if (sHasStableTSC) { // For stable TSC there is no need to check + return deltaQPC; + } + + // Check QPC is sane before using it. + int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC)); + if (diff <= sGTCResulutionThreshold) { + return deltaQPC; + } + + // Treat absolutely for calibration purposes + int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); + int64_t overflow = diff - sGTCResulutionThreshold; + + LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms", + mt2ms(duration), mt2ms_f(overflow))); + + if (overflow <= sFailureThreshold) { // We are in the limit, let go. + return deltaQPC; + } + + // QPC deviates, don't use it, since now this method may only return deltaGTC. + + if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance algorithm. + return deltaGTC; + } + + LOG(("TimeStamp: QPC jittered over failure threshold")); + + if (duration < sHardFailureLimit) { + // Interval between the two time stamps is very short, consider + // QPC as unstable and record a failure. + uint64_t now = ms2mt(sGetTickCount64()); + + AutoCriticalSection lock(&sTimeStampLock); + + if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) { + // There's already been an error in the last fault intollerant interval. + // Time since now to the checkpoint actually holds information on how many + // failures there were in the failure free interval we have defined. + uint64_t failureCount = + (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) / + sFailureFreeInterval; + if (failureCount > kMaxFailuresPerInterval) { + sUseQPC = false; + LOG(("TimeStamp: QPC disabled")); + } else { + // Move the fault intolerance checkpoint more to the future, prolong it + // to reflect the number of detected failures. + ++failureCount; + sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval; + LOG(("TimeStamp: recording %dth QPC failure", failureCount)); + } + } else { + // Setup fault intolerance checkpoint in the future for first detected error. + sFaultIntoleranceCheckpoint = now + sFailureFreeInterval; + LOG(("TimeStamp: recording 1st QPC failure")); + } + } + + return deltaGTC; +} + +MFBT_API uint64_t +TimeStampValue::operator-(const TimeStampValue& aOther) const +{ + if (mIsNull && aOther.mIsNull) { + return uint64_t(0); + } + + return CheckQPC(aOther); +} + +// ---------------------------------------------------------------------------- +// TimeDuration and TimeStamp implementation +// ---------------------------------------------------------------------------- + +MFBT_API double +BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) +{ + // Converting before arithmetic avoids blocked store forward + return double(aTicks) / (double(sFrequencyPerSec) * 1000.0); +} + +MFBT_API double +BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) +{ + // don't report a value < mResolution ... + LONGLONG resolution = sResolution; + LONGLONG resolutionSigDigs = sResolutionSigDigs; + LONGLONG valueSigDigs = resolution * (aTicks / resolution); + // and chop off insignificant digits + valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; +} + +MFBT_API int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) +{ + double result = ms2mt(aMilliseconds); + if (result > INT64_MAX) { + return INT64_MAX; + } else if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +MFBT_API int64_t +BaseTimeDurationPlatformUtils::ResolutionInTicks() +{ + return static_cast<int64_t>(sResolution); +} + +static bool +HasStableTSC() +{ + union + { + int regs[4]; + struct + { + int nIds; + char cpuString[12]; + }; + } cpuInfo; + + __cpuid(cpuInfo.regs, 0); + // Only allow Intel CPUs for now + // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the + // string so that we can compare in one go. + if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", + sizeof(cpuInfo.cpuString))) { + return false; + } + + int regs[4]; + + // detect if the Advanced Power Management feature is supported + __cpuid(regs, 0x80000000); + if (regs[0] < 0x80000007) { + return false; + } + + __cpuid(regs, 0x80000007); + // if bit 8 is set than TSC will run at a constant rate + // in all ACPI P-state, C-states and T-states + return regs[3] & (1 << 8); +} + +static bool gInitialized = false; + +MFBT_API void +TimeStamp::Startup() +{ + if (gInitialized) { + return; + } + + gInitialized = true; + + // Decide which implementation to use for the high-performance timer. + + HMODULE kernelDLL = GetModuleHandleW(L"kernel32.dll"); + sGetTickCount64 = reinterpret_cast<GetTickCount64_t>( + GetProcAddress(kernelDLL, "GetTickCount64")); + if (!sGetTickCount64) { + // If the platform does not support the GetTickCount64 (Windows XP doesn't), + // then use our fallback implementation based on GetTickCount. + sGetTickCount64 = MozGetTickCount64; + } + + InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); + + sHasStableTSC = HasStableTSC(); + LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); + + LARGE_INTEGER freq; + sUseQPC = ::QueryPerformanceFrequency(&freq); + if (!sUseQPC) { + // No Performance Counter. Fall back to use GetTickCount. + InitResolution(); + + LOG(("TimeStamp: using GetTickCount")); + return; + } + + sFrequencyPerSec = freq.QuadPart; + LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); + + InitThresholds(); + InitResolution(); + + return; +} + +MFBT_API void +TimeStamp::Shutdown() +{ + DeleteCriticalSection(&sTimeStampLock); +} + +MFBT_API TimeStamp +TimeStamp::Now(bool aHighResolution) +{ + // sUseQPC is volatile + bool useQPC = (aHighResolution && sUseQPC); + + // Both values are in [mt] units. + ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0); + ULONGLONG GTC = ms2mt(sGetTickCount64()); + return TimeStamp(TimeStampValue(GTC, QPC, useQPC)); +} + +// Computes and returns the process uptime in microseconds. +// Returns 0 if an error was encountered. + +MFBT_API uint64_t +TimeStamp::ComputeProcessUptime() +{ + SYSTEMTIME nowSys; + GetSystemTime(&nowSys); + + FILETIME now; + bool success = SystemTimeToFileTime(&nowSys, &now); + + if (!success) { + return 0; + } + + FILETIME start, foo, bar, baz; + success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz); + + if (!success) { + return 0; + } + + ULARGE_INTEGER startUsec = {{ + start.dwLowDateTime, + start.dwHighDateTime + }}; + ULARGE_INTEGER nowUsec = {{ + now.dwLowDateTime, + now.dwHighDateTime + }}; + + return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL; +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_windows.h b/mozglue/misc/TimeStamp_windows.h new file mode 100644 index 000000000..c87d34efc --- /dev/null +++ b/mozglue/misc/TimeStamp_windows.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimeStamp_windows_h +#define mozilla_TimeStamp_windows_h + +#include "mozilla/Types.h" + +namespace mozilla { + +class TimeStamp; + +class TimeStampValue +{ + friend struct IPC::ParamTraits<mozilla::TimeStampValue>; + friend class TimeStamp; + friend void StartupTimelineRecordExternal(int, uint64_t); + + // Both QPC and GTC are kept in [mt] units. + uint64_t mGTC; + uint64_t mQPC; + bool mHasQPC; + bool mIsNull; + + MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC); + + MFBT_API uint64_t CheckQPC(const TimeStampValue& aOther) const; + + struct _SomethingVeryRandomHere; + constexpr TimeStampValue(_SomethingVeryRandomHere* aNullValue) + : mGTC(0) + , mQPC(0) + , mHasQPC(false) + , mIsNull(true) + { + } + +public: + MFBT_API uint64_t operator-(const TimeStampValue& aOther) const; + + TimeStampValue operator+(const int64_t aOther) const + { + return TimeStampValue(mGTC + aOther, mQPC + aOther, mHasQPC); + } + TimeStampValue operator-(const int64_t aOther) const + { + return TimeStampValue(mGTC - aOther, mQPC - aOther, mHasQPC); + } + MFBT_API TimeStampValue& operator+=(const int64_t aOther); + MFBT_API TimeStampValue& operator-=(const int64_t aOther); + + bool operator<(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) < 0; + } + bool operator>(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) > 0; + } + bool operator<=(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) <= 0; + } + bool operator>=(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) >= 0; + } + bool operator==(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) == 0; + } + bool operator!=(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) != 0; + } +}; + +} + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build new file mode 100644 index 000000000..9c2ef399c --- /dev/null +++ b/mozglue/misc/moz.build @@ -0,0 +1,37 @@ +FINAL_LIBRARY = 'mozglue' + +EXPORTS.mozilla += [ + 'StackWalk.h', + 'TimeStamp.h', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS.mozilla += [ + 'StackWalk_windows.h', + 'TimeStamp_windows.h', + ] + +SOURCES += [ + 'StackWalk.cpp', + 'TimeStamp.cpp', +] + +OS_LIBS += CONFIG['REALTIME_LIBS'] + +DEFINES['IMPL_MFBT'] = True + +if CONFIG['OS_ARCH'] == 'WINNT': + SOURCES += [ + 'TimeStamp_windows.cpp', + ] + OS_LIBS += ['dbghelp'] +elif CONFIG['HAVE_CLOCK_MONOTONIC']: + SOURCES += [ + 'TimeStamp_posix.cpp', + ] +elif CONFIG['OS_ARCH'] == 'Darwin': + SOURCES += [ + 'TimeStamp_darwin.cpp', + ] +elif CONFIG['COMPILE_ENVIRONMENT']: + error('No TimeStamp implementation on this platform. Build will not succeed') |