diff mbox series

[ovs-dev,v2,4/5] dpif-netdev: Add JSON output to pmd-stats-show.

Message ID d2fa3909e90d356f80d2fa6dbdc3e712ac66eb7f.1775568640.git.tredaelli@redhat.com
State New
Delegated to: Eelco Chaudron
Headers show
Series Add JSON output to several ovs-appctl commands | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed

Commit Message

Timothy Redaelli April 7, 2026, 1:34 p.m. UTC
When --format json is passed to ovs-appctl, pmd-stats-show returns a
JSON object keyed by thread identifier ("pmd-cNN" for PMD threads,
"main" for the main thread).  Each entry groups counters into
sub-objects: "packets" (received, recirculated), "flow-cache-hits"
(exact, megaflow, signature, etc.), "upcalls" (success, failure),
"cycles" (idle/processing with count and percentage), and several
"average-*" fields.

The "core" and "numa" fields are always present for schema consistency:
they contain the numeric value for PMD threads, or null for the main
thread and when the value is unspecified.

The "pmd-cNN" key format is chosen over the full thread name for
stability (see pmd-sleep-show commit for rationale).

Example output (abbreviated):
  {"main": {"core": null, "numa": null, "packets": {"received": 0, ...}, ...},
   "pmd-c03": {"core": 3, "numa": 0, "packets": {"received": 1000, ...}, ...}}

Signed-off-by: Timothy Redaelli <tredaelli@redhat.com>
---
 lib/dpif-netdev.c | 143 ++++++++++++++++++++++++++++++++++++++++++----
 tests/pmd.at      |  73 +++++++++++++++++++++++
 2 files changed, 205 insertions(+), 11 deletions(-)
diff mbox series

Patch

diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 32e1dc938..b2b61a514 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -724,6 +724,111 @@  pmd_info_show_stats(struct ds *reply,
                   stats[PMD_CYCLES_ITER_BUSY], total_packets);
 }
 
+static struct json *
+pmd_info_show_stats_json(struct dp_netdev_pmd_thread *pmd)
+{
+    uint64_t stats[PMD_N_STATS];
+    uint64_t total_cycles, total_packets;
+    double passes_per_pkt = 0;
+    double lookups_per_hit = 0;
+    double packets_per_batch = 0;
+    struct json *json, *json_averages, *json_cycles, *json_flow_cache;
+    struct json *json_idle, *json_packets, *json_processing, *json_upcalls;
+
+    pmd_perf_read_counters(&pmd->perf_stats, stats);
+    total_cycles = stats[PMD_CYCLES_ITER_IDLE] + stats[PMD_CYCLES_ITER_BUSY];
+    total_packets = stats[PMD_STAT_RECV];
+
+    if (total_packets > 0) {
+        passes_per_pkt = (total_packets + stats[PMD_STAT_RECIRC])
+                            / (double) total_packets;
+    }
+    if (stats[PMD_STAT_MASKED_HIT] > 0) {
+        lookups_per_hit = stats[PMD_STAT_MASKED_LOOKUP]
+                            / (double) stats[PMD_STAT_MASKED_HIT];
+    }
+    if (stats[PMD_STAT_SENT_BATCHES] > 0) {
+        packets_per_batch = stats[PMD_STAT_SENT_PKTS]
+                            / (double) stats[PMD_STAT_SENT_BATCHES];
+    }
+
+    json = json_object_create();
+    json_object_put(json, "core",
+                    (pmd->core_id != OVS_CORE_UNSPEC
+                     && pmd->core_id != NON_PMD_CORE_ID)
+                    ? json_integer_create(pmd->core_id)
+                    : json_null_create());
+    json_object_put(json, "numa",
+                    pmd->numa_id != OVS_NUMA_UNSPEC
+                    ? json_integer_create(pmd->numa_id)
+                    : json_null_create());
+    json_averages = json_object_create();
+    json_object_put(json_averages, "cycles-per-packet",
+                    json_real_create((total_cycles > 0 && total_packets > 0)
+                        ? total_cycles / (double) total_packets : 0.0));
+    json_object_put(json_averages, "datapath-passes-per-packet",
+                    json_real_create(passes_per_pkt));
+    json_object_put(json_averages, "packets-per-output-batch",
+                    json_real_create(packets_per_batch));
+    json_object_put(json_averages, "processing-cycles-per-packet",
+                    json_real_create((total_cycles > 0 && total_packets > 0)
+                        ? stats[PMD_CYCLES_ITER_BUSY]
+                            / (double) total_packets : 0.0));
+    json_object_put(json_averages, "subtable-lookups-per-megaflow-hit",
+                    json_real_create(lookups_per_hit));
+    json_object_put(json, "averages", json_averages);
+
+    json_idle = json_object_create();
+    json_object_put(json_idle, "count",
+                    json_integer_create(stats[PMD_CYCLES_ITER_IDLE]));
+    json_object_put(json_idle, "percentage",
+                    json_real_create(total_cycles > 0
+                        ? stats[PMD_CYCLES_ITER_IDLE]
+                            / (double) total_cycles * 100 : 0.0));
+    json_processing = json_object_create();
+    json_object_put(json_processing, "count",
+                    json_integer_create(stats[PMD_CYCLES_ITER_BUSY]));
+    json_object_put(json_processing, "percentage",
+                    json_real_create(total_cycles > 0
+                        ? stats[PMD_CYCLES_ITER_BUSY]
+                            / (double) total_cycles * 100 : 0.0));
+    json_cycles = json_object_create();
+    json_object_put(json_cycles, "idle", json_idle);
+    json_object_put(json_cycles, "processing", json_processing);
+    json_object_put(json, "cycles", json_cycles);
+
+    json_flow_cache = json_object_create();
+    json_object_put(json_flow_cache, "exact",
+                    json_integer_create(stats[PMD_STAT_EXACT_HIT]));
+    json_object_put(json_flow_cache, "megaflow",
+                    json_integer_create(stats[PMD_STAT_MASKED_HIT]));
+    json_object_put(json_flow_cache, "miniflow-extract",
+                    json_integer_create(stats[PMD_STAT_MFEX_OPT_HIT]));
+    json_object_put(json_flow_cache, "partial-hardware-offload",
+                    json_integer_create(stats[PMD_STAT_PHWOL_HIT]));
+    json_object_put(json_flow_cache, "signature",
+                    json_integer_create(stats[PMD_STAT_SMC_HIT]));
+    json_object_put(json_flow_cache, "simple",
+                    json_integer_create(stats[PMD_STAT_SIMPLE_HIT]));
+    json_object_put(json, "flow-cache-hits", json_flow_cache);
+
+    json_packets = json_object_create();
+    json_object_put(json_packets, "received",
+                    json_integer_create(total_packets));
+    json_object_put(json_packets, "recirculated",
+                    json_integer_create(stats[PMD_STAT_RECIRC]));
+    json_object_put(json, "packets", json_packets);
+
+    json_upcalls = json_object_create();
+    json_object_put(json_upcalls, "failure",
+                    json_integer_create(stats[PMD_STAT_LOST]));
+    json_object_put(json_upcalls, "success",
+                    json_integer_create(stats[PMD_STAT_MISS]));
+    json_object_put(json, "upcalls", json_upcalls);
+
+    return json;
+}
+
 static void
 pmd_info_show_perf(struct ds *reply,
                    struct dp_netdev_pmd_thread *pmd,
@@ -1418,6 +1523,7 @@  dpif_netdev_pmd_info(struct unixctl_conn *conn, int argc, const char *argv[],
                                       / INTERVAL_USEC_TO_SEC;
     bool show_header = true;
     uint64_t max_sleep;
+    struct json *json_result = NULL;
 
     ovs_mutex_lock(&dp_netdev_mutex);
 
@@ -1457,15 +1563,17 @@  dpif_netdev_pmd_info(struct unixctl_conn *conn, int argc, const char *argv[],
 
     sorted_poll_thread_list(dp, &pmd_list, &n);
 
-    if (type == PMD_INFO_SLEEP_SHOW
-        && unixctl_command_get_output_format(conn)
-               == UNIXCTL_OUTPUT_FMT_JSON) {
-        struct json *json_result = pmd_info_sleep_show_json(dp, pmd_list, n);
-        free(pmd_list);
-        ovs_mutex_unlock(&dp_netdev_mutex);
-        unixctl_command_reply_json(conn, json_result);
-        ds_destroy(&reply);
-        return;
+    if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
+        if (type == PMD_INFO_SHOW_STATS) {
+            json_result = json_object_create();
+        } else if (type == PMD_INFO_SLEEP_SHOW) {
+            json_result = pmd_info_sleep_show_json(dp, pmd_list, n);
+            free(pmd_list);
+            ovs_mutex_unlock(&dp_netdev_mutex);
+            unixctl_command_reply_json(conn, json_result);
+            ds_destroy(&reply);
+            return;
+        }
     }
 
     for (size_t i = 0; i < n; i++) {
@@ -1492,7 +1600,16 @@  dpif_netdev_pmd_info(struct unixctl_conn *conn, int argc, const char *argv[],
         } else if (type == PMD_INFO_CLEAR_STATS) {
             pmd_perf_stats_clear(&pmd->perf_stats);
         } else if (type == PMD_INFO_SHOW_STATS) {
-            pmd_info_show_stats(&reply, pmd);
+            if (json_result) {
+                bool is_main = pmd->core_id == NON_PMD_CORE_ID;
+                char *key = is_main
+                    ? xstrdup("main")
+                    : xasprintf("pmd-c%02u", pmd->core_id);
+                json_object_put_nocopy(json_result, key,
+                                       pmd_info_show_stats_json(pmd));
+            } else {
+                pmd_info_show_stats(&reply, pmd);
+            }
         } else if (type == PMD_INFO_PERF_SHOW) {
             pmd_info_show_perf(&reply, pmd, (struct pmd_perf_params *)aux);
         } else if (type == PMD_INFO_SLEEP_SHOW) {
@@ -1510,7 +1627,11 @@  dpif_netdev_pmd_info(struct unixctl_conn *conn, int argc, const char *argv[],
 
     ovs_mutex_unlock(&dp_netdev_mutex);
 
-    unixctl_command_reply(conn, ds_cstr(&reply));
+    if (json_result) {
+        unixctl_command_reply_json(conn, json_result);
+    } else {
+        unixctl_command_reply(conn, ds_cstr(&reply));
+    }
     ds_destroy(&reply);
 }
 
diff --git a/tests/pmd.at b/tests/pmd.at
index 702c9b950..4d442b0a6 100644
--- a/tests/pmd.at
+++ b/tests/pmd.at
@@ -490,6 +490,79 @@  pmd thread numa_id <cleared> core_id <cleared>:
   miss with failed upcall: 0
 ])
 
+dnl Check JSON output.
+dnl Mask dynamic values: core id, cycle counts and percentages,
+dnl cycles-per-packet, processing-cycles-per-packet,
+dnl and packets-per-output-batch.
+AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-stats-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/"count": [[0-9]][[0-9]]*/"count": <cleared>/g' | dnl
+          sed 's/"percentage": [[0-9.]][[0-9.]]*/"percentage": <cleared>/g' | dnl
+          sed 's/"cycles-per-packet": [[0-9.]][[0-9.]]*/"cycles-per-packet": <cleared>/g' | dnl
+          sed 's/"packets-per-output-batch": [[0-9.]][[0-9.]]*/"packets-per-output-batch": <cleared>/g' | dnl
+          sed 's/"processing-cycles-per-packet": [[0-9.]][[0-9.]]*/"processing-cycles-per-packet": <cleared>/g'], [0], [dnl
+{
+  "main": {
+    "averages": {
+      "cycles-per-packet": <cleared>,
+      "datapath-passes-per-packet": 0.0,
+      "packets-per-output-batch": <cleared>,
+      "processing-cycles-per-packet": <cleared>,
+      "subtable-lookups-per-megaflow-hit": 0.0},
+    "core": null,
+    "cycles": {
+      "idle": {
+        "count": <cleared>,
+        "percentage": <cleared>},
+      "processing": {
+        "count": <cleared>,
+        "percentage": <cleared>}},
+    "flow-cache-hits": {
+      "exact": 0,
+      "megaflow": 0,
+      "miniflow-extract": 0,
+      "partial-hardware-offload": 0,
+      "signature": 0,
+      "simple": 0},
+    "numa": null,
+    "packets": {
+      "received": 0,
+      "recirculated": 0},
+    "upcalls": {
+      "failure": 0,
+      "success": 0}},
+  "pmd-c<cleared>": {
+    "averages": {
+      "cycles-per-packet": <cleared>,
+      "datapath-passes-per-packet": 1.0,
+      "packets-per-output-batch": <cleared>,
+      "processing-cycles-per-packet": <cleared>,
+      "subtable-lookups-per-megaflow-hit": 0.0},
+    "core": <cleared>,
+    "cycles": {
+      "idle": {
+        "count": <cleared>,
+        "percentage": <cleared>},
+      "processing": {
+        "count": <cleared>,
+        "percentage": <cleared>}},
+    "flow-cache-hits": {
+      "exact": 19,
+      "megaflow": 0,
+      "miniflow-extract": 0,
+      "partial-hardware-offload": 0,
+      "signature": 0,
+      "simple": 0},
+    "numa": 0,
+    "packets": {
+      "received": 20,
+      "recirculated": 0},
+    "upcalls": {
+      "failure": 0,
+      "success": 1}}}
+])
+
 OVS_VSWITCHD_STOP
 AT_CLEANUP