[PATCH grout v2] replace clock_t with Grout specific high-res clock

Robin Jarry rjarry at redhat.com
Mon May 25 19:11:34 CEST 2026


Morten Brørup, May 25, 2026 at 17:41:
> The clock_t type is implementation specific.
> E.g. on Linux, it is microseconds, and on Windows it is milliseconds.
>
> Replace all uses of clock_t and its accompanying function, gr_clock_us(),
> with a new nanosecond resolution clock type, gr_clock_ns_t, and its
> accompanying function, gr_grout_ns().
>
> Note about resolution and size of the new clock type:
> A clock with nanosecond resolution can be used for purposes not possible
> with microsecond resolution, e.g. shaping and pacing. For reference, the
> duration of a minimum size (84 byte) packet on a 100 Gbit/s Ethernet link
> is 6.72 ns.
> The size of the new type is 64 bit, the same size as the type it replaces
> (on supported implementations). Changing the resolution to nanoseconds
> thus does not impact the size of data structures where it is used.
>
> Note about signedness of the new clock type:
> I condidered making the gr_clock_ns_t unsigned, but that would require
> additional considerations in code where it is used, e.g. for calculating
> age, to prevent wraparound in race conditions. E.g.:
> age = (gr_clock_ns() - fdb->last_seen) / NS_PER_S;
> If the current thread reads the clock using gr_clock_ns(), and another
> thread races to set fdb->last_seen afterwards, the result of the
> subtraction is negative. The division by NS_PER_S makes the age zero.
> If gr_clock_ns_t was unsigned, the negative result of the subtraction
> would be a very large unsigned number. Dividing this very large number
> by NS_PER_S would be a large unsigned number, not zero.
> Obviously, this could be fixed by type casting gr_clock_ns_t values to
> signed int64_t everywhere they are used with subtraction. But such a
> requirement increases the risk of bugs.
> So I decided to make it signed, like type_t.
>
> Signed-off-by: Morten Brørup <mb at smartsharesystems.com>
> ---
> v2:
> * Fixed lines exceeding 100 characters.
> * Added note about resolution and size to the patch description.
> ---

Acked-by: Robin Jarry <rjarry at redhat.com>

Applied after fixing clang-format and a few cosmetic details, thanks.

https://github.com/DPDK/grout/commit/fe0c7f9a36548c05a6537bcf068324352b823cac

Here is the diff against your patch:

@@ Metadata
 Author: Morten Brørup <mb at smartsharesystems.com>

  ## Commit message ##
-    replace clock_t with Grout specific high-res clock
+    api: replace clock_t with high-res type

-    The clock_t type is implementation specific.
-    E.g. on Linux, it is microseconds, and on Windows it is milliseconds.
+    The clock_t type is implementation specific. E.g. on Linux, it is
+    microseconds, and on Windows it is milliseconds.

     Replace all uses of clock_t and its accompanying function, gr_clock_us(),
     with a new nanosecond resolution clock type, gr_clock_ns_t, and its
-    accompanying function, gr_grout_ns().
+    accompanying function, gr_clock_ns().

     Note about resolution and size of the new clock type:
+
     A clock with nanosecond resolution can be used for purposes not possible
     with microsecond resolution, e.g. shaping and pacing. For reference, the
-    duration of a minimum size (84 byte) packet on a 100 Gbit/s Ethernet link
-    is 6.72 ns.
-    The size of the new type is 64 bit, the same size as the type it replaces
-    (on supported implementations). Changing the resolution to nanoseconds
-    thus does not impact the size of data structures where it is used.
+    duration of a minimum size (84 byte) packet on a 100 Gbit/s Ethernet
+    link is 6.72 ns.
+
+    The size of the new type is 64 bit, the same size as the type it
+    replaces (on supported implementations). Changing the resolution to
+    nanoseconds thus does not impact the size of data structures where it is
+    used.

     Note about signedness of the new clock type:
-    I condidered making the gr_clock_ns_t unsigned, but that would require
+
+    I considered making the gr_clock_ns_t unsigned, but that would require
     additional considerations in code where it is used, e.g. for calculating
     age, to prevent wraparound in race conditions. E.g.:
-    age = (gr_clock_ns() - fdb->last_seen) / NS_PER_S;
+
+            age = (gr_clock_ns() - fdb->last_seen) / GR_NS_PER_S;
+
     If the current thread reads the clock using gr_clock_ns(), and another
     thread races to set fdb->last_seen afterwards, the result of the
-    subtraction is negative. The division by NS_PER_S makes the age zero.
+    subtraction is negative. The division by GR_NS_PER_S makes the age zero.
     If gr_clock_ns_t was unsigned, the negative result of the subtraction
     would be a very large unsigned number. Dividing this very large number
     by NS_PER_S would be a large unsigned number, not zero.
+
     Obviously, this could be fixed by type casting gr_clock_ns_t values to
-    signed int64_t everywhere they are used with subtraction. But such a
-    requirement increases the risk of bugs.
-    So I decided to make it signed, like type_t.
+    signed int64_t everywhere they are used with subtraction. But such
+    a requirement increases the risk of bugs. So I decided to make it
+    signed, like time_t.

     Signed-off-by: Morten Brørup <mb at smartsharesystems.com>
+    Acked-by: Robin Jarry <rjarry at redhat.com>

  ## Notes ##
@@ Notes
     > * Added note about resolution and size to the patch description.
     > ---

-    Applied, thanks.
+    Acked-by: Robin Jarry <rjarry at redhat.com>
+
+    Applied after applying clang-format and fixing a few cosmetic details,
+    thanks.

  ## api/gr_clock.h ##
 @@
@@ api/gr_clock.h

 -// Get elapsed time since last boot in microseconds.
 -static inline clock_t gr_clock_us(void) {
--	struct timespec tp = gr_clock_raw();
--	return (tp.tv_sec * CLOCKS_PER_SEC) + (tp.tv_nsec / 1000);
++#define GR_NS_PER_S (gr_clock_ns_t)1000000000LL
++
 +// Get powered-on (non-suspended, non-hibernated) time since last boot [nanoseconds],
 +// using a common clock across all processes.
 +// Does not return negative values.
 +static inline gr_clock_ns_t gr_clock_ns(void) {
-+	struct timespec tp;
-+	clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
-+	uint64_t ret = (uint64_t)tp.tv_sec * UINT64_C(1000000000) + (uint64_t)tp.tv_nsec;
-+	return (int64_t)ret;
+ 	struct timespec tp = gr_clock_raw();
+-	return (tp.tv_sec * CLOCKS_PER_SEC) + (tp.tv_nsec / 1000);
++	return tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
  }

  ## cli/main.c ##
@@ modules/infra/control/l3_nexthop.c: static struct nexthop_type_ops l3_nh_ops = {

  	ops = af_ops[l3->af];
 -	reply_age = (now - l3->last_reply) / CLOCKS_PER_SEC;
-+	reply_age = (now - l3->last_reply) / NS_PER_S;
++	reply_age = (now - l3->last_reply) / GR_NS_PER_S;
  	max_probes = nh_conf.max_ucast_probes + nh_conf.max_bcast_probes;
  	probes = l3->ucast_probes + l3->bcast_probes;

@@ modules/infra/control/lacp.c: void lacp_input_cb(void *obj, uintptr_t, const str

  	// Save old member state to detect changes
  	bool old_active = member->active;
-@@ modules/infra/control/lacp.c: static void lacp_periodic(evutil_socket_t, short, void *) {
+@@ modules/infra/control/lacp.c: static int lacp_send(const struct bond_member *member) {
+ // Periodic timer callback to send LACP PDUs and check timeouts
+ static void lacp_periodic(evutil_socket_t, short, void *) {
  	struct iface_info_bond *bond;
++	gr_clock_ns_t now, timeout;
  	struct bond_member *member;
  	const struct iface *port;
 -	clock_t now, timeout;
-+	gr_clock_ns_t now, timeout;
  	struct iface *iface;

 -	now = gr_clock_us();
@@ modules/infra/control/lacp.c: static void lacp_periodic(evutil_socket_t, short,
 -					timeout = LACP_SHORT_TIMEOUT * US_PER_S;
 -				else
 -					timeout = LACP_LONG_TIMEOUT * US_PER_S;
-+				timeout = (member->local.state & LACP_STATE_FAST)
-+					? LACP_SHORT_TIMEOUT * (gr_clock_ns_t)NS_PER_S
-+					: LACP_LONG_TIMEOUT * (gr_clock_ns_t)NS_PER_S;
++				timeout = (member->local.state & LACP_STATE_FAST) ?
++					LACP_SHORT_TIMEOUT * GR_NS_PER_S :
++					LACP_LONG_TIMEOUT * GR_NS_PER_S;

  				if (now - member->last_rx > timeout && member->active) {
  					// Partner timed out - enter FAILED state
@@ modules/infra/control/lacp.c: static void lacp_periodic(evutil_socket_t, short,
 -				member->next_tx = now + LACP_SHORT_TIMEOUT * US_PER_S;
 -			else
 -				member->next_tx = now + LACP_LONG_TIMEOUT * US_PER_S;
-+			member->next_tx = now + (member->remote.state & LACP_STATE_FAST)
-+				? LACP_SHORT_TIMEOUT * (gr_clock_ns_t)NS_PER_S;
-+				: LACP_LONG_TIMEOUT * (gr_clock_ns_t)NS_PER_S;
++			member->next_tx = now + (member->remote.state & LACP_STATE_FAST) ?
++				LACP_SHORT_TIMEOUT * GR_NS_PER_S :
++				LACP_LONG_TIMEOUT * GR_NS_PER_S;
  		}

  		// Update active members list if any port timed out
@@ modules/ip/api/gr_ip4.h: struct gr_ip4_icmp_recv_resp {
  ## modules/ip/control/icmp.c ##
 @@

+ #include <rte_icmp.h>
+
+-#include <time.h>
+-
  struct icmp_queue_item {
  	struct rte_mbuf *mbuf;
 -	clock_t timestamp;
@@ modules/ip/control/icmp.c: static void icmp_input_cb(void *m, uintptr_t timestam
  // Search for the oldest ICMP response matching the given identifier.
  // If found, the packet is removed from the queue.
 -static struct rte_mbuf *get_icmp_response(uint16_t ident, uint16_t seq_num, clock_t *timestamp) {
-+static struct rte_mbuf *get_icmp_response(
-+	uint16_t ident,
-+	uint16_t seq_num,
-+	gr_clock_ns_t *timestamp
-+) {
++static struct rte_mbuf *
++get_icmp_response(uint16_t ident, uint16_t seq_num, gr_clock_ns_t *timestamp) {
  	struct icmp_queue_item *i, *tmp;
  	struct rte_mbuf *mbuf = NULL;

 @@ modules/ip/control/icmp.c: out:
+
  static struct api_out icmp_recv(const void *request, struct api_ctx *) {
  	const struct gr_ip4_icmp_recv_req *icmp_req = request;
++	gr_clock_ns_t *pkt_timestamp, rcv_timestamp;
  	struct gr_ip4_icmp_recv_resp *resp = NULL;
 -	clock_t *pkt_timestamp, rcv_timestamp;
-+	gr_clock_ns_t *pkt_timestamp, rcv_timestamp;
  	struct rte_icmp_hdr *icmp;
  	struct rte_ipv4_hdr *ip;
  	struct rte_mbuf *m;
@@ modules/ip/datapath/icmp_input.c: icmp_input_process(struct rte_graph *graph, st

  ## modules/ip/datapath/icmp_local_send.c ##
 @@ modules/ip/datapath/icmp_local_send.c: static uint16_t icmp_local_send_process(
+ 	struct ip_local_mbuf_data *data;
  	struct rte_icmp_hdr *icmp;
  	struct ctl_to_stack *msg;
++	gr_clock_ns_t *payload;
  	struct rte_mbuf *mbuf;
 -	clock_t *payload;
-+	gr_clock_ns_t *payload;
  	rte_edge_t next;

  	for (unsigned i = 0; i < n_objs; i++) {
@@ modules/ip6/api/gr_ip6.h: struct gr_ip6_icmp_recv_resp {

  ## modules/ip6/control/icmp6.c ##
 @@
+ #include <gr_api.h>
+ #include <gr_ip6.h>

+-#include <time.h>
+-
  struct icmp_queue_item {
  	struct rte_mbuf *mbuf;
 -	clock_t timestamp;
@@ modules/ip6/control/icmp6.c: static struct rte_mbuf *get_icmp6_echo_reply(
  			goto free_and_skip;

  		icmp6 = rte_pktmbuf_mtod(mbuf, struct icmp6 *);
-@@ modules/ip6/control/icmp6.c: static struct api_out icmp6_recv(const void *request, struct api_ctx *) {
+@@ modules/ip6/control/icmp6.c: static struct api_out icmp6_send(const void *request, struct api_ctx *) {
+
+ static struct api_out icmp6_recv(const void *request, struct api_ctx *) {
+ 	const struct gr_ip6_icmp_recv_req *recvreq = request;
++	gr_clock_ns_t *pkt_timestamp, rcv_timestamp;
++	struct icmp6_echo_reply *icmp6_echo;
+ 	struct gr_ip6_icmp_recv_resp *resp;
  	struct ip6_local_mbuf_data *d_ip6;
  	struct icmp6 *icmp6;
- 	struct icmp6_echo_reply *icmp6_echo;
+-	struct icmp6_echo_reply *icmp6_echo;
 -	clock_t *pkt_timestamp, rcv_timestamp;
-+	gr_clock_ns_t *pkt_timestamp, rcv_timestamp;
  	struct rte_mbuf *m;
  	int ret = 0;

@@ modules/ip6/datapath/icmp6_input.c: icmp6_input_process(struct rte_graph *graph,

  ## modules/ip6/datapath/icmp6_local_send.c ##
 @@ modules/ip6/datapath/icmp6_local_send.c: static uint16_t icmp6_local_send_process(
+ 	struct icmp6_echo_request *icmp6_echo;
+ 	struct ip6_local_mbuf_data *data;
  	struct ctl_to_stack *msg;
++	gr_clock_ns_t *payload;
  	struct rte_mbuf *mbuf;
  	struct icmp6 *icmp6;
 -	clock_t *payload;
-+	gr_clock_ns_t *payload;
  	rte_edge_t next;
  	size_t pkt_len;

@@ modules/l2/cli/fdb.c: static cmd_status_t fdb_show(struct gr_api_client *c, cons
  			gr_table_cell(table, 5, "%s", flags);

 -		gr_table_cell(table, 6, "%ld", (gr_clock_us() - fdb->last_seen) / CLOCKS_PER_SEC);
-+		gr_table_cell(table, 6, "%ld",
-+        	(gr_clock_ns() - fdb->last_seen) / INT64_C(1000000000));
++		gr_table_cell(table, 6, "%ld", (gr_clock_ns() - fdb->last_seen) / GR_NS_PER_S);

  		if (gr_table_print_row(table) < 0)
  			break;
@@ modules/l2/control/fdb.c: static struct api_out fdb_add(const void *request, str

  		event_push(GR_EVENT_FDB_UPDATE, e);
  	} else {
-@@ modules/l2/control/fdb.c: static void fdb_ageing_cb(evutil_socket_t, short /*what*/, void * /*priv*/) {
+@@ modules/l2/control/fdb.c: void fdb_sync_hardware(const struct iface *bridge, struct iface *member, bool ad
+ static void fdb_ageing_cb(evutil_socket_t, short /*what*/, void * /*priv*/) {
+ 	const struct iface *bridge;
+ 	struct gr_fdb_entry *fdb;
++	gr_clock_ns_t now;
  	uint32_t next = 0;
  	uint16_t max_age;
  	const void *key;
 -	clock_t now;
-+	gr_clock_ns_t now;
  	void *data;
  	time_t age;

@@ modules/l2/control/fdb.c: static void fdb_ageing_cb(evutil_socket_t, short /*wha
  			continue;

 -		age = (now - fdb->last_seen) / CLOCKS_PER_SEC;
-+		age = (now - fdb->last_seen) / NS_PER_S;
++		age = (now - fdb->last_seen) / GR_NS_PER_S;

  		bridge = iface_from_id(fdb->bridge_id);
  		if (bridge != NULL)
@@ modules/policy/cli/conntrack.c: static cmd_status_t conn_list(struct gr_api_clie
  		gr_table_cell(table, 7, "%u", ntohs(conn->fwd_flow.src_id));
  		gr_table_cell(table, 8, "%u", ntohs(conn->fwd_flow.dst_id));
 -		gr_table_cell(table, 9, "%lu", (now - conn->last_update) / 1000000);
-+		gr_table_cell(table, 9, "%ld", (now - conn->last_update) / NS_PER_S);
++		gr_table_cell(table, 9, "%ld", (now - conn->last_update) / GR_NS_PER_S);

  		if (gr_table_print_row(table) < 0)
  			break;
@@ modules/policy/control/conntrack.c: static void do_ageing(evutil_socket_t, short
  		if (last > now)
  			continue;
 -		age = (now - last) / 1000000ULL;
-+		age = (now - last) / NS_PER_S;
++		age = (now - last) / GR_NS_PER_S;
  		if (age > timeout)
  			gr_conn_destroy(conn);
  	}
@@ modules/policy/control/conntrack.h: struct conn {

  ## smoke/fib_inject.c ##
 @@ smoke/fib_inject.c: int main(int argc, char **argv) {
+ 	unsigned count = 10000;
+ 	uint32_t installed = 0;
  	unsigned dist_count;
++	gr_clock_ns_t time;
  	bool ipv6 = false;
  	float duration;
 -	clock_t time;
-+	gr_clock_ns_t time;
  	int ret;
  	int o;

@@ smoke/fib_inject.c: int main(int argc, char **argv) {
  		ret = inject_ipv4(c, count);

 -	duration = (float)(gr_clock_us() - time) / (float)CLOCKS_PER_SEC;
-+	duration = (float)(gr_clock_ns() - time) / (float)1000000000;
++	duration = (float)(gr_clock_ns() - time) / (float)GR_NS_PER_S;

  	printf("total time: %.1fs (%.1f routes/s)\n", duration, (float)count / duration);




-- 
Robin

> If condition persists, consult your physician.



More information about the grout mailing list