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 | 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;
+
+ 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
+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));
+ }
+ 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,
@@ -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;
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
+
+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