[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