Line data Source code
1 : /*
2 : * Copyright (c) 2015 Cisco and/or its affiliates.
3 : * Licensed under the Apache License, Version 2.0 (the "License");
4 : * you may not use this file except in compliance with the License.
5 : * You may obtain a copy of the License at:
6 : *
7 : * http://www.apache.org/licenses/LICENSE-2.0
8 : *
9 : * Unless required by applicable law or agreed to in writing, software
10 : * distributed under the License is distributed on an "AS IS" BASIS,
11 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : * See the License for the specific language governing permissions and
13 : * limitations under the License.
14 : */
15 : /*
16 : Copyright (c) 2005 Eliot Dresselhaus
17 :
18 : Permission is hereby granted, free of charge, to any person obtaining
19 : a copy of this software and associated documentation files (the
20 : "Software"), to deal in the Software without restriction, including
21 : without limitation the rights to use, copy, modify, merge, publish,
22 : distribute, sublicense, and/or sell copies of the Software, and to
23 : permit persons to whom the Software is furnished to do so, subject to
24 : the following conditions:
25 :
26 : The above copyright notice and this permission notice shall be
27 : included in all copies or substantial portions of the Software.
28 :
29 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 : EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 : MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 : NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 : OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 : WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 : */
37 :
38 : #include <vppinfra/os.h>
39 : #include <vppinfra/time.h>
40 : #include <vppinfra/format.h>
41 : #include <vppinfra/cpu.h>
42 : #include <math.h>
43 :
44 : #ifdef CLIB_UNIX
45 :
46 : #include <math.h>
47 : #include <sys/time.h>
48 : #include <fcntl.h>
49 :
50 : /* Not very accurate way of determining cpu clock frequency
51 : for unix. Better to use /proc/cpuinfo on linux. */
52 : static f64
53 0 : estimate_clock_frequency (f64 sample_time)
54 : {
55 : f64 time_now, time_start, time_limit, freq;
56 : u64 t[2];
57 :
58 0 : time_start = time_now = unix_time_now ();
59 0 : time_limit = time_now + sample_time;
60 0 : t[0] = clib_cpu_time_now ();
61 0 : while (time_now < time_limit)
62 0 : time_now = unix_time_now ();
63 0 : t[1] = clib_cpu_time_now ();
64 :
65 0 : freq = (t[1] - t[0]) / (time_now - time_start);
66 :
67 0 : return freq;
68 : }
69 :
70 : /* Fetch cpu frequency via parseing /proc/cpuinfo.
71 : Only works for Linux. */
72 : static f64
73 0 : clock_frequency_from_proc_filesystem (void)
74 : {
75 0 : f64 cpu_freq = 1e9; /* better than 40... */
76 0 : f64 ppc_timebase = 0; /* warnings be gone */
77 : int fd;
78 : unformat_input_t input;
79 :
80 : /* $$$$ aarch64 kernel doesn't report "cpu MHz" */
81 : #if defined(__aarch64__)
82 : return 0.0;
83 : #endif
84 :
85 0 : cpu_freq = 0;
86 0 : fd = open ("/proc/cpuinfo", 0);
87 0 : if (fd < 0)
88 0 : return cpu_freq;
89 :
90 0 : unformat_init_clib_file (&input, fd);
91 :
92 0 : ppc_timebase = 0;
93 0 : while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
94 : {
95 0 : if (unformat (&input, "cpu MHz : %f", &cpu_freq))
96 0 : cpu_freq *= 1e6;
97 0 : else if (unformat (&input, "timebase : %f", &ppc_timebase))
98 : ;
99 : else
100 0 : unformat_skip_line (&input);
101 : }
102 :
103 0 : unformat_free (&input);
104 :
105 0 : close (fd);
106 :
107 : /* Override CPU frequency with time base for PPC. */
108 0 : if (ppc_timebase != 0)
109 0 : cpu_freq = ppc_timebase;
110 :
111 0 : return cpu_freq;
112 : }
113 :
114 : /* Fetch cpu frequency via reading /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
115 : Only works for Linux. */
116 : static f64
117 0 : clock_frequency_from_sys_filesystem (void)
118 : {
119 0 : f64 cpu_freq = 0.0;
120 : int fd;
121 : unformat_input_t input;
122 :
123 : /* Time stamp always runs at max frequency. */
124 0 : cpu_freq = 0;
125 0 : fd = open ("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", 0);
126 0 : if (fd < 0)
127 0 : goto done;
128 :
129 0 : unformat_init_clib_file (&input, fd);
130 0 : (void) unformat (&input, "%f", &cpu_freq);
131 0 : cpu_freq *= 1e3; /* measured in kHz */
132 0 : unformat_free (&input);
133 0 : close (fd);
134 0 : done:
135 0 : return cpu_freq;
136 : }
137 :
138 : __clib_export f64
139 1879 : os_cpu_clock_frequency (void)
140 : {
141 : #if defined (__aarch64__)
142 : /* The system counter increments at a fixed frequency. It is distributed
143 : * to each core which has registers for reading the current counter value
144 : * as well as the clock frequency. The system counter is not clocked at
145 : * the same frequency as the core. */
146 : u64 hz;
147 : asm volatile ("mrs %0, cntfrq_el0":"=r" (hz));
148 : return (f64) hz;
149 : #endif
150 : f64 cpu_freq;
151 :
152 : #ifdef __x86_64__
153 1879 : u32 __clib_unused eax = 0, ebx = 0, ecx = 0, edx = 0;
154 1879 : clib_get_cpuid (0x00, &eax, &ebx, &ecx, &edx);
155 1881 : if (eax >= 0x15)
156 : {
157 1881 : u32 max_leaf = eax;
158 : /*
159 : CPUID Leaf 0x15 - Time Stamp Counter and Nominal Core Crystal Clock Info
160 : eax - denominator of the TSC/”core crystal clock” ratio
161 : ebx - numerator of the TSC/”core crystal clock” ratio
162 : ecx - nominal frequency of the core crystal clock in Hz
163 : edx - reseved
164 : */
165 :
166 1881 : clib_get_cpuid (0x15, &eax, &ebx, &ecx, &edx);
167 1881 : if (ebx && ecx)
168 0 : return (u64) ecx *ebx / eax;
169 :
170 1881 : if (max_leaf >= 0x16)
171 : {
172 : /*
173 : CPUID Leaf 0x16 - Processor Frequency Information Leaf
174 : eax - Bits 15 - 00: Processor Base Frequency (in MHz).
175 : */
176 :
177 1881 : clib_get_cpuid (0x16, &eax, &ebx, &ecx, &edx);
178 1883 : if (eax)
179 1883 : return 1e6 * (eax & 0xffff);
180 : }
181 : }
182 : #endif
183 :
184 : /* If we have an invariant TSC, use it to estimate the clock frequency */
185 0 : if (clib_cpu_supports_invariant_tsc ())
186 0 : return estimate_clock_frequency (1e-3);
187 :
188 : /* Next, try /sys version. */
189 0 : cpu_freq = clock_frequency_from_sys_filesystem ();
190 0 : if (cpu_freq != 0)
191 0 : return cpu_freq;
192 :
193 : /* Next try /proc version. */
194 0 : cpu_freq = clock_frequency_from_proc_filesystem ();
195 0 : if (cpu_freq != 0)
196 0 : return cpu_freq;
197 :
198 : /* If /proc/cpuinfo fails (e.g. not running on Linux) fall back to
199 : gettimeofday based estimated clock frequency. */
200 0 : return estimate_clock_frequency (1e-3);
201 : }
202 :
203 : #endif /* CLIB_UNIX */
204 :
205 : /* Initialize time. */
206 : __clib_export void
207 1839 : clib_time_init (clib_time_t * c)
208 : {
209 1839 : clib_memset (c, 0, sizeof (c[0]));
210 1836 : c->clocks_per_second = os_cpu_clock_frequency ();
211 : /*
212 : * Sporadic reports of os_cpu_clock_frequency() returning 0.0
213 : * in highly parallel container environments.
214 : * To avoid immediate division by zero:
215 : * Step 1: try estimate_clock_frequency().
216 : * Step 2: give up. Pretend we have a 2gHz clock.
217 : */
218 1841 : if (PREDICT_FALSE (c->clocks_per_second == 0.0))
219 : {
220 0 : c->clocks_per_second = estimate_clock_frequency (1e-3);
221 0 : if (c->clocks_per_second == 0.0)
222 : {
223 0 : clib_warning ("os_cpu_clock_frequency() returned 0.0, use 2e9...");
224 0 : c->clocks_per_second = 2e9;
225 : }
226 : }
227 1841 : c->seconds_per_clock = 1 / c->clocks_per_second;
228 1841 : c->log2_clocks_per_second = min_log2_u64 ((u64) c->clocks_per_second);
229 :
230 : /* Verify frequency every 16 sec */
231 1840 : c->log2_clocks_per_frequency_verify = c->log2_clocks_per_second + 4;
232 :
233 1840 : c->last_verify_reference_time = unix_time_now ();
234 1840 : c->init_reference_time = c->last_verify_reference_time;
235 1840 : c->last_cpu_time = clib_cpu_time_now ();
236 1839 : c->init_cpu_time = c->last_verify_cpu_time = c->last_cpu_time;
237 1839 : c->total_cpu_time = 0ULL;
238 :
239 : /*
240 : * Use exponential smoothing, with a half-life of 1 minute
241 : * reported_rate(t) = reported_rate(t-1) * K + rate(t)*(1-K)
242 : * where K = e**(-1.0/3.75);
243 : * 15 samples in 4 minutes
244 : * 7.5 samples in 2 minutes,
245 : * 3.75 samples in 1 minute, etc.
246 : */
247 1839 : c->damping_constant = exp (-1.0 / 3.75);
248 1839 : }
249 :
250 : __clib_export void
251 472 : clib_time_verify_frequency (clib_time_t * c)
252 : {
253 : f64 now_reference, delta_reference, delta_reference_max;
254 : f64 delta_clock_in_seconds;
255 : u64 now_clock, delta_clock;
256 : f64 new_clocks_per_second, delta;
257 :
258 : /* Ask the kernel and the CPU what time it is... */
259 472 : now_reference = unix_time_now ();
260 473 : now_clock = clib_cpu_time_now ();
261 :
262 : /* Compute change in the reference clock */
263 473 : delta_reference = now_reference - c->last_verify_reference_time;
264 :
265 : /* And change in the CPU clock */
266 473 : delta_clock_in_seconds = (f64) (now_clock - c->last_verify_cpu_time) *
267 473 : c->seconds_per_clock;
268 :
269 : /*
270 : * Recompute vpp start time reference, and total clocks
271 : * using the current clock rate
272 : */
273 473 : c->init_reference_time += (delta_reference - delta_clock_in_seconds);
274 473 : c->total_cpu_time = (now_reference - c->init_reference_time)
275 473 : * c->clocks_per_second;
276 :
277 473 : c->last_cpu_time = now_clock;
278 :
279 : /* Calculate a new clock rate sample */
280 473 : delta_clock = c->last_cpu_time - c->last_verify_cpu_time;
281 :
282 473 : c->last_verify_cpu_time = c->last_cpu_time;
283 473 : c->last_verify_reference_time = now_reference;
284 :
285 : /*
286 : * Is the reported reference interval non-positive,
287 : * or off by a factor of two - or 8 seconds - whichever is larger?
288 : * Someone reset the clock behind our back.
289 : */
290 473 : delta_reference_max = (f64) (2ULL << c->log2_clocks_per_frequency_verify) /
291 473 : (f64) (1ULL << c->log2_clocks_per_second);
292 473 : delta_reference_max = delta_reference_max > 8.0 ? delta_reference_max : 8.0;
293 :
294 : /* Ignore this sample */
295 473 : if (delta_reference <= 0.0 || delta_reference > delta_reference_max)
296 0 : return;
297 :
298 : /*
299 : * Reject large frequency changes, another consequence of
300 : * system clock changes particularly with old kernels.
301 : */
302 473 : new_clocks_per_second = ((f64) delta_clock) / delta_reference;
303 :
304 : /* Compute abs(rate change) */
305 473 : delta = new_clocks_per_second - c->clocks_per_second;
306 473 : if (delta < 0.0)
307 364 : delta = -delta;
308 :
309 : /* If rate change > 1%, reject this sample */
310 473 : if (PREDICT_FALSE ((delta / c->clocks_per_second) > .01))
311 : {
312 0 : clib_warning ("Rejecting large frequency change of %.2f%%",
313 : (delta / c->clocks_per_second) * 100.0);
314 0 : return;
315 : }
316 :
317 : /* Add sample to the exponentially-smoothed rate */
318 473 : c->clocks_per_second = c->clocks_per_second * c->damping_constant +
319 473 : (1.0 - c->damping_constant) * new_clocks_per_second;
320 473 : c->seconds_per_clock = 1.0 / c->clocks_per_second;
321 :
322 : /*
323 : * Recalculate total_cpu_time based on the kernel timebase, and
324 : * the calculated clock rate
325 : */
326 473 : c->total_cpu_time =
327 473 : (now_reference - c->init_reference_time) * c->clocks_per_second;
328 : }
329 :
330 :
331 : __clib_export u8 *
332 78 : format_clib_time (u8 * s, va_list * args)
333 : {
334 78 : clib_time_t *c = va_arg (*args, clib_time_t *);
335 78 : int verbose = va_arg (*args, int);
336 : f64 now, reftime, delta_reftime_in_seconds, error;
337 :
338 : /* Compute vpp elapsed time from the CPU clock */
339 78 : reftime = unix_time_now ();
340 78 : now = clib_time_now (c);
341 :
342 78 : s = format (s, "Time now %.6f", now);
343 78 : if (verbose == 0)
344 78 : return s;
345 :
346 : /* And also from the kernel */
347 0 : delta_reftime_in_seconds = reftime - c->init_reference_time;
348 :
349 0 : error = now - delta_reftime_in_seconds;
350 :
351 0 : s = format (s, ", reftime %.6f, error %.6f, clocks/sec %.6f",
352 : delta_reftime_in_seconds, error, c->clocks_per_second);
353 0 : return (s);
354 : }
355 :
356 : /*
357 : * fd.io coding-style-patch-verification: ON
358 : *
359 : * Local Variables:
360 : * eval: (c-set-style "gnu")
361 : * End:
362 : */
|