Add a new notion of "stamps" to be a fast 32-bit monotonic timestamp

The goal here is to replace our use of msec-based timestamps with
something less precise, but easier to calculate.  We're doing this
because calculating lots of msec-based timestamps requires lots of
64/32 division operations, which can be inefficient on 32-bit
platforms.

We make sure that these stamps can be calculated using only the
coarse monotonic timer and 32-bit bitwise operations.
This commit is contained in:
Nick Mathewson 2017-11-21 12:28:16 -05:00
parent da82e0b579
commit c3c0a05f51
10 changed files with 171 additions and 66 deletions

View File

@ -472,7 +472,7 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped)
chunk = chunk_new_with_alloc_size(buf_preferred_chunk_size(capacity));
}
chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec();
chunk->inserted_time = monotime_coarse_get_stamp();
if (buf->tail) {
tor_assert(buf->head);
@ -487,8 +487,8 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped)
}
/** Return the age of the oldest chunk in the buffer <b>buf</b>, in
* milliseconds. Requires the current monotonic time, in truncated msec,
* as its input <b>now</b>.
* timestamp units. Requires the current monotonic timestamp as its
* input <b>now</b>.
*/
uint32_t
buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now)

View File

@ -79,8 +79,7 @@ typedef struct chunk_t {
size_t DBG_alloc;
#endif
char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */
uint32_t inserted_time; /**< Timestamp in truncated ms since epoch
* when this chunk was inserted. */
uint32_t inserted_time; /**< Timestamp when this chunk was inserted. */
char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in
* this chunk. */
} chunk_t;

View File

@ -279,6 +279,7 @@ monotime_reset_ratchets_for_testing(void)
* nanoseconds.
*/
static struct mach_timebase_info mach_time_info;
static int monotime_shift = 0;
static void
monotime_init_internal(void)
@ -287,6 +288,14 @@ monotime_init_internal(void)
int r = mach_timebase_info(&mach_time_info);
tor_assert(r == 0);
tor_assert(mach_time_info.denom != 0);
{
// approximate only.
uint64_t ns_per_tick = mach_time_info.numer / mach_time_info.denom;
uint64_t ms_per_tick = ns_per_tick * ONE_MILLION;
// requires that tor_log2(0) == 0.
monotime_shift = tor_log2(ms_per_tick);
}
}
/**
@ -321,6 +330,12 @@ monotime_diff_nsec(const monotime_t *start,
return diff_nsec;
}
uint32_t
monotime_coarse_to_stamp(const monotime_coarse_t *t)
{
return (uint32_t)(t->abstime_ >> monotime_shift);
}
/* end of "__APPLE__" */
#elif defined(HAVE_CLOCK_GETTIME)
@ -399,6 +414,18 @@ monotime_diff_nsec(const monotime_t *start,
return diff_nsec;
}
/* This value is ONE_BILLION >> 20. */
const uint32_t STAMP_TICKS_PER_SECOND = 953;
uint32_t
monotime_coarse_to_stamp(const monotime_coarse_t *t)
{
uint32_t nsec = (uint32_t)t->ts_.tv_nsec;
uint32_t sec = (uint32_t)t->ts_.tv_sec;
return (sec * STAMP_TICKS_PER_SECOND) + (nsec >> 20);
}
/* end of "HAVE_CLOCK_GETTIME" */
#elif defined (_WIN32)
@ -531,6 +558,14 @@ monotime_coarse_diff_nsec(const monotime_coarse_t *start,
return monotime_coarse_diff_msec(start, end) * ONE_MILLION;
}
const uint32_t STAMP_TICKS_PER_SECOND = 1000;
uint32_t
monotime_coarse_to_stamp(const monotime_coarse_t *t)
{
return (uint32_t) t->tick_count;
}
/* end of "_WIN32" */
#elif defined(MONOTIME_USING_GETTIMEOFDAY)
@ -567,6 +602,17 @@ monotime_diff_nsec(const monotime_t *start,
return (diff.tv_sec * ONE_BILLION + diff.tv_usec * 1000);
}
/* This value is ONE_MILLION >> 10. */
const uint32_t STAMP_TICKS_PER_SECOND = 976;
uint32_t
monotime_coarse_to_stamp(const monotime_coarse_t *t)
{
const uint32_t usec = (uint32_t)t->tv_.tv_usec;
const uint32_t sec = (uint32_t)t->tv_.tv_sec;
return (sec * STAMP_TICKS_PER_SECOND) | (nsec >> 10);
}
/* end of "MONOTIME_USING_GETTIMEOFDAY" */
#else
#error "No way to implement monotonic timers."
@ -653,5 +699,35 @@ monotime_coarse_absolute_msec(void)
{
return monotime_coarse_absolute_nsec() / ONE_MILLION;
}
#else
#define initalized_at_coarse initialized_at
#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */
/**
* Return the current time "stamp" as described by monotime_coarse_to_stamp.
*/
uint32_t
monotime_coarse_get_stamp(void)
{
monotime_coarse_t now;
monotime_coarse_get(&now);
return monotime_coarse_to_stamp(&now);
}
#ifdef __APPLE__
uint64_t
monotime_coarse_stamp_units_to_approx_msec(uint64_t units)
{
/* Recover as much precision as we can. */
uint64_t abstime_diff = (units << monotime_shift);
return (abstime_diff * mach_time_info.numer) /
(mach_time_info.denom * ONE_MILLION);
}
#else
uint64_t
monotime_coarse_stamp_units_to_approx_msec(uint64_t units)
{
return (units * 1000) / STAMP_TICKS_PER_SECOND;
}
#endif

View File

@ -117,6 +117,23 @@ uint64_t monotime_coarse_absolute_msec(void);
#define monotime_coarse_absolute_msec monotime_absolute_msec
#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */
/**
* Return a "timestamp" approximation for a coarse monotonic timer.
* This timestamp is meant to be fast to calculate and easy to
* compare, and have a unit of something roughly around 1 msec.
*
* It will wrap over from time to time.
*
* It has no defined zero point.
*/
uint32_t monotime_coarse_to_stamp(const monotime_coarse_t *t);
/**
* Convert a difference, expressed in the units of monotime_coarse_to_stamp,
* into an approximate number of milliseconds.
*/
uint64_t monotime_coarse_stamp_units_to_approx_msec(uint64_t units);
uint32_t monotime_coarse_get_stamp(void);
#if defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT)
int64_t monotime_coarse_diff_nsec(const monotime_coarse_t *start,
const monotime_coarse_t *end);

View File

@ -2176,12 +2176,12 @@ n_cells_in_circ_queues(const circuit_t *c)
}
/**
* Return the age of the oldest cell queued on <b>c</b>, in milliseconds.
* Return the age of the oldest cell queued on <b>c</b>, in timestamp units.
* Return 0 if there are no cells queued on c. Requires that <b>now</b> be
* the current time in milliseconds since the epoch, truncated.
* the current coarse timestamp.
*
* This function will return incorrect results if the oldest cell queued on
* the circuit is older than 2**32 msec (about 49 days) old.
* the circuit is older than about 2**32 msec (about 49 days) old.
*/
STATIC uint32_t
circuit_max_queued_cell_age(const circuit_t *c, uint32_t now)
@ -2190,12 +2190,12 @@ circuit_max_queued_cell_age(const circuit_t *c, uint32_t now)
packed_cell_t *cell;
if (NULL != (cell = TOR_SIMPLEQ_FIRST(&c->n_chan_cells.head)))
age = now - cell->inserted_time;
age = now - cell->inserted_timestamp;
if (! CIRCUIT_IS_ORIGIN(c)) {
const or_circuit_t *orcirc = CONST_TO_OR_CIRCUIT(c);
if (NULL != (cell = TOR_SIMPLEQ_FIRST(&orcirc->p_chan_cells.head))) {
uint32_t age2 = now - cell->inserted_time;
uint32_t age2 = now - cell->inserted_timestamp;
if (age2 > age)
return age2;
}
@ -2203,31 +2203,30 @@ circuit_max_queued_cell_age(const circuit_t *c, uint32_t now)
return age;
}
/** Return the age in milliseconds of the oldest buffer chunk on <b>conn</b>,
* where age is taken in milliseconds before the time <b>now</b> (in truncated
* absolute monotonic msec). If the connection has no data, treat
* it as having age zero.
/** Return the age of the oldest buffer chunk on <b>conn</b>, where age is
* taken in timestamp units before the time <b>now</b>. If the connection has
* no data, treat it as having age zero.
**/
static uint32_t
conn_get_buffer_age(const connection_t *conn, uint32_t now)
conn_get_buffer_age(const connection_t *conn, uint32_t now_ts)
{
uint32_t age = 0, age2;
if (conn->outbuf) {
age2 = buf_get_oldest_chunk_timestamp(conn->outbuf, now);
age2 = buf_get_oldest_chunk_timestamp(conn->outbuf, now_ts);
if (age2 > age)
age = age2;
}
if (conn->inbuf) {
age2 = buf_get_oldest_chunk_timestamp(conn->inbuf, now);
age2 = buf_get_oldest_chunk_timestamp(conn->inbuf, now_ts);
if (age2 > age)
age = age2;
}
return age;
}
/** Return the age in milliseconds of the oldest buffer chunk on any stream in
* the linked list <b>stream</b>, where age is taken in milliseconds before
* the time <b>now</b> (in truncated milliseconds since the epoch). */
/** Return the age in timestamp units of the oldest buffer chunk on any stream
* in the linked list <b>stream</b>, where age is taken in timestamp units
* before the timestamp <b>now</b>. */
static uint32_t
circuit_get_streams_max_data_age(const edge_connection_t *stream, uint32_t now)
{
@ -2246,9 +2245,9 @@ circuit_get_streams_max_data_age(const edge_connection_t *stream, uint32_t now)
return age;
}
/** Return the age in milliseconds of the oldest buffer chunk on any stream
* attached to the circuit <b>c</b>, where age is taken in milliseconds before
* the time <b>now</b> (in truncated milliseconds since the epoch). */
/** Return the age in timestamp units of the oldest buffer chunk on any stream
* attached to the circuit <b>c</b>, where age is taken before the timestamp
* <b>now</b>. */
STATIC uint32_t
circuit_max_queued_data_age(const circuit_t *c, uint32_t now)
{
@ -2262,8 +2261,8 @@ circuit_max_queued_data_age(const circuit_t *c, uint32_t now)
}
/** Return the age of the oldest cell or stream buffer chunk on the circuit
* <b>c</b>, where age is taken in milliseconds before the time <b>now</b> (in
* truncated milliseconds since the epoch). */
* <b>c</b>, where age is taken in timestamp units before the timestamp
* <b>now</b> */
STATIC uint32_t
circuit_max_queued_item_age(const circuit_t *c, uint32_t now)
{
@ -2293,7 +2292,7 @@ circuits_compare_by_oldest_queued_item_(const void **a_, const void **b_)
return -1;
}
static uint32_t now_ms_for_buf_cmp;
static uint32_t now_ts_for_buf_cmp;
/** Helper to sort a list of circuit_t by age of oldest item, in descending
* order. */
@ -2302,8 +2301,8 @@ conns_compare_by_buffer_age_(const void **a_, const void **b_)
{
const connection_t *a = *a_;
const connection_t *b = *b_;
time_t age_a = conn_get_buffer_age(a, now_ms_for_buf_cmp);
time_t age_b = conn_get_buffer_age(b, now_ms_for_buf_cmp);
time_t age_a = conn_get_buffer_age(a, now_ts_for_buf_cmp);
time_t age_b = conn_get_buffer_age(b, now_ts_for_buf_cmp);
if (age_a < age_b)
return 1;
@ -2328,7 +2327,7 @@ circuits_handle_oom(size_t current_allocation)
size_t mem_recovered=0;
int n_circuits_killed=0;
int n_dirconns_killed=0;
uint32_t now_ms;
uint32_t now_ts;
log_notice(LD_GENERAL, "We're low on memory. Killing circuits with "
"over-long queues. (This behavior is controlled by "
"MaxMemInQueues.)");
@ -2341,11 +2340,11 @@ circuits_handle_oom(size_t current_allocation)
mem_to_recover = current_allocation - mem_target;
}
now_ms = (uint32_t)monotime_coarse_absolute_msec();
now_ts = monotime_coarse_get_stamp();
circlist = circuit_get_global_list();
SMARTLIST_FOREACH_BEGIN(circlist, circuit_t *, circ) {
circ->age_tmp = circuit_max_queued_item_age(circ, now_ms);
circ->age_tmp = circuit_max_queued_item_age(circ, now_ts);
} SMARTLIST_FOREACH_END(circ);
/* This is O(n log n); there are faster algorithms we could use instead.
@ -2358,9 +2357,9 @@ circuits_handle_oom(size_t current_allocation)
} SMARTLIST_FOREACH_END(circ);
/* Now sort the connection array ... */
now_ms_for_buf_cmp = now_ms;
now_ts_for_buf_cmp = now_ts;
smartlist_sort(connection_array, conns_compare_by_buffer_age_);
now_ms_for_buf_cmp = 0;
now_ts_for_buf_cmp = 0;
/* Fix up the connection array to its new order. */
SMARTLIST_FOREACH_BEGIN(connection_array, connection_t *, conn) {
@ -2379,7 +2378,7 @@ circuits_handle_oom(size_t current_allocation)
* data older than this circuit. */
while (conn_idx < smartlist_len(connection_array)) {
connection_t *conn = smartlist_get(connection_array, conn_idx);
uint32_t conn_age = conn_get_buffer_age(conn, now_ms);
uint32_t conn_age = conn_get_buffer_age(conn, now_ts);
if (conn_age < circ->age_tmp) {
break;
}

View File

@ -1166,8 +1166,8 @@ typedef struct packed_cell_t {
/** Next cell queued on this circuit. */
TOR_SIMPLEQ_ENTRY(packed_cell_t) next;
char body[CELL_MAX_NETWORK_SIZE]; /**< Cell as packed for network. */
uint32_t inserted_time; /**< Time (in milliseconds since epoch, with high
* bits truncated) when this cell was inserted. */
uint32_t inserted_timestamp; /**< Time (in timestamp units) when this cell
* was inserted */
} packed_cell_t;
/** A queue of cells on a circuit, waiting to be added to the

View File

@ -2482,7 +2482,7 @@ cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue,
(void)exitward;
(void)use_stats;
copy->inserted_time = (uint32_t) monotime_coarse_absolute_msec();
copy->inserted_timestamp = monotime_coarse_get_stamp();
cell_queue_append(queue, copy);
}
@ -2789,9 +2789,10 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
/* Calculate the exact time that this cell has spent in the queue. */
if (get_options()->CellStatistics ||
get_options()->TestingEnableCellStatsEvent) {
uint32_t msec_waiting;
uint32_t msec_now = (uint32_t)monotime_coarse_absolute_msec();
msec_waiting = msec_now - cell->inserted_time;
uint32_t timestamp_now = monotime_coarse_get_stamp();
uint32_t msec_waiting =
(uint32_t) monotime_coarse_stamp_units_to_approx_msec(
timestamp_now - cell->inserted_timestamp);
if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) {
or_circ = TO_OR_CIRCUIT(circ);

View File

@ -450,52 +450,54 @@ test_buffer_time_tracking(void *arg)
tt_assert(buf);
monotime_coarse_set_mock_time_nsec(START_NSEC);
const uint32_t START_MSEC = (uint32_t)monotime_coarse_absolute_msec();
const uint32_t START_TS = monotime_coarse_get_stamp();
/* Empty buffer means the timestamp is 0. */
tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC));
tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+1000));
tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_TS));
tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_TS+1000));
buf_add(buf, "ABCDEFG", 7);
tt_int_op(1000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+1000));
tt_int_op(1000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_TS+1000));
buf2 = buf_copy(buf);
tt_assert(buf2);
tt_int_op(1234, OP_EQ,
buf_get_oldest_chunk_timestamp(buf2, START_MSEC+1234));
buf_get_oldest_chunk_timestamp(buf2, START_TS+1234));
/* Now add more bytes; enough to overflow the first chunk. */
monotime_coarse_set_mock_time_nsec(START_NSEC + 123 * (uint64_t)1000000);
const uint32_t TS2 = monotime_coarse_get_stamp();
for (i = 0; i < 600; ++i)
buf_add(buf, "ABCDEFG", 7);
tt_int_op(4207, OP_EQ, buf_datalen(buf));
/* The oldest bytes are still in the front. */
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2000));
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_TS+2000));
/* Once those bytes are dropped, the chunk is still on the first
* timestamp. */
buf_get_bytes(buf, tmp, 100);
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2000));
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_TS+2000));
/* But once we discard the whole first chunk, we get the data in the second
* chunk. */
buf_get_bytes(buf, tmp, 4000);
tt_int_op(107, OP_EQ, buf_datalen(buf));
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2123));
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, TS2+2000));
/* This time we'll be grabbing a chunk from the freelist, and making sure
its time gets updated */
monotime_coarse_set_mock_time_nsec(START_NSEC + 5617 * (uint64_t)1000000);
const uint32_t TS3 = monotime_coarse_get_stamp();
for (i = 0; i < 600; ++i)
buf_add(buf, "ABCDEFG", 7);
tt_int_op(4307, OP_EQ, buf_datalen(buf));
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2123));
tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, TS2+2000));
buf_get_bytes(buf, tmp, 4000);
buf_get_bytes(buf, tmp, 306);
tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+5617));
tt_int_op(383, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+6000));
tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, TS3));
tt_int_op(383, OP_EQ, buf_get_oldest_chunk_timestamp(buf, TS3+383));
done:
buf_free(buf);

View File

@ -202,7 +202,7 @@ test_oom_streambuf(void *arg)
{
or_options_t *options = get_options_mutable();
circuit_t *c1 = NULL, *c2 = NULL, *c3 = NULL, *c4 = NULL, *c5 = NULL;
uint32_t tvms;
uint32_t tvts;
int i;
smartlist_t *edgeconns = smartlist_new();
const uint64_t start_ns = 1389641159 * (uint64_t)1000000000;
@ -222,6 +222,7 @@ test_oom_streambuf(void *arg)
tt_int_op(buf_get_total_allocation(), OP_EQ, 0);
monotime_coarse_set_mock_time_nsec(start_ns);
const uint32_t ts_start = monotime_coarse_get_stamp();
/* Start all circuits with a bit of data queued in cells */
@ -270,22 +271,24 @@ test_oom_streambuf(void *arg)
now_ns -= now_ns % 1000000000;
now_ns += 1000000000;
monotime_coarse_set_mock_time_nsec(now_ns);
tvms = (uint32_t) monotime_coarse_absolute_msec();
tvts = monotime_coarse_get_stamp();
const uint32_t diff = tvts - ts_start;
printf("\n!!!! %u\n", (unsigned) diff);
tt_int_op(circuit_max_queued_cell_age(c1, tvms), OP_EQ, 500);
tt_int_op(circuit_max_queued_cell_age(c2, tvms), OP_EQ, 490);
tt_int_op(circuit_max_queued_cell_age(c3, tvms), OP_EQ, 480);
tt_int_op(circuit_max_queued_cell_age(c4, tvms), OP_EQ, 0);
tt_int_op(circuit_max_queued_cell_age(c1, tvts), OP_EQ, 500);
tt_int_op(circuit_max_queued_cell_age(c2, tvts), OP_EQ, 490);
tt_int_op(circuit_max_queued_cell_age(c3, tvts), OP_EQ, 480);
tt_int_op(circuit_max_queued_cell_age(c4, tvts), OP_EQ, 0);
tt_int_op(circuit_max_queued_data_age(c1, tvms), OP_EQ, 390);
tt_int_op(circuit_max_queued_data_age(c2, tvms), OP_EQ, 380);
tt_int_op(circuit_max_queued_data_age(c3, tvms), OP_EQ, 0);
tt_int_op(circuit_max_queued_data_age(c4, tvms), OP_EQ, 370);
tt_int_op(circuit_max_queued_data_age(c1, tvts), OP_EQ, 390);
tt_int_op(circuit_max_queued_data_age(c2, tvts), OP_EQ, 380);
tt_int_op(circuit_max_queued_data_age(c3, tvts), OP_EQ, 0);
tt_int_op(circuit_max_queued_data_age(c4, tvts), OP_EQ, 370);
tt_int_op(circuit_max_queued_item_age(c1, tvms), OP_EQ, 500);
tt_int_op(circuit_max_queued_item_age(c2, tvms), OP_EQ, 490);
tt_int_op(circuit_max_queued_item_age(c3, tvms), OP_EQ, 480);
tt_int_op(circuit_max_queued_item_age(c4, tvms), OP_EQ, 370);
tt_int_op(circuit_max_queued_item_age(c1, tvts), OP_EQ, 500);
tt_int_op(circuit_max_queued_item_age(c2, tvts), OP_EQ, 490);
tt_int_op(circuit_max_queued_item_age(c3, tvts), OP_EQ, 480);
tt_int_op(circuit_max_queued_item_age(c4, tvts), OP_EQ, 370);
tt_int_op(cell_queues_get_total_allocation(), OP_EQ,
packed_cell_mem_cost() * 80);
@ -301,7 +304,7 @@ test_oom_streambuf(void *arg)
smartlist_add(edgeconns, ec);
}
tt_int_op(buf_get_total_allocation(), OP_EQ, 4096*17*2);
tt_int_op(circuit_max_queued_item_age(c4, tvms), OP_EQ, 1000);
tt_int_op(circuit_max_queued_item_age(c4, tvts), OP_EQ, 1000);
tt_int_op(cell_queues_check_size(), OP_EQ, 0);

View File

@ -5801,6 +5801,7 @@ test_util_monotonic_time(void *arg)
monotime_coarse_t mtc1, mtc2;
uint64_t nsec1, nsec2, usec1, msec1;
uint64_t nsecc1, nsecc2, usecc1, msecc1;
uint32_t stamp1, stamp2;
monotime_init();
@ -5812,6 +5813,7 @@ test_util_monotonic_time(void *arg)
nsecc1 = monotime_coarse_absolute_nsec();
usecc1 = monotime_coarse_absolute_usec();
msecc1 = monotime_coarse_absolute_msec();
stamp1 = monotime_coarse_to_stamp(&mtc1);
tor_sleep_msec(200);
@ -5819,6 +5821,7 @@ test_util_monotonic_time(void *arg)
monotime_coarse_get(&mtc2);
nsec2 = monotime_absolute_nsec();
nsecc2 = monotime_coarse_absolute_nsec();
stamp2 = monotime_coarse_to_stamp(&mtc2);
/* We need to be a little careful here since we don't know the system load.
*/
@ -5840,6 +5843,11 @@ test_util_monotonic_time(void *arg)
tt_u64_op(msecc1, OP_LE, nsecc1 / 1000000 + 1);
tt_u64_op(usecc1, OP_LE, nsecc1 / 1000 + 1000);
uint64_t coarse_stamp_diff =
monotime_coarse_stamp_units_to_approx_msec(stamp2-stamp1);
tt_u64_op(coarse_stamp_diff, OP_GE, 120);
tt_u64_op(coarse_stamp_diff, OP_LE, 1200);
done:
;
}