Some Ado about Time between Python and ROS2

Posted by shengliangd's blog on January 9, 2021

Recently I needed synchronized time between a Python program and a piece of code in ROS2. With a little bit of googling I used time.monotonic() in Python and the clock given by rcl_steady_clock_init() in ROS2. However, when running them I observed around 20ms difference between them.

By inspecting the code in ROS2, I found that the time reported by a ‘steady clock’ is computed as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rcutils_ret_t
rcutils_steady_time_now(rcutils_time_point_value_t * now)
{
  RCUTILS_CHECK_ARGUMENT_FOR_NULL(now, RCUTILS_RET_INVALID_ARGUMENT);
  LARGE_INTEGER cpu_frequency, performance_count;
  // These should not ever fail since XP is already end of life:
  // From https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx and
  //      https://msdn.microsoft.com/en-us/library/windows/desktop/ms644904(v=vs.85).aspx:
  // "On systems that run Windows XP or later, the function will always succeed and will
  //  thus never return zero."
  QueryPerformanceFrequency(&cpu_frequency);
  QueryPerformanceCounter(&performance_count);
  // Calculate nanoseconds and seconds separately because
  // otherwise overflow can happen in intermediate calculations
  // This conversion will overflow if the PC runs >292 years non-stop
  const rcutils_time_point_value_t whole_seconds =
    performance_count.QuadPart / cpu_frequency.QuadPart;
  const rcutils_time_point_value_t remainder_count =
    performance_count.QuadPart % cpu_frequency.QuadPart;
  const rcutils_time_point_value_t remainder_ns =
    RCUTILS_S_TO_NS(remainder_count) / cpu_frequency.QuadPart;
  const rcutils_time_point_value_t total_ns =
    RCUTILS_S_TO_NS(whole_seconds) + remainder_ns;
  *now = total_ns;
  return RCUTILS_RET_OK;
}

That’s roughly computing the time from CPU performance counter.

Then what about Python’s time.monotonic()? The official document says:

Return the value (in fractional seconds) of a monotonic clock, i.e. a clock that cannot go backwards. The clock is not affected by system clock updates. The reference point of the returned value is undefined, so that only the difference between the results of consecutive calls is valid.

Yes, it doesn’t go backwards, and the reference point of the returned value is undefined. But in my case I have to make sure the time in Python and ROS2 the same, and it didn’t mention how it is implemented (even if it may be platform-dependent).

Then I noticed the section of Clock ID Constants. There are CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW. Googled with both of them lead me to Stack Overflow. It seems that ROS2 steady clock is exactly implemented the same as CLOCK_MONOTONIC_RAW. Then reconsider use of CLOCK_MONOTONIC_RAW · Issue #43 · ros2/rcutils (github.com) confirmed it. Then I just replaced the ROS2 steady clock with C version clock_gettime(CLOCK_MONOTONIC, &tspec) and the difference is gone.