On 17 Jun 2026, at 22:46, Timothy Redaelli via dev wrote:
> When --format json is passed to ovs-appctl, pmd-perf-show returns a
> JSON object keyed by thread identifier ("pmd-cNN" for PMD threads,
> "main" for the main thread). Each entry contains:
>
> - "core" and "numa" fields (numeric or null)
> - "measurement-duration-s" (seconds as real)
> - "iterations" (object with cycle/iteration stats, or null for main)
> - "rx-packets" (count, kpps, cycles-per-packet)
> - "datapath-passes" (count, per-packet ratio, all hit types, upcalls)
> - "tx-packets" (count, kpps, batches, packets-per-batch)
>
> Values that depend on TSC cycle accounting (cycles-per-packet,
> us-per-upcall) are null for the main thread.
>
> The JSON formatting function is placed in dpif-netdev-perf.c alongside
> the text formatter, since both need access to the file-local tsc_hz.
>
> Signed-off-by: Timothy Redaelli <[email protected]>
Hi Timothy,
See some comments below. I have copied in Ilya for some of the code
style questions, as he was planning to review the series anyway.
//Eelco
> ---
> lib/dpif-netdev-perf.c | 129 +++++++++++++++++++++++++++++++++++++++++
> lib/dpif-netdev-perf.h | 5 ++
> lib/dpif-netdev.c | 65 +++++++++++++++++----
> tests/pmd.at | 78 +++++++++++++++++++++++++
> 4 files changed, 266 insertions(+), 11 deletions(-)
>
> diff --git a/lib/dpif-netdev-perf.c b/lib/dpif-netdev-perf.c
> index 4b3b35852..536d15a07 100644
> --- a/lib/dpif-netdev-perf.c
> +++ b/lib/dpif-netdev-perf.c
> @@ -315,6 +315,135 @@ pmd_perf_format_overall_stats(struct ds *str, struct
> pmd_perf_stats *s,
> }
> }
>
> +void
> +pmd_perf_format_overall_stats_json(struct json *json,
> + struct pmd_perf_stats *s,
> + double duration, bool show_iterations)
> +{
> + uint64_t stats[PMD_N_STATS];
> +
> + if (duration == 0) {
> + json_object_put(json, "iterations", json_null_create());
> + json_object_put(json, "rx-packets", json_null_create());
> + json_object_put(json, "datapath-passes", json_null_create());
> + json_object_put(json, "tx-packets", json_null_create());
> + return;
> + }
> +
> + double us_per_cycle = 1000000.0 / tsc_hz;
> +
> + pmd_perf_read_counters(s, stats);
> + uint64_t tot_cycles = stats[PMD_CYCLES_ITER_IDLE] +
> + stats[PMD_CYCLES_ITER_BUSY];
> + uint64_t rx_packets = stats[PMD_STAT_RECV];
> + uint64_t tx_packets = stats[PMD_STAT_SENT_PKTS];
> + uint64_t tx_batches = stats[PMD_STAT_SENT_BATCHES];
> + uint64_t passes = stats[PMD_STAT_RECV] +
> + stats[PMD_STAT_RECIRC];
> + uint64_t upcalls = stats[PMD_STAT_MISS];
> + uint64_t upcall_cycles = stats[PMD_CYCLES_UPCALL];
> + uint64_t tot_iter = histogram_samples(&s->pkts);
> + uint64_t idle_iter = s->pkts.bin[0];
> + uint64_t busy_iter = tot_iter >= idle_iter ? tot_iter - idle_iter : 0;
> + uint64_t sleep_iter = stats[PMD_SLEEP_ITER];
> + uint64_t tot_sleep_cycles = stats[PMD_CYCLES_SLEEP];
I earlier mentioned I prefer definitions at the top, but given the
number of variables here I think it is fine as is. Ilya, what is
your take?
> + /* Iterations. */
> + if (show_iterations) {
> + struct json *iter = json_object_create();
New line.
> + json_object_put(iter, "total", json_integer_create(tot_iter));
> + json_object_put(iter, "us-per-iteration",
> + json_real_create(tot_iter
> + ? (tot_cycles + tot_sleep_cycles)
> + * us_per_cycle / tot_iter
> + : 0));
> + json_object_put(iter, "used-tsc-cycles",
> + json_integer_create(tot_cycles));
> + json_object_put(iter, "used-tsc-pct",
> + json_real_create(
> + 100.0 * (tot_cycles / duration) / tsc_hz));
> + json_object_put(iter, "idle", json_integer_create(idle_iter));
> + json_object_put(iter, "idle-pct",
> + json_real_create(tot_cycles
> + ? 100.0 * stats[PMD_CYCLES_ITER_IDLE] /
> tot_cycles
> + : 0));
> + json_object_put(iter, "busy", json_integer_create(busy_iter));
> + json_object_put(iter, "busy-pct",
> + json_real_create(tot_cycles
> + ? 100.0 * stats[PMD_CYCLES_ITER_BUSY] /
> tot_cycles
> + : 0));
> + json_object_put(iter, "sleep", json_integer_create(sleep_iter));
> + json_object_put(iter, "sleep-pct",
> + json_real_create(tot_iter
> + ? 100.0 * sleep_iter / tot_iter : 0));
> + json_object_put(iter, "sleep-time-us",
> + json_real_create(tot_sleep_cycles * us_per_cycle));
> + json_object_put(iter, "sleep-us-per-iteration",
> + json_real_create(sleep_iter
> + ? (tot_sleep_cycles * us_per_cycle) / sleep_iter
> + : 0));
All these indentations look odd compared to the rest of the code base.
Mayeb this looks better?
json_object_put(iter, "us-per-iteration",
json_real_create(
tot_iter
? (tot_cycles + tot_sleep_cycles)
* us_per_cycle / tot_iter
: 0));
Ilya, WDYT?
> + json_object_put(json, "iterations", iter);
> + } else {
> + json_object_put(json, "iterations", json_null_create());
> + }
> +
> + /* Rx packets. */
> + struct json *rx = json_object_create();
New line or definition at the top?
> + json_object_put(rx, "count", json_integer_create(rx_packets));
> + json_object_put(rx, "kpps",
> + json_real_create(rx_packets
> + ? (rx_packets / duration) / 1000 : 0));
> + json_object_put(rx, "cycles-per-packet",
> + tot_iter && rx_packets
> + ? json_real_create(
> + 1.0 * stats[PMD_CYCLES_ITER_BUSY] / rx_packets)
> + : json_null_create());
> + json_object_put(json, "rx-packets", rx);
> +
> + /* Datapath passes. */
> + struct json *dp = json_object_create();
New line or definition at the top?
> + json_object_put(dp, "count", json_integer_create(passes));
> + json_object_put(dp, "passes-per-packet",
> + json_real_create(rx_packets
> + ? 1.0 * passes / rx_packets : 0));
> + json_object_put(dp, "phwol-hits",
> + json_integer_create(stats[PMD_STAT_PHWOL_HIT]));
> + json_object_put(dp, "simple-match-hits",
> + json_integer_create(stats[PMD_STAT_SIMPLE_HIT]));
> + json_object_put(dp, "emc-hits",
> + json_integer_create(stats[PMD_STAT_EXACT_HIT]));
> + json_object_put(dp, "smc-hits",
> + json_integer_create(stats[PMD_STAT_SMC_HIT]));
> + json_object_put(dp, "megaflow-hits",
> + json_integer_create(stats[PMD_STAT_MASKED_HIT]));
> + json_object_put(dp, "subtable-lookups-per-megaflow-hit",
> + json_real_create(stats[PMD_STAT_MASKED_HIT]
> + ? 1.0 * stats[PMD_STAT_MASKED_LOOKUP]
> + / stats[PMD_STAT_MASKED_HIT]
> + : 0));
> + json_object_put(dp, "upcalls", json_integer_create(upcalls));
> + json_object_put(dp, "us-per-upcall",
> + tot_iter
> + ? json_real_create(upcalls
> + ? (upcall_cycles * us_per_cycle) / upcalls : 0)
> + : json_null_create());
> + json_object_put(dp, "lost-upcalls",
> + json_integer_create(stats[PMD_STAT_LOST]));
> + json_object_put(json, "datapath-passes", dp);
> +
> + /* Tx packets. */
> + struct json *tx = json_object_create();
New line or definition at the top?
> + json_object_put(tx, "count", json_integer_create(tx_packets));
> + json_object_put(tx, "kpps",
> + json_real_create(tx_packets
> + ? (tx_packets / duration) / 1000 : 0));
> + json_object_put(tx, "batches", json_integer_create(tx_batches));
> + json_object_put(tx, "packets-per-batch",
> + json_real_create(tx_batches
> + ? 1.0 * tx_packets / tx_batches : 0));
> + json_object_put(json, "tx-packets", tx);
> +}
> +
> void
> pmd_perf_format_histograms(struct ds *str, struct pmd_perf_stats *s)
> {
> diff --git a/lib/dpif-netdev-perf.h b/lib/dpif-netdev-perf.h
> index cf4b6e103..9b0cb789a 100644
> --- a/lib/dpif-netdev-perf.h
> +++ b/lib/dpif-netdev-perf.h
> @@ -29,6 +29,7 @@
> #include <rte_cycles.h>
> #endif
>
> +#include "openvswitch/json.h"
> #include "openvswitch/vlog.h"
> #include "ovs-atomic.h"
> #include "timeval.h"
> @@ -423,6 +424,10 @@ struct pmd_perf_params {
>
> void pmd_perf_format_overall_stats(struct ds *str, struct pmd_perf_stats *s,
> double duration, bool format_iterations);
> +void pmd_perf_format_overall_stats_json(struct json *json,
Just 'struct json *,' is engouht here, same for below
'struct pmd_perf_stats *,'.
> + struct pmd_perf_stats *s,
> + double duration,
> + bool show_iterations);
> void pmd_perf_format_histograms(struct ds *str, struct pmd_perf_stats *s);
> void pmd_perf_format_iteration_history(struct ds *str,
> struct pmd_perf_stats *s,
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index b9233ba99..e3b782354 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -641,6 +641,32 @@ format_pmd_thread(struct ds *reply, struct
> dp_netdev_pmd_thread *pmd)
> ds_put_cstr(reply, ":\n");
> }
>
> +static struct json *
> +pmd_info_show_perf_json(struct dp_netdev_pmd_thread *pmd)
> +{
> + long long now = time_msec();
> + double duration = (now - pmd->perf_stats.start_ms) / 1000.0;
> + bool show_iterations = (pmd->core_id != NON_PMD_CORE_ID);
> +
Remove newline
> + struct json *json = json_object_create();
New line
> + json_object_put(json, "core",
> + (pmd->core_id != OVS_CORE_UNSPEC
> + && pmd->core_id != NON_PMD_CORE_ID)
> + ? json_integer_create(pmd->core_id)
> + : json_null_create());
> + json_object_put(json, "numa",
> + pmd->numa_id != OVS_NUMA_UNSPEC
> + ? json_integer_create(pmd->numa_id)
> + : json_null_create());
> + json_object_put(json, "measurement-duration-s",
> + json_real_create(duration));
> +
> + pmd_perf_format_overall_stats_json(json, &pmd->perf_stats,
> + duration, show_iterations);
> +
> + return json;
> +}
> +
> static void
> pmd_info_show_perf(struct ds *reply,
> struct dp_netdev_pmd_thread *pmd,
> @@ -942,6 +968,7 @@ dpif_netdev_pmd_info(struct unixctl_conn *conn, int argc,
> const char *argv[],
> / INTERVAL_USEC_TO_SEC;
> bool show_header = true;
> uint64_t max_sleep;
> + struct json *json_result = NULL;
Reverse christmas tree.
> ovs_mutex_lock(&dp_netdev_mutex);
>
> @@ -981,15 +1008,17 @@ dpif_netdev_pmd_info(struct unixctl_conn *conn, int
> argc, const char *argv[],
>
> sorted_poll_thread_list(dp, &pmd_list, &n);
>
> - if (type == PMD_INFO_SLEEP_SHOW
> - && unixctl_command_get_output_format(conn)
> - == UNIXCTL_OUTPUT_FMT_JSON) {
> - struct json *json_result = pmd_info_sleep_show_json(dp, pmd_list, n);
> - free(pmd_list);
> - ovs_mutex_unlock(&dp_netdev_mutex);
> - unixctl_command_reply_json(conn, json_result);
> - ds_destroy(&reply);
> - return;
> + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
> + if (type == PMD_INFO_PERF_SHOW) {
> + json_result = json_object_create();
> + } else if (type == PMD_INFO_SLEEP_SHOW) {
> + json_result = pmd_info_sleep_show_json(dp, pmd_list, n);
> + free(pmd_list);
> + ovs_mutex_unlock(&dp_netdev_mutex);
> + unixctl_command_reply_json(conn, json_result);
> + ds_destroy(&reply);
> + return;
> + }
> }
>
> for (size_t i = 0; i < n; i++) {
> @@ -1016,7 +1045,17 @@ dpif_netdev_pmd_info(struct unixctl_conn *conn, int
> argc, const char *argv[],
> } else if (type == PMD_INFO_CLEAR_STATS) {
> pmd_perf_stats_clear(&pmd->perf_stats);
> } else if (type == PMD_INFO_PERF_SHOW) {
> - pmd_info_show_perf(&reply, pmd, (struct pmd_perf_params *)aux);
> + if (json_result) {
> + bool is_main = pmd->core_id == NON_PMD_CORE_ID;
> + char *key = is_main
> + ? xstrdup("main")
> + : xasprintf("pmd-c%02u", pmd->core_id);
> + json_object_put_nocopy(json_result, key,
> + pmd_info_show_perf_json(pmd));
> + } else {
> + pmd_info_show_perf(&reply, pmd,
> + (struct pmd_perf_params *) aux);
> + }
I would not do this json part here, move all JSON handling to the
'if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {'
branch.
> } else if (type == PMD_INFO_SLEEP_SHOW) {
> if (show_header) {
> ds_put_format(&reply, "Default max sleep: %4"PRIu64" us\n",
> @@ -1032,7 +1071,11 @@ dpif_netdev_pmd_info(struct unixctl_conn *conn, int
> argc, const char *argv[],
>
> ovs_mutex_unlock(&dp_netdev_mutex);
>
> - unixctl_command_reply(conn, ds_cstr(&reply));
> + if (json_result) {
> + unixctl_command_reply_json(conn, json_result);
> + } else {
> + unixctl_command_reply(conn, ds_cstr(&reply));
> + }
> ds_destroy(&reply);
> }
>
> diff --git a/tests/pmd.at b/tests/pmd.at
> index 79ee2d470..9a2865b8f 100644
> --- a/tests/pmd.at
> +++ b/tests/pmd.at
> @@ -484,6 +484,84 @@ pmd thread numa_id <cleared> core_id <cleared>:
> Tx batches: 20 (1.00 pkts/batch)
> ])
>
> +dnl Check JSON output.
> +dnl Mask dynamic values: core id, measurement duration, all iteration fields,
> +dnl kpps, cycles-per-packet, and us-per-upcall.
> +AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-perf-show | dnl
> + sed 's/"pmd-c[[0-9]][[0-9]]"/"pmd-c<cleared>"/g' | dnl
> + sed 's/"core": [[0-9]][[0-9]]*/"core": <cleared>/g' | dnl
> + sed 's/"measurement-duration-s":
> [[0-9.]][[0-9.]]*/"measurement-duration-s": <cleared>/g' | dnl
> + sed 's/"kpps": [[0-9.]][[0-9.]]*/"kpps": <cleared>/g' | dnl
> + sed 's/"cycles-per-packet": [[0-9.]][[0-9.]]*/"cycles-per-packet":
> <cleared>/g' | dnl
> + sed 's/"us-per-upcall": [[0-9.]][[0-9.]]*/"us-per-upcall":
> <cleared>/g' | dnl
> + sed '/"iterations": {/,/}/{ s/: [[0-9.e+-]][[0-9.e+-]]*/:
> <cleared>/g; }'], [0], [dnl
> +{
> + "main": {
> + "core": null,
> + "datapath-passes": {
> + "count": 0,
> + "emc-hits": 0,
> + "lost-upcalls": 0,
> + "megaflow-hits": 0,
> + "passes-per-packet": 0,
> + "phwol-hits": 0,
> + "simple-match-hits": 0,
> + "smc-hits": 0,
> + "subtable-lookups-per-megaflow-hit": 0,
> + "upcalls": 0,
> + "us-per-upcall": null},
> + "iterations": null,
> + "measurement-duration-s": <cleared>,
> + "numa": null,
> + "rx-packets": {
> + "count": 0,
> + "cycles-per-packet": null,
> + "kpps": <cleared>},
> + "tx-packets": {
> + "batches": 0,
> + "count": 0,
> + "kpps": <cleared>,
> + "packets-per-batch": 0}},
> + "pmd-c<cleared>": {
> + "core": <cleared>,
> + "datapath-passes": {
> + "count": 20,
> + "emc-hits": 19,
> + "lost-upcalls": 0,
> + "megaflow-hits": 0,
> + "passes-per-packet": 1,
> + "phwol-hits": 0,
> + "simple-match-hits": 0,
> + "smc-hits": 0,
> + "subtable-lookups-per-megaflow-hit": 0,
> + "upcalls": 1,
> + "us-per-upcall": <cleared>},
> + "iterations": {
> + "busy": <cleared>,
> + "busy-pct": <cleared>,
> + "idle": <cleared>,
> + "idle-pct": <cleared>,
> + "sleep": <cleared>,
> + "sleep-pct": <cleared>,
> + "sleep-time-us": <cleared>,
> + "sleep-us-per-iteration": <cleared>,
> + "total": <cleared>,
> + "us-per-iteration": <cleared>,
> + "used-tsc-cycles": <cleared>,
> + "used-tsc-pct": <cleared>},
> + "measurement-duration-s": <cleared>,
> + "numa": 0,
> + "rx-packets": {
> + "count": 20,
> + "cycles-per-packet": <cleared>,
> + "kpps": <cleared>},
> + "tx-packets": {
> + "batches": 20,
> + "count": 20,
> + "kpps": <cleared>,
> + "packets-per-batch": 1}}}
> +])
> +
> OVS_VSWITCHD_STOP
> AT_CLEANUP
>
> --
> 2.54.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev