LCOV - code coverage report
Current view: top level - vppinfra - time.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 60 122 49.2 %
Date: 2023-07-05 22:20:52 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             :   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             :  */

Generated by: LCOV version 1.14