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 : unformat_input_t input;
78 :
79 : /* $$$$ aarch64 kernel doesn't report "cpu MHz" */
80 : #if defined(__aarch64__)
81 : return 0.0;
82 : #endif
83 :
84 0 : cpu_freq = 0;
85 :
86 0 : ppc_timebase = 0;
87 0 : if (unformat_init_file (&input, "/proc/cpuinfo"))
88 : {
89 0 : while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
90 : {
91 0 : if (unformat (&input, "cpu MHz : %f", &cpu_freq))
92 0 : cpu_freq *= 1e6;
93 0 : else if (unformat (&input, "timebase : %f", &ppc_timebase))
94 : ;
95 : else
96 0 : unformat_skip_line (&input);
97 : }
98 :
99 0 : unformat_free (&input);
100 : }
101 : else
102 0 : return cpu_freq;
103 :
104 : /* Override CPU frequency with time base for PPC. */
105 0 : if (ppc_timebase != 0)
106 0 : cpu_freq = ppc_timebase;
107 :
108 0 : return cpu_freq;
109 : }
110 :
111 : /* Fetch cpu frequency via reading /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
112 : Only works for Linux. */
113 : static f64
114 0 : clock_frequency_from_sys_filesystem (void)
115 : {
116 0 : f64 cpu_freq = 0.0;
117 : unformat_input_t input;
118 :
119 : /* Time stamp always runs at max frequency. */
120 0 : cpu_freq = 0;
121 :
122 0 : if (unformat_init_file (
123 : &input, "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"))
124 : {
125 0 : if (unformat (&input, "%f", &cpu_freq))
126 0 : cpu_freq *= 1e3; /* measured in kHz */
127 0 : unformat_free (&input);
128 : }
129 :
130 0 : return cpu_freq;
131 : }
132 :
133 : __clib_export f64
134 1928 : os_cpu_clock_frequency (void)
135 : {
136 : #if defined (__aarch64__)
137 : /* The system counter increments at a fixed frequency. It is distributed
138 : * to each core which has registers for reading the current counter value
139 : * as well as the clock frequency. The system counter is not clocked at
140 : * the same frequency as the core. */
141 : u64 hz;
142 : asm volatile ("mrs %0, cntfrq_el0":"=r" (hz));
143 : return (f64) hz;
144 : #endif
145 : f64 cpu_freq;
146 :
147 : #ifdef __x86_64__
148 1928 : u32 __clib_unused eax = 0, ebx = 0, ecx = 0, edx = 0;
149 1928 : clib_get_cpuid (0x00, &eax, &ebx, &ecx, &edx);
150 1926 : if (eax >= 0x15)
151 : {
152 1926 : u32 max_leaf = eax;
153 : /*
154 : CPUID Leaf 0x15 - Time Stamp Counter and Nominal Core Crystal Clock Info
155 : eax - denominator of the TSC/”core crystal clock” ratio
156 : ebx - numerator of the TSC/”core crystal clock” ratio
157 : ecx - nominal frequency of the core crystal clock in Hz
158 : edx - reseved
159 : */
160 :
161 1926 : clib_get_cpuid (0x15, &eax, &ebx, &ecx, &edx);
162 1930 : if (ebx && ecx)
163 0 : return (u64) ecx *ebx / eax;
164 :
165 1930 : if (max_leaf >= 0x16)
166 : {
167 : /*
168 : CPUID Leaf 0x16 - Processor Frequency Information Leaf
169 : eax - Bits 15 - 00: Processor Base Frequency (in MHz).
170 : */
171 :
172 1930 : clib_get_cpuid (0x16, &eax, &ebx, &ecx, &edx);
173 1931 : if (eax)
174 1932 : return 1e6 * (eax & 0xffff);
175 : }
176 : }
177 : #endif
178 :
179 : /* If we have an invariant TSC, use it to estimate the clock frequency */
180 0 : if (clib_cpu_supports_invariant_tsc ())
181 0 : return estimate_clock_frequency (1e-3);
182 :
183 : /* Next, try /sys version. */
184 0 : cpu_freq = clock_frequency_from_sys_filesystem ();
185 0 : if (cpu_freq != 0)
186 0 : return cpu_freq;
187 :
188 : /* Next try /proc version. */
189 0 : cpu_freq = clock_frequency_from_proc_filesystem ();
190 0 : if (cpu_freq != 0)
191 0 : return cpu_freq;
192 :
193 : /* If /proc/cpuinfo fails (e.g. not running on Linux) fall back to
194 : gettimeofday based estimated clock frequency. */
195 0 : return estimate_clock_frequency (1e-3);
196 : }
197 :
198 : #endif /* CLIB_UNIX */
199 :
200 : /* Initialize time. */
201 : __clib_export void
202 1886 : clib_time_init (clib_time_t * c)
203 : {
204 1886 : clib_memset (c, 0, sizeof (c[0]));
205 1885 : c->clocks_per_second = os_cpu_clock_frequency ();
206 : /*
207 : * Sporadic reports of os_cpu_clock_frequency() returning 0.0
208 : * in highly parallel container environments.
209 : * To avoid immediate division by zero:
210 : * Step 1: try estimate_clock_frequency().
211 : * Step 2: give up. Pretend we have a 2gHz clock.
212 : */
213 1890 : if (PREDICT_FALSE (c->clocks_per_second == 0.0))
214 : {
215 0 : c->clocks_per_second = estimate_clock_frequency (1e-3);
216 0 : if (c->clocks_per_second == 0.0)
217 : {
218 0 : clib_warning ("os_cpu_clock_frequency() returned 0.0, use 2e9...");
219 0 : c->clocks_per_second = 2e9;
220 : }
221 : }
222 1890 : c->seconds_per_clock = 1 / c->clocks_per_second;
223 1890 : c->log2_clocks_per_second = min_log2_u64 ((u64) c->clocks_per_second);
224 :
225 : /* Verify frequency every 16 sec */
226 1889 : c->log2_clocks_per_frequency_verify = c->log2_clocks_per_second + 4;
227 :
228 1889 : c->last_verify_reference_time = unix_time_now ();
229 1891 : c->init_reference_time = c->last_verify_reference_time;
230 1891 : c->last_cpu_time = clib_cpu_time_now ();
231 1891 : c->init_cpu_time = c->last_verify_cpu_time = c->last_cpu_time;
232 1891 : c->total_cpu_time = 0ULL;
233 :
234 : /*
235 : * Use exponential smoothing, with a half-life of 1 minute
236 : * reported_rate(t) = reported_rate(t-1) * K + rate(t)*(1-K)
237 : * where K = e**(-1.0/3.75);
238 : * 15 samples in 4 minutes
239 : * 7.5 samples in 2 minutes,
240 : * 3.75 samples in 1 minute, etc.
241 : */
242 1891 : c->damping_constant = exp (-1.0 / 3.75);
243 1891 : }
244 :
245 : __clib_export void
246 503 : clib_time_verify_frequency (clib_time_t * c)
247 : {
248 : f64 now_reference, delta_reference, delta_reference_max;
249 : f64 delta_clock_in_seconds;
250 : u64 now_clock, delta_clock;
251 : f64 new_clocks_per_second, delta;
252 :
253 : /* Ask the kernel and the CPU what time it is... */
254 503 : now_reference = unix_time_now ();
255 509 : now_clock = clib_cpu_time_now ();
256 :
257 : /* Compute change in the reference clock */
258 504 : delta_reference = now_reference - c->last_verify_reference_time;
259 :
260 : /* And change in the CPU clock */
261 504 : delta_clock_in_seconds = (f64) (now_clock - c->last_verify_cpu_time) *
262 504 : c->seconds_per_clock;
263 :
264 : /*
265 : * Recompute vpp start time reference, and total clocks
266 : * using the current clock rate
267 : */
268 504 : c->init_reference_time += (delta_reference - delta_clock_in_seconds);
269 504 : c->total_cpu_time = (now_reference - c->init_reference_time)
270 504 : * c->clocks_per_second;
271 :
272 504 : c->last_cpu_time = now_clock;
273 :
274 : /* Calculate a new clock rate sample */
275 504 : delta_clock = c->last_cpu_time - c->last_verify_cpu_time;
276 :
277 504 : c->last_verify_cpu_time = c->last_cpu_time;
278 504 : c->last_verify_reference_time = now_reference;
279 :
280 : /*
281 : * Is the reported reference interval non-positive,
282 : * or off by a factor of two - or 8 seconds - whichever is larger?
283 : * Someone reset the clock behind our back.
284 : */
285 504 : delta_reference_max = (f64) (2ULL << c->log2_clocks_per_frequency_verify) /
286 504 : (f64) (1ULL << c->log2_clocks_per_second);
287 504 : delta_reference_max = delta_reference_max > 8.0 ? delta_reference_max : 8.0;
288 :
289 : /* Ignore this sample */
290 504 : if (delta_reference <= 0.0 || delta_reference > delta_reference_max)
291 0 : return;
292 :
293 : /*
294 : * Reject large frequency changes, another consequence of
295 : * system clock changes particularly with old kernels.
296 : */
297 505 : new_clocks_per_second = ((f64) delta_clock) / delta_reference;
298 :
299 : /* Compute abs(rate change) */
300 505 : delta = new_clocks_per_second - c->clocks_per_second;
301 505 : if (delta < 0.0)
302 458 : delta = -delta;
303 :
304 : /* If rate change > 1%, reject this sample */
305 505 : if (PREDICT_FALSE ((delta / c->clocks_per_second) > .01))
306 : {
307 0 : clib_warning ("Rejecting large frequency change of %.2f%%",
308 : (delta / c->clocks_per_second) * 100.0);
309 0 : return;
310 : }
311 :
312 : /* Add sample to the exponentially-smoothed rate */
313 505 : c->clocks_per_second = c->clocks_per_second * c->damping_constant +
314 505 : (1.0 - c->damping_constant) * new_clocks_per_second;
315 505 : c->seconds_per_clock = 1.0 / c->clocks_per_second;
316 :
317 : /*
318 : * Recalculate total_cpu_time based on the kernel timebase, and
319 : * the calculated clock rate
320 : */
321 505 : c->total_cpu_time =
322 505 : (now_reference - c->init_reference_time) * c->clocks_per_second;
323 : }
324 :
325 :
326 : __clib_export u8 *
327 78 : format_clib_time (u8 * s, va_list * args)
328 : {
329 78 : clib_time_t *c = va_arg (*args, clib_time_t *);
330 78 : int verbose = va_arg (*args, int);
331 : f64 now, reftime, delta_reftime_in_seconds, error;
332 :
333 : /* Compute vpp elapsed time from the CPU clock */
334 78 : reftime = unix_time_now ();
335 78 : now = clib_time_now (c);
336 :
337 78 : s = format (s, "Time now %.6f", now);
338 78 : if (verbose == 0)
339 78 : return s;
340 :
341 : /* And also from the kernel */
342 0 : delta_reftime_in_seconds = reftime - c->init_reference_time;
343 :
344 0 : error = now - delta_reftime_in_seconds;
345 :
346 0 : s = format (s, ", reftime %.6f, error %.6f, clocks/sec %.6f",
347 : delta_reftime_in_seconds, error, c->clocks_per_second);
348 0 : return (s);
349 : }
350 :
351 : /*
352 : * fd.io coding-style-patch-verification: ON
353 : *
354 : * Local Variables:
355 : * eval: (c-set-style "gnu")
356 : * End:
357 : */
|