On 9/25/26 12:16 PM, 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" (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]>
> ---
>  NEWS                        |   2 +
>  lib/dpif-netdev-perf.c      | 456 +++++++++++++++++++++++++++++++-----
>  lib/dpif-netdev-perf.h      |  11 +
>  lib/dpif-netdev-unixctl.man |   9 +
>  lib/dpif-netdev.c           | 107 +++++++--
>  tests/pmd.at                | 229 ++++++++++++++++++
>  6 files changed, 745 insertions(+), 69 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 3c8ec26e8..1870ea4c6 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'.

Same comment as in 2/6.

>  
>  
>  v4.0.0 - 17 Aug 2026
> diff --git a/lib/dpif-netdev-perf.c b/lib/dpif-netdev-perf.c
> index 4b3b35852..daf687d78 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"));

Misaligned.

>  
>          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,173 @@ 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)
> +{
> +    double us_per_cycle = 1000000.0 / tsc_hz;
> +    uint64_t stats[PMD_N_STATS];
> +
> +    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));

I think, many places here would benefit from having a temporary
variable with the value calculated before the json_object_put()
call, so we can avoid these multi-level complex constructs that
are hard to read.

Also, do we need to report vlaues that we can't calculate?  It may
be better to just omit them in the output.  Same for many other
elements below.

> +        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));

Should be integer?

> +            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));

Ditto.

> +            }
> +        }
> +
> +        if (tx_packets) {
> +            json_object_put(averages, "packets-per-tx-batch",
> +                            json_real_create(1.0 * tx_packets / tx_batches));

This turns into incorrect JSON in case tx_batches is zero.  It should not be,
since tx_packets is not zero, but they are updated separately, so it may be
possible that one is not updated while the other is.

> +            json_object_put(averages, "tx-kpps",
> +                            json_real_create((tx_packets / duration) / 
> 1000));

integer?

> +        }
> +
> +        json_object_put(json, "averages", averages);
> +    }
>  }
>  
>  void
> @@ -377,6 +563,168 @@ 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..5589cbe55 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, omitted when
> +\fB-nh\fR is given, are reported under "histograms", each metric as an
> +object with 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.

I don't think this eplains enough what are the "walls" and "bins".  It may
also be better to just report as an object with walls being the keys and
the counts being the values, which would be more understandlabe.  For the
last bin the key could be something like "more".

> 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 65d3abf69..350a8c503 100644
> --- a/lib/dpif-netdev.c
> +++ b/lib/dpif-netdev.c
> @@ -644,6 +644,75 @@ 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));
> +    }
> +
> +    if (pmd->numa_id != OVS_NUMA_UNSPEC) {
> +        json_object_put(json, "numa", json_integer_create(pmd->numa_id));
> +    }
> +
> +    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,
> @@ -935,18 +1004,19 @@ static void
>  dpif_netdev_pmd_info(struct unixctl_conn *conn, int argc, const char *argv[],
>                       void *aux)
>  {
> -    struct ds reply = DS_EMPTY_INITIALIZER;
> +    unsigned long long max_secs = (PMD_INTERVAL_LEN * PMD_INTERVAL_MAX)
> +                                      / INTERVAL_USEC_TO_SEC;
> +    enum pmd_info_type type = *(enum pmd_info_type *) aux;
>      struct dp_netdev_pmd_thread **pmd_list;
> +    struct ds reply = DS_EMPTY_INITIALIZER;
> +    struct json *json_result = NULL;
>      struct dp_netdev *dp = NULL;
> -    enum pmd_info_type type = *(enum pmd_info_type *) aux;
> -    unsigned int core_id;
>      bool filter_on_pmd = false;
> -    size_t n;
> -    unsigned int secs = 0;
> -    unsigned long long max_secs = (PMD_INTERVAL_LEN * PMD_INTERVAL_MAX)
> -                                      / INTERVAL_USEC_TO_SEC;
> +    unsigned int core_id = 0;
>      bool show_header = true;
> +    unsigned int secs = 0;
>      uint64_t max_sleep;
> +    size_t n;
>  
>      ovs_mutex_lock(&dp_netdev_mutex);
>  
> @@ -986,15 +1056,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);
> +    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);
> +        }
>  
> -        ovs_mutex_unlock(&dp_netdev_mutex);
> -        free(pmd_list);
> -        unixctl_command_reply_json(conn, json_result);
> -        return;
> +        if (json_result) {
> +            ovs_mutex_unlock(&dp_netdev_mutex);
> +            free(pmd_list);
> +            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..e6074b23f 100644
> --- a/tests/pmd.at
> +++ b/tests/pmd.at
> @@ -484,6 +484,235 @@ 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>},

I think, the split here between "cycles" and "iterations" is still not
clear here.  The "used-tsc" thinds may need to be moved to "cylces" ?
The structure and the naming of some of the entries is also confusing.
For exmaple, "sleep" is in interations, which is consistent with being
inside the "iterations" block, but "sleep-percentage" is against total
cycles, and not iterations.  The text output is very clear about the
percentages, what they are calculated from.  But this JOSN output is
lacking this information.  It should either be encoded structurally
or in the names, and try to be consistent with what is located in the
"cycles" and what is in "iterations".  Some cycle percentations are
also calculated based on total cycles, some based on used cycles.
This is also not reflected in the output.

> +    "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.
> +AT_CHECK([ovs-vsctl set Open_vSwitch . other_config:pmd-perf-metrics=true])
> +ovs-appctl time/warp 100

AT_CHECK([ovs-appctl time/warp 100], [0], [ignore])

> +
> +AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-perf-show dnl
> +            -it 2 -ms 3 > perf.json])
> +
> +dnl The "averages" object of the histograms lists every reported histogram.
> +AT_CHECK([sed -n '/"histograms": {/,/"upcalls-per-iteration": /p' perf.json 
> | dnl
> +          sed 's/: [[0-9.e+-]][[0-9.e+-]]*/: <cleared>/'], [0], [dnl
> +    "histograms": {
> +      "averages": {
> +        "cycles-per-iteration": <cleared>,
> +        "cycles-per-packet": <cleared>,
> +        "cycles-per-upcall": <cleared>,
> +        "max-vhost-qlen": <cleared>,
> +        "packets-per-batch": <cleared>,
> +        "packets-per-iteration": <cleared>,
> +        "upcalls-per-iteration": <cleared>},
> +])
> +
> +dnl Every histogram has the same shape, so only one of them is checked.  The
> +dnl walls are constants, the bins depend on the recorded iterations.  There
> +dnl is one bin more than there are walls.
> +AT_CHECK([sed -n '/"packets-per-batch": {$/,/@:>@},$/p' perf.json | dnl
> +          sed '/"bins": \[[/,/\]]/{ s/^\( *\)[[0-9]][[0-9]]*/\1<cleared>/; 
> }'], [0], [dnl
> +      "packets-per-batch": {
> +        "bins": [[
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>,
> +          <cleared>]],
> +        "walls": [[
> +          0,
> +          1,
> +          2,
> +          3,
> +          4,
> +          5,
> +          6,
> +          7,
> +          8,
> +          9,
> +          10,
> +          11,
> +          12,
> +          13,
> +          14,
> +          16,
> +          17,
> +          18,
> +          19,
> +          20,
> +          21,
> +          22,
> +          23,
> +          24,
> +          25,
> +          26,
> +          27,
> +          28,
> +          29,
> +          30,
> +          32]]},
> +])
> +
> +dnl -it and -ms select how many samples of the two histories are reported.
> +AT_CHECK([sed -n '/"iteration-history": /,/}@:>@/p' perf.json | dnl
> +          sed 's/: [[0-9.e+-]][[0-9.e+-]]*/: <cleared>/'], [0], [dnl
> +    "iteration-history": [[
> +      {
> +        "cycles": <cleared>,
> +        "cycles-per-packet": <cleared>,
> +        "cycles-per-upcall": <cleared>,
> +        "iteration": <cleared>,
> +        "max-vhost-qlen": <cleared>,
> +        "packets": <cleared>,
> +        "packets-per-batch": <cleared>,
> +        "upcalls": <cleared>},
> +      {
> +        "cycles": <cleared>,
> +        "cycles-per-packet": <cleared>,
> +        "cycles-per-upcall": <cleared>,
> +        "iteration": <cleared>,
> +        "max-vhost-qlen": <cleared>,
> +        "packets": <cleared>,
> +        "packets-per-batch": <cleared>,
> +        "upcalls": <cleared>}]],
> +])
> +
> +AT_CHECK([sed -n '/"millisecond-history": /,/}@:>@/p' perf.json | dnl
> +          sed 's/: [[0-9.e+-]][[0-9.e+-]]*/: <cleared>/'], [0], [dnl
> +    "millisecond-history": [[
> +      {
> +        "cycles-per-iteration": <cleared>,
> +        "cycles-per-packet": <cleared>,
> +        "cycles-per-upcall": <cleared>,
> +        "iterations": <cleared>,
> +        "kpps": <cleared>,
> +        "max-vhost-qlen": <cleared>,
> +        "millisecond": <cleared>,
> +        "packets-per-batch": <cleared>,
> +        "upcalls": <cleared>},
> +      {
> +        "cycles-per-iteration": <cleared>,
> +        "cycles-per-packet": <cleared>,
> +        "cycles-per-upcall": <cleared>,
> +        "iterations": <cleared>,
> +        "kpps": <cleared>,
> +        "max-vhost-qlen": <cleared>,
> +        "millisecond": <cleared>,
> +        "packets-per-batch": <cleared>,
> +        "upcalls": <cleared>},
> +      {
> +        "cycles-per-iteration": <cleared>,
> +        "cycles-per-packet": <cleared>,
> +        "cycles-per-upcall": <cleared>,
> +        "iterations": <cleared>,
> +        "kpps": <cleared>,
> +        "max-vhost-qlen": <cleared>,
> +        "millisecond": <cleared>,
> +        "packets-per-batch": <cleared>,
> +        "upcalls": <cleared>}]],
> +])
> +
> +dnl With -nh no histogram is reported, and without -it and -ms neither
> +dnl history is.
> +AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-perf-show -nh dnl
> +            > perf-nh.json])
> +AT_CHECK([sed -n -e '/"histograms": /p' -e '/"iteration-history": /p' dnl
> +              -e '/"millisecond-history": /p' perf-nh.json], [0], [])
> +
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
>  

Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to