LCOV - code coverage report
Current view: top level - tango/tempo - fd_tempo.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 6 6 100.0 %
Date: 2026-08-14 04:54:57 Functions: 9 782 1.2 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_tango_tempo_fd_tempo_h
       2             : #define HEADER_fd_src_tango_tempo_fd_tempo_h
       3             : 
       4             : /* APIs for measuring time and tick intervals */
       5             : 
       6             : #include "../fd_tango_base.h"
       7             : 
       8             : FD_PROTOTYPES_BEGIN
       9             : 
      10             : /* fd_tempo_wallclock_model returns an estimate of t0, the minimum cost
      11             :    of fd_log_wallclock() in ticks.  If opt_tau is non_NULL, on return,
      12             :    *opt_tau will contain an estimate of typical jitter associated with
      13             :    fd_log_wallclock() (such that fd_log_wallclock() can be roughly
      14             :    modeled as a shifted exponential distribution with minimum of t0 and
      15             :    wait time of tau, average cost of t0 + tau, rms of tau).  The first
      16             :    call of this in a thread group will be slow and all subsequent calls
      17             :    in the thread group will be fast and return the identical parameters
      18             :    to the first call.  t0 will be finite and positive and the tau will
      19             :    be finite and non-negative.  If the fd_log_wallclock() cannot be
      20             :    sanely parameterized on the first call, logs a warning and uses a
      21             :    fallback parameterization. */
      22             : 
      23             : double
      24             : fd_tempo_wallclock_model( double * opt_tau );
      25             : 
      26             : /* fd_tempo_tickcount_model does the same as fd_tempo_wallclock model
      27             :    for fd_tickcount().  The model parameter units will be in ticks
      28             :    instead of nanoseconds. */
      29             : 
      30             : double
      31             : fd_tempo_tickcount_model( double * opt_tau );
      32             : 
      33             : /* fd_tempo_set_tick_per_ns explicitly sets the return values of
      34             :    fd_tempo_tick_per_ns below, subsequent calls to that function will
      35             :    return the values given here.
      36             : 
      37             :    These should not be arbitrarily provided, and this function is here
      38             :    primarily to enable different processes to synchronize their
      39             :    tick_per_ns value. */
      40             : 
      41             : void
      42             : fd_tempo_set_tick_per_ns( double _mu,
      43             :                           double _sigma );
      44             : 
      45             : /* fd_tempo_tick_per_ns is the same as the above but gives an estimate
      46             :    of the rate fd_tickcount() ticks relative to fd_log_wallclock() (this
      47             :    is in Ghz).  The returned value is the observed rate when
      48             :    fd_tempo_tick_per_ns was first called in the thread group (this call
      49             :    will take around ~0.5 s).  If opt_sigma is non-NULL, on return,
      50             :    *opt_sigma will have an estimate how much the rate was fluctuating
      51             :    when observed during the first call.
      52             : 
      53             :    IMPORTANT!  Though fd_tickcount() is meant to tick at a constant rate
      54             :    relative to fd_log_wallclock(), the instantaneous rate it ticks can
      55             :    fluctuate for the usual of clock synchronization reasons (e.g.
      56             :    thermal and electrical effects from CPU load, CPU clock timing
      57             :    jitter, similar for the wallclock, etc).  As this is an invariant
      58             :    counter, reasons for it to fluctuate do _NOT_ include directly things
      59             :    like turbo mode cpu clock frequency changes (it might slightly
      60             :    indirectly impact it due to correlated changes to system thermal and
      61             :    electric conditions from the changed power draw).  As such, this is
      62             :    mostly meant for useful for getting a thread group wide consistent
      63             :    estimate of the number of ticks in a short interval of ns.
      64             : 
      65             :    TL;DR This returns an estimate of fd_tickcount()'s clock speed in
      66             :    GHz.  This is _NOT_ the current clock speed of the processor though
      67             :    it will usually superficially look like it.  This is _NOT_ the
      68             :    instantaneous rate the tickcounter is ticking relative to the
      69             :    wallclock though it will usually superficially look like it. */
      70             : 
      71             : double
      72             : fd_tempo_tick_per_ns( double * opt_sigma );
      73             : 
      74             : /* fd_tempo_tick_per_ns_dev is a development-only variant of
      75             :    fd_tempo_tick_per_ns that measures the rate with a much smaller
      76             :    wall-clock budget (~1 ms vs ~0.5 s) at the cost of a few ppm of
      77             :    accuracy.  It is intended for firedancer-dev, where boot latency
      78             :    matters and the rate is not used for anything critical.  Like
      79             :    fd_tempo_tick_per_ns it is FD_ONCE guarded and honors a prior
      80             :    fd_tempo_set_tick_per_ns; additionally, calling this marks the value
      81             :    explicit so a later fd_tempo_tick_per_ns() returns this fast
      82             :    measurement rather than re-running the slow production sampling.  Do
      83             :    NOT use in production. */
      84             : 
      85             : double
      86             : fd_tempo_tick_per_ns_dev( double * opt_sigma );
      87             : 
      88             : /* fd_tempo_observe_pair observes the fd_log_wallclock() and
      89             :    fd_tickcount() at the "same time".  More precisely, it alternately
      90             :    observes both a few times and estimates from the "best" wallclock
      91             :    read what tickcount would have been observed at that time had
      92             :    fd_tickcount() been called instead.  Returns a non-negative measure
      93             :    of the jitter in ticks in the sense observed tickcount is within
      94             :    +/-0.5 jitter ticks of the time the wallclock was observed.  On
      95             :    return, if opt_now is non-NULL, *opt_now will contain the actual
      96             :    fd_log_wallclock() observation and, if opt_tic is non-NULL, *opt_tic
      97             :    will contain the estimated simultaneous fd_tickcount() observation.
      98             : 
      99             :    If anything wonky is detected in the measurement, logs a warning and
     100             :    returns a best effort.  As this does multiple reads under the hood
     101             :    and uses only one of them, the observed value should be interpreted
     102             :    as at some point in time between when the call was made and when
     103             :    the call returned but not always at the same point (can be roughly
     104             :    modeled as uniformly distributed between when the call was made and
     105             :    when it returned).
     106             : 
     107             :    While this isn't particularly expensive, it isn't particularly cheap
     108             :    either.  Cost is on the order of a few calls to fd_wallclock plus a
     109             :    few calls to fd_tickcount().  This is mostly meant for doing
     110             :    precision timing calibrations. */
     111             : 
     112             : long
     113             : fd_tempo_observe_pair( long * opt_now,
     114             :                        long * opt_tic );
     115             : 
     116             : /* fd_tempo_lazy_default returns a target interval between housekeeping
     117             :    events in ns (laziness) for a producer / consumer that has a maximum
     118             :    credits of cr_max / lag behind the producer of lag_max.
     119             : 
     120             :    To understand this default, note that a producer should receive / a
     121             :    consumer should transmit complete flow control credits least as often
     122             :    as the time it takes a producer to exhaust all its credits / a slow
     123             :    consumer to process a worst case backlog of lag_max credits.
     124             :    Otherwise, the communications throughput will be limited by the rate
     125             :    credits can flow from consumer to producer rather than the rate the
     126             :    producer can publish / consumer can receive.  At the same time, we
     127             :    don't want to be too eager to return credits to avoid consumer to
     128             :    producer credit communications competing for NOC resources with
     129             :    producer to consumer communications.
     130             : 
     131             :    This implies we need to update all flow control information on a tile
     132             :    somewhat faster than:
     133             : 
     134             :      cr_max max( typical time it takes a producer to consume a credit,
     135             :                  typical time it takes a consumer to produce a credit )
     136             : 
     137             :    Practical considerations applied to this yield a useful conservative
     138             :    lower bound:
     139             : 
     140             :    Assume credits are network packets (as is often the case), the above
     141             :    times are the typical time it takes a producer / consumer to generate
     142             :    / process a packet.  Given a producer line-rating minimal sized
     143             :    Ethernet frames (672 bits) at 100G into a mcache / dcache and
     144             :    consumers that are keeping up with this producer (both highly
     145             :    unrealistically harsh situations in the real world as this implies
     146             :    Ethernet payloads much much smaller than typical real world payloads
     147             :    and a consumer that can process packets in just a handful of ns), the
     148             :    above suggests housekeeping done somewhat than:
     149             : 
     150             :      ~(cr_max pkt)(672 bit/pkt/100 Gbit/ns)
     151             : 
     152             :    will be adequate for all practical purposes. Given that the typical
     153             :    randomized housekeeping event will be at most ~1.5 lazy, we have:
     154             : 
     155             :      lazy < ~cr_max*672/100e9/1.5 ~ 4.48 cr_max
     156             : 
     157             :    We use 1+floor( 9*cr_max/4 )) ~ 2.25 cr_max to keep things simple.
     158             :    Note that that while this might seem aggressive per credit, since
     159             :    cr_max is typically values in thousands to hundreds of thousands,
     160             :    this corresponds to default laziness in the tens microseconds to
     161             :    milliseconds.  We also saturate cr_max to keep the returned value in
     162             :    [1,2^31] ns for all cr_max. */
     163             : 
     164             : FD_FN_CONST static inline long
     165          48 : fd_tempo_lazy_default( ulong cr_max ) {
     166          48 :   return fd_long_if( cr_max>954437176UL, (long)INT_MAX, (long)(1UL+((9UL*cr_max)>>2)) );
     167          48 : }
     168             : 
     169             : /* fd_tempo_async_min picks a reasonable minimum interval in ticks
     170             :    between housekeeping events.  On success, returns positive integer
     171             :    power of two in [1,2^31].  On failure, returns zero (logs details).
     172             :    Reasons for failure include lazy is not in [1,2^31), event_cnt is not
     173             :    in [1,2^31), tick_per_ns is not in (0,~1.5e29), the combination would
     174             :    require an unreasonably small (sub-tick) or large (more than 2^31)
     175             :    async_min.
     176             : 
     177             :    More precisely, consider a run loop where event_cnt out-of-band
     178             :    housekeeping events are cyclicly scheduled to be done with a IID
     179             :    uniform random interval between events in [async_min,2*async_min]
     180             :    ticks (as is commonly the case).  And suppose we need housekeeping
     181             :    to complete an event cycle roughly every lazy ns for system
     182             :    considerations.
     183             : 
     184             :    If we were to use a regularly scheduled interval between events (which
     185             :    is a stunningly bad idea in an distributed system and all too
     186             :    commonly done), we'd space housekeeping events by:
     187             : 
     188             :      async_target ~ tick_per_ns*lazy/event_cnt ticks
     189             : 
     190             :    where tick_per_ns is the conversion ratio to use between the
     191             :    wallclock and the tickrate of whatever counter is used to schedule
     192             :    housekeeping events.
     193             : 
     194             :    Consider using the largest integer power of two less than or equal to
     195             :    async_target for async_min.  In ns then, async_min will be at least
     196             :    ~0.5*lazy/event_cnt and at most lazy/event_cnt.  And since it takes,
     197             :    on average, 1.5*async_min*event_cnt to process a cycle, this value
     198             :    for async min will yield an average cycle time of at least ~0.75*lazy
     199             :    in ns and at most ~1.5*lazy ns. */
     200             : 
     201             : ulong
     202             : fd_tempo_async_min( long  lazy,
     203             :                     ulong event_cnt,
     204             :                     float tick_per_ns );
     205             : 
     206             : /* fd_tempo_async_reload returns a quality random number very quickly in
     207             :    [async_min,2*async_min).  Assumes async_min is an integer power of 2
     208             :    in [1,2^31].  Consumes exactly 1 rng slot.  This is typically used to
     209             :    randomize the timing of background task processing to avoid auto
     210             :    synchronization anomalies while providing given strong lower and
     211             :    upper bounds on the interval between between processing background
     212             :    tasks. */
     213             : 
     214             : static inline ulong
     215             : fd_tempo_async_reload( fd_rng_t * rng,
     216   135667342 :                        ulong      async_min ) {
     217   135667342 :   return async_min + (((ulong)fd_rng_uint( rng )) & (async_min-1UL));
     218   135667342 : }
     219             : 
     220             : FD_PROTOTYPES_END
     221             : 
     222             : #endif /* HEADER_fd_src_tango_tempo_fd_tempo_h */

Generated by: LCOV version 1.14