[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