[PATCH grout] add high-performance clock

Morten Brørup mb at smartsharesystems.com
Sat Jun 13 15:51:47 CEST 2026


When considering the right location for this module, keep in mind that support for wall clock (like CLOCK_REALTIME) might be added to it.

It could be a main thread periodic event firing e.g. once per minute.
When the event fires, the handler will take a snapshot of CLOCK_REALTIME and the rte_rdtsc() based Grout clock, something like this:

clock_update();
wallclock = timsepec_to_ns(clock_gettime(CLOCK_REALTIME));
wallclock_sampled = clock_ns();

Then the workers can derive the wall clock by adding the difference, like this:

return wallclock + (clock_ns() - wallclock_sampled);

Race protection must be added; but the code above shows the principle.


Venlig hilsen / Kind regards,
-Morten Brørup

> -----Original Message-----
> From: Morten Brørup
> Sent: Friday, 12 June 2026 20.45
> To: 'grout at dpdk.org'
> Subject: RE: [PATCH grout] add high-performance clock
> 
> 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