-
-
Notifications
You must be signed in to change notification settings - Fork 604
Telling the time
As OSv is ABI-compatible with Linux, applications or tests can always use the familiar Posix and C++11 APIs that work on both Linux and OSv. Examples include Posix's time(2), clock_gettime(2), gettimeofday(2), and C++11's std::chrono::high_resolution_clock::now() and friends.
But in OSv code, it is faster and nicer to use our own API.
Our API is based on the C++11's std::chrono type system, which is based on two concepts, a "time_point", and a "duration".
A time_point (std::chrono::time_point<...>), as its name suggests, is a point in time of a particular clock. For example in our API, we have
#include <osv/clock.hh>
auto now = osv::clock::uptime::now();
After that, "now" is a time point, representing the current time using the uptime clock (the monotonic clock ticking since OSv's boot). Internally, this timepoint will contain a s64 counting the number of nanoseconds since boot, but the important thing is that now's type contains the information that 1. it was determined using the uptime clock (not the wall clock), and 2. that its precision is nanoseconds. This guarantees that these cannot be confused later, as we'll see.
Now, what happens if we subtract two time_points from the same clock?
auto now = osv::clock::uptime::now();
// do something
....
auto later = osv::clock::uptime::now();
auto diff = later - now;
"diff", the subtraction of two timepoints from the same clock, is a duration (std::chrono::duration<...>). A duration is again a counter of ticks (in our case s64), which is aware of the tick's length - in this case "diff" is using nanoseconds (i.e., diff's type is really std::chrono::nanoseconds).
If we want to print the number of nanoseconds in this duration, the bad way to do it is this:
std::cout << "Number of ns passed: " << diff.count() << "\n";
This is bad, because it assumes that our uptime clock counts in nanoseconds. This happens to be true, but there is no reason to assume this will always be the case. C++11 provides us with a much better way:
using namespace std::chrono;
std::cout << "Number of ns passed: " << duration_cast<nanoseconds>(diff).count() << "\n";
std::chrono::duration_cast<std::chrono::nanoseconds>
takes a duration which uses any resolution, and converts it to the desired resolution (nanoseconds). This is done at compile time - the compiler already knows the resolution our "diff" used - so this conversion is compiled to absolutely no code. But the above syntax ensures this line will continue to work as intended - printing nanoseconds - even if our uptime clock is changed to work in milliseconds, in picoseconds, or in femtofortnights.
We can easily use the same trick to print the same duration in other units, e.g., milliseconds:
std::cout << "Number of ms passed: " << duration_cast<milliseconds>(diff).count() << "\n";
Again, the code that the compiler actually generates for this simply takes the nanosecond counter in diff and divides it by 1,000,000. But this is done without risk of error, and will continue to work even if the uptime clock's resolution change.
In addition to the uptime clock osv::clock::uptime::now()
, we also have the wall clock: osv::clock::wall::now()
. This gives our best estimate of the wall-clock, again with nanosecond resolution. This clock's epoch is the UNIX epoch (midnight, Jan 1st, 1970).
For examples, to get the number of second since the epoch, we can do this
auto wall_time = osv::clock::wall::now();
auto dur = wall_time.time_since_epoch();
using namespace std::chrono;
auto secs = duration_cast<seconds>(dur).count();
The "dur", as a difference of times, is again a std::chrono::duration
- the duration from the epoch until now, and we converted this duration to seconds with duration_cast
, as above.
As before, the compiler will automatically figure out that dur uses nanoseconds (our wall clock has nanosecdond resolution) and generate code to divide this counter by one billion to get the number of seconds.
Durations don't always result from subtraction of two time_points - you often want to create durations directly.
For example, our sched::sleep()
function takes a duration to determine how much to sleep. So one can write:
sched::sleep(std::chrono::milliseconds(10));
sched::sleep(std::chrono::seconds(2));
In this example, std::chrono::milliseconds(10)
creates, as expected, a duration of 10 milliseconds.
The beautiful thing about the above is that you don't care about which tick resolution sleep() actually uses. It turns out that sleep() uses nanoseconds internally (it uses the uptime clock, which has this resolution). But all the conversions happen automatically, and they happen efficiently, often during compile time. For sleep(milliseconds(10))
the conversion of 10ms to 10,000,0000ns will happen completely at compile time. For sleep(milliseconds(a))
, the compiler will generate code for a*1,000,000
- with no additional checking or processing at runtime.
As a shortcut to the above std::chrono::milliseconds()
et al., we also implement C++11 so-called "user-defined literals". The above two lines can be written as the following two, with identical meaning:
using namespace osv::clock::literals;
sched::sleep(10_ms);
sched::sleep(2_s);
Note how the the C++11 std::chrono
type system protects you from many stupid mistakes. When you pass 10_ms, the compiler knows it is milliseconds so the called function (sleep) cannot acidentally think it is nanoseconds. You cannot accidentally pass an absolute time (osv::clock::wall::now()
) to a function taking relative time (sched::sleep
) because time_point and duration are different type.
Finally, someone will probably ask this so I better just give an answer now:
You probably don't, but if you really need the number of nanoseconds since the Unix epoch, like the old nanotime() function gave you, one way you can do this is using the APIs I explained above:
std::chrono::duration_cast<std::chrono::nanoseconds>(osv::clock::wall::now().time_since_epoch()).count()
A shorter version is also available, but not recommended:
clock::get()->time()