The pcapng library converts the TSC into nanoseconds since 1/1/1970, because that is what the pcapng file format records. Nothing about that conversion is specific to packet capture: it needs the TSC frequency and a reference point relating the TSC to the wall clock, both of which EAL already has or can take at init.
Record the TSC alongside a CLOCK_REALTIME reading while the TSC frequency is being determined, and add rte_tsc_to_ns() to convert any TSC value using that reference point. The seconds are computed before the remainder is scaled, so the multiply by NS_PER_S cannot overflow. The reference point is taken per process rather than shared through the memory config, so a secondary process may differ from the primary by the sampling error. Signed-off-by: Stephen Hemminger <[email protected]> --- doc/api/doxy-api-index.md | 1 + doc/guides/rel_notes/release_26_11.rst | 6 +++ lib/eal/common/eal_common_timer.c | 59 ++++++++++++++++++++++++++ lib/eal/include/rte_time.h | 34 +++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 62390f7be3..97b908667c 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -73,6 +73,7 @@ The public API headers are grouped by topics: - **timers**: [cycles](@ref rte_cycles.h), + [time](@ref rte_time.h), [timer](@ref rte_timer.h), [alarm](@ref rte_alarm.h) diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst index be758a0c02..5f960692e2 100644 --- a/doc/guides/rel_notes/release_26_11.rst +++ b/doc/guides/rel_notes/release_26_11.rst @@ -73,6 +73,12 @@ New Features Added the experimental ``rte_cpu_socket_id()`` function to map an OS logical CPU ID to the NUMA socket containing that CPU. +* **Added API to convert TSC to epoch time.** + + Added the experimental ``rte_tsc_to_ns()`` function in ``rte_time.h`` + to convert a TSC value into nanoseconds since 1/1/1970, + using a reference point recorded during EAL initialization. + * **Added pile stack and mempool driver.** Introduced pile, a lock-free, stack-like implementation, diff --git a/lib/eal/common/eal_common_timer.c b/lib/eal/common/eal_common_timer.c index 67ede70c62..4dfb7099b3 100644 --- a/lib/eal/common/eal_common_timer.c +++ b/lib/eal/common/eal_common_timer.c @@ -8,8 +8,10 @@ #include <rte_common.h> #include <rte_log.h> #include <rte_cycles.h> +#include <rte_os_shim.h> #include <rte_pause.h> #include <rte_eal.h> +#include <rte_time.h> #include <eal_export.h> #include "eal_private.h" @@ -18,6 +20,10 @@ /* The frequency of the RDTSC timer resolution */ static uint64_t eal_tsc_resolution_hz; +/* Reference point relating the TSC to the wall clock, taken at init */ +static uint64_t eal_tsc_base; +static uint64_t eal_epoch_ns; + /* Pointer to user delay function */ RTE_EXPORT_SYMBOL(rte_delay_us) void (*rte_delay_us)(unsigned int) = NULL; @@ -39,6 +45,36 @@ rte_get_tsc_hz(void) return eal_tsc_resolution_hz; } +/* + * Convert a count of cycles to nanoseconds. + * + * Compute the whole seconds first, so that the remainder is always + * less than the frequency and scaling it by NS_PER_S cannot overflow. + */ +static uint64_t +cycles_to_ns(uint64_t cycles) +{ + uint64_t secs = cycles / eal_tsc_resolution_hz; + uint64_t rem = cycles % eal_tsc_resolution_hz; + + return secs * NS_PER_S + (rem * NS_PER_S) / eal_tsc_resolution_hz; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_tsc_to_ns, 26.11) +uint64_t +rte_tsc_to_ns(uint64_t tsc) +{ + /* + * A caller may convert a TSC value sampled before the reference + * point was taken, so handle both directions on an unsigned + * magnitude. + */ + if (unlikely(tsc < eal_tsc_base)) + return eal_epoch_ns - cycles_to_ns(eal_tsc_base - tsc); + + return eal_epoch_ns + cycles_to_ns(tsc - eal_tsc_base); +} + static uint64_t estimate_tsc_freq(void) { @@ -52,6 +88,27 @@ estimate_tsc_freq(void) return RTE_ALIGN_MUL_NEAR(rte_rdtsc() - start, CYC_PER_10MHZ); } +/* + * Record the TSC value matching a wall clock reading, so that any TSC + * value can later be expressed as nanoseconds since the Unix epoch. + * Read the TSC either side of the clock to halve the sampling error. + */ +static void +init_tsc_base(void) +{ + struct timespec ts; + uint64_t cycles; + + cycles = rte_get_tsc_cycles(); + if (clock_gettime(CLOCK_REALTIME, &ts) < 0) { + EAL_LOG(WARNING, "Could not read wall clock," + " TSC to epoch conversion unavailable"); + return; + } + eal_tsc_base = (cycles + rte_get_tsc_cycles()) / 2; + eal_epoch_ns = rte_timespec_to_ns(&ts); +} + int init_tsc_freq(void) { @@ -71,6 +128,7 @@ init_tsc_freq(void) } eal_tsc_resolution_hz = mcfg->tsc_hz; + init_tsc_base(); return 0; } @@ -87,6 +145,7 @@ init_tsc_freq(void) EAL_LOG(DEBUG, "TSC frequency is ~%" PRIu64 " KHz", freq / 1000); eal_tsc_resolution_hz = freq; mcfg->tsc_hz = freq; + init_tsc_base(); return 0; } diff --git a/lib/eal/include/rte_time.h b/lib/eal/include/rte_time.h index c5c3a233e4..3122e062ab 100644 --- a/lib/eal/include/rte_time.h +++ b/lib/eal/include/rte_time.h @@ -5,9 +5,17 @@ #ifndef _RTE_TIME_H_ #define _RTE_TIME_H_ +/** + * @file + * + * Time and cycle counter conversion. + */ + #include <stdint.h> #include <time.h> +#include <rte_compat.h> + #ifdef __cplusplus extern "C" { #endif @@ -102,6 +110,32 @@ rte_ns_to_timespec(uint64_t nsec) return ts; } +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. + * + * Convert a TSC value into nanoseconds since the Unix epoch. + * + * EAL records the TSC alongside a CLOCK_REALTIME reading during + * initialization, and this converts using that reference point and the + * TSC frequency. Values sampled before initialization are also handled, + * and the result tracks the wall clock as it was at init: later changes + * to the system time are not reflected. + * + * Each process takes its own reference point, so values converted in a + * secondary process may differ from the primary by the sampling error. + * + * Must be called after rte_eal_init(), which fails if the TSC frequency + * cannot be determined. + * + * @param tsc + * A value read from rte_get_tsc_cycles() or rte_rdtsc(). + * @return + * Nanoseconds since 1/1/1970. + */ +__rte_experimental +uint64_t rte_tsc_to_ns(uint64_t tsc); + #ifdef __cplusplus } #endif -- 2.53.0

