[PATCH grout] add high-performance clock

Morten Brørup mb at smartsharesystems.com
Fri Jun 12 20:45:06 CEST 2026


I really want a high-performance clock in Grout.
And since the rte_rdtsc() clock is out of sync with any system wide clock, e.g. CLOCK_MONOTONIC_RAW, I am making the clock private to Grout, and exposing an API for other processes to read it.
This also means that gr_clock_ns() is no longer available as a generic utility function for other processes. (I.e. it cannot be used for profiling in smoke/fib_inject.c or for random generator seeding in cli/main.c, so I have fixed both of these.)
The gr_clock_ns() function has become a client API that reads the clock from the Grout process.

So far, so good!

Now, I'm unsure about where the new clock API and API "implementation" should go.
So I am asking for guidance on this.

For now, there is only one API function, "gr_clock_ns_t gr_clock_ns(struct gr_api_client *);".
I have put it in gr_clock.h as an inline function, but is that the right place?
Should I make it non-inline and add the implementation to gr_api_client_impl.h?
And should I move the clock module stuff from gr_clock.h to gr_api.h?

The "clock" module seems essential, and not really an "infra" module, so I have put it in the main/ directory rather than under modules/infra/. Is that wrong? Should I just consider the "clock" module an "infra" module, and move everything there?

Please see the diff below.

PS: The gr_clock_raw() function was unused, so I removed it.

-Morten


---
cat main/clock.h
---
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 SmartShare Systems

#pragma once

#include <gr_clock.h>

#include <rte_common.h>
#include <rte_cycles.h>
#include <rte_per_lcore.h>

RTE_DECLARE_PER_LCORE(gr_clock_ns_t, clock_ns);
RTE_DECLARE_PER_LCORE(int32_t, clock_s);

// Get common (monotonically increasing) clock from snapshot [nanoseconds].
//
// Resembles CLOCK_MONOTONIC_RAW:
// - Pauses (does not increase) while the system is suspended or hibernated.
// - Accurate for short intervals, where NTP adjustments would distort the measurement.
// - Not accurate for long intervals. It drifts with hardware.
// - - Drifts up to 4.3 seconds/day = 26 minutes/year. (Typical PC XTAL with 50 PPM accuracy.)
//
// Wraps around after hundreds of years.
// Does not return negative values.
//
// Call clock_update() to update the clock snapshots for the current thread.
static __rte_always_inline gr_clock_ns_t clock_ns(void) {
        RTE_ASSERT(RTE_PER_LCORE(clock_ns) != INT64_C(-1));
        __rte_assume(RTE_PER_LCORE(clock_ns) >= 0);
        return RTE_PER_LCORE(clock_ns);
}

// Get common (monotonically increasing) clock from snapshot [seconds].
//
// Resembles CLOCK_MONOTONIC_RAW:
// - Pauses (does not increase) while the system is suspended or hibernated.
// - Not accurate for long intervals. It drifts with hardware.
// - - Drifts up to 4.3 seconds/day = 26 minutes/year. (Typical PC XTAL with 50 PPM accuracy.)
//
// Wraps around after hundreds of years.
// Does not return negative values.
//
// Call clock_update() to update the clock snapshots for the current thread.
static __rte_always_inline int32_t clock_s(void) {
        RTE_ASSERT(RTE_PER_LCORE(clock_s) != INT32_C(-1));
        __rte_assume(RTE_PER_LCORE(clock_s) >= 0);
        return RTE_PER_LCORE(clock_s);
}

// Update the clock snapshots for the current thread.
void clock_update(void);

---
cat main/clock.c
---
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 SmartShare Systems

#include "clock.h"
#include "log.h"
#include "module.h"

#include <fcntl.h>
#include <time.h>
#include <unistd.h>

LOG_TYPE("clock");

RTE_DEFINE_PER_LCORE(gr_clock_ns_t, clock_ns) = INT64_C(-1);
RTE_DEFINE_PER_LCORE(int32_t, clock_s) = INT32_C(-1);

static uint64_t clock_tsc_hz = 0;

void clock_update(void) {
        // Considerations regarding types and boundaries, assuming a 10 GHz CPU:
        // clock_tsc_hz = 10*10^9 > UINT32_MAX (~4.29*10^9): Requires uint64_t.
        // max(tsc % clock_tsc_hz) * GR_NS_PER_S = ~9.99*10^18 < UINT64_MAX (~18.4*10^18): OK.
        const uint64_t tsc = rte_rdtsc();
        RTE_PER_LCORE(clock_ns) = (tsc / clock_tsc_hz) * GR_NS_PER_S
                                  + (tsc % clock_tsc_hz) * GR_NS_PER_S / clock_tsc_hz;
        RTE_PER_LCORE(clock_s) = (tsc / clock_tsc_hz);
}

static __rte_cold void clock_init(struct event_base *) {
        clock_tsc_hz = rte_get_tsc_hz();
        LOG(DEBUG, "tsc hz: %lu", clock_tsc_hz);

#if 1
        // Performance information
        uint64_t tsc;
        gr_clock_ns_t ns;

        rte_compiler_barrier();
        tsc = rte_rdtsc();
        ns = (tsc / clock_tsc_hz) * GR_NS_PER_S
             + (tsc % clock_tsc_hz) * GR_NS_PER_S / clock_tsc_hz;
        rte_compiler_barrier();
        tsc = rte_rdtsc() - tsc;
        rte_compiler_barrier();
        __asm__ __volatile__("" :: "r" (ns)); // Don't optimize away the calculation of ns.
        LOG(DEBUG, "rdtsc: %lu cycles, %.2f ns",
            tsc, (float)(tsc * GR_NS_PER_S) / (float)clock_tsc_hz);
#endif
}

static struct api_out clock_get_ns_handler(const void *, struct api_ctx *ctx) {
        clock_update();
        gr_clock_ns_t clock = clock_ns();
        api_send(ctx, sizeof(clock), &clock);
        return api_out(0, 0, NULL);
}

static struct module clock_module = {
        .name = "clock",
        .init = clock_init,
};

RTE_INIT(clock_constructor) {
        module_register(&clock_module);
        api_handler(GR_CLOCK_GET_NS, clock_get_ns_handler);
}
---

diff --git a/api/gr_clock.h b/api/gr_clock.h
index d2d98fba..ccdcdcf6 100644
--- a/api/gr_clock.h
+++ b/api/gr_clock.h
@@ -4,30 +4,47 @@

 #pragma once

+#include <gr_api.h>
+
 #include <stdint.h>
 #include <time.h>

 // High-resolution clock [nanoseconds].
-// Used with CLOCK_MONOTONIC_RAW, unless otherwise specified.
+// Used with Grout clock (resembles CLOCK_MONOTONIC_RAW), unless otherwise specified.
 // Note: Does not have Y2038 problems. Not even with CLOCK_REALTIME.
 // Note: Using signed, to avoid need for casting to signed
 // in calculations where race conditions may cause negative differences.
 typedef int64_t gr_clock_ns_t;

-// Get powered-on (non-suspended, non-hibernated) time since last boot,
-// using a common clock across all processes.
-static inline struct timespec gr_clock_raw(void) {
-       struct timespec tp = {0};
-       clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
-       return tp;
-}
+#define GR_NS_PER_S (gr_clock_ns_t)INT64_C(1000000000)
+
+// clock module ////////////////////////////////////////////////////////////////
+
+#define GR_CLOCK_MODULE 0xc10c
+
+enum gr_clock_requests : uint32_t {
+       GR_CLOCK_GET_NS = GR_MSG_TYPE(GR_CLOCK_MODULE, 0x0001),
+};
+
+GR_REQ(GR_CLOCK_GET_NS, struct gr_empty, gr_clock_ns_t);

-#define GR_NS_PER_S (gr_clock_ns_t)1000000000LL
+// Get powered-on (non-suspended, non-hibernated) time since last boot [nanoseconds].
+//
+// Resembles CLOCK_MONOTONIC_RAW:
+// - Pauses (does not increase) while the system is suspended or hibernated.
+// - Accurate for short intervals, where NTP adjustments would distort the measurement.
+// - Not accurate for long intervals. It drifts with hardware.
+// - - Drifts up to 4.3 seconds/day = 26 minutes/year. (Typical PC XTAL with 50 PPM accuracy.)
+//
+// Wraps around after hundreds of years.
+// Returns positive value on success, negative errno on failure.
+static inline gr_clock_ns_t gr_clock_ns(struct gr_api_client *client) {
+       gr_clock_ns_t ret;
+       void *resp_ptr = NULL;

-// 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 = gr_clock_raw();
-       return tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
+       ret = gr_api_client_send_recv(client, GR_CLOCK_GET_NS, 0, NULL, &resp_ptr);
+       if (ret == 0)
+               ret = *(gr_clock_ns_t *)resp_ptr;
+       free(resp_ptr);
+       return ret;
 }
diff --git a/cli/main.c b/cli/main.c
index defaedcf..4510c7b4 100644
--- a/cli/main.c
+++ b/cli/main.c
@@ -179,7 +179,7 @@ int main(int argc, char **argv) {
        tty_init();

        // initialize a non-constant seed for random() calls
-       srandom(gr_clock_ns());
+       srandom(time(NULL));

        if (ec_init() < 0) {
                errorf("ec_init: %s", strerror(errno));
diff --git a/main/meson.build b/main/meson.build
index a57d8600..f0823ff3 100644
--- a/main/meson.build
+++ b/main/meson.build
@@ -3,6 +3,7 @@

 src += files(
   'api.c',
+  'clock.c',
   'control_queue.c',
   'dpdk.c',
   'event.c',
diff --git a/modules/infra/control/l3_nexthop.c b/modules/infra/control/l3_nexthop.c
index 79258d54..c4cff9fe 100644
--- a/modules/infra/control/l3_nexthop.c
+++ b/modules/infra/control/l3_nexthop.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2026 Robin Jarry

+#include "clock.h"
 #include "iface.h"
 #include "log.h"
 #include "mbuf.h"
@@ -8,8 +9,6 @@
 #include "nexthop.h"
 #include "rcu.h"

-#include <gr_clock.h>
-
 #include <rte_hash.h>

 #include <stdint.h>
@@ -295,12 +294,12 @@ static struct nexthop_type_ops l3_nh_ops = {

 static void l3_age(struct nexthop *nh, struct nexthop_info_l3 *l3) {
        const struct nexthop_af_ops *ops;
-       gr_clock_ns_t now = gr_clock_ns();
        unsigned probes, max_probes;
        time_t reply_age;

+       clock_update();
        ops = af_ops[l3->af];
-       reply_age = (now - l3->last_reply) / GR_NS_PER_S;
+       reply_age = (clock_ns() - 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;

diff --git a/modules/infra/control/lacp.c b/modules/infra/control/lacp.c
index 54f7930a..faa84bdd 100644
--- a/modules/infra/control/lacp.c
+++ b/modules/infra/control/lacp.c
@@ -2,14 +2,13 @@
 // Copyright (c) 2025 Robin Jarry

 #include "bond.h"
+#include "clock.h"
 #include "iface.h"
 #include "lacp.h"
 #include "log.h"
 #include "mbuf.h"
 #include "module.h"

-#include <gr_clock.h>
-
 #include <event2/event.h>
 #include <rte_mbuf.h>

@@ -64,8 +63,9 @@ void lacp_input_cb(void *obj, uintptr_t, const struct control_queue_drain *drain
        pdu = rte_pktmbuf_mtod(mbuf, struct lacp_pdu *);

        // Store partner information from received PDU
+       clock_update();
        member->remote = pdu->actor;
-       member->last_rx = gr_clock_ns();
+       member->last_rx = clock_ns();

        // Save old member state to detect changes
        bool old_active = member->active;
@@ -117,12 +117,12 @@ 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;
+       gr_clock_ns_t timeout;
        struct bond_member *member;
        const struct iface *port;
        struct iface *iface;

-       now = gr_clock_ns();
+       clock_update();

        iface = NULL;
        while ((iface = iface_next(GR_IFACE_TYPE_BOND, iface)) != NULL) {
@@ -143,7 +143,7 @@ static void lacp_periodic(evutil_socket_t, short, void *) {
                                else
                                        timeout = LACP_LONG_TIMEOUT * GR_NS_PER_S;

-                               if (now - member->last_rx > timeout && member->active) {
+                               if (clock_ns() - member->last_rx > timeout && member->active) {
                                        // Partner timed out - enter FAILED state
                                        member->active = false;
                                        member->local.state &= ~LACP_STATE_SYNCHRONIZED;
@@ -161,7 +161,7 @@ static void lacp_periodic(evutil_socket_t, short, void *) {
                        }

                        // Send LACP PDU if needed
-                       if (!member->need_to_transmit && member->next_tx > now
+                       if (!member->need_to_transmit && member->next_tx > clock_ns()
                            && member->last_rx > 0)
                                continue;
                        if (!(port->flags & GR_IFACE_F_UP) || !(port->state & GR_IFACE_S_RUNNING))
@@ -175,9 +175,9 @@ static void lacp_periodic(evutil_socket_t, short, void *) {

                        member->need_to_transmit = false;
                        if (member->remote.state & LACP_STATE_FAST)
-                               member->next_tx = now + LACP_SHORT_TIMEOUT * GR_NS_PER_S;
+                               member->next_tx = clock_ns() + LACP_SHORT_TIMEOUT * GR_NS_PER_S;
                        else
-                               member->next_tx = now + LACP_LONG_TIMEOUT * GR_NS_PER_S;
+                               member->next_tx = clock_ns() + LACP_LONG_TIMEOUT * GR_NS_PER_S;
                }

                // Update active members list if any port timed out
diff --git a/modules/infra/datapath/main_loop.c b/modules/infra/datapath/main_loop.c
index f462cfbd..4127631d 100644
--- a/modules/infra/datapath/main_loop.c
+++ b/modules/infra/datapath/main_loop.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2023 Robin Jarry
+// Copyright (c) 2026 SmartShare Systems

+#include "clock.h"
 #include "config.h"
 #include "datapath.h"
 #include "log.h"
@@ -258,6 +260,7 @@ reconfig:
        sleep = 0;
        timestamp = rte_rdtsc();
        for (;;) {
+               clock_update();
                rte_graph_walk(graph);

                if (++loop == HOUSEKEEPING_INTERVAL) {
diff --git a/modules/ip/control/nexthop.c b/modules/ip/control/nexthop.c
index 3ef0c3e9..9563bdb3 100644
--- a/modules/ip/control/nexthop.c
+++ b/modules/ip/control/nexthop.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry

+#include "clock.h"
 #include "control_input.h"
 #include "iface.h"
 #include "ip4.h"
@@ -9,7 +10,6 @@
 #include "log.h"
 #include "module.h"

-#include <gr_clock.h>
 #include <gr_net_types.h>

 #include <rte_arp.h>
@@ -187,7 +187,8 @@ void arp_probe_input_cb(void *obj, uintptr_t, const struct control_queue_drain *
        // static next hops never need updating
        if (!(l3->flags & GR_NH_F_STATIC)) {
                // Refresh all fields.
-               l3->last_reply = gr_clock_ns();
+               clock_update();
+               l3->last_reply = clock_ns();
                l3->state = GR_NH_S_REACHABLE;
                l3->ucast_probes = 0;
                l3->bcast_probes = 0;
diff --git a/modules/ip/datapath/arp_output_request.c b/modules/ip/datapath/arp_output_request.c
index 9633cdcf..a1b31786 100644
--- a/modules/ip/datapath/arp_output_request.c
+++ b/modules/ip/datapath/arp_output_request.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry

+#include "clock.h"
 #include "control_input.h"
 #include "eth.h"
 #include "graph.h"
@@ -9,8 +10,6 @@
 #include "ip4_datapath.h"
 #include "trace.h"

-#include <gr_clock.h>
-
 #include <rte_arp.h>
 #include <rte_ether.h>
 #include <rte_mbuf.h>
@@ -35,7 +34,8 @@ int arp_output_request_solicit(struct nexthop *nh) {
        } else {
                // This function is called by the control plane main thread.
                // It is OK to modify the nexthop here.
-               l3->last_request = gr_clock_ns();
+               clock_update();
+               l3->last_request = clock_ns();
                if (l3->ucast_probes < nh_conf.max_ucast_probes)
                        l3->ucast_probes++;
                else
diff --git a/modules/ip/datapath/icmp_input.c b/modules/ip/datapath/icmp_input.c
index b0426246..537fa477 100644
--- a/modules/ip/datapath/icmp_input.c
+++ b/modules/ip/datapath/icmp_input.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry

+#include "clock.h"
 #include "control_output.h"
 #include "graph.h"
 #include "ip4_datapath.h"
@@ -8,8 +9,6 @@
 #include "mbuf.h"
 #include "trace.h"

-#include <gr_clock.h>
-
 #include <rte_icmp.h>

 enum {
@@ -55,7 +54,7 @@ icmp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs,
                        ip_data->src = ip;
                        edge = OUTPUT;
                } else if (icmp_cb[icmp->icmp_type]) {
-                       control_output_set_cb(mbuf, icmp_cb[icmp->icmp_type], gr_clock_ns());
+                       control_output_set_cb(mbuf, icmp_cb[icmp->icmp_type], clock_ns());
                        edge = CONTROL;
                } else {
                        edge = UNSUPPORTED;
diff --git a/modules/ip/datapath/icmp_local_send.c b/modules/ip/datapath/icmp_local_send.c
index 17fe08a4..4e4570f7 100644
--- a/modules/ip/datapath/icmp_local_send.c
+++ b/modules/ip/datapath/icmp_local_send.c
@@ -1,14 +1,13 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Christophe Fontaine

+#include "clock.h"
 #include "control_input.h"
 #include "graph.h"
 #include "ip4.h"
 #include "ip4_datapath.h"
 #include "mbuf.h"

-#include <gr_clock.h>
-
 #include <rte_icmp.h>

 #include <netinet/in.h>
@@ -101,7 +100,7 @@ static uint16_t icmp_local_send_process(
                );

                payload = rte_pktmbuf_mtod_offset(mbuf, gr_clock_ns_t *, sizeof(*icmp));
-               *payload = gr_clock_ns();
+               *payload = clock_ns();

                // Build ICMP packet
                icmp->icmp_type = RTE_ICMP_TYPE_ECHO_REQUEST;
diff --git a/modules/ip6/control/nexthop.c b/modules/ip6/control/nexthop.c
index 5e53cc52..8412f4e5 100644
--- a/modules/ip6/control/nexthop.c
+++ b/modules/ip6/control/nexthop.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry

+#include "clock.h"
 #include "control_input.h"
 #include "icmp6.h"
 #include "iface.h"
@@ -10,7 +11,6 @@
 #include "log.h"
 #include "module.h"

-#include <gr_clock.h>
 #include <gr_net_types.h>

 #include <event2/event.h>
@@ -236,7 +236,8 @@ void ndp_probe_input_cb(void *obj, uintptr_t, const struct control_queue_drain *

        if (!(l3->flags & GR_NH_F_STATIC) && lladdr_found == ICMP6_OPT_FOUND) {
                // Refresh all fields.
-               l3->last_reply = gr_clock_ns();
+               clock_update();
+               l3->last_reply = clock_ns();
                l3->state = GR_NH_S_REACHABLE;
                l3->ucast_probes = 0;
                l3->bcast_probes = 0;
diff --git a/modules/ip6/datapath/icmp6_input.c b/modules/ip6/datapath/icmp6_input.c
index cbeb3f7e..84c6e664 100644
--- a/modules/ip6/datapath/icmp6_input.c
+++ b/modules/ip6/datapath/icmp6_input.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry

+#include "clock.h"
 #include "control_output.h"
 #include "graph.h"
 #include "icmp6.h"
@@ -10,8 +11,6 @@
 #include "mbuf.h"
 #include "trace.h"

-#include <gr_clock.h>
-
 enum {
        ICMP6_OUTPUT = 0,
        NEIGH_SOLICIT,
@@ -82,7 +81,7 @@ icmp6_input_process(struct rte_graph *graph, struct rte_node *node, void **objs,
                case ICMP6_TYPE_ROUTER_ADVERT:
                default:
                        if (icmp6_cb[icmp6->type] != NULL) {
-                               control_output_set_cb(mbuf, icmp6_cb[icmp6->type], gr_clock_ns());
+                               control_output_set_cb(mbuf, icmp6_cb[icmp6->type], clock_ns());
                                next = CONTROL;
                        } else {
                                next = UNSUPPORTED;
diff --git a/modules/ip6/datapath/icmp6_local_send.c b/modules/ip6/datapath/icmp6_local_send.c
index c37fb05d..fcb11a32 100644
--- a/modules/ip6/datapath/icmp6_local_send.c
+++ b/modules/ip6/datapath/icmp6_local_send.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2025 Olivier Gournet

+#include "clock.h"
 #include "control_input.h"
 #include "graph.h"
 #include "icmp6.h"
@@ -8,8 +9,6 @@
 #include "ip6_datapath.h"
 #include "mbuf.h"

-#include <gr_clock.h>
-
 #include <rte_ip6.h>

 #include <netinet/in.h>
@@ -108,7 +107,7 @@ static uint16_t icmp6_local_send_process(
                mbuf->ol_flags |= RTE_MBUF_F_RX_RSS_HASH;

                payload = PAYLOAD(icmp6_echo);
-               *payload = gr_clock_ns();
+               *payload = clock_ns();

                data = ip6_local_mbuf_data(mbuf);
                data->iface = iface_from_id(msg->iface_id);
diff --git a/modules/ip6/datapath/ndp_ns_output.c b/modules/ip6/datapath/ndp_ns_output.c
index 7c059392..f0b08643 100644
--- a/modules/ip6/datapath/ndp_ns_output.c
+++ b/modules/ip6/datapath/ndp_ns_output.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry

+#include "clock.h"
 #include "control_input.h"
 #include "graph.h"
 #include "icmp6.h"
@@ -9,7 +10,6 @@
 #include "ip6_datapath.h"
 #include "trace.h"

-#include <gr_clock.h>
 #include <gr_macro.h>

 #include <rte_mbuf.h>
@@ -32,7 +32,8 @@ int nh6_solicit(struct nexthop *nh) {

        // This function is called by the control plane main thread.
        // It is OK to modify the nexthop here.
-       l3->last_request = gr_clock_ns();
+       clock_update();
+       l3->last_request = clock_ns();
        if (l3->ucast_probes < nh_conf.max_ucast_probes)
                l3->ucast_probes++;
        else
diff --git a/modules/l2/cli/fdb.c b/modules/l2/cli/fdb.c
index 48d90b16..d93f0b33 100644
--- a/modules/l2/cli/fdb.c
+++ b/modules/l2/cli/fdb.c
@@ -97,8 +97,12 @@ static cmd_status_t fdb_show(struct gr_api_client *c, const struct ec_pnode *p)
        };
        const struct gr_fdb_entry *fdb;
        char flags[128];
+       gr_clock_ns now;
        int ret;

+       if ((now = gr_clock_ns(c)) < 0)
+               return CMD_ERROR;
+
        if (arg_str(p, "BRIDGE") != NULL) {
                if (arg_iface(c, p, "BRIDGE", GR_IFACE_TYPE_BRIDGE, &req.bridge_id) < 0)
                        return CMD_ERROR;
@@ -138,7 +142,7 @@ static cmd_status_t fdb_show(struct gr_api_client *c, const struct ec_pnode *p)
                if (fdb_format_flags(flags, sizeof(flags), fdb->flags))
                        gr_table_cell(table, 5, "%s", flags);

-               gr_table_cell(table, 6, "%ld", (gr_clock_ns() - fdb->last_seen) / GR_NS_PER_S);
+               gr_table_cell(table, 6, "%ld", (now - fdb->last_seen) / GR_NS_PER_S);

                if (gr_table_print_row(table) < 0)
                        break;
diff --git a/modules/l2/control/fdb.c b/modules/l2/control/fdb.c
index 823e8c81..b42c5873 100644
--- a/modules/l2/control/fdb.c
+++ b/modules/l2/control/fdb.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2026 Robin Jarry

+#include "clock.h"
 #include "event.h"
 #include "iface.h"
 #include "l2.h"
@@ -8,8 +9,6 @@
 #include "module.h"
 #include "rcu.h"

-#include <gr_clock.h>
-
 #include <rte_common.h>
 #include <rte_hash.h>

@@ -138,7 +137,7 @@ void fdb_learn(
                fdb = data;
        }

-       fdb->last_seen = gr_clock_ns();
+       fdb->last_seen = clock_ns();

        if ((fdb->flags & GR_FDB_F_LEARN)
            && (fdb->iface_id != iface_id || !l3_addr_eq(&fdb->vtep, vtep))) {
@@ -204,10 +203,11 @@ static struct api_out fdb_add(const void *request, struct api_ctx *) {
                if ((ret = rte_mempool_get(fdb_pool, &data)) < 0)
                        return api_out(-ret, 0, NULL);

+               clock_update();
                e = data;
                *e = req->fdb;
                e->bridge_id = iface->id;
-               e->last_seen = gr_clock_ns();
+               e->last_seen = clock_ns();

                if ((ret = rte_hash_add_key_data(fdb_hash, &key, data)) < 0) {
                        rte_mempool_put(fdb_pool, e);
@@ -216,10 +216,11 @@ static struct api_out fdb_add(const void *request, struct api_ctx *) {

                event_push(GR_EVENT_FDB_ADD, e);
        } else if (req->exist_ok) {
+               clock_update();
                e = data;
                *e = req->fdb;
                e->bridge_id = iface->id;
-               e->last_seen = gr_clock_ns();
+               e->last_seen = clock_ns();

                event_push(GR_EVENT_FDB_UPDATE, e);
        } else {
@@ -397,14 +398,13 @@ 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;
        void *data;
        time_t age;

-       now = gr_clock_ns();
+       clock_update();

        while (rte_hash_iterate(fdb_hash, &key, &data, &next) >= 0) {
                fdb = data;
@@ -412,7 +412,7 @@ static void fdb_ageing_cb(evutil_socket_t, short /*what*/, void * /*priv*/) {
                if ((fdb->flags & GR_FDB_F_STATIC) || !(fdb->flags & GR_FDB_F_LEARN))
                        continue;

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

                bridge = iface_from_id(fdb->bridge_id);
                if (bridge != NULL)
diff --git a/modules/policy/cli/conntrack.c b/modules/policy/cli/conntrack.c
index 051cd01c..11596342 100644
--- a/modules/policy/cli/conntrack.c
+++ b/modules/policy/cli/conntrack.c
@@ -19,6 +19,9 @@ static cmd_status_t conn_list(struct gr_api_client *c, const struct ec_pnode *)
        gr_clock_ns_t now;
        int ret;

+       if ((now = gr_clock_ns(c)) < 0)
+               return CMD_ERROR;
+
        struct gr_table *table = gr_table_new();
        gr_table_column(table, "IFACE", GR_DISP_LEFT); // 0
        gr_table_column(table, "ID", GR_DISP_RIGHT); // 1
@@ -31,8 +34,6 @@ static cmd_status_t conn_list(struct gr_api_client *c, const struct ec_pnode *)
        gr_table_column(table, "DPORT", GR_DISP_RIGHT | GR_DISP_INT); // 8
        gr_table_column(table, "LAST_UPDATE", GR_DISP_RIGHT); // 9

-       now = gr_clock_ns();
-
        gr_api_client_stream_foreach (conn, ret, c, GR_CONNTRACK_LIST, 0, NULL) {
                gr_table_cell(table, 0, "%s", iface_name_from_id(c, conn->iface_id));
                gr_table_cell(table, 1, "0x%08x", conn->id);
diff --git a/modules/policy/control/conntrack.c b/modules/policy/control/conntrack.c
index 43793fa2..0d1cf180 100644
--- a/modules/policy/control/conntrack.c
+++ b/modules/policy/control/conntrack.c
@@ -1,12 +1,12 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2025 Robin Jarry

+#include "clock.h"
 #include "conntrack.h"
 #include "log.h"
 #include "module.h"
 #include "rcu.h"

-#include <gr_clock.h>
 #include <gr_net_types.h>

 #include <rte_hash.h>
@@ -240,7 +240,7 @@ again:
                        goto again;
        }

-       atomic_store(&c->last_update, gr_clock_ns());
+       atomic_store(&c->last_update, clock_ns());
 }

 bool gr_conn_parse_key(
@@ -389,13 +389,14 @@ struct conn *gr_conn_insert(const struct conn_key *fwd_key, const struct conn_ke
 }

 static void do_ageing(evutil_socket_t, short /*what*/, void * /*priv*/) {
-       gr_clock_ns_t now = gr_clock_ns(), last;
+       gr_clock_ns_t last;
        uint64_t age, timeout;
        struct conn *conn;
        const void *key;
        uint32_t iter;
        void *data;

+       clock_update();
        iter = 0;
        while (rte_hash_iterate(conn_hash, &key, &data, &iter) >= 0) {
                conn = conn_ptr(data);
@@ -442,9 +443,9 @@ static void do_ageing(evutil_socket_t, short /*what*/, void * /*priv*/) {
                }

                last = atomic_load(&conn->last_update);
-               if (last > now)
+               if (last > clock_ns())
                        continue;
-               age = (now - last) / GR_NS_PER_S;
+               age = (clock_ns() - last) / GR_NS_PER_S;
                if (age > timeout)
                        gr_conn_destroy(conn);
        }
diff --git a/smoke/fib_inject.c b/smoke/fib_inject.c
index dc050625..8871a54b 100644
--- a/smoke/fib_inject.c
+++ b/smoke/fib_inject.c
@@ -192,7 +192,7 @@ int main(int argc, char **argv) {
        unsigned count = 10000;
        uint32_t installed = 0;
        unsigned dist_count;
-       gr_clock_ns_t time;
+       struct timespec before, after;
        bool ipv6 = false;
        float duration;
        int ret;
@@ -242,14 +242,17 @@ int main(int argc, char **argv) {
        if (create_nexthops(c) < 0)
                return EXIT_FAILURE;

-       time = gr_clock_ns();
+       clock_gettime(CLOCK_MONOTONIC_RAW, &before);

        if (ipv6)
                ret = inject_ipv6(c, count);
        else
                ret = inject_ipv4(c, count);

-       duration = (float)(gr_clock_ns() - time) / (float)GR_NS_PER_S;
+       clock_gettime(CLOCK_MONOTONIC_RAW, &after);
+
+       duration = (float)(after.tv_sec - before.tv_sec)
+                  + (float)(after.tv_nsec - before.tv_nsec) / (float)GR_NS_PER_S;

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



More information about the grout mailing list