Type: improvement Change-Id: I57a9f85f7df1fc48656b72592349f4c544302f77 Signed-off-by: Damjan Marion <damarion@cisco.com>
363 lines
10 KiB
C
363 lines
10 KiB
C
/*
|
|
* Copyright (c) 2015 Cisco and/or its affiliates.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/*
|
|
Copyright (c) 2005 Eliot Dresselhaus
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
a copy of this software and associated documentation files (the
|
|
"Software"), to deal in the Software without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <vppinfra/os.h>
|
|
#include <vppinfra/time.h>
|
|
#include <vppinfra/format.h>
|
|
#include <vppinfra/cpu.h>
|
|
#include <math.h>
|
|
|
|
#ifdef CLIB_UNIX
|
|
|
|
#include <math.h>
|
|
#include <sys/time.h>
|
|
#include <fcntl.h>
|
|
|
|
/* Not very accurate way of determining cpu clock frequency
|
|
for unix. Better to use /proc/cpuinfo on linux. */
|
|
static f64
|
|
estimate_clock_frequency (f64 sample_time)
|
|
{
|
|
f64 time_now, time_start, time_limit, freq;
|
|
u64 t[2];
|
|
|
|
time_start = time_now = unix_time_now ();
|
|
time_limit = time_now + sample_time;
|
|
t[0] = clib_cpu_time_now ();
|
|
while (time_now < time_limit)
|
|
time_now = unix_time_now ();
|
|
t[1] = clib_cpu_time_now ();
|
|
|
|
freq = (t[1] - t[0]) / (time_now - time_start);
|
|
|
|
return freq;
|
|
}
|
|
|
|
/* Fetch cpu frequency via parseing /proc/cpuinfo.
|
|
Only works for Linux. */
|
|
static f64
|
|
clock_frequency_from_proc_filesystem (void)
|
|
{
|
|
f64 cpu_freq = 1e9; /* better than 40... */
|
|
f64 ppc_timebase = 0; /* warnings be gone */
|
|
int fd;
|
|
unformat_input_t input;
|
|
|
|
/* $$$$ aarch64 kernel doesn't report "cpu MHz" */
|
|
#if defined(__aarch64__)
|
|
return 0.0;
|
|
#endif
|
|
|
|
cpu_freq = 0;
|
|
fd = open ("/proc/cpuinfo", 0);
|
|
if (fd < 0)
|
|
return cpu_freq;
|
|
|
|
unformat_init_clib_file (&input, fd);
|
|
|
|
ppc_timebase = 0;
|
|
while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
|
|
{
|
|
if (unformat (&input, "cpu MHz : %f", &cpu_freq))
|
|
cpu_freq *= 1e6;
|
|
else if (unformat (&input, "timebase : %f", &ppc_timebase))
|
|
;
|
|
else
|
|
unformat_skip_line (&input);
|
|
}
|
|
|
|
unformat_free (&input);
|
|
|
|
close (fd);
|
|
|
|
/* Override CPU frequency with time base for PPC. */
|
|
if (ppc_timebase != 0)
|
|
cpu_freq = ppc_timebase;
|
|
|
|
return cpu_freq;
|
|
}
|
|
|
|
/* Fetch cpu frequency via reading /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
|
|
Only works for Linux. */
|
|
static f64
|
|
clock_frequency_from_sys_filesystem (void)
|
|
{
|
|
f64 cpu_freq = 0.0;
|
|
int fd;
|
|
unformat_input_t input;
|
|
|
|
/* Time stamp always runs at max frequency. */
|
|
cpu_freq = 0;
|
|
fd = open ("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", 0);
|
|
if (fd < 0)
|
|
goto done;
|
|
|
|
unformat_init_clib_file (&input, fd);
|
|
(void) unformat (&input, "%f", &cpu_freq);
|
|
cpu_freq *= 1e3; /* measured in kHz */
|
|
unformat_free (&input);
|
|
close (fd);
|
|
done:
|
|
return cpu_freq;
|
|
}
|
|
|
|
__clib_export f64
|
|
os_cpu_clock_frequency (void)
|
|
{
|
|
#if defined (__aarch64__)
|
|
/* The system counter increments at a fixed frequency. It is distributed
|
|
* to each core which has registers for reading the current counter value
|
|
* as well as the clock frequency. The system counter is not clocked at
|
|
* the same frequency as the core. */
|
|
u64 hz;
|
|
asm volatile ("mrs %0, cntfrq_el0":"=r" (hz));
|
|
return (f64) hz;
|
|
#endif
|
|
f64 cpu_freq;
|
|
|
|
#ifdef __x86_64__
|
|
u32 __clib_unused eax = 0, ebx = 0, ecx = 0, edx = 0;
|
|
clib_get_cpuid (0x00, &eax, &ebx, &ecx, &edx);
|
|
if (eax >= 0x15)
|
|
{
|
|
u32 max_leaf = eax;
|
|
/*
|
|
CPUID Leaf 0x15 - Time Stamp Counter and Nominal Core Crystal Clock Info
|
|
eax - denominator of the TSC/”core crystal clock” ratio
|
|
ebx - numerator of the TSC/”core crystal clock” ratio
|
|
ecx - nominal frequency of the core crystal clock in Hz
|
|
edx - reseved
|
|
*/
|
|
|
|
clib_get_cpuid (0x15, &eax, &ebx, &ecx, &edx);
|
|
if (ebx && ecx)
|
|
return (u64) ecx *ebx / eax;
|
|
|
|
if (max_leaf >= 0x16)
|
|
{
|
|
/*
|
|
CPUID Leaf 0x16 - Processor Frequency Information Leaf
|
|
eax - Bits 15 - 00: Processor Base Frequency (in MHz).
|
|
*/
|
|
|
|
clib_get_cpuid (0x16, &eax, &ebx, &ecx, &edx);
|
|
if (eax)
|
|
return 1e6 * (eax & 0xffff);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* If we have an invariant TSC, use it to estimate the clock frequency */
|
|
if (clib_cpu_supports_invariant_tsc ())
|
|
return estimate_clock_frequency (1e-3);
|
|
|
|
/* Next, try /sys version. */
|
|
cpu_freq = clock_frequency_from_sys_filesystem ();
|
|
if (cpu_freq != 0)
|
|
return cpu_freq;
|
|
|
|
/* Next try /proc version. */
|
|
cpu_freq = clock_frequency_from_proc_filesystem ();
|
|
if (cpu_freq != 0)
|
|
return cpu_freq;
|
|
|
|
/* If /proc/cpuinfo fails (e.g. not running on Linux) fall back to
|
|
gettimeofday based estimated clock frequency. */
|
|
return estimate_clock_frequency (1e-3);
|
|
}
|
|
|
|
#endif /* CLIB_UNIX */
|
|
|
|
/* Initialize time. */
|
|
__clib_export void
|
|
clib_time_init (clib_time_t * c)
|
|
{
|
|
clib_memset (c, 0, sizeof (c[0]));
|
|
c->clocks_per_second = os_cpu_clock_frequency ();
|
|
/*
|
|
* Sporadic reports of os_cpu_clock_frequency() returning 0.0
|
|
* in highly parallel container environments.
|
|
* To avoid immediate division by zero:
|
|
* Step 1: try estimate_clock_frequency().
|
|
* Step 2: give up. Pretend we have a 2gHz clock.
|
|
*/
|
|
if (PREDICT_FALSE (c->clocks_per_second == 0.0))
|
|
{
|
|
c->clocks_per_second = estimate_clock_frequency (1e-3);
|
|
if (c->clocks_per_second == 0.0)
|
|
{
|
|
clib_warning ("os_cpu_clock_frequency() returned 0.0, use 2e9...");
|
|
c->clocks_per_second = 2e9;
|
|
}
|
|
}
|
|
c->seconds_per_clock = 1 / c->clocks_per_second;
|
|
c->log2_clocks_per_second = min_log2_u64 ((u64) c->clocks_per_second);
|
|
|
|
/* Verify frequency every 16 sec */
|
|
c->log2_clocks_per_frequency_verify = c->log2_clocks_per_second + 4;
|
|
|
|
c->last_verify_reference_time = unix_time_now ();
|
|
c->init_reference_time = c->last_verify_reference_time;
|
|
c->last_cpu_time = clib_cpu_time_now ();
|
|
c->init_cpu_time = c->last_verify_cpu_time = c->last_cpu_time;
|
|
c->total_cpu_time = 0ULL;
|
|
|
|
/*
|
|
* Use exponential smoothing, with a half-life of 1 minute
|
|
* reported_rate(t) = reported_rate(t-1) * K + rate(t)*(1-K)
|
|
* where K = e**(-1.0/3.75);
|
|
* 15 samples in 4 minutes
|
|
* 7.5 samples in 2 minutes,
|
|
* 3.75 samples in 1 minute, etc.
|
|
*/
|
|
c->damping_constant = exp (-1.0 / 3.75);
|
|
}
|
|
|
|
__clib_export void
|
|
clib_time_verify_frequency (clib_time_t * c)
|
|
{
|
|
f64 now_reference, delta_reference, delta_reference_max;
|
|
f64 delta_clock_in_seconds;
|
|
u64 now_clock, delta_clock;
|
|
f64 new_clocks_per_second, delta;
|
|
|
|
/* Ask the kernel and the CPU what time it is... */
|
|
now_reference = unix_time_now ();
|
|
now_clock = clib_cpu_time_now ();
|
|
|
|
/* Compute change in the reference clock */
|
|
delta_reference = now_reference - c->last_verify_reference_time;
|
|
|
|
/* And change in the CPU clock */
|
|
delta_clock_in_seconds = (f64) (now_clock - c->last_verify_cpu_time) *
|
|
c->seconds_per_clock;
|
|
|
|
/*
|
|
* Recompute vpp start time reference, and total clocks
|
|
* using the current clock rate
|
|
*/
|
|
c->init_reference_time += (delta_reference - delta_clock_in_seconds);
|
|
c->total_cpu_time = (now_reference - c->init_reference_time)
|
|
* c->clocks_per_second;
|
|
|
|
c->last_cpu_time = now_clock;
|
|
|
|
/* Calculate a new clock rate sample */
|
|
delta_clock = c->last_cpu_time - c->last_verify_cpu_time;
|
|
|
|
c->last_verify_cpu_time = c->last_cpu_time;
|
|
c->last_verify_reference_time = now_reference;
|
|
|
|
/*
|
|
* Is the reported reference interval non-positive,
|
|
* or off by a factor of two - or 8 seconds - whichever is larger?
|
|
* Someone reset the clock behind our back.
|
|
*/
|
|
delta_reference_max = (f64) (2ULL << c->log2_clocks_per_frequency_verify) /
|
|
(f64) (1ULL << c->log2_clocks_per_second);
|
|
delta_reference_max = delta_reference_max > 8.0 ? delta_reference_max : 8.0;
|
|
|
|
/* Ignore this sample */
|
|
if (delta_reference <= 0.0 || delta_reference > delta_reference_max)
|
|
return;
|
|
|
|
/*
|
|
* Reject large frequency changes, another consequence of
|
|
* system clock changes particularly with old kernels.
|
|
*/
|
|
new_clocks_per_second = ((f64) delta_clock) / delta_reference;
|
|
|
|
/* Compute abs(rate change) */
|
|
delta = new_clocks_per_second - c->clocks_per_second;
|
|
if (delta < 0.0)
|
|
delta = -delta;
|
|
|
|
/* If rate change > 1%, reject this sample */
|
|
if (PREDICT_FALSE ((delta / c->clocks_per_second) > .01))
|
|
{
|
|
clib_warning ("Rejecting large frequency change of %.2f%%",
|
|
(delta / c->clocks_per_second) * 100.0);
|
|
return;
|
|
}
|
|
|
|
/* Add sample to the exponentially-smoothed rate */
|
|
c->clocks_per_second = c->clocks_per_second * c->damping_constant +
|
|
(1.0 - c->damping_constant) * new_clocks_per_second;
|
|
c->seconds_per_clock = 1.0 / c->clocks_per_second;
|
|
|
|
/*
|
|
* Recalculate total_cpu_time based on the kernel timebase, and
|
|
* the calculated clock rate
|
|
*/
|
|
c->total_cpu_time =
|
|
(now_reference - c->init_reference_time) * c->clocks_per_second;
|
|
}
|
|
|
|
|
|
__clib_export u8 *
|
|
format_clib_time (u8 * s, va_list * args)
|
|
{
|
|
clib_time_t *c = va_arg (*args, clib_time_t *);
|
|
int verbose = va_arg (*args, int);
|
|
f64 now, reftime, delta_reftime_in_seconds, error;
|
|
|
|
/* Compute vpp elapsed time from the CPU clock */
|
|
reftime = unix_time_now ();
|
|
now = clib_time_now (c);
|
|
|
|
s = format (s, "Time now %.6f", now);
|
|
if (verbose == 0)
|
|
return s;
|
|
|
|
/* And also from the kernel */
|
|
delta_reftime_in_seconds = reftime - c->init_reference_time;
|
|
|
|
error = now - delta_reftime_in_seconds;
|
|
|
|
s = format (s, ", reftime %.6f, error %.6f, clocks/sec %.6f",
|
|
delta_reftime_in_seconds, error, c->clocks_per_second);
|
|
return (s);
|
|
}
|
|
|
|
/*
|
|
* fd.io coding-style-patch-verification: ON
|
|
*
|
|
* Local Variables:
|
|
* eval: (c-set-style "gnu")
|
|
* End:
|
|
*/
|