Time
Ark generally uses std::chrono
for all of its timestamps and durations. This section
is broken into two major categories: helper routines for working with timestamps,
and time synchronization itself.
Internally, pipelines tend to work with “pipeline time”, which is represented by the steady clock (starting from zero when the pipeline starts). Transforms are provided to get into “global time” (represented by the system clock).
Helper APIs
This section lists some of the APIs used to help you work with time.
Formatting Time
The fmt
library is available and can format timestamps. There is also a set of
re-entrant, C++ focused time APIs for formatting time in ark/time/strftime.hh
.
For example:
std::chrono::system_clock::time_point tp{<unix time>};
std::cout << ark::time::strftime(localtime(tp)) << std::endl;
std::cout << ark::time::strftime(gmtime(tp)) << std::endl;
You can also provide a formatting string to strftime, which matches std::strftime’s formatting strings. The big difference between the APIs is that we are re-entrant by default and handle errors via exception throwing.
You can also format ‘durations’ (such as std::chrono::seconds) into a human-readable
string that looks like “X days, Y hours, Z minutes” (based on how large the number is)
by using the ark::time::format_duration
API, also defined in strftime.hh
.
Hardware Clock
You can synchronized the current system clock to the hardware clock (or fake
hardware clock) by using the sync_hardware_clock()
API, defined in ark/time/hwclock.hh
.
Steady/System Conversion
It can be useful (although likely ill-advised) to convert between system time and steady time on the same machine. For example, if you have a system timestamp at X, and want to know the steady time at system time X, you can retrieve that.
This uses a series of time calls to build a quick time model between system time and steady time to return the likely conversion. This is mostly useful for low-level APIs that return time in one clock or the other, but not both.
See ark/time/convert.hh
and the system_to_steady_time()
or steady_to_system_time()
APIs.
Time Synchronization
There are a few APIs that assist with time synchronization.
Time Estimator
This class will take in timestamps from two different systems (a local time and a remote time), and build up a time model that will give you a translation from local to remote.
Additionally, it takes latency into account, so is suitable for estimating time sync between remote machines. Internally, it works by filtering out some timestamps and building a linear regression to estimate both offset and drift.
As an example, where you are translating from one steady clock to another steady clock:
using SteadyClock = std::chrono::steady_clock;
time::TimeEstimator<SteadyClock, SteadyClock> estimator;
estimator.consider_time_pair(SteadyClock::time_point{5s}, SteadyClock::time_point{10s}, 0ns);
estimator.consider_time_pair(SteadyClock::time_point{6s}, SteadyClock::time_point{11s}, 5ns);
estimator.consider_time_pair(SteadyClock::time_point{7s}, SteadyClock::time_point{12s}, -10ns);
auto model = estimator.estimate();
The model can then be used to transform local time (such as ‘7’) to a remote time (in the case of ‘7’, it would be ‘12’):
auto time = model.local_to_remote(Clock::time_point{7s});
Note that the two clocks do not need to be the same. In this example, we translate from
a SteadyClock
type to a SystemClock
type:
using SteadyClock = std::chrono::steady_clock;
using SystemClock = std::chrono::system_clock;
time::TimeEstimator<SteadyClock, SystemClock> estimator;
estimator.consider_time_pair(SteadyClock::time_point{1s}, SystemClock::time_point{1645629564s}, 0ns);
estimator.consider_time_pair(SteadyClock::time_point{2s}, SystemClock::time_point{1645629565s}, 0ns);
estimator.consider_time_pair(SteadyClock::time_point{3s}, SystemClock::time_point{1645629566s}, 0ns);
auto model = estimator.estimate();
auto now_utc = model.local_to_remote(SteadyClock::time_point{2500ms});
At this point, the model allows you to translate from the local time (starting at 1s) to the UTC
time, and you can get the timestamps back as SystemClock::time_point
structures.
Rollover Clock
There is a roll-over aware clock that can handle taking 32-bit clocks and will watch for “rollovers”. For example, if you had a 32-bit clock that was in microseconds, it will reset every hour and fifteen minutes or so. The rollover clock will track that, so you can use that 32-bit clock and not worry about rollover.
You will likely want to log the rollover count so you can reconstruct where the clock is when restoring it from logs or serialization.
RolloverAwareClock clock;
std::cout << clock.now() << ", " << clock.rollover_count() << std::endl;
clock.consider_timestamp(4294967295);
clock.consider_timestamp(20);
std::cout << clock.now() << ", " << clock.rollover_count() << std::endl;
This will print out something like:
0, 0
4294967315, 1
NTP Client
A very simple NTP client is provided, for quick and easy time synchronization in your pipeline without bringing in other packages.
See the ark/time/ntp_client.hh
header. This allows you to instantiate a client
against a particular NTP server, and then you invoke poll
periodically for it
to query the remote server and produce time pairs (which can then be fed into
a time estimator).
It is likely better to just use the NTP Client Stage
that is provided – it will
handle this for you, and emit a global time periodically (or optionally sync the system
clock).