From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by smtp.lore.kernel.org (Postfix) with ESMTP id EB15EEF99CD for ; Fri, 13 Feb 2026 19:21:13 +0000 (UTC) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 5AF2140671; Fri, 13 Feb 2026 20:20:56 +0100 (CET) Received: from mail-wr1-f42.google.com (mail-wr1-f42.google.com [209.85.221.42]) by mails.dpdk.org (Postfix) with ESMTP id 8519B40653 for ; Fri, 13 Feb 2026 20:20:52 +0100 (CET) Received: by mail-wr1-f42.google.com with SMTP id ffacd0b85a97d-436e8758b91so992271f8f.0 for ; Fri, 13 Feb 2026 11:20:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=networkplumber-org.20230601.gappssmtp.com; s=20230601; t=1771010452; x=1771615252; darn=dpdk.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qeWZN7rGbzrPKFatnN+B2Rdgxf61epO+QZ7OzLcI8BQ=; b=emfnm8o1WRS7KNvWdlOP0Emn5hEzHC4pfZMn4GtXqhT5lEXeguG2yAbxfVPvBYPWE1 a34WJCQwFrPorJ/IzU8So0gQaqBUxtMAgjYQ5nglXb22BNpcmUztnlbnrCt7is7cqNBs c1jBo4KKhm72h5JvG+yeR9vXvyXBXKppX+PnJnWWkdNpZa6oYsQwSqUL9duk4DNaatgi NABAB/5hQBq0r6wNTB9YdjxetQStYiGniwN7MMq/hOCXVGiWsfEg+3kdicmBbuIwBmPO f2ksSic0J7f00bM8OoFFf0mY/FOEOTZ7bBSXRvy9hDeMuat+boudbTehhrrE5gHt201c 6NfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771010452; x=1771615252; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qeWZN7rGbzrPKFatnN+B2Rdgxf61epO+QZ7OzLcI8BQ=; b=D+N1+G7E0/y5w7c/tnqfFFP1QnxL27qR3P3iY0lvQbhht+SQaz9e7u7/qmreA0IlYu kT9BPL54GBtSsbXrXa6Z///p6VzuvZKRzJCtejZ/aYyDq39nDfbWZM9CvIupw/HqKI1D FO0OoQ3okiGczUQM7vJXK5qI7JZJb0ylUAmrlGzDav5LngGD6z5v77BW5so/jWKYoLHE LB3kbmTw3LFWaYsNX5nLssuCKmg3ln+pU91RkKLTuweADr8alOK+kE84tZqlVfLbjbMu QCyihzYrZfORXVlxaSTYU5nIrmGnMtZlL9ZWrk2C4dVGlOVwtCPWrC+44UDsi4IUE1vF sNpg== X-Gm-Message-State: AOJu0YxRxkyZAeun4a+vk7CDMldUPpwl7QdVdjlZEsgt3tfsE5328Db6 SpYhLXRg6qBn8GIWYLzvMTrxbFkLazMdbbbQHNbbrYhJJmKgOrG9wDKGzpOyEkyYCpulN7brrJO 2Yc2a X-Gm-Gg: AZuq6aJN5VuzYCaylNx6XT10UMVNUnWuohkOyMj0Ma40KikJFwNRXgHU+NpN9IXbQIh w4YrcyQTbYg20Dha1JhbJwDH4R2mk0VCx/T7SYk2Y8QZlz0vg6Mnv5hrBrezSdewmPf8U0nlj3o vUyI+lnG7e3GPhA5aDlZc1SUSwzxgm+YyuxVnyguWCAMjejJBqJyi7erbNXZYU2RXoXIMiS3dVe q3l+lQG5dwdiMhbCRfsol/OeUHaEXNWnPwN4UzpRWGvCiR4ErPypwNO6twln34myMi2OhNrMlBa o53UiywTWp1micejVXd2lOXZZlvsgnQwp2mofcMD/932cJHf5/6HV2sVGHUw41Bt8NhYzu7Cpey Skb0/cQo5aHhhnrBxUTtgy/w73AtyZ0e3oTKQj7g0BKpl/kQZwoi4/pvgCsS0zbZxtvAHGZJTZ8 C4GzKnwLakMkCOq3LNpMJGZTvdC4xDbt9ERCZGeSwFIc1+ctBVle4n+FuN4oxfSQ== X-Received: by 2002:a05:6000:2509:b0:435:e451:2f93 with SMTP id ffacd0b85a97d-4379dba7c63mr1037723f8f.60.1771010451984; Fri, 13 Feb 2026 11:20:51 -0800 (PST) Received: from phoenix.lan (204-195-96-226.wavecable.com. [204.195.96.226]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43796ac8d82sm7758723f8f.31.2026.02.13.11.20.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 13 Feb 2026 11:20:51 -0800 (PST) From: Stephen Hemminger To: dev@dpdk.org Cc: Stephen Hemminger , Reshma Pattan Subject: [PATCH v7 4/7] pcapng: improve performance of timestamping Date: Fri, 13 Feb 2026 11:18:21 -0800 Message-ID: <20260213192039.221213-5-stephen@networkplumber.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260213192039.221213-1-stephen@networkplumber.org> References: <20260126210615.175816-1-stephen@networkplumber.org> <20260213192039.221213-1-stephen@networkplumber.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Avoid doing expensive divide operations when converting timestamps from cycles (TSC) to nanoseconds for pcapng. Precompute a rte_reciprocal_u64 inverse of the TSC frequency and a right-shift count chosen so that the intermediate product (delta >> shift) * NSEC_PER_SEC cannot overflow uint64_t. The per-packet conversion then requires only a shift, a multiply, and a reciprocal divide—no division. For TSC frequencies less than 18.4 GHz the shift value will be zero but code is defensive to be future proof. Signed-off-by: Stephen Hemminger --- lib/pcapng/rte_pcapng.c | 97 +++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 24 deletions(-) diff --git a/lib/pcapng/rte_pcapng.c b/lib/pcapng/rte_pcapng.c index a2254ba807..7eedbaf298 100644 --- a/lib/pcapng/rte_pcapng.c +++ b/lib/pcapng/rte_pcapng.c @@ -37,12 +37,23 @@ /* upper bound for strings in pcapng option data */ #define PCAPNG_STR_MAX UINT16_MAX +/* + * Converter from TSC values to nanoseconds since Unix epoch. + * Uses reciprocal multiply to avoid runtime division. + */ +struct tsc_clock { + uint64_t tsc_base; /* TSC value at initialization. */ + uint64_t ns_base; /* Nanoseconds since epoch at init. */ + struct rte_reciprocal_u64 tsc_hz_inv; /* Reciprocal of TSC frequency. */ + uint32_t shift; /* Pre-shift to avoid overflow. */ +}; + /* Format of the capture file handle */ struct rte_pcapng { int outfd; /* output file */ unsigned int ports; /* number of interfaces added */ - uint64_t offset_ns; /* ns since 1/1/1970 when initialized */ - uint64_t tsc_base; /* TSC when started */ + + struct tsc_clock clock; /* DPDK port id to interface index in file */ uint32_t port_index[RTE_MAX_ETHPORTS]; @@ -98,21 +109,59 @@ static ssize_t writev(int fd, const struct iovec *iov, int iovcnt) #define if_indextoname(ifindex, ifname) NULL #endif -/* Convert from TSC (CPU cycles) to nanoseconds */ -static uint64_t -pcapng_timestamp(const rte_pcapng_t *self, uint64_t cycles) +/* + * Initialize TSC-to-epoch-ns converter. + * + * Captures current TSC and system clock as a reference point. + */ +static int +tsc_clock_init(struct tsc_clock *clk) { - uint64_t delta, rem, secs, ns; - const uint64_t hz = rte_get_tsc_hz(); + struct timespec ts; + uint64_t cycles, tsc_hz, divisor; + uint32_t shift; + + memset(clk, 0, sizeof(*clk)); + + /* If Hz is zero, something is seriously broken. */ + tsc_hz = rte_get_tsc_hz(); + if (tsc_hz == 0) + return -1; + + /* + * Choose shift so (delta >> shift) * NSEC_PER_SEC fits in uint64_t. + * For typical GHz-range TSC and ~1s deltas this is 0. + */ + shift = 0; + divisor = tsc_hz; + while (divisor > UINT64_MAX / NSEC_PER_SEC) { + divisor >>= 1; + shift++; + } + + clk->shift = shift; + clk->tsc_hz_inv = rte_reciprocal_value_u64(divisor); + + /* Sample TSC and system clock as close together as possible. */ + cycles = rte_get_tsc_cycles(); + clock_gettime(CLOCK_REALTIME, &ts); + clk->tsc_base = (cycles + rte_get_tsc_cycles()) / 2; + clk->ns_base = (uint64_t)ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; + + return 0; +} - delta = cycles - self->tsc_base; +/* Convert a TSC value to nanoseconds since Unix epoch. */ +static inline uint64_t +tsc_to_ns_epoch(const struct tsc_clock *clk, uint64_t tsc) +{ + uint64_t delta, ns; - /* Avoid numeric wraparound by computing seconds first */ - secs = delta / hz; - rem = delta % hz; - ns = (rem * NS_PER_S) / hz; + delta = tsc - clk->tsc_base; + ns = (delta >> clk->shift) * NSEC_PER_SEC; + ns = rte_reciprocal_divide_u64(ns, &clk->tsc_hz_inv); - return secs * NS_PER_S + ns + self->offset_ns; + return clk->ns_base + ns; } /* length of option including padding */ @@ -344,7 +393,7 @@ rte_pcapng_write_stats(rte_pcapng_t *self, uint16_t port_id, { struct pcapng_statistics *hdr; struct pcapng_option *opt; - uint64_t start_time = self->offset_ns; + uint64_t start_time = self->clock.ns_base; uint64_t sample_time; uint32_t optlen, len; uint32_t *buf; @@ -397,7 +446,7 @@ rte_pcapng_write_stats(rte_pcapng_t *self, uint16_t port_id, hdr->block_length = len; hdr->interface_id = self->port_index[port_id]; - sample_time = pcapng_timestamp(self, rte_get_tsc_cycles()); + sample_time = tsc_to_ns_epoch(&self->clock, rte_get_tsc_cycles()); hdr->timestamp_hi = sample_time >> 32; hdr->timestamp_lo = (uint32_t)sample_time; @@ -684,10 +733,13 @@ rte_pcapng_write_packets(rte_pcapng_t *self, return -1; } - /* adjust timestamp recorded in packet */ + /* + * When data is captured pcapng_copy the current TSC is stored. + * Adjust the value recorded in file to PCAP epoch units. + */ cycles = (uint64_t)epb->timestamp_hi << 32; cycles += epb->timestamp_lo; - timestamp = pcapng_timestamp(self, cycles); + timestamp = tsc_to_ns_epoch(&self->clock, cycles); epb->timestamp_hi = timestamp >> 32; epb->timestamp_lo = (uint32_t)timestamp; @@ -733,8 +785,6 @@ rte_pcapng_fdopen(int fd, { unsigned int i; rte_pcapng_t *self; - struct timespec ts; - uint64_t cycles; int ret; if ((osname && strlen(osname) > PCAPNG_STR_MAX) || @@ -754,11 +804,10 @@ rte_pcapng_fdopen(int fd, self->outfd = fd; self->ports = 0; - /* record start time in ns since 1/1/1970 */ - cycles = rte_get_tsc_cycles(); - clock_gettime(CLOCK_REALTIME, &ts); - self->tsc_base = (cycles + rte_get_tsc_cycles()) / 2; - self->offset_ns = rte_timespec_to_ns(&ts); + if (tsc_clock_init(&self->clock) < 0) { + rte_errno = ENODEV; + goto fail; + } for (i = 0; i < RTE_MAX_ETHPORTS; i++) self->port_index[i] = UINT32_MAX; -- 2.51.0