LCOV - code coverage report
Current view: top level - vppinfra - time.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 60 114 52.6 %
Date: 2023-10-26 01:39:38 Functions: 4 7 57.1 %

          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             :  */

Generated by: LCOV version 1.14