1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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
|