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).