1.. _module-pw_chrono: 2 3========= 4pw_chrono 5========= 6Pigweed's chrono module provides facilities for applications to deal with time, 7leveraging many pieces of STL's the ``std::chrono`` library but with a focus 8on portability for constrained embedded devices and maintaining correctness. 9 10.. note:: 11 This module is still under construction, the API is not yet stable. 12 13------------------------------- 14``duration`` and ``time_point`` 15------------------------------- 16Pigweed's time primitives rely on C++'s 17`<chrono> <https://en.cppreference.com/w/cpp/header/chrono>`_ library to enable 18users to express intents with strongly typed real time units through 19`std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_ 20and 21`std::chrono::time_point <https://en.cppreference.com/w/cpp/chrono/time_point>`_ 22. 23 24What are they? 25============== 26At a high level, durations and time_points at run time are tick counts which 27are wrapped in templated metadata which is only used at compile time. 28 29The STL's 30`std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_ 31class template represents a time interval. It consists of a count of ticks of 32type ``rep`` and a tick ``period``, where the tick period is a ``std::ratio`` 33representing the time in seconds from one tick to the next. 34 35The only data stored in a duration is a tick count of type ``rep``. The 36``period`` is included as part of the duration's type, and is only used when 37converting between different durations. 38 39Similarly, the STL's 40`std::chrono::time_point <https://en.cppreference.com/w/cpp/chrono/time_point>`_ 41class template represents a point in time (i.e. timestamp). It consists of a 42value of type ``duration`` which represents the time interval from the start of 43the ``clock``'s epoch. 44 45The ``duration`` and ``time_point`` class templates can be represented with the 46following simplified model, ignoring most of their member functions: 47 48.. code-block:: cpp 49 50 namespace std::chrono { 51 52 template<class Rep, class Period = std::ratio<1, 1>> 53 class duration { 54 public: 55 using rep = Rep; 56 using period = Period; 57 58 constexpr rep count() const { return tick_count_; } 59 60 static constexpr duration zero() noexcept { 61 return duration(0); 62 } 63 64 // Other member functions... 65 66 private: 67 rep tick_count_; 68 }; 69 70 template<class Clock, class Duration = typename Clock::duration> 71 class time_point { 72 public: 73 using duration = Duration; 74 using rep = Duration::rep; 75 using period = Duration::period; 76 using clock = Clock; 77 78 constexpr duration time_since_epoch() const { return time_since_epoch_; } 79 80 // Other member functions... 81 82 private: 83 duration time_since_epoch_; 84 }; 85 86 } // namespace std::chrono 87 88What ``rep`` type should be used? 89================================= 90The duration's ``rep``, or tick count type, can be a floating point or a signed 91integer. For most applications, this is a signed integer just as how one may 92represent the number of ticks for an RTOS API or the number of nanoseconds in 93POSIX. 94 95The ``rep`` should be able to represent the durations of time necessary for the 96application. Pigweed recommends that the duration's ``rep`` used for a clock use 97``int64_t`` in order to trivially avoid integer underflow and overflow risks by 98covering a range of at least ±292 years. This matches the STL's requirements 99for the duration helper types which are relevant for a clock's tick period: 100 101* ``std::chrono::nanoseconds duration</*signed integer type of at least 64 bits*/, std::nano>`` 102* ``std::chrono::microseconds duration</*signed integer type of at least 55 bits*/, std::micro>`` 103* ``std::chrono::milliseconds duration</*signed integer type of at least 45 bits*/, std::milli>`` 104* ``std::chrono::seconds duration</*signed integer type of at least 35 bits*/>`` 105 106With this guidance one can avoid common pitfalls like ``uint32_t`` millisecond 107tick rollover bugs when using RTOSes every 49.7 days. 108 109.. warning:: 110 We recommend avoiding the ``duration<>::min()`` and ``duration<>::max()`` 111 helper member functions where possible as they exceed the ±292 years duration 112 limit assumption. There's an immediate risk of integer underflow or overflow 113 for any arithmetic operations. Consider using ``std::optional`` instead of 114 priming a variable with a value at the limit. 115 116Helper duration types and literals 117================================== 118The STL's ``<chrono>`` library includes a set of helper types based on actual 119time units, including the following (and more): 120 121* ``std::chrono::nanoseconds`` 122* ``std::chrono::microseconds`` 123* ``std::chrono::milliseconds`` 124* ``std::chrono::seconds`` 125* ``std::chrono::minutes`` 126* ``std::chrono::hours`` 127 128As an example you can use these as follows: 129 130.. code-block:: cpp 131 132 #include <chrono> 133 134 void Foo() { 135 Bar(std::chrono::milliseconds(42)); 136 } 137 138In addition, the inline namespace ``std::literals::chrono_literals`` includes: 139 140* ``operator""ns`` for ``std::chrono::nanoseconds`` 141* ``operator""us`` for ``std::chrono::microseconds`` 142* ``operator""ms`` for ``std::chrono::milliseconds`` 143* ``operator""s`` for ``std::chrono::seconds`` 144* ``operator""min`` for ``std::chrono::minutes`` 145* ``operator""h`` for ``std::chrono::hours`` 146 147As an example you can use these as follows: 148 149.. code-block:: cpp 150 151 using std::literals::chrono_literals::ms; 152 // Or if you want them all: using namespace std::chrono_literals; 153 154 void Foo() { 155 Bar(42ms); 156 } 157 158For these helper duration types to be compatible with API's that take a 159`SystemClock::duration` either an :ref:`implicit<Implicit lossless conversions>` 160or :ref:`explicit lossy<Explicit lossy conversions>` conversion must be done. 161 162Converting between time units and clock durations 163================================================= 164So why go through all of this trouble instead of just using ticks or instead 165just using one time unit such as nanoseconds? For example, imagine that you have 166a 1kHz RTOS tick period and you would like to express a timeout duration: 167 168.. code-block:: cpp 169 170 // Instead of using ticks which are not portable between RTOS configurations, 171 // as the tick period may be different: 172 constexpr uint32_t kFooNotificationTimeoutTicks = 42; 173 bool TryGetNotificationFor(uint32_t ticks); 174 175 // And instead of using a time unit which is prone to accidental conversion 176 // errors as all variables must maintain the time units: 177 constexpr uint32_t kFooNotificationTimeoutMs = 42; 178 bool TryGetNotificationFor(uint32_t milliseconds); 179 180 // We can instead use a defined clock and its duration for the kernel and rely 181 // on implicit lossless conversions: 182 #include <chrono> 183 #include "pw_chrono/system_clock.h" 184 constexpr SystemClock::duration kFooNotificationTimeout = 185 std::chrono::milliseconds(42); 186 bool TryGetNotificationFor(SystemClock::duration timeout); 187 188 void MaybeProcessNotification() { 189 if (TryGetNotificationFor(kFooNotificationTimeout)) { 190 ProcessNotification(); 191 } 192 } 193 194.. _Implicit lossless conversions: 195 196Implicit lossless conversions 197----------------------------- 198Wait, but how does this work? Is there a hidden cost? The ``duration`` type 199comes with built in implicit lossless conversion support which is evaluated at 200compile time where possible. 201 202If you rely on implicit conversions then the worst case cost is multiplication, 203there is no risk of a division operation. 204 205If the implicit conversion cannot be guaranteed at compile time to be lossless 206for all possible tick count values, then it will fail to compile. 207 208As an example you can always convert from ``std::chrono::seconds`` to 209``std::chrono::milliseconds`` in a lossless manner. However, you cannot 210guarantee for all tick count values that ``std::chrono::milliseconds`` can be 211losslessly converted to ``std::chrono::seconds``, even though it may work for 212some values like ``0``, ``1000``, etc. 213 214.. code-block:: cpp 215 216 #include <chrono> 217 218 constexpr std::chrono::milliseconds this_compiles = 219 std::chrono::seconds(42); 220 221 // This cannot compile, because for some duration values it is lossy even 222 // though this particular value can be in theory converted to whole seconds. 223 // constexpr std::chrono::seconds this_does_not_compile = 224 // std::chrono::milliseconds(1000); 225 226.. _Explicit lossy conversions: 227 228Explicit lossy conversions 229-------------------------- 230Although we recommend sticking to implicit lossless conversions, what if for 231some reason a lossy conversion is required? For example what if we're using a 232128Hz RTOS tick clock? 233 234The 128Hz ``period`` can be perfectly represented with a ``std::ratio<1,128>``. 235However you will not be able to implicitly convert any real time unit durations 236to this duration type. Instead explicit lossy conversions must be used. Pigweed 237recommends explicitly using: 238 239* `std::chrono::floor <https://en.cppreference.com/w/cpp/chrono/duration/floor>`_ 240 to round down. 241* `std::chrono::round <https://en.cppreference.com/w/cpp/chrono/duration/round>`_ 242 to round to the nearest, rounding to even in halfway cases. 243* `std::chrono::ceil <https://en.cppreference.com/w/cpp/chrono/duration/ceil>`_ 244 to round up. 245* `pw::chrono::SystemClock::for_at_least` to round up using the `SystemClock::period`, 246 as a more explicit form of std::chrono::ceil. 247 248.. Note:: 249 Pigweed does not recommend using ``std::chrono::duration_cast<>`` which 250 truncates dowards zero like ``static_cast``. This is typically not the desired 251 rounding behavior when dealing with time units. Instead, where possible we 252 recommend the more explicit, self-documenting ``std::chrono::floor``, 253 ``std::chrono::round``, and ``std::chrono::ceil``. 254 255Now knowing this, the previous example could be portably and correctly handled 256as follows: 257 258.. code-block:: cpp 259 260 #include <chrono> 261 262 #include "pw_chrono/system_clock.h" 263 264 // We want to round up to ensure we block for at least the specified duration, 265 // instead of rounding down. Imagine for example the extreme case where you 266 // may round down to zero or one, you would definitely want to at least block. 267 constexpr SystemClock::duration kFooNotificationTimeout = 268 std::chrono::ceil(std::chrono::milliseconds(42)); 269 bool TryGetNotificationFor(SystemClock::duration timeout); 270 271 void MaybeProcessNotification() { 272 if (TryGetNotificationFor(kFooNotificationTimeout)) { 273 ProcessNotification(); 274 } 275 } 276 277This code is lossless if the clock period is 1kHz and it's correct using a 278division which rounds up when the clock period is 128Hz. 279 280.. Note:: 281 When using ``pw::chrono::SystemClock::duration`` for timeouts we recommend 282 using its ``SystemClock::for_at_least()`` to round up timeouts in a more 283 explicit, self documenting manner which uses ``std::chrono::ceil`` internally. 284 285Use of ``count()`` and ``time_since_epoch()`` 286============================================= 287It's easy to escape the typesafe chrono types through the use of 288``duration<>::count()`` and ``time_point<>::time_since_epoch()``, however this 289increases the risk of accidentally introduce conversion and arithmetic errors. 290 291For this reason, we recommend avoiding these two escape hatches until it's 292absolutely necessary due to I/O such as RPCs or writing to non-volatile storage. 293 294Discrete Timeouts 295================= 296We briefly want to mention a common pitfall when working with discrete 297representations of time durations for timeouts (ticks and real time units) 298on systems with a continously running clock which is backed by discrete time 299intervals (i.e. whole integer constant tick periods). 300 301Imagine an RTOS system where we have a constant tick interval. If we attempt to 302sleep for 1 tick, how long will the kernel actually let us sleep? 303 304In most kernels you will end up sleeping somewhere between 0 and 1 tick periods 305inclusively, i.e. ``[0, 1]``, if we ignore scheduling latency and preemption. 306**This means it can randomly be non-blocking vs blocking!** 307 308This is because internally kernels use a decrementing timeout counter or a 309deadline without taking the current current progression through the existing 310tick period into account. 311 312For this reason all of Pigweed's time bound APIs will internally add an extra 313tick to timeout intents when needed to guarantee that we will block for at least 314the specified timeout. 315 316This same risk exists if a continuously running hardware timer is used for a 317software timer service. 318 319.. Note:: 320 When calculating deadlines based on a timeout when using 321 ``pw::chrono::SystemClock::timeout``, we recommend using its 322 ``SystemClock::TimePointAfterAtLeast()`` which adds an extra tick for you 323 internally. 324 325------ 326Clocks 327------ 328We do not recomend using the clocks provided by ``<chrono>`` including but not 329limited to the ``std::chrono::system_clock``, ``std::chrono::steady_clock``, and 330``std::chrono::high_resolution_clock``. These clocks typically do not work on 331embedded systems, as they are not backed by any actual clocks although they 332often do compile. In addition, their APIs miss guarantees and parameters which 333make them difficult and risky to use on embedded systems. 334 335In addition, the STL time bound APIs heavily rely on templating to permit 336different clocks and durations to be used. We believe this level of template 337metaprogramming and the indirection that comes with that can be confusing. On 338top of this, accidental use of the wrong clock and/or conversions between them 339is a frequent source of bugs. For example using a real time clock which is not 340monotonic for a timeout or deadline can wreak havoc when the clock is adjusted. 341 342For this reason Pigweed's timeout and deadline APIs will not permit arbitrary 343clock and duration selection. Outside of small templated helpers, all APIs will 344require a specific clock's duration and/or time-point. For almost all of Pigweed 345this means that the ``pw::chrono::SystemClock`` is used which is usually backed 346by the kernel's clock. 347 348PigweedClock Requirements 349========================= 350``pw_chrono`` extends the C++ named 351`Clock <https://en.cppreference.com/w/cpp/named_req/Clock>`_ and 352`TrivialClock <https://en.cppreference.com/w/cpp/named_req/TrivialClock>`_ 353requirements with the ``PigweedClock Requirements`` to make clocks more friendly 354for embedded systems. 355 356This permits the clock compatibility to be verified through ``static_assert`` at 357compile time which the STL's requirements do not address. For example whether 358the clock continues to tick while interrupts are masked or whether the clock is 359monotonic even if the clock period may not be steady due to the use of low power 360sleep modes. 361 362For a type ``PWC`` to meet the ``PigweedClock Requirements``: 363 364* The type PWC must meet C++14's 365 `Clock <https://en.cppreference.com/w/cpp/named_req/Clock>`_ and 366 `TrivialClock <https://en.cppreference.com/w/cpp/named_req/TrivialClock>`_ 367 requirements. 368* The ``PWC::rep`` must be ``int64_t`` to ensure that there cannot be any 369 overflow risk regardless of the ``PWC::period`` configuration. 370 This is done because we do not expect any clocks with periods coarser than 371 seconds which already require 35 bits. 372* ``const bool PWC::is_monotonic`` must return true if and only if the clock 373 can never move backwards. 374 This effectively allows one to describe an unsteady but monotonic clock by 375 combining the C++14's Clock requirement's ``const bool PWC::is_steady``. 376* ``const bool PWC::is_free_running`` must return true if and only if the clock 377 continues to move forward, without risk of overflow, regardless of whether 378 global interrupts are disabled or whether one is in a critical section or even 379 non maskable interrupt. 380* ``const bool PWC::is_always_enabled`` must return true if the clock is always 381 enabled and available. If false, the clock must: 382 383 + Ensure the ``const bool is_{steady,monotonic,free_running}`` attributes 384 are all valid while the clock is not enabled to ensure they properly meet 385 the previously stated requirements. 386 + Meet C++14's 387 `BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_ 388 requirements (i.e. provide ``void lock()`` & ``void unlock()``) in order 389 to provide ``std::scoped_lock`` support to enable a user to enable the 390 clock. 391 + Provide ``const bool is_{steady,monotonic,free_running}_while_enabled`` 392 attributes which meet the attributes only while the clock is enabled. 393* ``const bool PWC::is_stopped_in_halting_debug_mode`` must return true if and 394 only if the clock halts, without further modification, during halting debug 395 mode , for example during a breakpoint while a hardware debugger is used. 396* ``const Epoch PWC::epoch`` must return the epoch type of the clock, the 397 ``Epoch`` enumeration is defined in ``pw_chrono/epoch.h``. 398* The function ``time_point PWC::now() noexcept`` must always be thread and 399 interrupt safe, but not necessarily non-masking and bare-metal interrupt safe. 400* ``const bool PWC::is_non_masking_interrupt_safe`` must return true if and only 401 if the clock is safe to use from non-masking and bare-metal interrupts. 402 403The PigweedClock requirement will not require ``now()`` to be a static function, 404however the upstream façades will follow this approach. 405 406SystemClock facade 407================== 408The ``pw::chrono::SystemClock`` is meant to serve as the clock used for time 409bound operations such as thread sleeping, waiting on mutexes/semaphores, etc. 410The ``SystemClock`` always uses a signed 64 bit as the underlying type for time 411points and durations. This means users do not have to worry about clock overflow 412risk as long as rational durations and time points as used, i.e. within a range 413of ±292 years. 414 415The ``SystemClock`` represents an unsteady, monotonic clock. 416 417The epoch of this clock is unspecified and may not be related to wall time 418(for example, it can be time since boot). The time between ticks of this 419clock may vary due to sleep modes and potential interrupt handling. 420``SystemClock`` meets the requirements of C++'s ``TrivialClock`` and Pigweed's 421``PigweedClock``. 422 423This clock is used for expressing timeout and deadline semantics with the 424scheduler in Pigweed including pw_sync, pw_thread, etc. 425 426C++ 427--- 428.. doxygenstruct:: pw::chrono::SystemClock 429 :members: 430 431Example in C++ 432-------------- 433.. code-block:: cpp 434 435 #include <chrono> 436 437 #include "pw_chrono/system_clock.h" 438 439 void Foo() { 440 const SystemClock::time_point before = SystemClock::now(); 441 TakesALongTime(); 442 const SystemClock::duration time_taken = SystemClock::now() - before; 443 bool took_way_too_long = false; 444 if (time_taken > std::chrono::seconds(42)) { 445 took_way_too_long = true; 446 } 447 } 448 449Protobuf 450======== 451Sometimes it's desirable to communicate high resolution time points and 452durations from one device to another. For this, ``pw_chrono`` provides protobuf 453representations of clock parameters (``pw.chrono.ClockParameters``) and time 454points (``pw.chrono.TimePoint``). These types are less succinct than simple 455single-purpose fields like ``ms_since_boot`` or ``unix_timestamp``, but allow 456timestamps to be communicated in terms of the tick rate of a device, potentially 457providing significantly higher resolution. Logging, tracing, and system state 458snapshots are use cases that benefit from this additional resolution. 459 460This module provides an overlay proto (``pw.chrono.SnapshotTimestamps``) for 461usage with ``pw_snapshot`` to encourage capture of high resolution timestamps 462in device snapshots. Simplified capture utilies and host-side tooling to 463interpret this data are not yet provided by ``pw_chrono``. 464 465There is tooling that take these proto and make them more human readable. 466 467--------------- 468Software Timers 469--------------- 470 471SystemTimer facade 472================== 473The SystemTimer facade enables deferring execution of a callback until a later 474time. For example, enabling low power mode after a period of inactivity. 475 476The base SystemTimer only supports a one-shot style timer with a callback. 477A periodic timer can be implemented by rescheduling the timer in the callback 478through ``InvokeAt(kDesiredPeriod + expired_deadline)``. 479 480When implementing a periodic layer on top, the user should be mindful of 481handling missed periodic callbacks. They could opt to invoke the callback 482multiple times with the expected ``expired_deadline`` values or instead saturate 483and invoke the callback only once with the latest ``expired_deadline``. 484 485The entire API is thread safe, however it is NOT always IRQ safe. 486 487The ExpiryCallback is either invoked from a high priority thread or an 488interrupt. Ergo ExpiryCallbacks should be treated as if they are executed by an 489interrupt, meaning: 490 491* Processing inside of the callback should be kept to a minimum. 492 493* Callbacks should never attempt to block. 494 495* APIs which are not interrupt safe such as pw::sync::Mutex should not be used! 496 497C++ 498--- 499.. doxygenclass:: pw::chrono::SystemTimer 500 :members: 501 502.. cpp:namespace-push:: pw::chrono::SystemTimer 503 504.. list-table:: 505 :widths: 70 10 10 10 506 :header-rows: 1 507 508 * - Safe to use in context 509 - Thread 510 - Interrupt 511 - NMI 512 * - :cpp:func:`pw::chrono::SystemTimer::SystemTimer` 513 - ✔ 514 - 515 - 516 * - :cpp:func:`pw::chrono::SystemTimer::~SystemTimer` 517 - ✔ 518 - 519 - 520 * - :cpp:func:`InvokeAfter` 521 - ✔ 522 - 523 - 524 * - :cpp:func:`InvokeAt` 525 - ✔ 526 - 527 - 528 * - :cpp:func:`Cancel` 529 - ✔ 530 - 531 - 532 533.. cpp:namespace-pop:: 534 535Example in C++ 536-------------- 537.. code-block:: cpp 538 539 #include "pw_chrono/system_clock.h" 540 #include "pw_chrono/system_timer.h" 541 #include "pw_log/log.h" 542 543 using namespace std::chrono_literals; 544 545 void DoFoo(pw::chrono::SystemClock::time_point expired_deadline) { 546 PW_LOG_INFO("Timer callback invoked!"); 547 } 548 549 pw::chrono::SystemTimer foo_timer(DoFoo); 550 551 void DoFooLater() { 552 foo_timer.InvokeAfter(42ms); // DoFoo will be invoked after 42ms. 553 } 554