diff options
Diffstat (limited to 'thirdparty/GetTimeSinceProcessStart/GetTimeSinceProcessStart.h')
| -rw-r--r-- | thirdparty/GetTimeSinceProcessStart/GetTimeSinceProcessStart.h | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/thirdparty/GetTimeSinceProcessStart/GetTimeSinceProcessStart.h b/thirdparty/GetTimeSinceProcessStart/GetTimeSinceProcessStart.h new file mode 100644 index 000000000..86e700b23 --- /dev/null +++ b/thirdparty/GetTimeSinceProcessStart/GetTimeSinceProcessStart.h @@ -0,0 +1,245 @@ +/* Copyright 2024 Max Liani + SPDX-License-Identifier: MIT + + GetTimeSinceProcessStart v1.0.0 + Single header implementation of a function to determine the time in seconds that + passed since the start of the current process. This differs from measuring time + starting at main() because GetTimeSinceProcessStart() accounts for computations + performed after the process creation, including OS loader activities, standard + library initialization, memory allocation, DLL/DSO loading, and static symbol + construction. + This library is primarily designed to be called at the very first line of main() + to measure the time spent initializing the process.. After that, add that time + to other time measurement you retrieve using your favorite timer implementation. + Do not use this library as a general purpose timer. + + Intended audience: Developers focused on high-performance software who want to + benchmark and optimize process startup times, for precise execution statistics + reporting measured from the program itself. + Typically, the process startup time is a few milliseconds quick, but sometimes + your code, or some library, executes complex initialization, like the allocation + of memory pools, or the computation of tables, or complex global objects... + You don't want performance degradation to creep into the init of the executable, + unaccounted for. Measure the initialization time, keep a record of it, benchmark + it as your program evolves. If the measurement changes over time, profile the + program form the start with your sampling profiler of choice, bisect the changes + and figure out what can be done about it. + + GetTimeSinceProcessStart currently has specific implementations for the main OSs + including: Windows, Linux and Mac OSX. It can be called from C and C++ modules. + + To use this library, declare "#define GTSPS_IMPLEMENTATION" in a single C or C++ + file before including this header to generate the implementation. + + // Example: Including and using the library in a C++ file + #include <stdio.h> + #define GTSPS_IMPLEMENTATION + #include "GetTimeSinceProcessStart.h" + + In a C++ project you can place the content of the library in a namespace of your + choice: + #define GTSPS_NAMESPACE YourCoolNamespace + + The implementation outputs any error to stderr using fprintf, but if you prefer + to disable any logging define the following (the library returns 0.0 in case of + error): + #define GTSPS_DONT_LOG_ERRORS + + If instead you prefer to divert the errors logging to something else: + #define GTSPS_LOG_ERROR(str) YourCoolLoggingFunction(str) + The default definition is: + #define GTSPS_LOG_ERROR(str) fprintf(stderr, str) + + To test if the library works, temporarily insert some known wait time in the + creation of a global symbol and compare against a normal run. For example: + + #define GTSPS_IMPLEMENTATION + #include "GetTimeSinceProcessStart.h" + + #warning remove me!!! + int sooooTiredAlready = usleep(5000000); //< Sleep fot 5 seconds + + int main() { + double timeInSeconds = GetTimeSinceProcessStart(); + printf("Startup time %f seconds\n", timeInSeconds); + + [...] + } + + Beware the first measurement after recompiling an executable tends to be longer, + due to caching, security scanning, and other OS checks. So, run the test several + times. +*/ + +#pragma once + +#ifdef GTSPS_NAMESPACE +#define GTSPS_NAMESPACE_BEGIN namespace GTSPS_NAMESPACE { +#define GTSPS_NAMESPACE_END } +#else +#define GTSPS_NAMESPACE_BEGIN +#define GTSPS_NAMESPACE_END +#endif + +#ifndef GTSPS_IMPLEMENTATION +GTSPS_NAMESPACE_BEGIN + +// @brief Measures the time passed since the process start, this accounts for any +// time spends loading and configuring the address space, global symbols, +// the standard library and any other library the executable links against. +// Call this as you first line of main(...) to get the exact measurement +// up to that point. Do not use this as a general-purpose timer, because +// the system calls required to obtain the measurement can be slower than +// the alternatives. +// +// @return time in seconds, or 0.0 in case of error. +double GetTimeSinceProcessStart(); + +GTSPS_NAMESPACE_END + +#else +/////////////////////////////////////////////////////////////////////////////// +// Implementation + +#if defined(_WIN32) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include <Windows.h> +#elif defined(linux) || defined(__linux__) || defined(__LINUX__) +# include <unistd.h> //< for sysconf() +# include <stdio.h> +# include <stdint.h> +#elif defined(__APPLE__) || defined(MACOSX) || defined(__MACOSX__) +# include <unistd.h> //< for getpid() +# include <libproc.h> +#endif + +#ifdef GTSPS_DONT_LOG_ERRORS +# define GTSPS_LOG_ERROR(str) +#elif ! defined(GTSPS_LOG_ERROR) +# define GTSPS_LOG_ERROR(str) fprintf(stderr, str) +# include <stdio.h> +#endif + +GTSPS_NAMESPACE_BEGIN + +double GetTimeSinceProcessStart() +{ +#if defined(_WIN32) + double startTime = 0.0; + { + FILETIME creationTime, exitTime, kernelTime, userTime; + if (!GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) + { + GTSPS_LOG_ERROR("Error: Failed to call GetProcessTimes\n"); + return 0.0; + } + + ULARGE_INTEGER converter; + converter.LowPart = creationTime.dwLowDateTime; + converter.HighPart = creationTime.dwHighDateTime; + startTime = (double)converter.QuadPart / 10000000.0; // Convert 100-ns intervals to seconds + } + + double currentTime = 0.0; + { + FILETIME systemTime; + GetSystemTimeAsFileTime(&systemTime); // Current time + + ULARGE_INTEGER converter; + converter.LowPart = systemTime.dwLowDateTime; + converter.HighPart = systemTime.dwHighDateTime; + currentTime = (double)converter.QuadPart / 10000000.0; // Convert 100-ns intervals to seconds + } + + double timeInSeconds = currentTime - startTime; + return timeInSeconds; + +#elif defined(linux) || defined(__linux__) || defined(__LINUX__) + double processStartTimeInSeconds = 0.0; + if (FILE* file = fopen("/proc/self/stat", "r")) + { + // Start time is the 22nd field (https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html) + uint64_t startTime = 0; + int decoded = fscanf(file, "%*s (%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s " + "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %zu", + &startTime); + fclose(file); + if (decoded != 1) + { + GTSPS_LOG_ERROR("Error: Failed decoding /proc/self/stat.\n"); + return 0.0; + } + + // Thypically this is 100Hz, it has nothing to do with CPU clock, rather with + // interrupts and how the OS probes the process and increment timers. It gives + // this measurement a resolution of 10 ms. + double clockTicksPerSecond = (double)sysconf(_SC_CLK_TCK); + + // Read start time in clock ticks (since kernel start time), convert it to seconds + processStartTimeInSeconds = (double)startTime / clockTicksPerSecond; + } + else + { + GTSPS_LOG_ERROR("Error: Failed to open /proc/self/stat.\n"); + return 0.0; + } + + double kernelUpTimeInSeconds = 0.0; + if (FILE* file = fopen("/proc/uptime", "r")) + { + // Total kernel uptime in seconds is the first field + int decoded = fscanf(file, "%lf", &kernelUpTimeInSeconds); + fclose(file); + if (decoded != 1) + { + GTSPS_LOG_ERROR("Error: Failed decoding /proc/uptime.\n"); + return 0.0; + } + } + else + { + GTSPS_LOG_ERROR("Error: Failed to open /proc/uptime.\n"); + return 0.0; + } + + double timeInSeconds = kernelUpTimeInSeconds - processStartTimeInSeconds; + return timeInSeconds; + +#elif defined(__APPLE__) || defined(MACOSX) || defined(__MACOSX__) + struct timeval startTime = {}; + { + // Get current process info, including the startup time + pid_t pid = getpid(); + struct proc_bsdinfo task_info = {}; + if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &task_info, sizeof(task_info)) <= 0) + { + GTSPS_LOG_ERROR("Error: proc_pidinfo failed"); + return 0.0; + } + startTime.tv_sec = task_info.pbi_start_tvsec; + startTime.tv_usec = task_info.pbi_start_tvusec; + } + + struct timeval currentTime = {}; + gettimeofday(¤tTime, NULL); + + double timeInSeconds = (double)(currentTime.tv_sec - startTime.tv_sec ) + //< Seconds + (double)(currentTime.tv_usec - startTime.tv_usec) / 1000000.0; //< Microseconds + return timeInSeconds; +#else + #warning unsupported platform + return 0.0; +#endif +} + +GTSPS_NAMESPACE_END +#undef GTSPS_NAMESPACE_BEGIN +#undef GTSPS_NAMESPACE_END +#undef GTSPS_LOG_ERROR + +#endif |