On 12 Aug 2026, at 15:07, Timothy Redaelli 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" (only for PMD threads) and "numa"
> - "measurement-duration-s" (seconds as real)
> - "iterations" (iteration counts and times, only for PMD threads)
> - "packets" (received, recirculated, transmitted and tx batches)
> - "cycles" (idle and processing cycles, only for PMD threads)
> - "flow-cache-hits" and "upcalls" (only if packets were received)
> - "averages" (per packet, per batch and per upcall ratios)
>
> Keys are omitted when the value is not available for a thread, rather
> than being reported as null.
>
> When the detailed metrics are enabled, the command options select the
> same additional sections as in text mode: "histograms" unless -nh is
> given, "iteration-history" for -it and "millisecond-history" for -ms.
> Each histogram is an object with a "walls" array of upper bin bounds
> and a "bins" array of sample counts, one element longer than "walls"
> because the last bin collects everything above the last wall. The two
> histories are arrays with the most recent sample first.
>
> The counters are read and the arithmetic is done in a single place:
> pmd_perf_format_overall_stats_json() builds the JSON object and
> pmd_perf_format_overall_stats() renders the text output from it, so
> the two formats cannot drift apart.
>
> Example output:
> {"pmd-c03": {"core": 3, "numa": 0,
> "measurement-duration-s": 1.0,
> "packets": {"received": 20, "recirculated": 0,
> "transmitted": 20, "tx-batches": 20},
> "cycles": {"idle": {"count": 0, "percentage": 0},
> "processing": {"count": 8, "percentage": 100}},
> "flow-cache-hits": {"partial-hardware-offload": 0,
> "simple": 0, "exact": 19,
> "signature": 0, "megaflow": 0},
> "upcalls": {"success": 1, "failure": 0},
> "averages": {"datapath-passes-per-packet": 1,
> "packets-per-tx-batch": 1}}}
>
> Signed-off-by: Timothy Redaelli <[email protected]>
Thanks for the new revision Timothy, see some comments below.
//Eelco
> ---
> NEWS | 2 +
> lib/dpif-netdev-perf.c | 450 +++++++++++++++++++++++++++++++-----
> lib/dpif-netdev-perf.h | 11 +
> lib/dpif-netdev-unixctl.man | 9 +
> lib/dpif-netdev.c | 92 +++++++-
> tests/pmd.at | 92 ++++++++
> 6 files changed, 593 insertions(+), 63 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 82b3d9500..845b0798b 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -5,6 +5,8 @@ Post-v4.0.0
> * Added JSON output support (--format json) for 'upcall/show'.
> * Added JSON output support (--format json) for
> 'dpif-netdev/pmd-sleep-show'.
> + * Added JSON output support (--format json) for
> + 'dpif-netdev/pmd-perf-show'.
>
>
> v4.0.0 - xx xxx xxxx
> diff --git a/lib/dpif-netdev-perf.c b/lib/dpif-netdev-perf.c
> index 4b3b35852..766bc57e6 100644
> --- a/lib/dpif-netdev-perf.c
> +++ b/lib/dpif-netdev-perf.c
> @@ -206,34 +206,64 @@ pmd_perf_stats_init(struct pmd_perf_stats *s)
> s->log_reason = NULL;
> }
>
> +static const struct json *
> +pmd_perf_json_get(const struct json *json, const char *name)
> +{
> + return json ? shash_find_data(json_object(json), name) : NULL;
> +}
> +
> +static uint64_t
> +pmd_perf_json_get_int(const struct json *json, const char *name)
> +{
> + const struct json *value = pmd_perf_json_get(json, name);
> +
> + return value ? json_integer(value) : 0;
> +}
> +
> +static double
> +pmd_perf_json_get_real(const struct json *json, const char *name)
> +{
> + const struct json *value = pmd_perf_json_get(json, name);
> +
> + return value ? json_real(value) : 0;
> +}
> +
> void
> pmd_perf_format_overall_stats(struct ds *str, struct pmd_perf_stats *s,
> double duration, bool format_iterations)
> {
> - uint64_t stats[PMD_N_STATS];
> - double us_per_cycle = 1000000.0 / tsc_hz;
> + uint64_t exact, lost, megaflow, passes, phwol, simple, smc, upcalls;
> + const struct json *averages, *hits, *iter, *packets, *upcalls_json;
> + uint64_t rx_packets, tx_batches, tx_packets;
> + struct json *json = json_object_create();
>
> - if (duration == 0) {
> + pmd_perf_format_overall_stats_json(json, s, duration, format_iterations);
> +
> + packets = pmd_perf_json_get(json, "packets");
> + if (!packets) {
> + /* No statistics have been collected. */
> + json_destroy(json);
> return;
> }
>
> - 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];
> -
> - if (format_iterations) {
> + averages = pmd_perf_json_get(json, "averages");
> + hits = pmd_perf_json_get(json, "flow-cache-hits");
> + upcalls_json = pmd_perf_json_get(json, "upcalls");
> + iter = pmd_perf_json_get(json, "iterations");
> +
> + rx_packets = pmd_perf_json_get_int(packets, "received");
> + tx_packets = pmd_perf_json_get_int(packets, "transmitted");
> + tx_batches = pmd_perf_json_get_int(packets, "tx-batches");
> + passes = rx_packets + pmd_perf_json_get_int(packets, "recirculated");
> + phwol = pmd_perf_json_get_int(hits, "partial-hardware-offload");
> + simple = pmd_perf_json_get_int(hits, "simple");
> + exact = pmd_perf_json_get_int(hits, "exact");
> + smc = pmd_perf_json_get_int(hits, "signature");
> + megaflow = pmd_perf_json_get_int(hits, "megaflow");
> + upcalls = pmd_perf_json_get_int(upcalls_json, "success");
> + lost = pmd_perf_json_get_int(upcalls_json, "failure");
> +
> + if (iter) {
> ds_put_format(str,
> " Iterations: %12"PRIu64" (%.2f us/it)\n"
> " - Used TSC cycles: %12"PRIu64" (%5.1f %% of total cycles)\n"
> @@ -241,26 +271,26 @@ pmd_perf_format_overall_stats(struct ds *str, struct
> pmd_perf_stats *s,
> " - busy iterations: %12"PRIu64" (%5.1f %% of used cycles)\n"
> " - sleep iterations: %12"PRIu64" (%5.1f %% of iterations)\n"
> " Sleep time (us): %12.0f (%3.0f us/iteration avg.)\n",
> - tot_iter,
> - tot_iter
> - ? (tot_cycles + tot_sleep_cycles) * us_per_cycle / tot_iter
> - : 0,
> - tot_cycles, 100.0 * (tot_cycles / duration) / tsc_hz,
> - idle_iter,
> - tot_cycles ? 100.0 * stats[PMD_CYCLES_ITER_IDLE] / tot_cycles :
> 0,
> - busy_iter,
> - tot_cycles ? 100.0 * stats[PMD_CYCLES_ITER_BUSY] / tot_cycles :
> 0,
> - sleep_iter, tot_iter ? 100.0 * sleep_iter / tot_iter : 0,
> - tot_sleep_cycles * us_per_cycle,
> - sleep_iter ? (tot_sleep_cycles * us_per_cycle) / sleep_iter : 0);
> + pmd_perf_json_get_int(iter, "total"),
> + pmd_perf_json_get_real(iter, "us-per-iteration"),
> + pmd_perf_json_get_int(iter, "used-tsc-cycles"),
> + pmd_perf_json_get_real(iter, "used-tsc-percentage"),
> + pmd_perf_json_get_int(iter, "idle"),
> + pmd_perf_json_get_real(iter, "idle-percentage"),
> + pmd_perf_json_get_int(iter, "busy"),
> + pmd_perf_json_get_real(iter, "busy-percentage"),
> + pmd_perf_json_get_int(iter, "sleep"),
> + pmd_perf_json_get_real(iter, "sleep-percentage"),
> + pmd_perf_json_get_real(iter, "sleep-time-us"),
> + pmd_perf_json_get_real(iter, "sleep-us-per-iteration"));
> }
> if (rx_packets > 0) {
> ds_put_format(str,
> " Rx packets: %12"PRIu64" (%.0f Kpps",
> - rx_packets, (rx_packets / duration) / 1000);
> - if (tot_iter) {
> + rx_packets, pmd_perf_json_get_real(averages, "rx-kpps"));
> + if (pmd_perf_json_get(averages, "cycles-per-packet")) {
> ds_put_format(str, ", %.0f cycles/pkt",
> - 1.0 * stats[PMD_CYCLES_ITER_BUSY] / rx_packets);
> + pmd_perf_json_get_real(averages, "cycles-per-packet"));
> }
> ds_put_cstr(str, ")\n");
>
> @@ -272,33 +302,28 @@ pmd_perf_format_overall_stats(struct ds *str, struct
> pmd_perf_stats *s,
> " - SMC hits: %12"PRIu64" (%5.1f %%)\n"
> " - Megaflow hits: %12"PRIu64" (%5.1f %%, %.2f "
> "subtbl lookups/hit)\n",
> - passes, 1.0 * passes / rx_packets,
> - stats[PMD_STAT_PHWOL_HIT],
> - 100.0 * stats[PMD_STAT_PHWOL_HIT] / passes,
> - stats[PMD_STAT_SIMPLE_HIT],
> - 100.0 * stats[PMD_STAT_SIMPLE_HIT] / passes,
> - stats[PMD_STAT_EXACT_HIT],
> - 100.0 * stats[PMD_STAT_EXACT_HIT] / passes,
> - stats[PMD_STAT_SMC_HIT],
> - 100.0 * stats[PMD_STAT_SMC_HIT] / passes,
> - stats[PMD_STAT_MASKED_HIT],
> - 100.0 * stats[PMD_STAT_MASKED_HIT] / passes,
> - stats[PMD_STAT_MASKED_HIT]
> - ? 1.0 * stats[PMD_STAT_MASKED_LOOKUP] /
> stats[PMD_STAT_MASKED_HIT]
> - : 0);
> + passes,
> + pmd_perf_json_get_real(averages, "datapath-passes-per-packet"),
> + phwol, 100.0 * phwol / passes,
> + simple, 100.0 * simple / passes,
> + exact, 100.0 * exact / passes,
> + smc, 100.0 * smc / passes,
> + megaflow, 100.0 * megaflow / passes,
> + pmd_perf_json_get_real(averages,
> + "subtable-lookups-per-megaflow-hit"));
>
> ds_put_format(str,
> " - Upcalls: %12"PRIu64" (%5.1f %%",
> upcalls, 100.0 * upcalls / passes);
> - if (tot_iter) {
> + if (pmd_perf_json_get(averages, "us-per-upcall")) {
> ds_put_format(str, ", %.1f us/upcall",
> - upcalls ? (upcall_cycles * us_per_cycle) / upcalls : 0);
> + pmd_perf_json_get_real(averages, "us-per-upcall"));
> }
> ds_put_cstr(str, ")\n");
>
> ds_put_format(str,
> " - Lost upcalls: %12"PRIu64" (%5.1f %%)\n",
> - stats[PMD_STAT_LOST], 100.0 * stats[PMD_STAT_LOST] / passes);
> + lost, 100.0 * lost / passes);
> } else {
> ds_put_format(str,
> " Rx packets: %12d\n", 0);
> @@ -307,12 +332,171 @@ pmd_perf_format_overall_stats(struct ds *str, struct
> pmd_perf_stats *s,
> ds_put_format(str,
> " Tx packets: %12"PRIu64" (%.0f Kpps)\n"
> " Tx batches: %12"PRIu64" (%.2f pkts/batch)\n",
> - tx_packets, (tx_packets / duration) / 1000,
> - tx_batches, 1.0 * tx_packets / tx_batches);
> + tx_packets, pmd_perf_json_get_real(averages, "tx-kpps"),
> + tx_batches,
> + pmd_perf_json_get_real(averages, "packets-per-tx-batch"));
> } else {
> ds_put_format(str,
> " Tx packets: %12d\n\n", 0);
> }
> +
> + json_destroy(json);
> +}
> +
> +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];
> + double us_per_cycle = 1000000.0 / tsc_hz;
Swap the two lines above.
> +
> + if (duration == 0) {
> + return;
> + }
> +
> + 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];
> + struct json *packets = json_object_create();
> +
> + if (show_iterations) {
> + struct json *processing = json_object_create();
> + struct json *cycles = json_object_create();
> + struct json *idle = json_object_create();
> + struct json *iter = json_object_create();
> +
> + 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-percentage",
> + 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-percentage",
> + 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-percentage",
> + 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-percentage",
> + 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));
> + json_object_put(json, "iterations", iter);
> +
> + json_object_put(idle, "count",
> + json_integer_create(stats[PMD_CYCLES_ITER_IDLE]));
> + json_object_put(idle, "percentage",
> + json_real_create(
> + tot_cycles
> + ? 100.0 * stats[PMD_CYCLES_ITER_IDLE] /
> tot_cycles
> + : 0));
> + json_object_put(cycles, "idle", idle);
> + json_object_put(processing, "count",
> + json_integer_create(stats[PMD_CYCLES_ITER_BUSY]));
> + json_object_put(processing, "percentage",
> + json_real_create(
> + tot_cycles
> + ? 100.0 * stats[PMD_CYCLES_ITER_BUSY] /
> tot_cycles
> + : 0));
> + json_object_put(cycles, "processing", processing);
> + json_object_put(json, "cycles", cycles);
> + }
> +
> + json_object_put(packets, "received", json_integer_create(rx_packets));
> + json_object_put(packets, "recirculated",
> + json_integer_create(stats[PMD_STAT_RECIRC]));
> + json_object_put(packets, "transmitted", json_integer_create(tx_packets));
> + json_object_put(packets, "tx-batches", json_integer_create(tx_batches));
> + json_object_put(json, "packets", packets);
> +
> + if (rx_packets) {
> + struct json *upcalls_json = json_object_create();
> + struct json *hits = json_object_create();
> +
> + json_object_put(hits, "partial-hardware-offload",
> + json_integer_create(stats[PMD_STAT_PHWOL_HIT]));
> + json_object_put(hits, "simple",
> + json_integer_create(stats[PMD_STAT_SIMPLE_HIT]));
> + json_object_put(hits, "exact",
> + json_integer_create(stats[PMD_STAT_EXACT_HIT]));
> + json_object_put(hits, "signature",
> + json_integer_create(stats[PMD_STAT_SMC_HIT]));
> + json_object_put(hits, "megaflow",
> + json_integer_create(stats[PMD_STAT_MASKED_HIT]));
> + json_object_put(json, "flow-cache-hits", hits);
> +
> + json_object_put(upcalls_json, "success",
> + json_integer_create(upcalls));
> + json_object_put(upcalls_json, "failure",
> + json_integer_create(stats[PMD_STAT_LOST]));
> + json_object_put(json, "upcalls", upcalls_json);
> + }
> +
> + if (rx_packets || tx_packets) {
> + struct json *averages = json_object_create();
> +
> + if (rx_packets) {
> + json_object_put(averages, "datapath-passes-per-packet",
> + json_real_create(1.0 * passes / rx_packets));
> + json_object_put(averages, "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(averages, "rx-kpps",
> + json_real_create((rx_packets / duration) /
> 1000));
> + if (tot_iter) {
> + json_object_put(averages, "cycles-per-packet",
> + json_real_create(
> + 1.0 * stats[PMD_CYCLES_ITER_BUSY]
> + / rx_packets));
> + json_object_put(averages, "us-per-upcall",
> + json_real_create(
> + upcalls
> + ? (upcall_cycles * us_per_cycle) /
> upcalls
> + : 0));
> + }
> + }
> + if (tx_packets) {
> + json_object_put(averages, "packets-per-tx-batch",
> + json_real_create(1.0 * tx_packets / tx_batches));
> + json_object_put(averages, "tx-kpps",
> + json_real_create((tx_packets / duration) /
> 1000));
> + }
> + json_object_put(json, "averages", averages);
> + }
> }
>
> void
> @@ -377,6 +561,164 @@ pmd_perf_format_histograms(struct ds *str, struct
> pmd_perf_stats *s)
> ? s->totals.upcall_cycles / s->totals.upcalls : 0);
> }
>
> +static struct json *
> +histogram_to_json(const struct histogram *hist)
> +{
> + struct json *walls = json_array_create_empty();
> + struct json *bins = json_array_create_empty();
> + struct json *json = json_object_create();
> + int i;
> +
> + /* The last bin has no wall, it collects everything above the last
> + * wall. */
> + for (i = 0; i < NUM_BINS - 1; i++) {
> + json_array_add(walls, json_integer_create(hist->wall[i]));
> + }
> + for (i = 0; i < NUM_BINS; i++) {
> + json_array_add(bins, json_integer_create(hist->bin[i]));
> + }
> + json_object_put(json, "walls", walls);
> + json_object_put(json, "bins", bins);
> +
> + return json;
> +}
> +
> +void
> +pmd_perf_format_histograms_json(struct json *json, struct pmd_perf_stats *s)
> +{
> + struct json *histograms = json_object_create();
> + struct json *averages = json_object_create();
> +
> + json_object_put(histograms, "cycles-per-iteration",
> + histogram_to_json(&s->cycles));
> + json_object_put(histograms, "packets-per-iteration",
> + histogram_to_json(&s->pkts));
> + json_object_put(histograms, "cycles-per-packet",
> + histogram_to_json(&s->cycles_per_pkt));
> + json_object_put(histograms, "packets-per-batch",
> + histogram_to_json(&s->pkts_per_batch));
> + json_object_put(histograms, "max-vhost-qlen",
> + histogram_to_json(&s->max_vhost_qfill));
> + json_object_put(histograms, "upcalls-per-iteration",
> + histogram_to_json(&s->upcalls));
> + json_object_put(histograms, "cycles-per-upcall",
> + histogram_to_json(&s->cycles_per_upcall));
> +
> + json_object_put(averages, "cycles-per-iteration",
> + json_integer_create(s->totals.iterations
> + ? s->totals.cycles / s->totals.iterations : 0));
> + json_object_put(averages, "packets-per-iteration",
> + json_real_create(s->totals.iterations
> + ? 1.0 * s->totals.pkts / s->totals.iterations : 0));
> + json_object_put(averages, "cycles-per-packet",
> + json_integer_create(s->totals.pkts
> + ? s->totals.busy_cycles / s->totals.pkts : 0));
> + json_object_put(averages, "packets-per-batch",
> + json_real_create(s->totals.batches
> + ? 1.0 * s->totals.pkts / s->totals.batches : 0));
> + json_object_put(averages, "max-vhost-qlen",
> + json_real_create(s->totals.iterations
> + ? 1.0 * s->totals.max_vhost_qfill
> + / s->totals.iterations
> + : 0));
> + json_object_put(averages, "upcalls-per-iteration",
> + json_real_create(s->totals.iterations
> + ? 1.0 * s->totals.upcalls / s->totals.iterations :
> 0));
> + json_object_put(averages, "cycles-per-upcall",
> + json_integer_create(s->totals.upcalls
> + ? s->totals.upcall_cycles / s->totals.upcalls : 0));
> +
> + json_object_put(histograms, "averages", averages);
> + json_object_put(json, "histograms", histograms);
> +}
> +
> +void
> +pmd_perf_format_iteration_history_json(struct json *json,
> + struct pmd_perf_stats *s, int n_iter)
> +{
> + struct iter_stats *is;
> + struct json *history;
> + size_t index;
> + int i;
> +
> + if (n_iter == 0) {
> + return;
> + }
> +
> + history = json_array_create_empty();
> + for (i = 1; i <= n_iter; i++) {
> + struct json *sample = json_object_create();
> +
> + index = history_sub(s->iterations.idx, i);
> + is = &s->iterations.sample[index];
> +
> + json_object_put(sample, "iteration",
> + json_integer_create(is->timestamp));
> + json_object_put(sample, "cycles", json_integer_create(is->cycles));
> + json_object_put(sample, "packets", json_integer_create(is->pkts));
> + json_object_put(sample, "cycles-per-packet",
> + json_integer_create(is->pkts
> + ? is->cycles / is->pkts : 0));
> + json_object_put(sample, "packets-per-batch",
> + json_integer_create(is->batches
> + ? is->pkts / is->batches : 0));
> + json_object_put(sample, "max-vhost-qlen",
> + json_integer_create(is->max_vhost_qfill));
> + json_object_put(sample, "upcalls", json_integer_create(is->upcalls));
> + json_object_put(sample, "cycles-per-upcall",
> + json_integer_create(is->upcalls
> + ? is->upcall_cycles / is->upcalls : 0));
> + json_array_add(history, sample);
> + }
> + json_object_put(json, "iteration-history", history);
> +}
> +
> +void
> +pmd_perf_format_ms_history_json(struct json *json, struct pmd_perf_stats *s,
> + int n_ms)
> +{
> + struct iter_stats *is;
> + struct json *history;
> + size_t index;
> + int i;
> +
> + if (n_ms == 0) {
> + return;
> + }
> +
> + history = json_array_create_empty();
> + for (i = 1; i <= n_ms; i++) {
> + struct json *sample = json_object_create();
> +
> + index = history_sub(s->milliseconds.idx, i);
> + is = &s->milliseconds.sample[index];
> +
> + json_object_put(sample, "millisecond",
> + json_integer_create(is->timestamp));
> + json_object_put(sample, "iterations",
> + json_integer_create(is->iterations));
> + json_object_put(sample, "cycles-per-iteration",
> + json_integer_create(is->iterations
> + ? is->cycles / is->iterations : 0));
> + json_object_put(sample, "kpps", json_integer_create(is->pkts));
> + json_object_put(sample, "cycles-per-packet",
> + json_integer_create(is->pkts
> + ? is->busy_cycles / is->pkts : 0));
> + json_object_put(sample, "packets-per-batch",
> + json_integer_create(is->batches
> + ? is->pkts / is->batches : 0));
> + json_object_put(sample, "max-vhost-qlen",
> + json_integer_create(is->iterations
> + ? is->max_vhost_qfill / is->iterations : 0));
> + json_object_put(sample, "upcalls", json_integer_create(is->upcalls));
> + json_object_put(sample, "cycles-per-upcall",
> + json_integer_create(is->upcalls
> + ? is->upcall_cycles / is->upcalls : 0));
> + json_array_add(history, sample);
> + }
> + json_object_put(json, "millisecond-history", history);
> +}
> +
> void
> pmd_perf_format_iteration_history(struct ds *str, struct pmd_perf_stats *s,
> int n_iter)
> diff --git a/lib/dpif-netdev-perf.h b/lib/dpif-netdev-perf.h
> index cf4b6e103..6f7e99cb9 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,12 +424,22 @@ 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 *,
> + struct pmd_perf_stats *,
> + double duration,
> + bool show_iterations);
> void pmd_perf_format_histograms(struct ds *str, struct pmd_perf_stats *s);
> +void pmd_perf_format_histograms_json(struct json *, struct pmd_perf_stats *);
> void pmd_perf_format_iteration_history(struct ds *str,
> struct pmd_perf_stats *s,
> int n_iter);
> +void pmd_perf_format_iteration_history_json(struct json *,
> + struct pmd_perf_stats *,
> + int n_iter);
> void pmd_perf_format_ms_history(struct ds *str, struct pmd_perf_stats *s,
> int n_ms);
> +void pmd_perf_format_ms_history_json(struct json *, struct pmd_perf_stats *,
> + int n_ms);
> void pmd_perf_log_set_cmd(struct unixctl_conn *conn,
> int argc, const char *argv[],
> void *aux OVS_UNUSED);
> diff --git a/lib/dpif-netdev-unixctl.man b/lib/dpif-netdev-unixctl.man
> index 2fb2c5442..9b01e2689 100644
> --- a/lib/dpif-netdev-unixctl.man
> +++ b/lib/dpif-netdev-unixctl.man
> @@ -151,6 +151,15 @@ any packets. "busy iterations" refers to PMD iterations
> that included
> processing of at least one packet. The reported used TSC cycles include the
> cost for polling, processing and transmitting said packets.
>
> +JSON output is an object keyed by thread name ("main" or "pmd-c\fIXX\fR"),
> +whose values hold the same statistics. The histograms, selected by
> +\fB-nh\fR, are reported under "histograms", each metric as an object with
This is not correct, as the histograms are reported if -nh is not present.
> +a "walls" array of upper bin bounds and a "bins" array of sample counts,
> +one longer than "walls" because the last bin collects everything above the
> +last wall. The iteration and millisecond histories, selected by \fB-it\fR
> +and \fB-ms\fR, are reported as arrays under "iteration-history" and
> +"millisecond-history", most recent sample first.
> +
> To reset the counters and start a new measurement use
> \fBdpif-netdev/pmd-stats-clear\fR.
> .
> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
> index 79b5d1b7a..384eaa695 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -644,6 +644,71 @@ 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,
> + struct pmd_perf_params *par)
> +{
> + double duration = (time_msec() - pmd->perf_stats.start_ms) / 1000.0;
> + bool show_iterations = pmd->core_id != NON_PMD_CORE_ID;
> + struct json *json = json_object_create();
> +
> + if (pmd->core_id != OVS_CORE_UNSPEC
> + && pmd->core_id != NON_PMD_CORE_ID) {
> + json_object_put(json, "core", json_integer_create(pmd->core_id));
> + }
New line?
> + if (pmd->numa_id != OVS_NUMA_UNSPEC) {
> + json_object_put(json, "numa", json_integer_create(pmd->numa_id));
> + }
New line? Or at least be consistent in the code with newlines after if() and
for() blocks. Or look at existing code in the specific file. I like the style
of show_dpif_json() in the next patch; maybe apply the same in this patch in
all places?
> + json_object_put(json, "measurement-duration-s",
> + json_real_create(duration));
> +
> + pmd_perf_format_overall_stats_json(json, &pmd->perf_stats,
> + duration, show_iterations);
> +
> + if (pmd_perf_metrics_enabled(pmd) && pmd->core_id != NON_PMD_CORE_ID) {
> + /* Prevent parallel clearing of perf metrics. */
> + ovs_mutex_lock(&pmd->perf_stats.clear_mutex);
> + if (par->histograms) {
> + pmd_perf_format_histograms_json(json, &pmd->perf_stats);
> + }
> + pmd_perf_format_iteration_history_json(json, &pmd->perf_stats,
> + par->iter_hist_len);
> + pmd_perf_format_ms_history_json(json, &pmd->perf_stats,
> + par->ms_hist_len);
> + ovs_mutex_unlock(&pmd->perf_stats.clear_mutex);
> + }
> +
> + return json;
> +}
> +
> +static struct json *
> +pmd_info_perf_show_json(struct dp_netdev_pmd_thread **pmd_list, size_t n,
> + bool filter_on_pmd, unsigned int core_id,
> + struct pmd_perf_params *par)
> +{
> + struct json *json_threads = json_object_create();
> +
> + for (size_t i = 0; i < n; i++) {
> + struct dp_netdev_pmd_thread *pmd = pmd_list[i];
> + char *key;
> +
> + if (!pmd) {
> + break;
> + }
> + if (filter_on_pmd && pmd->core_id != core_id) {
> + continue;
> + }
> +
> + key = pmd->core_id == NON_PMD_CORE_ID
> + ? xstrdup("main")
> + : xasprintf("pmd-c%02u", pmd->core_id);
> + json_object_put_nocopy(json_threads, key,
> + pmd_info_show_perf_json(pmd, par));
> + }
> +
> + return json_threads;
> +}
> +
> static void
> pmd_info_show_perf(struct ds *reply,
> struct dp_netdev_pmd_thread *pmd,
> @@ -937,9 +1002,10 @@ dpif_netdev_pmd_info(struct unixctl_conn *conn, int
> argc, const char *argv[],
> {
> struct ds reply = DS_EMPTY_INITIALIZER;
> struct dp_netdev_pmd_thread **pmd_list;
> + struct json *json_result = NULL;
> struct dp_netdev *dp = NULL;
> enum pmd_info_type type = *(enum pmd_info_type *) aux;
> - unsigned int core_id;
> + unsigned int core_id = 0;
This block already does not follow the Christmas tree style; maybe it can be
fixed?
> bool filter_on_pmd = false;
> size_t n;
> unsigned int secs = 0;
> @@ -986,14 +1052,22 @@ 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);
> - return;
> + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
> + if (type == PMD_INFO_PERF_SHOW) {
> + json_result = pmd_info_perf_show_json(pmd_list, n, filter_on_pmd,
> + core_id,
> + (struct pmd_perf_params *)
> + aux);
> + } else if (type == PMD_INFO_SLEEP_SHOW) {
> + json_result = pmd_info_sleep_show_json(dp, pmd_list, n);
> + }
> +
> + if (json_result) {
> + free(pmd_list);
> + ovs_mutex_unlock(&dp_netdev_mutex);
> + unixctl_command_reply_json(conn, json_result);
> + return;
> + }
> }
>
> for (size_t i = 0; i < n; i++) {
> diff --git a/tests/pmd.at b/tests/pmd.at
> index 79ee2d470..3a806c071 100644
> --- a/tests/pmd.at
> +++ b/tests/pmd.at
> @@ -484,6 +484,98 @@ 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 and
> +dnl cycle fields, 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/"rx-kpps": [[0-9.]][[0-9.]]*/"rx-kpps": <cleared>/g' | dnl
> + sed 's/"tx-kpps": [[0-9.]][[0-9.]]*/"tx-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 '/"cycles": {/,/}}/{ s/: [[0-9.e+-]][[0-9.e+-]]*/:
> <cleared>/g; }' | dnl
> + sed '/"iterations": {/,/}/{ s/: [[0-9.e+-]][[0-9.e+-]]*/:
> <cleared>/g; }'], [0], [dnl
> +{
> + "main": {
> + "measurement-duration-s": <cleared>,
> + "packets": {
> + "received": 0,
> + "recirculated": 0,
> + "transmitted": 0,
> + "tx-batches": 0}},
> + "pmd-c<cleared>": {
> + "averages": {
> + "cycles-per-packet": <cleared>,
> + "datapath-passes-per-packet": 1,
> + "packets-per-tx-batch": 1,
> + "rx-kpps": <cleared>,
> + "subtable-lookups-per-megaflow-hit": 0,
> + "tx-kpps": <cleared>,
> + "us-per-upcall": <cleared>},
> + "core": <cleared>,
> + "cycles": {
> + "idle": {
> + "count": <cleared>,
> + "percentage": <cleared>},
> + "processing": {
> + "count": <cleared>,
> + "percentage": <cleared>}},
> + "flow-cache-hits": {
> + "exact": 19,
> + "megaflow": 0,
> + "partial-hardware-offload": 0,
> + "signature": 0,
> + "simple": 0},
> + "iterations": {
> + "busy": <cleared>,
> + "busy-percentage": <cleared>,
> + "idle": <cleared>,
> + "idle-percentage": <cleared>,
> + "sleep": <cleared>,
> + "sleep-percentage": <cleared>,
> + "sleep-time-us": <cleared>,
> + "sleep-us-per-iteration": <cleared>,
> + "total": <cleared>,
> + "us-per-iteration": <cleared>,
> + "used-tsc-cycles": <cleared>,
> + "used-tsc-percentage": <cleared>},
> + "measurement-duration-s": <cleared>,
> + "numa": 0,
> + "packets": {
> + "received": 20,
> + "recirculated": 0,
> + "transmitted": 20,
> + "tx-batches": 20},
> + "upcalls": {
> + "failure": 0,
> + "success": 1}}}
> +])
> +
> +dnl Check that the JSON output honours -nh, -it and -ms. The values depend
> +dnl on the recorded iterations, so only the structure is checked here.
> +AT_CHECK([ovs-vsctl set Open_vSwitch . other_config:pmd-perf-metrics=true])
> +ovs-appctl time/warp 100
Should we not check the actual perf metrics structure as we do above,
not just grep some values?
> +AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-perf-show dnl
> + -it 2 -ms 3 > perf.json])
> +AT_CHECK([grep -c '"histograms":' perf.json], [0], [1
> +])
> +AT_CHECK([grep -c '"iteration":' perf.json], [0], [2
> +])
> +AT_CHECK([grep -c '"millisecond":' perf.json], [0], [3
> +])
> +
> +AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-perf-show -nh dnl
> + > perf-nh.json])
> +AT_CHECK([grep -c '"histograms":' perf-nh.json], [1], [0
> +])
> +AT_CHECK([grep -c '"iteration-history":' perf-nh.json], [1], [0
> +])
> +AT_CHECK([grep -c '"millisecond-history":' perf-nh.json], [1], [0
> +])
> +
> OVS_VSWITCHD_STOP
> AT_CLEANUP
>
> --
> 2.55.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev