[PATCH v2] add high-performance clock
Morten Brørup
mb at smartsharesystems.com
Tue Aug 25 14:36:09 CEST 2026
Hang on...
I just got a better idea. Will submit as v3.
Venlig hilsen / Kind regards,
-Morten Brørup
> -----Original Message-----
> From: Morten Brørup [mailto:mb at smartsharesystems.com]
> Sent: Tuesday, 25 August 2026 13.12
> To: grout at dpdk.org; Robin Jarry
> Cc: Morten Brørup
> Subject: [PATCH v2] add high-performance clock
>
> Reading the common clock, gr_clock_ns(), is implemented as
> clock_gettime(CLOCK_MONOTONIC_RAW), which is quite slow, even though
> the
> kernel exposes it as a vDSO.
>
> For improved performance, especially in the dataplane, introduce a
> high-
> performance clock, based on snapshotting the output from gr_clock_ns(),
> for use where a snapshot is sufficiently accurate.
>
> Immediately before walking the graph in the dataplane worker thread's
> main
> loop, update the clock, so it is safe to use in the "process" functions
> of the graph nodes.
>
> Upgrade some graph node "process" functions to use this clock.
>
> Signed-off-by: Morten Brørup <mb at smartsharesystems.com>
> ---
> Note:
> Functions shared by dataplane threads and control plane threads were
> not
> upgraded with this patch.
> Future patches will address this.
>
> v2:
> * Eliminate the concept of a client API message required to read
> Grout's
> clock. (Robin Jarry)
> * Keep gr_clock_ns() as is.
> * For clock source, use gr_clock_ns() instead of rte_rdtsc().
> * Add instrumentation for debug builds.
> * Use __thread instead of RTE_PER_LCORE.
> This aligns with Grout's existing code style.
> ---
> api/gr_clock.h | 2 +
> main/clock.c | 51 +++++++++++++++
> main/clock.h | 82 +++++++++++++++++++++++++
> main/meson.build | 1 +
> modules/infra/datapath/main_loop.c | 3 +
> modules/ip/datapath/icmp_input.c | 5 +-
> modules/ip/datapath/icmp_local_send.c | 5 +-
> modules/ip6/datapath/icmp6_input.c | 5 +-
> modules/ip6/datapath/icmp6_local_send.c | 5 +-
> 9 files changed, 147 insertions(+), 12 deletions(-)
> create mode 100644 main/clock.c
> create mode 100644 main/clock.h
>
> diff --git a/api/gr_clock.h b/api/gr_clock.h
> index d2d98fba..4cc68749 100644
> --- a/api/gr_clock.h
> +++ b/api/gr_clock.h
> @@ -23,6 +23,8 @@ static inline struct timespec gr_clock_raw(void) {
> }
>
> #define GR_NS_PER_S (gr_clock_ns_t)1000000000LL
> +#define GR_NS_PER_MS (gr_clock_ns_t)1000000LL
> +#define GR_NS_PER_US (gr_clock_ns_t)1000LL
>
> // Get powered-on (non-suspended, non-hibernated) time since last boot
> [nanoseconds],
> // using a common clock across all processes.
> diff --git a/main/clock.c b/main/clock.c
> new file mode 100644
> index 00000000..8ae57bd1
> --- /dev/null
> +++ b/main/clock.c
> @@ -0,0 +1,51 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +// Copyright (c) 2026 SmartShare Systems
> +
> +#include "clock.h"
> +#include "log.h"
> +#include "module.h"
> +
> +LOG_TYPE("clock");
> +
> +__thread gr_clock_ns_t clock_snapshot_ns = INT64_C(-1);
> +__thread int32_t clock_snapshot_s = INT32_C(-1);
> +
> +void _clock_assert_updated(const char *file, int line, const char
> *func) {
> + gr_clock_ns_t delta = gr_clock_ns() - clock_snapshot_ns;
> +#define CLOCK_UPDATED_THRESHOLD (10 * GR_NS_PER_MS)
> + if (unlikely(delta > CLOCK_UPDATED_THRESHOLD))
> + LOG(ERR, "%s:%d %s: clock not updated, %ld ns behind",
> + file, line, func, delta);
> +}
> +
> +void _clock_log_update(const char *file, int line, const char *func) {
> + LOG(DEBUG, "%s:%d %s: clock update",
> + file, line, func);
> +}
> +
> +void _clock_update(void) {
> + struct timespec tp = gr_clock_raw();
> + clock_snapshot_ns = tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
> + clock_snapshot_s = tp.tv_sec;
> +}
> +
> +static __rte_cold void clock_init(struct event_base *) {
> + // Update the clock for the main thread.
> + clock_update();
> +}
> +
> +static struct module clock_module = {
> + .name = "clock",
> + .init = clock_init,
> +};
> +
> +RTE_INIT(clock_constructor) {
> + module_register(&clock_module);
> +}
> +
> +// For review of compiled inline functions
> +
> +gr_clock_ns_t clock_ns_review(void);
> +gr_clock_ns_t clock_ns_review(void) {
> + return clock_ns();
> +}
> diff --git a/main/clock.h b/main/clock.h
> new file mode 100644
> index 00000000..9572ee58
> --- /dev/null
> +++ b/main/clock.h
> @@ -0,0 +1,82 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +// Copyright (c) 2026 SmartShare Systems
> +
> +#pragma once
> +
> +#include "log.h"
> +
> +#include <gr_clock.h>
> +
> +#include <rte_common.h>
> +#include <rte_debug.h>
> +#include <rte_eal.h>
> +#include <rte_lcore.h>
> +
> +// (Internal)
> +// The clock.
> +// Implemented as per-thread snapshot of the common clock.
> +// Note: Per thread to avoid certain race conditions.
> +extern __thread gr_clock_ns_t clock_snapshot_ns;
> +extern __thread int32_t clock_snapshot_s;
> +
> +// (Internal)
> +// Assert that clock has been updated recently for the current thread.
> +// No need to call from dataplane worker thread; the main loop keeps
> the clock updated.
> +void _clock_assert_updated(const char *file, int line, const char
> *func);
> +
> +// (Internal)
> +// Log that the clock was updated.
> +// Do not call from dataplane worker thread.
> +void _clock_log_update(const char *file, int line, const char *func);
> +
> +// (Internal)
> +static __rte_always_inline gr_clock_ns_t _clock_ns(void) {
> + RTE_ASSERT(clock_snapshot_ns != INT64_C(-1));
> + __rte_assume(clock_snapshot_ns >= 0);
> + return clock_snapshot_ns;
> +}
> +
> +// Get the clock for the current thread [nanoseconds].
> +// Does not return negative values.
> +#ifndef NDEBUG
> +#define clock_ns() ({ \
> + if (unlikely(!rte_lcore_has_role(rte_lcore_id(), ROLE_NON_EAL)))
> \
> + _clock_assert_updated(__FILE__, __LINE__, __func__); \
> + _clock_ns(); /* statement expression value */ \
> +})
> +#else
> +#define clock_ns() _clock_ns()
> +#endif
> +
> +// (Internal)
> +static __rte_always_inline int32_t _clock_s(void) {
> + RTE_ASSERT(clock_snapshot_s != INT32_C(-1));
> + __rte_assume(clock_snapshot_s >= 0);
> + return clock_snapshot_s;
> +}
> +
> +// Get the clock for the current thread [seconds].
> +// Does not return negative values.
> +#ifndef NDEBUG
> +#define clock_s() ({ \
> + if (unlikely(!rte_lcore_has_role(rte_lcore_id(), ROLE_NON_EAL)))
> \
> + _clock_assert_updated(__FILE__, __LINE__, __func__); \
> + _clock_s(); /* statement expression value */ \
> +})
> +#else
> +#define clock_s() _clock_s()
> +#endif
> +
> +// (Internal)
> +void _clock_update(void);
> +
> +// Update the clock for the current thread.
> +#ifndef NDEBUG
> +#define clock_update() do { \
> + if (unlikely(!rte_lcore_has_role(rte_lcore_id(), ROLE_NON_EAL)))
> \
> + _clock_log_update(__FILE__, __LINE__, __func__); \
> + _clock_update(); \
> +} while (0)
> +#else
> +#define clock_update() _clock_update()
> +#endif
> diff --git a/main/meson.build b/main/meson.build
> index 1abc8389..f089a9d2 100644
> --- a/main/meson.build
> +++ b/main/meson.build
> @@ -3,6 +3,7 @@
>
> src += files(
> 'api.c',
> + 'clock.c',
> 'config.c',
> 'control_queue.c',
> 'dpdk.c',
> diff --git a/modules/infra/datapath/main_loop.c
> b/modules/infra/datapath/main_loop.c
> index 2aae4030..51dd167c 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 "control_input.h"
> #include "datapath.h"
> @@ -456,6 +458,7 @@ reconfig:
> worker_active_inc();
>
> for (;;) {
> + clock_update();
> rte_graph_walk(graph);
>
> if (++loop == HOUSEKEEPING_INTERVAL) {
> 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 fbbc6fc9..23bc3fe1 100644
> --- a/modules/ip/datapath/icmp_local_send.c
> +++ b/modules/ip/datapath/icmp_local_send.c
> @@ -1,6 +1,7 @@
> // SPDX-License-Identifier: BSD-3-Clause
> // Copyright (c) 2024 Christophe Fontaine
>
> +#include "clock.h"
> #include "control_input.h"
> #include "graph.h"
> #include "iface.h"
> @@ -8,8 +9,6 @@
> #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/datapath/icmp6_input.c
> b/modules/ip6/datapath/icmp6_input.c
> index 9a001aa3..14bf13cc 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,
> @@ -92,7 +91,7 @@ icmp6_input_process(struct rte_graph *graph, struct
> rte_node *node, void **objs,
> break;
> 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 66202ea5..da42568b 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"
> @@ -9,8 +10,6 @@
> #include "ip6_datapath.h"
> #include "mbuf.h"
>
> -#include <gr_clock.h>
> -
> #include <rte_ip6.h>
>
> #include <netinet/in.h>
> @@ -111,7 +110,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 = msg.iface;
> --
> 2.43.0
More information about the grout
mailing list