Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : /*
24 : * platform-independent high resolution timer
25 : */
26 :
27 : #include "precompiled.h"
28 : #include "lib/timer.h"
29 :
30 : #include <cfloat>
31 : #include <cmath>
32 : #include <cstdarg>
33 : #include <mutex>
34 : #include <numeric>
35 : #include <sstream> // std::stringstream
36 :
37 : #include "lib/module_init.h"
38 : #include "lib/posix/posix_time.h"
39 : #include "lib/sysdep/cpu.h"
40 :
41 : #if OS_WIN
42 : #include "lib/sysdep/os/win/win.h"
43 : #endif
44 : #if OS_UNIX
45 : # include <unistd.h>
46 : #endif
47 :
48 : #if OS_UNIX || OS_WIN
49 : # define HAVE_GETTIMEOFDAY 1
50 : #else
51 : # define HAVE_GETTIMEOFDAY 0
52 : #endif
53 :
54 : #if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0)
55 : # define HAVE_CLOCK_GETTIME 1
56 : #else
57 : # define HAVE_CLOCK_GETTIME 0
58 : #endif
59 :
60 : // rationale for wrapping gettimeofday and clock_gettime, instead of just
61 : // emulating them where not available: allows returning higher-resolution
62 : // timer values than their us / ns interface, via double [seconds].
63 : // they're also not guaranteed to be monotonic.
64 :
65 : #if OS_WIN
66 : static LARGE_INTEGER start;
67 : #elif HAVE_CLOCK_GETTIME
68 : static struct timespec start;
69 : #elif HAVE_GETTIMEOFDAY
70 : static struct timeval start;
71 : #endif
72 :
73 :
74 : //-----------------------------------------------------------------------------
75 : // timer API
76 :
77 :
78 : // Cached because the default implementation may take several milliseconds.
79 : static double resolution;
80 1 : static Status InitResolution()
81 : {
82 : #if OS_WIN
83 : LARGE_INTEGER frequency;
84 : ENSURE(QueryPerformanceFrequency(&frequency));
85 : resolution = 1.0 / static_cast<double>(frequency.QuadPart);
86 : #elif HAVE_CLOCK_GETTIME
87 : struct timespec ts;
88 1 : if (clock_getres(CLOCK_MONOTONIC, &ts) == 0)
89 1 : resolution = ts.tv_nsec * 1e-9;
90 : else
91 0 : resolution = 1e-9;
92 : #elif HAVE_GETTIMEOFDAY
93 : resolution = 1e-6;
94 : #else
95 : const double t0 = timer_Time();
96 : double t1, t2;
97 : do t1 = timer_Time(); while (t1 == t0);
98 : do t2 = timer_Time(); while (t2 == t1);
99 : resolution = t2 - t1;
100 : #endif
101 1 : return INFO::OK;
102 : }
103 :
104 1 : void timer_Init()
105 : {
106 : #if OS_WIN
107 : ENSURE(QueryPerformanceCounter(&start));
108 : #elif HAVE_CLOCK_GETTIME
109 1 : ENSURE(clock_gettime(CLOCK_MONOTONIC, &start) == 0);
110 : #elif HAVE_GETTIMEOFDAY
111 : ENSURE(gettimeofday(&start, 0) == 0);
112 : #endif
113 :
114 : static ModuleInitState initState;
115 1 : ModuleInit(&initState, InitResolution);
116 1 : ENSURE(resolution != 0.0);
117 1 : }
118 :
119 : static std::mutex ensure_monotonic_mutex;
120 : // NB: does not guarantee strict monotonicity - callers must avoid
121 : // dividing by the difference of two equal times.
122 2014 : static void EnsureMonotonic(double& newTime)
123 : {
124 4028 : std::lock_guard<std::mutex> lock(ensure_monotonic_mutex);
125 : static double maxTime;
126 2014 : maxTime = std::max(maxTime, newTime);
127 2014 : newTime = maxTime;
128 2014 : }
129 :
130 2014 : double timer_Time()
131 : {
132 : double t;
133 :
134 : #if OS_WIN
135 : ENSURE(start.QuadPart); // must have called timer_LatchStartTime first
136 : LARGE_INTEGER now;
137 : ENSURE(QueryPerformanceCounter(&now));
138 : t = static_cast<double>(now.QuadPart - start.QuadPart) * resolution;
139 : #elif HAVE_CLOCK_GETTIME
140 2014 : ENSURE(start.tv_sec || start.tv_nsec); // must have called timer_LatchStartTime first
141 : struct timespec cur;
142 2014 : ENSURE(clock_gettime(CLOCK_MONOTONIC, &cur) == 0);
143 2014 : t = (cur.tv_sec - start.tv_sec) + (cur.tv_nsec - start.tv_nsec) * resolution;
144 : #elif HAVE_GETTIMEOFDAY
145 : ENSURE(start.tv_sec || start.tv_usec); // must have called timer_LatchStartTime first
146 : struct timeval cur;
147 : ENSURE(gettimeofday(&cur, 0) == 0);
148 : t = (cur.tv_sec - start.tv_sec) + (cur.tv_usec - start.tv_usec) * resolution;
149 : #else
150 : # error "timer_Time: add timer implementation for this platform!"
151 : #endif
152 :
153 2014 : EnsureMonotonic(t);
154 2014 : return t;
155 : }
156 :
157 :
158 0 : double timer_Resolution()
159 : {
160 0 : return resolution;
161 : }
162 :
163 :
164 : //-----------------------------------------------------------------------------
165 : // client API
166 :
167 : // intrusive linked-list of all clients. a fixed-size limit would be
168 : // acceptable (since timers are added manually), but the list is easy
169 : // to implement and only has the drawback of exposing TimerClient to users.
170 : //
171 : // do not use std::list et al. for this! we must be callable at any time,
172 : // especially before NLSO ctors run or before heap init.
173 : static size_t numClients;
174 : static TimerClient* clients;
175 :
176 :
177 7 : TimerClient* timer_AddClient(TimerClient* tc, const wchar_t* description)
178 : {
179 7 : tc->sum.SetToZero();
180 :
181 7 : tc->description = description;
182 :
183 : // insert at front of list
184 7 : tc->next = clients;
185 7 : clients = tc;
186 7 : numClients++;
187 :
188 7 : return tc;
189 : }
190 :
191 :
192 0 : void timer_DisplayClientTotals()
193 : {
194 0 : debug_printf("TIMER TOTALS (%lu clients)\n", (unsigned long)numClients);
195 0 : debug_printf("-----------------------------------------------------\n");
196 :
197 0 : for(TimerClient* tc = clients; tc; tc = tc->next)
198 : {
199 0 : const std::string duration = tc->sum.ToString();
200 0 : debug_printf(" %s: %s (%lux)\n", utf8_from_wstring(tc->description).c_str(), duration.c_str(), (unsigned long)tc->num_calls);
201 : }
202 :
203 0 : debug_printf("-----------------------------------------------------\n");
204 0 : }
205 :
206 :
207 : //-----------------------------------------------------------------------------
208 :
209 20 : std::string StringForSeconds(double seconds)
210 : {
211 20 : double scale = 1e6;
212 20 : const char* unit = " us";
213 20 : if(seconds > 1.0)
214 0 : scale = 1, unit = " s";
215 20 : else if(seconds > 1e-3)
216 11 : scale = 1e3, unit = " ms";
217 :
218 40 : std::stringstream ss;
219 20 : ss << seconds*scale;
220 20 : ss << unit;
221 40 : return ss.str();
222 : }
223 :
224 :
225 0 : std::string StringForCycles(Cycles cycles)
226 : {
227 0 : double scale = 1.0;
228 0 : const char* unit = " c";
229 0 : if(cycles > 10000000000LL) // 10 Gc
230 0 : scale = 1e-9, unit = " Gc";
231 0 : else if(cycles > 10000000) // 10 Mc
232 0 : scale = 1e-6, unit = " Mc";
233 0 : else if(cycles > 10000) // 10 kc
234 0 : scale = 1e-3, unit = " kc";
235 :
236 0 : std::stringstream ss;
237 0 : ss << cycles*scale;
238 0 : ss << unit;
239 0 : return ss.str();
240 3 : }
|