diff --git a/ipm/patches/dpdk/25.11/0001-eal-add-lcore-busyness-telemetry.patch b/ipm/patches/dpdk/25.11/0001-eal-add-lcore-busyness-telemetry.patch new file mode 100644 index 0000000..9c2afc5 --- /dev/null +++ b/ipm/patches/dpdk/25.11/0001-eal-add-lcore-busyness-telemetry.patch @@ -0,0 +1,877 @@ +From 7361218c9389379265828d7d5e0e440da78fc873 Mon Sep 17 00:00:00 2001 +From: Anatoly Burakov +Date: Fri, 20 Sep 2024 09:08:28 +0100 +Subject: [PATCH 1/3] eal: add lcore busyness telemetry + +Currently, there is no way to measure lcore busyness in a passive way, +without any modifications to the application. This patch adds a new EAL +API that will be able to passively track core busyness. + +The busyness is calculated by relying on the fact that most DPDK API's +will poll for packets. Empty polls can be counted as "idle", while +non-empty polls can be counted as busy. To measure lcore busyness, we +simply call the telemetry timestamping function with the number of polls +a particular code section has processed, and count the number of cycles +we've spent processing empty bursts. The more empty bursts we encounter, +the less cycles we spend in "busy" state, and the less core busyness +will be reported. + +In order for all of the above to work without modifications to the +application, the library code needs to be instrumented with calls to +the lcore telemetry busyness timestamping function. The following parts +of DPDK are instrumented with lcore telemetry calls: + +- All major driver API's: + - ethdev + - cryptodev + - compressdev + - regexdev + - bbdev + - rawdev + - eventdev + - dmadev +- Some additional libraries: + - ring + - distributor + +To avoid performance impact from having lcore telemetry support, a +global variable is exported by EAL, and a call to timestamping function +is wrapped into a macro, so that whenever telemetry is disabled, it only +takes one additional branch and no function calls are performed. It is +also possible to disable it at compile time by commenting out +RTE_LCORE_BUSYNESS from build config. + +This patch also adds a telemetry endpoint to report lcore busyness, as +well as telemetry endpoints to enable/disable lcore telemetry. + +Signed-off-by: Kevin Laatz +Signed-off-by: Conor Walsh +Signed-off-by: David Hunt +Signed-off-by: Anatoly Burakov +--- + config/rte_config.h | 2 + + lib/bbdev/rte_bbdev.h | 5 + + lib/compressdev/rte_compressdev.c | 6 +- + lib/cryptodev/rte_cryptodev.h | 2 + + lib/distributor/rte_distributor.c | 21 +- + lib/distributor/rte_distributor_single.c | 14 +- + lib/dmadev/rte_dmadev.h | 4 + + lib/eal/common/eal_common_lcore_telemetry.c | 329 ++++++++++++++++++++ + lib/eal/common/meson.build | 1 + + lib/eal/freebsd/eal.c | 1 + + lib/eal/include/rte_lcore.h | 84 +++++ + lib/eal/linux/eal.c | 1 + + lib/eal/meson.build | 3 + + lib/ethdev/rte_ethdev.h | 2 + + lib/eventdev/rte_eventdev.h | 6 +- + lib/rawdev/rte_rawdev.c | 5 +- + lib/regexdev/rte_regexdev.h | 5 +- + lib/ring/rte_ring.h | 8 +- + lib/ring/rte_ring_elem_pvt.h | 1 + + 19 files changed, 484 insertions(+), 16 deletions(-) + create mode 100644 lib/eal/common/eal_common_lcore_telemetry.c + +diff --git a/config/rte_config.h b/config/rte_config.h +index a2609fa403..0f2502baa2 100644 +--- a/config/rte_config.h ++++ b/config/rte_config.h +@@ -51,6 +51,8 @@ + #define RTE_LOG_DP_LEVEL RTE_LOG_INFO + #define RTE_MAX_VFIO_CONTAINERS 64 + #define RTE_TRACE 1 ++#define RTE_LCORE_BUSYNESS 1 ++#define RTE_LCORE_BUSYNESS_PERIOD 4000000ULL + + /* bsd module defines */ + #define RTE_CONTIGMEM_MAX_NUM_BUFS 64 +diff --git a/lib/bbdev/rte_bbdev.h b/lib/bbdev/rte_bbdev.h +index 4912bc7b6f..3dcaa746a3 100644 +--- a/lib/bbdev/rte_bbdev.h ++++ b/lib/bbdev/rte_bbdev.h +@@ -25,6 +25,7 @@ + + #include + #include ++#include + + #include "rte_bbdev_op.h" + +@@ -768,6 +769,7 @@ rte_bbdev_dequeue_enc_ops(uint16_t dev_id, uint16_t queue_id, + if (num_ops_dequeued > 0) + rte_bbdev_trace_dequeue(dev_id, queue_id, (void **)ops, num_ops, + num_ops_dequeued, rte_bbdev_op_type_str(RTE_BBDEV_OP_TURBO_ENC)); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(num_ops_dequeued); + return num_ops_dequeued; + } + +@@ -804,6 +806,7 @@ rte_bbdev_dequeue_dec_ops(uint16_t dev_id, uint16_t queue_id, + if (num_ops_dequeued > 0) + rte_bbdev_trace_dequeue(dev_id, queue_id, (void **)ops, num_ops, + num_ops_dequeued, rte_bbdev_op_type_str(RTE_BBDEV_OP_TURBO_DEC)); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(num_ops_dequeued); + return num_ops_dequeued; + } + +@@ -839,6 +842,7 @@ rte_bbdev_dequeue_ldpc_enc_ops(uint16_t dev_id, uint16_t queue_id, + if (num_ops_dequeued > 0) + rte_bbdev_trace_dequeue(dev_id, queue_id, (void **)ops, num_ops, + num_ops_dequeued, rte_bbdev_op_type_str(RTE_BBDEV_OP_LDPC_ENC)); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(num_ops_dequeued); + return num_ops_dequeued; + } + +@@ -873,6 +877,7 @@ rte_bbdev_dequeue_ldpc_dec_ops(uint16_t dev_id, uint16_t queue_id, + if (num_ops_dequeued > 0) + rte_bbdev_trace_dequeue(dev_id, queue_id, (void **)ops, num_ops, + num_ops_dequeued, rte_bbdev_op_type_str(RTE_BBDEV_OP_LDPC_DEC)); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(num_ops_dequeued); + return num_ops_dequeued; + } + +diff --git a/lib/compressdev/rte_compressdev.c b/lib/compressdev/rte_compressdev.c +index 790b2cd658..36930b339a 100644 +--- a/lib/compressdev/rte_compressdev.c ++++ b/lib/compressdev/rte_compressdev.c +@@ -612,7 +612,11 @@ rte_compressdev_dequeue_burst(uint8_t dev_id, uint16_t qp_id, + { + struct rte_compressdev *dev = &rte_comp_devices[dev_id]; + +- return dev->dequeue_burst(dev->data->queue_pairs[qp_id], ops, nb_ops); ++ nb_ops = dev->dequeue_burst(dev->data->queue_pairs[qp_id], ops, nb_ops); ++ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_ops); ++ ++ return nb_ops; + } + + RTE_EXPORT_SYMBOL(rte_compressdev_enqueue_burst) +diff --git a/lib/cryptodev/rte_cryptodev.h b/lib/cryptodev/rte_cryptodev.h +index 37a6a5e49b..34b1a8a93a 100644 +--- a/lib/cryptodev/rte_cryptodev.h ++++ b/lib/cryptodev/rte_cryptodev.h +@@ -2026,6 +2026,8 @@ rte_cryptodev_dequeue_burst(uint8_t dev_id, uint16_t qp_id, + rte_rcu_qsbr_thread_offline(list->qsbr, 0); + } + #endif ++ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_ops); + return nb_ops; + } + +diff --git a/lib/distributor/rte_distributor.c b/lib/distributor/rte_distributor.c +index dde7ce2677..3b874334f7 100644 +--- a/lib/distributor/rte_distributor.c ++++ b/lib/distributor/rte_distributor.c +@@ -60,6 +60,8 @@ rte_distributor_request_pkt(struct rte_distributor *d, + + while (rte_rdtsc() < t) + rte_pause(); ++ /* this was an empty poll */ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(0); + } + + /* +@@ -140,24 +142,29 @@ rte_distributor_get_pkt(struct rte_distributor *d, + + if (unlikely(d->alg_type == RTE_DIST_ALG_SINGLE)) { + if (return_count <= 1) { ++ uint16_t cnt; + pkts[0] = rte_distributor_get_pkt_single(d->d_single, +- worker_id, return_count ? oldpkt[0] : NULL); +- return (pkts[0]) ? 1 : 0; +- } else +- return -EINVAL; ++ worker_id, ++ return_count ? oldpkt[0] : NULL); ++ cnt = (pkts[0] != NULL) ? 1 : 0; ++ RTE_LCORE_TELEMETRY_TIMESTAMP(cnt); ++ return cnt; ++ } ++ return -EINVAL; + } + + rte_distributor_request_pkt(d, worker_id, oldpkt, return_count); + +- count = rte_distributor_poll_pkt(d, worker_id, pkts); +- while (count == -1) { ++ while ((count = rte_distributor_poll_pkt(d, worker_id, pkts)) == -1) { + uint64_t t = rte_rdtsc() + 100; + + while (rte_rdtsc() < t) + rte_pause(); + +- count = rte_distributor_poll_pkt(d, worker_id, pkts); ++ /* this was an empty poll */ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(0); + } ++ RTE_LCORE_TELEMETRY_TIMESTAMP(count); + return count; + } + +diff --git a/lib/distributor/rte_distributor_single.c b/lib/distributor/rte_distributor_single.c +index d4b3e12648..3c18805ecd 100644 +--- a/lib/distributor/rte_distributor_single.c ++++ b/lib/distributor/rte_distributor_single.c +@@ -31,8 +31,13 @@ rte_distributor_request_pkt_single(struct rte_distributor_single *d, + union rte_distributor_buffer_single *buf = &d->bufs[worker_id]; + int64_t req = (((int64_t)(uintptr_t)oldpkt) << RTE_DISTRIB_FLAG_BITS) + | RTE_DISTRIB_GET_BUF; +- RTE_WAIT_UNTIL_MASKED(&buf->bufptr64, RTE_DISTRIB_FLAGS_MASK, +- ==, 0, rte_memory_order_relaxed); ++ ++ while ((__atomic_load_n(&buf->bufptr64, __ATOMIC_RELAXED) ++ & RTE_DISTRIB_FLAGS_MASK) != 0) { ++ rte_pause(); ++ /* this was an empty poll */ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(0); ++ } + + /* Sync with distributor on GET_BUF flag. */ + rte_atomic_store_explicit(&buf->bufptr64, req, rte_memory_order_release); +@@ -59,8 +64,11 @@ rte_distributor_get_pkt_single(struct rte_distributor_single *d, + { + struct rte_mbuf *ret; + rte_distributor_request_pkt_single(d, worker_id, oldpkt); +- while ((ret = rte_distributor_poll_pkt_single(d, worker_id)) == NULL) ++ while ((ret = rte_distributor_poll_pkt_single(d, worker_id)) == NULL) { + rte_pause(); ++ /* this was an empty poll */ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(0); ++ } + return ret; + } + +diff --git a/lib/dmadev/rte_dmadev.h b/lib/dmadev/rte_dmadev.h +index 81dfe187e2..96aa01b478 100644 +--- a/lib/dmadev/rte_dmadev.h ++++ b/lib/dmadev/rte_dmadev.h +@@ -150,6 +150,8 @@ + #include + #include + #include ++#include ++#include + + #ifdef __cplusplus + extern "C" { +@@ -1380,6 +1382,7 @@ rte_dma_completed(int16_t dev_id, uint16_t vchan, const uint16_t nb_cpls, + ret = obj->completed(obj->dev_private, vchan, nb_cpls, last_idx, has_error); + rte_dma_trace_completed(dev_id, vchan, nb_cpls, last_idx, has_error, + ret); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(ret); + + return ret; + } +@@ -1431,6 +1434,7 @@ rte_dma_completed_status(int16_t dev_id, uint16_t vchan, + ret = obj->completed_status(obj->dev_private, vchan, nb_cpls, last_idx, status); + rte_dma_trace_completed_status(dev_id, vchan, nb_cpls, last_idx, status, + ret); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(ret); + + return ret; + } +diff --git a/lib/eal/common/eal_common_lcore_telemetry.c b/lib/eal/common/eal_common_lcore_telemetry.c +new file mode 100644 +index 0000000000..df7eab1fd4 +--- /dev/null ++++ b/lib/eal/common/eal_common_lcore_telemetry.c +@@ -0,0 +1,329 @@ ++/* SPDX-License-Identifier: BSD-3-Clause ++ * Copyright(c) 2022 Intel Corporation ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef RTE_LCORE_BUSYNESS ++#include ++#endif ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(__rte_lcore_telemetry_enabled, 20.11); ++int __rte_lcore_telemetry_enabled; ++ ++#ifdef RTE_LCORE_BUSYNESS ++ ++#include "eal_private.h" ++ ++struct lcore_telemetry { ++ int busyness; ++ /**< Calculated busyness (gets set/returned by the API) */ ++ int raw_busyness; ++ /**< Calculated busyness times 100. */ ++ uint64_t interval_ts; ++ /**< when previous telemetry interval started */ ++ uint64_t empty_cycles; ++ /**< empty cycle count since last interval */ ++ uint64_t last_poll_ts; ++ /**< last poll timestamp */ ++ bool last_empty; ++ /**< if last poll was empty */ ++ unsigned int contig_poll_cnt; ++ /**< contiguous (always empty/non empty) poll counter */ ++} __rte_cache_aligned; ++ ++static struct lcore_telemetry *telemetry_data; ++ ++#define LCORE_BUSYNESS_MAX 100 ++#define LCORE_BUSYNESS_NOT_SET -1 ++#define LCORE_BUSYNESS_MIN 0 ++ ++#define SMOOTH_COEFF 5 ++#define STATE_CHANGE_OPT 32 ++ ++/* Helper function to check if the lcore is enabled. ++ * Cannot use rte_lcore_is_enabled since it only catches ROLE_RTE threads which ++ * does not include ROLE_NON_EAL threads which some application threads, for ++ * example OvS polling threads, are marked as. ++ */ ++static int ++lcore_enabled(unsigned int lcore_id) ++{ ++ enum rte_lcore_role_t role = rte_eal_lcore_role(lcore_id); ++ ++ return role == ROLE_RTE || role == ROLE_NON_EAL; ++} ++ ++static void lcore_config_init(void) ++{ ++ struct lcore_telemetry *td; ++ int lcore_id; ++ ++ /* Foreach lcore - can't use macro since it excludes ROLE_NON_EAL */ ++ for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) { ++ if (!lcore_enabled(lcore_id)) ++ continue; ++ ++ td = &telemetry_data[lcore_id]; ++ ++ td->interval_ts = 0; ++ td->last_poll_ts = 0; ++ td->empty_cycles = 0; ++ td->last_empty = true; ++ td->contig_poll_cnt = 0; ++ td->busyness = LCORE_BUSYNESS_NOT_SET; ++ td->raw_busyness = 0; ++ } ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness, 20.11); ++int rte_lcore_busyness(unsigned int lcore_id) ++{ ++ const uint64_t active_thresh = RTE_LCORE_BUSYNESS_PERIOD * 1000; ++ struct lcore_telemetry *tdata; ++ ++ if (lcore_id >= RTE_MAX_LCORE) ++ return -EINVAL; ++ tdata = &telemetry_data[lcore_id]; ++ ++ /* if the lcore is not active */ ++ if (tdata->interval_ts == 0) ++ return LCORE_BUSYNESS_NOT_SET; ++ /* if the core hasn't been active in a while */ ++ else if ((rte_rdtsc() - tdata->interval_ts) > active_thresh) ++ return LCORE_BUSYNESS_NOT_SET; ++ ++ /* this core is active, report its busyness */ ++ return telemetry_data[lcore_id].busyness; ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness_enabled, 20.11); ++int rte_lcore_busyness_enabled(void) ++{ ++ return __rte_lcore_telemetry_enabled; ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness_enabled_set, 20.11); ++void rte_lcore_busyness_enabled_set(int enable) ++{ ++ __rte_lcore_telemetry_enabled = !!enable; ++ ++ if (!enable) ++ lcore_config_init(); ++} ++ ++static inline int calc_raw_busyness(const struct lcore_telemetry *tdata, ++ const uint64_t empty, const uint64_t total) ++{ ++ /* ++ * we don't want to use floating point math here, but we want for our ++ * busyness to react smoothly to sudden changes, while still keeping the ++ * accuracy and making sure that over time the average follows busyness ++ * as measured just-in-time. therefore, we will calculate the average ++ * busyness using integer math, but shift the decimal point two places ++ * to the right, so that 100.0 becomes 10000. this allows us to report ++ * integer values (0..100) while still allowing ourselves to follow the ++ * just-in-time measurements when we calculate our averages. ++ */ ++ const int max_raw_idle = LCORE_BUSYNESS_MAX * 100; ++ ++ const int prev_raw_idle = max_raw_idle - tdata->raw_busyness; ++ ++ /* calculate rate of idle cycles, times 100 */ ++ const int cur_raw_idle = (int)((empty * max_raw_idle) / total); ++ ++ /* smoothen the idleness */ ++ const int smoothened_idle = ++ (cur_raw_idle + prev_raw_idle * (SMOOTH_COEFF - 1)) / SMOOTH_COEFF; ++ ++ /* convert idleness back to busyness */ ++ return max_raw_idle - smoothened_idle; ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(__rte_lcore_telemetry_timestamp, 20.11); ++void __rte_lcore_telemetry_timestamp(uint16_t nb_rx) ++{ ++ const unsigned int lcore_id = rte_lcore_id(); ++ uint64_t interval_ts, empty_cycles, cur_tsc, last_poll_ts; ++ struct lcore_telemetry *tdata; ++ const bool empty = nb_rx == 0; ++ uint64_t diff_int, diff_last; ++ bool last_empty; ++ ++ /* This telemetry is not supported for unregistered non-EAL threads */ ++ if (lcore_id >= RTE_MAX_LCORE) { ++ RTE_LOG(DEBUG, EAL, ++ "Lcore telemetry not supported on unregistered non-EAL thread %d", ++ lcore_id); ++ return; ++ } ++ ++ tdata = &telemetry_data[lcore_id]; ++ last_empty = tdata->last_empty; ++ ++ /* optimization: don't do anything if status hasn't changed */ ++ if (last_empty == empty && tdata->contig_poll_cnt++ < STATE_CHANGE_OPT) ++ return; ++ /* status changed or we're waiting for too long, reset counter */ ++ tdata->contig_poll_cnt = 0; ++ ++ cur_tsc = rte_rdtsc(); ++ ++ interval_ts = tdata->interval_ts; ++ empty_cycles = tdata->empty_cycles; ++ last_poll_ts = tdata->last_poll_ts; ++ ++ diff_int = cur_tsc - interval_ts; ++ diff_last = cur_tsc - last_poll_ts; ++ ++ /* is this the first time we're here? */ ++ if (interval_ts == 0) { ++ tdata->busyness = LCORE_BUSYNESS_MIN; ++ tdata->raw_busyness = 0; ++ tdata->interval_ts = cur_tsc; ++ tdata->empty_cycles = 0; ++ tdata->contig_poll_cnt = 0; ++ goto end; ++ } ++ ++ /* update the empty counter if we got an empty poll earlier */ ++ if (last_empty) ++ empty_cycles += diff_last; ++ ++ /* have we passed the interval? */ ++ if (diff_int > RTE_LCORE_BUSYNESS_PERIOD) { ++ int raw_busyness; ++ ++ /* get updated busyness value */ ++ raw_busyness = calc_raw_busyness(tdata, empty_cycles, diff_int); ++ ++ /* set a new interval, reset empty counter */ ++ tdata->interval_ts = cur_tsc; ++ tdata->empty_cycles = 0; ++ tdata->raw_busyness = raw_busyness; ++ /* bring busyness back to 0..100 range, biased to round up */ ++ tdata->busyness = (raw_busyness + 50) / 100; ++ } else ++ /* we may have updated empty counter */ ++ tdata->empty_cycles = empty_cycles; ++ ++end: ++ /* update status for next poll */ ++ tdata->last_poll_ts = cur_tsc; ++ tdata->last_empty = empty; ++} ++ ++static int ++lcore_busyness_enable(const char *cmd __rte_unused, ++ const char *params __rte_unused, ++ struct rte_tel_data *d) ++{ ++ rte_lcore_busyness_enabled_set(1); ++ ++ rte_tel_data_start_dict(d); ++ ++ rte_tel_data_add_dict_int(d, "busyness_enabled", 1); ++ ++ return 0; ++} ++ ++static int ++lcore_busyness_disable(const char *cmd __rte_unused, ++ const char *params __rte_unused, ++ struct rte_tel_data *d) ++{ ++ rte_lcore_busyness_enabled_set(0); ++ ++ rte_tel_data_start_dict(d); ++ ++ rte_tel_data_add_dict_int(d, "busyness_enabled", 0); ++ ++ return 0; ++} ++ ++static int ++lcore_handle_busyness(const char *cmd __rte_unused, ++ const char *params __rte_unused, struct rte_tel_data *d) ++{ ++ char corenum[64]; ++ int i; ++ ++ rte_tel_data_start_dict(d); ++ ++ /* Foreach lcore - can't use macro since it excludes ROLE_NON_EAL */ ++ for (i = 0; i < RTE_MAX_LCORE; i++) { ++ if (!lcore_enabled(i)) ++ continue; ++ snprintf(corenum, sizeof(corenum), "%d", i); ++ rte_tel_data_add_dict_int(d, corenum, rte_lcore_busyness(i)); ++ } ++ ++ return 0; ++} ++ ++void ++eal_lcore_telemetry_free(void) ++{ ++ if (telemetry_data != NULL) { ++ free(telemetry_data); ++ telemetry_data = NULL; ++ } ++} ++ ++RTE_INIT(lcore_init_telemetry) ++{ ++ telemetry_data = calloc(RTE_MAX_LCORE, sizeof(telemetry_data[0])); ++ if (telemetry_data == NULL) ++ rte_panic("Could not init lcore telemetry data: Out of memory\n"); ++ ++ lcore_config_init(); ++ ++ rte_telemetry_register_cmd("/eal/lcore/busyness", lcore_handle_busyness, ++ "return percentage busyness of cores"); ++ ++ rte_telemetry_register_cmd("/eal/lcore/busyness_enable", lcore_busyness_enable, ++ "enable lcore busyness measurement"); ++ ++ rte_telemetry_register_cmd("/eal/lcore/busyness_disable", lcore_busyness_disable, ++ "disable lcore busyness measurement"); ++ ++ __rte_lcore_telemetry_enabled = true; ++} ++ ++#else ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness, 20.11); ++int rte_lcore_busyness(unsigned int lcore_id __rte_unused) ++{ ++ return -ENOTSUP; ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness_enabled, 20.11); ++int rte_lcore_busyness_enabled(void) ++{ ++ return -ENOTSUP; ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness_enabled_set, 20.11); ++void rte_lcore_busyness_enabled_set(int enable __rte_unused) ++{ ++} ++ ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(__rte_lcore_telemetry_timestamp, 20.11); ++void __rte_lcore_telemetry_timestamp(uint16_t nb_rx __rte_unused) ++{ ++} ++ ++void eal_lcore_telemetry_free(void) ++{ ++} ++ ++#endif +diff --git a/lib/eal/common/meson.build b/lib/eal/common/meson.build +index e273745e93..96afcd7825 100644 +--- a/lib/eal/common/meson.build ++++ b/lib/eal/common/meson.build +@@ -17,6 +17,7 @@ sources += files( + 'eal_common_hexdump.c', + 'eal_common_interrupts.c', + 'eal_common_launch.c', ++ 'eal_common_lcore_telemetry.c', + 'eal_common_lcore.c', + 'eal_common_lcore_var.c', + 'eal_common_mcfg.c', +diff --git a/lib/eal/freebsd/eal.c b/lib/eal/freebsd/eal.c +index 6215245ad5..f03ea2e801 100644 +--- a/lib/eal/freebsd/eal.c ++++ b/lib/eal/freebsd/eal.c +@@ -783,6 +783,7 @@ rte_eal_cleanup(void) + rte_eal_alarm_cleanup(); + rte_trace_save(); + eal_trace_fini(); ++ eal_lcore_telemetry_free(); + /* after this point, any DPDK pointers will become dangling */ + rte_eal_memory_detach(); + eal_cleanup_config(internal_conf); +diff --git a/lib/eal/include/rte_lcore.h b/lib/eal/include/rte_lcore.h +index 10f965b4f0..1d0a5a22f6 100644 +--- a/lib/eal/include/rte_lcore.h ++++ b/lib/eal/include/rte_lcore.h +@@ -402,6 +402,90 @@ rte_thread_register(void); + void + rte_thread_unregister(void); + ++/** ++ * @warning ++ * @b EXPERIMENTAL: this API may change without prior notice. ++ * ++ * Read busyness value corresponding to an lcore. ++ * ++ * @param lcore_id ++ * Lcore to read busyness value for. ++ * @return ++ * - value between 0 and 100 on success ++ * - -1 if lcore is not active ++ * - -EINVAL if lcore is invalid ++ * - -ENOMEM if not enough memory available ++ * - -ENOTSUP if not supported ++ */ ++__rte_experimental ++int ++rte_lcore_busyness(unsigned int lcore_id); ++ ++/** ++ * @warning ++ * @b EXPERIMENTAL: this API may change without prior notice. ++ * ++ * Check if lcore busyness telemetry is enabled. ++ * ++ * @return ++ * - 1 if lcore telemetry is enabled ++ * - 0 if lcore telemetry is disabled ++ * - -ENOTSUP if not lcore telemetry supported ++ */ ++__rte_experimental ++int ++rte_lcore_busyness_enabled(void); ++ ++/** ++ * @warning ++ * @b EXPERIMENTAL: this API may change without prior notice. ++ * ++ * Enable or disable busyness telemetry. ++ * ++ * @param enable ++ * 1 to enable, 0 to disable ++ */ ++__rte_experimental ++void ++rte_lcore_busyness_enabled_set(int enable); ++ ++/** ++ * @warning ++ * @b EXPERIMENTAL: this API may change without prior notice. ++ * ++ * Lcore telemetry timestamping function. ++ * ++ * @param nb_rx ++ * Number of buffers processed by lcore. ++ */ ++__rte_experimental ++void ++__rte_lcore_telemetry_timestamp(uint16_t nb_rx); ++ ++/** @internal lcore telemetry enabled status */ ++extern int __rte_lcore_telemetry_enabled; ++ ++/** @internal free memory allocated for lcore telemetry */ ++void ++eal_lcore_telemetry_free(void); ++ ++/** ++ * Call lcore telemetry timestamp function. ++ * ++ * @param nb_rx ++ * Number of buffers processed by lcore. ++ */ ++#ifdef RTE_LCORE_BUSYNESS ++#define RTE_LCORE_TELEMETRY_TIMESTAMP(nb_rx) \ ++ do { \ ++ if (__rte_lcore_telemetry_enabled) \ ++ __rte_lcore_telemetry_timestamp(nb_rx); \ ++ } while (0) ++#else ++#define RTE_LCORE_TELEMETRY_TIMESTAMP(nb_rx) \ ++ while (0) {} ++#endif ++ + #ifdef __cplusplus + } + #endif +diff --git a/lib/eal/linux/eal.c b/lib/eal/linux/eal.c +index b12f325ddd..ad18f5fb05 100644 +--- a/lib/eal/linux/eal.c ++++ b/lib/eal/linux/eal.c +@@ -983,6 +983,7 @@ rte_eal_cleanup(void) + rte_eal_alarm_cleanup(); + rte_trace_save(); + eal_trace_fini(); ++ eal_lcore_telemetry_free(); + eal_mp_dev_hotplug_cleanup(); + /* after this point, any DPDK pointers will become dangling */ + rte_eal_memory_detach(); +diff --git a/lib/eal/meson.build b/lib/eal/meson.build +index f9fcee24ee..6a205ddb9f 100644 +--- a/lib/eal/meson.build ++++ b/lib/eal/meson.build +@@ -17,6 +17,9 @@ subdir(arch_subdir) + deps += ['argparse', 'kvargs'] + if not is_windows + deps += ['telemetry'] ++else ++ # core busyness telemetry depends on telemetry library ++ dpdk_conf.set('RTE_LCORE_BUSYNESS', false) + endif + if dpdk_conf.has('RTE_USE_LIBBSD') + ext_deps += libbsd +diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h +index a66c2abbdb..7ef326e23b 100644 +--- a/lib/ethdev/rte_ethdev.h ++++ b/lib/ethdev/rte_ethdev.h +@@ -6402,6 +6402,8 @@ rte_eth_rx_burst(uint16_t port_id, uint16_t queue_id, + rte_ethdev_trace_rx_burst_nonempty(port_id, queue_id, (void **)rx_pkts, nb_rx); + else + rte_ethdev_trace_rx_burst_empty(port_id, queue_id, (void **)rx_pkts); ++ ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_rx); + return nb_rx; + } + +diff --git a/lib/eventdev/rte_eventdev.h b/lib/eventdev/rte_eventdev.h +index 88c52a5520..3d7bdc1ffa 100644 +--- a/lib/eventdev/rte_eventdev.h ++++ b/lib/eventdev/rte_eventdev.h +@@ -2844,6 +2844,7 @@ rte_event_dequeue_burst(uint8_t dev_id, uint8_t port_id, struct rte_event ev[], + uint16_t nb_events, uint64_t timeout_ticks) + { + const struct rte_event_fp_ops *fp_ops; ++ uint16_t nb_evts; + void *port; + + fp_ops = &rte_event_fp_ops[dev_id]; +@@ -2862,7 +2863,10 @@ rte_event_dequeue_burst(uint8_t dev_id, uint8_t port_id, struct rte_event ev[], + #endif + rte_eventdev_trace_deq_burst(dev_id, port_id, ev, nb_events); + +- return (fp_ops->dequeue_burst)(port, ev, nb_events, timeout_ticks); ++ nb_evts = (fp_ops->dequeue_burst)(port, ev, nb_events, timeout_ticks); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_evts); ++ ++ return nb_evts; + } + + #define RTE_EVENT_DEV_MAINT_OP_FLUSH (1 << 0) +diff --git a/lib/rawdev/rte_rawdev.c b/lib/rawdev/rte_rawdev.c +index d193061842..c96e803dbe 100644 +--- a/lib/rawdev/rte_rawdev.c ++++ b/lib/rawdev/rte_rawdev.c +@@ -249,13 +249,16 @@ rte_rawdev_dequeue_buffers(uint16_t dev_id, + rte_rawdev_obj_t context) + { + struct rte_rawdev *dev; ++ int nb_ops; + + RTE_RAWDEV_VALID_DEVID_OR_ERR_RET(dev_id, -EINVAL); + dev = &rte_rawdevs[dev_id]; + + if (dev->dev_ops->dequeue_bufs == NULL) + return -ENOTSUP; +- return dev->dev_ops->dequeue_bufs(dev, buffers, count, context); ++ nb_ops = dev->dev_ops->dequeue_bufs(dev, buffers, count, context); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_ops); ++ return nb_ops; + } + + RTE_EXPORT_SYMBOL(rte_rawdev_dump) +diff --git a/lib/regexdev/rte_regexdev.h b/lib/regexdev/rte_regexdev.h +index 1534e9f84a..ce01d99b12 100644 +--- a/lib/regexdev/rte_regexdev.h ++++ b/lib/regexdev/rte_regexdev.h +@@ -1531,6 +1531,7 @@ rte_regexdev_dequeue_burst(uint8_t dev_id, uint16_t qp_id, + struct rte_regex_ops **ops, uint16_t nb_ops) + { + struct rte_regexdev *dev = &rte_regex_devices[dev_id]; ++ uint16_t deq_ops; + #ifdef RTE_LIBRTE_REGEXDEV_DEBUG + RTE_REGEXDEV_VALID_DEV_ID_OR_ERR_RET(dev_id, -EINVAL); + if (dev->dequeue == NULL) +@@ -1540,7 +1541,9 @@ rte_regexdev_dequeue_burst(uint8_t dev_id, uint16_t qp_id, + return -EINVAL; + } + #endif +- return dev->dequeue(dev, qp_id, ops, nb_ops); ++ deq_ops = dev->dequeue(dev, qp_id, ops, nb_ops); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(deq_ops); ++ return deq_ops; + } + + #ifdef __cplusplus +diff --git a/lib/ring/rte_ring.h b/lib/ring/rte_ring.h +index 3382a8e2f2..509beabe99 100644 +--- a/lib/ring/rte_ring.h ++++ b/lib/ring/rte_ring.h +@@ -431,8 +431,10 @@ static __rte_always_inline unsigned int + rte_ring_dequeue_bulk(struct rte_ring *r, void **obj_table, unsigned int n, + unsigned int *available) + { +- return rte_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *), ++ uint32_t nb_rx = rte_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *), + n, available); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_rx); ++ return nb_rx; + } + + /** +@@ -831,8 +833,10 @@ static __rte_always_inline unsigned int + rte_ring_dequeue_burst(struct rte_ring *r, void **obj_table, + unsigned int n, unsigned int *available) + { +- return rte_ring_dequeue_burst_elem(r, obj_table, sizeof(void *), ++ uint32_t nb_rx = rte_ring_dequeue_burst_elem(r, obj_table, sizeof(void *), + n, available); ++ RTE_LCORE_TELEMETRY_TIMESTAMP(nb_rx); ++ return nb_rx; + } + + #ifdef __cplusplus +diff --git a/lib/ring/rte_ring_elem_pvt.h b/lib/ring/rte_ring_elem_pvt.h +index 6eafae121f..884f142914 100644 +--- a/lib/ring/rte_ring_elem_pvt.h ++++ b/lib/ring/rte_ring_elem_pvt.h +@@ -470,6 +470,7 @@ __rte_ring_do_dequeue_elem(struct rte_ring *r, void *obj_table, + end: + if (available != NULL) + *available = entries - n; ++ RTE_LCORE_TELEMETRY_TIMESTAMP(n); + return n; + } + +-- +2.43.0 + diff --git a/ipm/patches/dpdk/25.11/0002-eal-add-cpuset-lcore-telemetry-entries.patch b/ipm/patches/dpdk/25.11/0002-eal-add-cpuset-lcore-telemetry-entries.patch new file mode 100644 index 0000000..9dc0fa1 --- /dev/null +++ b/ipm/patches/dpdk/25.11/0002-eal-add-cpuset-lcore-telemetry-entries.patch @@ -0,0 +1,79 @@ +From f75258c3bde7aa97dcae97a01e6fb8eb59138099 Mon Sep 17 00:00:00 2001 +From: Hoang Nguyen +Date: Thu, 1 Aug 2024 16:11:56 +0000 +Subject: [PATCH 2/3] eal: add cpuset lcore telemetry entries + +Expose per-lcore cpuset information to telemetry. + +Signed-off-by: Anatoly Burakov +--- + lib/eal/common/eal_common_lcore_telemetry.c | 46 +++++++++++++++++++++ + 1 file changed, 46 insertions(+) + +diff --git a/lib/eal/common/eal_common_lcore_telemetry.c b/lib/eal/common/eal_common_lcore_telemetry.c +index df7eab1fd4..8a6d13c57c 100644 +--- a/lib/eal/common/eal_common_lcore_telemetry.c ++++ b/lib/eal/common/eal_common_lcore_telemetry.c +@@ -269,6 +269,49 @@ lcore_handle_busyness(const char *cmd __rte_unused, + return 0; + } + ++static int ++lcore_handle_cpuset(const char *cmd __rte_unused, ++ const char *params __rte_unused, ++ struct rte_tel_data *d) ++{ ++ char corenum[64]; ++ int i; ++ ++ rte_tel_data_start_dict(d); ++ ++ /* Foreach lcore - can't use macro since it excludes ROLE_NON_EAL */ ++ for (i = 0; i < RTE_MAX_LCORE; i++) { ++ const struct lcore_config *cfg = &lcore_config[i]; ++ const rte_cpuset_t *cpuset = &cfg->cpuset; ++ struct rte_tel_data *ld; ++ unsigned int cpu; ++ ++ if (!lcore_enabled(i)) ++ continue; ++ ++ /* create an array of integers */ ++ ld = rte_tel_data_alloc(); ++ if (ld == NULL) ++ return -ENOMEM; ++ rte_tel_data_start_array(ld, RTE_TEL_INT_VAL); ++ ++ /* add cpu ID's from cpuset to the array */ ++ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { ++ if (!CPU_ISSET(cpu, cpuset)) ++ continue; ++ rte_tel_data_add_array_int(ld, cpu); ++ } ++ ++ /* add array to the per-lcore container */ ++ snprintf(corenum, sizeof(corenum), "%d", i); ++ ++ /* tell telemetry library to free this array automatically */ ++ rte_tel_data_add_dict_container(d, corenum, ld, 0); ++ } ++ ++ return 0; ++} ++ + void + eal_lcore_telemetry_free(void) + { +@@ -295,6 +338,9 @@ RTE_INIT(lcore_init_telemetry) + rte_telemetry_register_cmd("/eal/lcore/busyness_disable", lcore_busyness_disable, + "disable lcore busyness measurement"); + ++ rte_telemetry_register_cmd("/eal/lcore/cpuset", lcore_handle_cpuset, ++ "list physical core affinity for each lcore"); ++ + __rte_lcore_telemetry_enabled = true; + } + +-- +2.43.0 + diff --git a/ipm/patches/dpdk/25.11/0003-add-capacity-endpoint-to-telemetry-thread.patch b/ipm/patches/dpdk/25.11/0003-add-capacity-endpoint-to-telemetry-thread.patch new file mode 100644 index 0000000..4bacd1c --- /dev/null +++ b/ipm/patches/dpdk/25.11/0003-add-capacity-endpoint-to-telemetry-thread.patch @@ -0,0 +1,346 @@ +From e9e3baf4a86cbd4f4e7eb03fc17d04b07b6cef82 Mon Sep 17 00:00:00 2001 +From: David Hunt +Date: Fri, 20 Sep 2024 09:11:45 +0100 +Subject: [PATCH 3/3] add capacity endpoint to telemetry thread + +Busyness is calculated on how busy the current core is, ignoring the +current frequency. So a core that's 50% busy at P1 (e.g. 2GHz), shows +as 100% busy at 1GHz. + +This patch adds a new 'capacity' metric that shows a percentage based on +the P1 (base) freqency of the core, so that if the core is 50% busy at +P1, it should show 50% regardless of what the current frequency is. + +Signed-off-by: David Hunt +--- + lib/eal/common/eal_common_lcore_telemetry.c | 243 ++++++++++++++++++++ + lib/eal/include/rte_lcore.h | 21 ++ + 2 files changed, 264 insertions(+) + +diff --git a/lib/eal/common/eal_common_lcore_telemetry.c b/lib/eal/common/eal_common_lcore_telemetry.c +index 8a6d13c57c..54d826069c 100644 +--- a/lib/eal/common/eal_common_lcore_telemetry.c ++++ b/lib/eal/common/eal_common_lcore_telemetry.c +@@ -11,9 +11,18 @@ + #include + #include + #include ++#include ++#include ++#include + + #ifdef RTE_LCORE_BUSYNESS + #include ++#define MSR_PLATFORM_INFO 0xCE ++#define POWER_SYSFS_CUR_PATH "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq" ++#define POWER_SYSFS_BASE_FREQ_PATH "/sys/devices/system/cpu/cpu%u/cpufreq/base_frequency" ++#define POWER_SYSFS_SCALING_DRIVER_PATH "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_driver" ++#define POWER_SYSFS_SCALING_MAX_FREQ_PATH "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_max_freq" ++#define POWER_SYSFS_MSR_PATH "/dev/cpu/%u/msr" + #endif + + RTE_EXPORT_EXPERIMENTAL_SYMBOL(__rte_lcore_telemetry_enabled, 20.11); +@@ -49,6 +58,183 @@ static struct lcore_telemetry *telemetry_data; + #define SMOOTH_COEFF 5 + #define STATE_CHANGE_OPT 32 + ++static int p1_freq[RTE_MAX_LCORE] = {0}; ++ ++static int ++try_read_base_frequency(unsigned int lcore_id) ++{ ++ char path[PATH_MAX]; ++ int fd; ++ snprintf(path, sizeof(path), POWER_SYSFS_BASE_FREQ_PATH, rte_lcore_to_cpu_id(lcore_id)); ++ ++ fd = open(path, O_RDONLY); ++ if (fd == -1) { ++ return -1; ++ } ++ char buffer[16]; ++ ssize_t bytesRead = pread(fd, buffer, sizeof(buffer) - 1, 0); ++ if (bytesRead == -1) { ++ close(fd); ++ return -1; ++ } ++ buffer[bytesRead] = '\0'; // Null-terminate the buffer ++ close(fd); ++ ++ p1_freq[lcore_id] = atoi(buffer); ++ return p1_freq[lcore_id]; ++ ++ ++} ++ ++static int ++try_read_scaling_max_freq(unsigned int lcore_id) ++{ ++ char path[PATH_MAX]; ++ int freq; ++ int fd; ++ ++ /* ++ * If the driver is acpi_cpufreq, we can read the scaling_max_freq file ++ */ ++ ++ snprintf(path, sizeof(path), POWER_SYSFS_SCALING_DRIVER_PATH, rte_lcore_to_cpu_id(lcore_id)); ++ fd = open(path, O_RDONLY); ++ if (fd == -1) { ++ return -1; ++ } ++ char buffer[16]; ++ ssize_t bytesRead = pread(fd, buffer, sizeof(buffer) - 1, 0); ++ if (bytesRead == -1) { ++ close(fd); ++ return -1; ++ } ++ buffer[bytesRead] = '\0'; // Null-terminate the buffer ++ ++ close(fd); ++ ++ if (strncmp(buffer, "acpi-cpufreq", 12) == 0) { ++ /* we can use the scaling_max_freq to get the p1 */ ++ snprintf(path, sizeof(path), POWER_SYSFS_SCALING_MAX_FREQ_PATH, rte_lcore_to_cpu_id(lcore_id)); ++ fd = open(path, O_RDONLY); ++ if (fd == -1) { ++ return -1; ++ } ++ ssize_t bytesRead = pread(fd, buffer, sizeof(buffer) - 1, 0); ++ if (bytesRead == -1) { ++ close(fd); ++ return -1; ++ } ++ buffer[bytesRead] = '\0'; // Null-terminate the buffer ++ close(fd); ++ freq = atoi(buffer) / 1000; /* convert to KHz */ ++ ++ /* ++ * If the freq value ends with '1', then, turbo is enabled. ++ * Round it down to the nearest 100. Otherwuse use the value. ++ */ ++ return (freq & ~1) * 1000; /* convert to Hz */ ++ } ++ return -1; ++} ++ ++static int ++try_read_msr(unsigned int lcore_id) ++{ ++ char path[PATH_MAX]; ++ int fd; ++ int freq; ++ uint64_t data; ++ ++ /* ++ * If the msr driver is present, we can read p1 from MSR_PLATFORM_INFO register ++ */ ++ snprintf(path, sizeof(path), POWER_SYSFS_MSR_PATH, rte_lcore_to_cpu_id(lcore_id)); ++ fd = open(path, O_RDONLY); ++ if (fd < 0) { ++ return -1; ++ } ++ ++ if (pread(fd, &data, sizeof(data), MSR_PLATFORM_INFO) != sizeof(data)) { ++ close(fd); ++ return -1; ++ } ++ ++ close(fd); ++ ++ freq = ((data >> 8) & 0xff) * 100 * 1000; ++ ++ return freq; ++} ++ ++ ++static ++int read_sysfs_p1_freq(unsigned int lcore_id) { ++ int freq; ++ ++ /* We've previously got the p1 frequency. */ ++ if (p1_freq[lcore_id] != 0) ++ return p1_freq[lcore_id]; ++ ++ /* ++ * Check the base_frequency file, if it's there ++ */ ++ freq = try_read_base_frequency(lcore_id); ++ if (freq != -1) { ++ p1_freq[lcore_id] = freq; ++ return freq; ++ } ++ ++ /* ++ * Check the scaling_max_freq file for the acpi-freq driver ++ */ ++ freq = try_read_scaling_max_freq(lcore_id); ++ if (freq != -1) { ++ p1_freq[lcore_id] = freq; ++ return freq; ++ } ++ ++ /* ++ * Try reading from the MSR register ++ */ ++ freq = try_read_msr(lcore_id); ++ if (freq != -1) { ++ p1_freq[lcore_id] = freq; ++ return freq; ++ } ++ ++ RTE_LOG(ERR, EAL, "Capacity telemetry for lcore %d not supported: no p1 frequency found", ++ lcore_id); ++ ++ return -1; ++} ++ ++ ++int current_fds[RTE_MAX_LCORE] = {0}; ++ ++static ++int read_sysfs_cur_freq(unsigned int lcore_id) { ++ char path[PATH_MAX]; ++ ++ if (current_fds[lcore_id] == 0) { ++ snprintf(path, sizeof(path), POWER_SYSFS_CUR_PATH, rte_lcore_to_cpu_id(lcore_id)); ++ current_fds[lcore_id] = open(path, O_RDONLY); ++ if (current_fds[lcore_id] == -1) { ++ return -1; ++ } ++ } ++ ++ char buffer[16]; ++ ssize_t bytesRead = pread(current_fds[lcore_id], buffer, sizeof(buffer) - 1, 0); ++ if (bytesRead == -1) { ++ return -1; ++ } ++ ++ buffer[bytesRead] = '\0'; // Null-terminate the buffer ++ ++ int value = atoi(buffer); ++ return value; ++} ++ + /* Helper function to check if the lcore is enabled. + * Cannot use rte_lcore_is_enabled since it only catches ROLE_RTE threads which + * does not include ROLE_NON_EAL threads which some application threads, for +@@ -105,6 +291,34 @@ int rte_lcore_busyness(unsigned int lcore_id) + return telemetry_data[lcore_id].busyness; + } + ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_capacity, 20.11); ++int rte_lcore_capacity(unsigned int lcore_id) ++{ ++ const uint64_t active_thresh = RTE_LCORE_BUSYNESS_PERIOD * 1000; ++ struct lcore_telemetry *tdata; ++ ++ if (lcore_id >= RTE_MAX_LCORE) ++ return -EINVAL; ++ tdata = &telemetry_data[lcore_id]; ++ ++ /* if the lcore is not active */ ++ if (tdata->interval_ts == 0) ++ return LCORE_BUSYNESS_NOT_SET; ++ /* if the core hasn't been active in a while */ ++ else if ((rte_rdtsc() - tdata->interval_ts) > active_thresh) ++ return LCORE_BUSYNESS_NOT_SET; ++ ++ int cur_freq = read_sysfs_cur_freq(rte_lcore_to_cpu_id(lcore_id)); ++ int busy = telemetry_data[lcore_id].busyness; ++ int p1 = read_sysfs_p1_freq(lcore_id) ; ++ ++ if ((busy == -1) || (p1 <= 0)) { ++ return -1; ++ } else { ++ return busy * cur_freq / p1; ++ } ++} ++ + RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness_enabled, 20.11); + int rte_lcore_busyness_enabled(void) + { +@@ -269,6 +483,26 @@ lcore_handle_busyness(const char *cmd __rte_unused, + return 0; + } + ++static int ++lcore_handle_capacity(const char *cmd __rte_unused, ++ const char *params __rte_unused, struct rte_tel_data *d) ++{ ++ char corenum[64]; ++ int i; ++ ++ rte_tel_data_start_dict(d); ++ ++ /* Foreach lcore - can't use macro since it excludes ROLE_NON_EAL */ ++ for (i = 0; i < RTE_MAX_LCORE; i++) { ++ if (!lcore_enabled(i)) ++ continue; ++ snprintf(corenum, sizeof(corenum), "%d", i); ++ rte_tel_data_add_dict_int(d, corenum, rte_lcore_capacity(i)); ++ } ++ ++ return 0; ++} ++ + static int + lcore_handle_cpuset(const char *cmd __rte_unused, + const char *params __rte_unused, +@@ -332,6 +566,9 @@ RTE_INIT(lcore_init_telemetry) + rte_telemetry_register_cmd("/eal/lcore/busyness", lcore_handle_busyness, + "return percentage busyness of cores"); + ++ rte_telemetry_register_cmd("/eal/lcore/capacity_used", lcore_handle_capacity, ++ "return percentage capacity of cores"); ++ + rte_telemetry_register_cmd("/eal/lcore/busyness_enable", lcore_busyness_enable, + "enable lcore busyness measurement"); + +@@ -346,6 +583,12 @@ RTE_INIT(lcore_init_telemetry) + + #else + ++RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_capacity, 20.11); ++int rte_lcore_capacity(unsigned int lcore_id __rte_unused) ++{ ++ return -ENOTSUP; ++} ++ + RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_busyness, 20.11); + int rte_lcore_busyness(unsigned int lcore_id __rte_unused) + { +diff --git a/lib/eal/include/rte_lcore.h b/lib/eal/include/rte_lcore.h +index 1d0a5a22f6..7af3532b17 100644 +--- a/lib/eal/include/rte_lcore.h ++++ b/lib/eal/include/rte_lcore.h +@@ -421,6 +421,27 @@ __rte_experimental + int + rte_lcore_busyness(unsigned int lcore_id); + ++/** ++ * @warning ++ * @b EXPERIMENTAL: this API may change without prior notice. ++ * ++ * Read capacity value corresponding to an lcore. ++ * This differs from busyness in that it is related to the current usage ++ * of the lcore compared to P1 frequency, not the current frequency. ++ * ++ * @param lcore_id ++ * Lcore to read capacity value for. ++ * @return ++ * - value between 0 and 100 on success ++ * - -1 if lcore is not active ++ * - -EINVAL if lcore is invalid ++ * - -ENOMEM if not enough memory available ++ * - -ENOTSUP if not supported ++ */ ++__rte_experimental ++int ++rte_lcore_capacity(unsigned int lcore_id); ++ + /** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. +-- +2.43.0 +