From patchwork Tue Apr 7 13:34:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timothy Redaelli X-Patchwork-Id: 2220547 X-Patchwork-Delegate: echaudro@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QGAkKXB0; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4fqnHz09qgz1xy1 for ; Tue, 07 Apr 2026 23:35:10 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 118DF40367; Tue, 7 Apr 2026 13:35:09 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Mdvc1M4C29Gn; Tue, 7 Apr 2026 13:35:08 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 18AB24035A Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QGAkKXB0 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id 18AB24035A; Tue, 7 Apr 2026 13:35:08 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id F09BDC04FB; Tue, 7 Apr 2026 13:35:07 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 466E1C04FA for ; Tue, 7 Apr 2026 13:35:06 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 2CE214035A for ; Tue, 7 Apr 2026 13:35:06 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id O32VnLbZWjS3 for ; Tue, 7 Apr 2026 13:35:05 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=tredaelli@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp4.osuosl.org 0B260402FD Authentication-Results: smtp4.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 0B260402FD Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp4.osuosl.org (Postfix) with ESMTPS id 0B260402FD for ; Tue, 7 Apr 2026 13:35:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775568903; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3oZ9wfSWx1HHGtwf/1AuseGJZ6I9NAr/iv6Qo2lX9w0=; b=QGAkKXB0m6X8pTpU3dEnT/DubkuPqhxL5px6C952I8PTLRkRAu4llcjgT8bYUDOytuRfw2 C9/Jb5zxhancgLHhX4II5RoJk3v5OGC0qFjzUU75/r3L3W1idGgd4f1OZh1kKeQDLMDU7t +I5386ObxqiH8ZJxTuU4bKERsvwRiAU= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-157-NJQY_ZHSMfOOjlxVkYs6jQ-1; Tue, 07 Apr 2026 09:35:02 -0400 X-MC-Unique: NJQY_ZHSMfOOjlxVkYs6jQ-1 X-Mimecast-MFC-AGG-ID: NJQY_ZHSMfOOjlxVkYs6jQ_1775568901 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6C9E01800344; Tue, 7 Apr 2026 13:35:01 +0000 (UTC) Received: from aldebaran.char-dominant.ts.net (unknown [10.44.34.48]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 25D7419560A6; Tue, 7 Apr 2026 13:34:59 +0000 (UTC) To: dev@openvswitch.org Date: Tue, 7 Apr 2026 15:34:26 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: XObea3coNNjVUAs8XHUe2IU9HK9HmVxVa5DD6foNjIc_1775568901 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 1/5] ofproto-dpif: Add JSON output to fdb/stats-show. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Timothy Redaelli via dev From: Timothy Redaelli Reply-To: Timothy Redaelli Cc: i.maximets@ovn.org Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" When --format json is passed to ovs-appctl, fdb/stats-show returns a JSON object keyed by bridge name. Each bridge contains two sub-objects: "entries" (with "current", "maximum", and "static" counts) and "events" (with "evicted", "expired", "learned", and "moved" counters). The implementation is split into separate text and JSON helper functions to keep the main handler simple. Example output: {"br0": {"entries": {"current": 17, "maximum": 8192, "static": 17}, "events": {"evicted": 0, "expired": 0, "learned": 17, "moved": 0}}} Reported-at: https://issues.redhat.com/browse/FDP-2444 Signed-off-by: Timothy Redaelli --- ofproto/ofproto-dpif.c | 87 ++++++++++++++++++++++++++++++++---------- tests/ofproto-dpif.at | 15 ++++++++ 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index a02afe8ef..af77577b8 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -6384,11 +6384,67 @@ ofproto_unixctl_fdb_stats_clear(struct unixctl_conn *conn, int argc, unixctl_command_reply(conn, "statistics successfully cleared"); } +static struct json * +ofproto_unixctl_fdb_stats_show_json(const struct ofproto_dpif *ofproto, + const char *bridge_name) +{ + struct json *entries = json_object_create(); + struct json *events = json_object_create(); + struct json *bridge = json_object_create(); + struct json *json = json_object_create(); + + json_object_put(entries, "current", + json_integer_create(hmap_count(&ofproto->ml->table))); + json_object_put(entries, "maximum", + json_integer_create(ofproto->ml->max_entries)); + json_object_put(entries, "static", + json_integer_create(ofproto->ml->static_entries)); + json_object_put(bridge, "entries", entries); + + json_object_put(events, "evicted", + json_integer_create(ofproto->ml->total_evicted)); + json_object_put(events, "expired", + json_integer_create(ofproto->ml->total_expired)); + json_object_put(events, "learned", + json_integer_create(ofproto->ml->total_learned)); + json_object_put(events, "moved", + json_integer_create(ofproto->ml->total_moved)); + json_object_put(bridge, "events", events); + + json_object_put(json, bridge_name, bridge); + return json; +} + +static void +ofproto_unixctl_fdb_stats_show_text(const struct ofproto_dpif *ofproto, + const char *bridge_name, struct ds *ds) +{ + ds_put_format(ds, "Statistics for bridge \"%s\":\n", bridge_name); + ds_put_format(ds, " Current/maximum MAC entries in the table: %" + PRIuSIZE"/%"PRIuSIZE"\n", + hmap_count(&ofproto->ml->table), + ofproto->ml->max_entries); + ds_put_format(ds, + " Current static MAC entries in the table : %" + PRIuSIZE"\n", ofproto->ml->static_entries); + ds_put_format(ds, + " Total number of learned MAC entries : %" + PRIu64"\n", ofproto->ml->total_learned); + ds_put_format(ds, + " Total number of expired MAC entries : %" + PRIu64"\n", ofproto->ml->total_expired); + ds_put_format(ds, + " Total number of evicted MAC entries : %" + PRIu64"\n", ofproto->ml->total_evicted); + ds_put_format(ds, + " Total number of port moved MAC entries : %" + PRIu64"\n", ofproto->ml->total_moved); +} + static void ofproto_unixctl_fdb_stats_show(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[], void *aux OVS_UNUSED) { - struct ds ds = DS_EMPTY_INITIALIZER; const struct ofproto_dpif *ofproto; ofproto = ofproto_dpif_lookup_by_name(argv[1]); if (!ofproto) { @@ -6396,28 +6452,17 @@ ofproto_unixctl_fdb_stats_show(struct unixctl_conn *conn, int argc OVS_UNUSED, return; } - ds_put_format(&ds, "Statistics for bridge \"%s\":\n", argv[1]); ovs_rwlock_rdlock(&ofproto->ml->rwlock); + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) { + struct json *json = ofproto_unixctl_fdb_stats_show_json(ofproto, + argv[1]); + ovs_rwlock_unlock(&ofproto->ml->rwlock); + unixctl_command_reply_json(conn, json); + return; + } - ds_put_format(&ds, " Current/maximum MAC entries in the table: %" - PRIuSIZE"/%"PRIuSIZE"\n", - hmap_count(&ofproto->ml->table), ofproto->ml->max_entries); - ds_put_format(&ds, - " Current static MAC entries in the table : %"PRIuSIZE"\n", - ofproto->ml->static_entries); - ds_put_format(&ds, - " Total number of learned MAC entries : %"PRIu64"\n", - ofproto->ml->total_learned); - ds_put_format(&ds, - " Total number of expired MAC entries : %"PRIu64"\n", - ofproto->ml->total_expired); - ds_put_format(&ds, - " Total number of evicted MAC entries : %"PRIu64"\n", - ofproto->ml->total_evicted); - ds_put_format(&ds, - " Total number of port moved MAC entries : %"PRIu64"\n", - ofproto->ml->total_moved); - + struct ds ds = DS_EMPTY_INITIALIZER; + ofproto_unixctl_fdb_stats_show_text(ofproto, argv[1], &ds); ovs_rwlock_unlock(&ofproto->ml->rwlock); unixctl_command_reply(conn, ds_cstr(&ds)); ds_destroy(&ds); diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 39e43d376..70e28b7d9 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -8248,6 +8248,21 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/stats-show br0 | grep static], [0], [dnl Current static MAC entries in the table : 17 ]) +dnl Check JSON output. +AT_CHECK([ovs-appctl --format json --pretty fdb/stats-show br0], [0], [dnl +{ + "br0": { + "entries": { + "current": 17, + "maximum": 8192, + "static": 17}, + "events": { + "evicted": 0, + "expired": 0, + "learned": 18, + "moved": 0}}} +]) + OVS_VSWITCHD_STOP AT_CLEANUP From patchwork Tue Apr 7 13:34:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timothy Redaelli X-Patchwork-Id: 2220548 X-Patchwork-Delegate: echaudro@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=TQM98vWY; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4fqnJK5cCmz1xy1 for ; Tue, 07 Apr 2026 23:35:29 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 6B96260071; Tue, 7 Apr 2026 13:35:28 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id PcOkceS9-Ska; Tue, 7 Apr 2026 13:35:26 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 003BA60D59 Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=TQM98vWY Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp3.osuosl.org (Postfix) with ESMTPS id 003BA60D59; Tue, 7 Apr 2026 13:35:25 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id D2E2CC04FB; Tue, 7 Apr 2026 13:35:25 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id F3815C04FA for ; Tue, 7 Apr 2026 13:35:24 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id B395380B3D for ; Tue, 7 Apr 2026 13:35:23 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 1Z2JCQNqALNL for ; Tue, 7 Apr 2026 13:35:22 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=tredaelli@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp1.osuosl.org A2E1C80B23 Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org A2E1C80B23 Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=TQM98vWY Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp1.osuosl.org (Postfix) with ESMTPS id A2E1C80B23 for ; Tue, 7 Apr 2026 13:35:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775568921; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=d3lLElKh21tJtBemj++tzI6TIT8unNWUAaquhHg3pvo=; b=TQM98vWYwYKiaFPkUmqvThV8At6jDYfh1hQg0DVFqNOuMQyi70P+iXYpy4ni8dhjC9BIno RvUG1Jdwk0E+p7Y6kJsQL5lCXMcvNBbuBciWDmdqxiAFUNTjVXt+iIGKX8Wzth+L/dRiur aOmvHaM4hvtZyGVYR1OFDJ+D54hzWO4= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-180-bY-J7ss7PZuox2v5ZhDq_A-1; Tue, 07 Apr 2026 09:35:18 -0400 X-MC-Unique: bY-J7ss7PZuox2v5ZhDq_A-1 X-Mimecast-MFC-AGG-ID: bY-J7ss7PZuox2v5ZhDq_A_1775568917 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 793CE1956067; Tue, 7 Apr 2026 13:35:17 +0000 (UTC) Received: from aldebaran.char-dominant.ts.net (unknown [10.44.34.48]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3DF7919560A6; Tue, 7 Apr 2026 13:35:15 +0000 (UTC) To: dev@openvswitch.org Date: Tue, 7 Apr 2026 15:34:28 +0200 Message-ID: <0ccb442b44a47164601c59739dc8cc9c1e01a39c.1775568640.git.tredaelli@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: z3Psl5X5MF2_YlfVLokwUSdz26m_RWKqpA09cQ3VVxk_1775568917 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 2/5] ofproto-dpif-upcall: Add JSON output to upcall/show. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Timothy Redaelli via dev From: Timothy Redaelli Reply-To: Timothy Redaelli Cc: i.maximets@ovn.org Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" When --format json is passed to ovs-appctl, upcall/show returns a JSON object keyed by datapath name. Each entry contains a "flows" sub-object (with "average", "current", "limit", "maximum", and "offloaded" counts), "dump-duration-ms", "ufid-enabled", and a "revalidators" object indexed by revalidator ID. The implementation is split into separate text and JSON helper functions. Example output: {"ovs-system": {"dump-duration-ms": 1, "flows": {"average": 0, "current": 0, "limit": 10000, "maximum": 0, "offloaded": 0}, "revalidators": {"4": {"keys": 0}}, "ufid-enabled": true}} Signed-off-by: Timothy Redaelli --- ofproto/ofproto-dpif-upcall.c | 94 +++++++++++++++++++++++++++++------ tests/ofproto-dpif.at | 29 +++++++++++ 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c index 8e4897202..1616b66cc 100644 --- a/ofproto/ofproto-dpif-upcall.c +++ b/ofproto/ofproto-dpif-upcall.c @@ -26,6 +26,7 @@ #include "dpif.h" #include "dpif-offload.h" #include "openvswitch/dynamic-string.h" +#include "openvswitch/json.h" #include "fail-open.h" #include "guarded-list.h" #include "latch.h" @@ -3160,13 +3161,65 @@ dp_purge_cb(void *aux, unsigned pmd_id) udpif_resume_revalidators(udpif); } +static struct json * +upcall_unixctl_show_json(void) +{ + struct json *json_udpifs = json_object_create(); + struct udpif *udpif; + + LIST_FOR_EACH (udpif, list_node, &all_udpifs) { + struct json *json_udpif = json_object_create(); + struct json *json_revalidators = json_object_create(); + struct json *json_flows = json_object_create(); + uint64_t n_offloaded_flows; + unsigned int flow_limit; + bool ufid_enabled; + size_t i; + + atomic_read_relaxed(&udpif->flow_limit, &flow_limit); + ufid_enabled = udpif_use_ufid(udpif); + n_offloaded_flows = dpif_offload_flow_count(udpif->dpif); + + json_object_put(json_flows, "average", + json_integer_create(udpif->avg_n_flows)); + json_object_put(json_flows, "current", + json_integer_create(udpif_get_n_flows(udpif))); + json_object_put(json_flows, "limit", + json_integer_create(flow_limit)); + json_object_put(json_flows, "maximum", + json_integer_create(udpif->max_n_flows)); + json_object_put(json_flows, "offloaded", + json_integer_create(n_offloaded_flows)); + json_object_put(json_udpif, "dump-duration-ms", + json_integer_create(udpif->dump_duration)); + json_object_put(json_udpif, "flows", json_flows); + + for (i = 0; i < udpif->n_revalidators; i++) { + struct revalidator *revalidator = &udpif->revalidators[i]; + struct json *json_rv = json_object_create(); + char *key = xasprintf("%u", revalidator->id); + int j, elements = 0; + + for (j = i; j < N_UMAPS; j += udpif->n_revalidators) { + elements += cmap_count(&udpif->ukeys[j].cmap); + } + json_object_put(json_rv, "keys", json_integer_create(elements)); + json_object_put_nocopy(json_revalidators, key, json_rv); + } + json_object_put(json_udpif, "revalidators", json_revalidators); + json_object_put(json_udpif, "ufid-enabled", + json_boolean_create(ufid_enabled)); + + json_object_put(json_udpifs, dpif_name(udpif->dpif), json_udpif); + } + return json_udpifs; +} + static void -upcall_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED, - const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) +upcall_unixctl_show_text(struct ds *ds) { - struct ds ds = DS_EMPTY_INITIALIZER; - uint64_t n_offloaded_flows; struct udpif *udpif; + uint64_t n_offloaded_flows; LIST_FOR_EACH (udpif, list_node, &all_udpifs) { unsigned int flow_limit; @@ -3176,23 +3229,23 @@ upcall_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED, atomic_read_relaxed(&udpif->flow_limit, &flow_limit); ufid_enabled = udpif_use_ufid(udpif); - ds_put_format(&ds, "%s:\n", dpif_name(udpif->dpif)); - ds_put_format(&ds, " flows : (current %lu)" + ds_put_format(ds, "%s:\n", dpif_name(udpif->dpif)); + ds_put_format(ds, " flows : (current %lu)" " (avg %u) (max %u) (limit %u)\n", udpif_get_n_flows(udpif), udpif->avg_n_flows, udpif->max_n_flows, flow_limit); n_offloaded_flows = dpif_offload_flow_count(udpif->dpif); if (n_offloaded_flows) { - ds_put_format(&ds, " offloaded flows : %"PRIu64"\n", + ds_put_format(ds, " offloaded flows : %"PRIu64"\n", n_offloaded_flows); } - ds_put_format(&ds, " dump duration : %lldms\n", udpif->dump_duration); - ds_put_format(&ds, " ufid enabled : "); + ds_put_format(ds, " dump duration : %lldms\n", udpif->dump_duration); + ds_put_format(ds, " ufid enabled : "); if (ufid_enabled) { - ds_put_format(&ds, "true\n"); + ds_put_format(ds, "true\n"); } else { - ds_put_format(&ds, "false\n"); + ds_put_format(ds, "false\n"); } - ds_put_char(&ds, '\n'); + ds_put_char(ds, '\n'); for (i = 0; i < udpif->n_revalidators; i++) { struct revalidator *revalidator = &udpif->revalidators[i]; @@ -3201,12 +3254,23 @@ upcall_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED, for (j = i; j < N_UMAPS; j += udpif->n_revalidators) { elements += cmap_count(&udpif->ukeys[j].cmap); } - ds_put_format(&ds, " %u: (keys %d)\n", revalidator->id, elements); + ds_put_format(ds, " %u: (keys %d)\n", revalidator->id, elements); } } +} - unixctl_command_reply(conn, ds_cstr(&ds)); - ds_destroy(&ds); +static void +upcall_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) +{ + if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) { + unixctl_command_reply_json(conn, upcall_unixctl_show_json()); + } else { + struct ds ds = DS_EMPTY_INITIALIZER; + upcall_unixctl_show_text(&ds); + unixctl_command_reply(conn, ds_cstr(&ds)); + ds_destroy(&ds); + } } /* Disable using the megaflows. diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 70e28b7d9..a03824fcf 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -13623,6 +13623,35 @@ dnl Make sure the ukey exists. AT_CHECK([ovs-appctl upcall/show | grep '(keys' | awk '{print $3}' | \ grep -q '1)'], [0]) +dnl Check upcall/show JSON output has the expected structure. +dnl The number of revalidators depends on CPU count, so set it to 1 +dnl for deterministic output. +AT_CHECK([ovs-vsctl set open_vswitch . other_config:n-revalidator-threads=1]) +OVS_WAIT_UNTIL([ovs-appctl upcall/show | grep -c '(keys' | grep -q '^1$']) +AT_CHECK([ovs-appctl --format json --pretty upcall/show | dnl + sed 's/"dummy@[[^"]]*"/""/g' | dnl + sed 's/"dump-duration-ms": [[0-9]][[0-9]]*/"dump-duration-ms": /g' | dnl + sed 's/"average": [[0-9]][[0-9]]*/"average": /g' | dnl + sed 's/"current": [[0-9]][[0-9]]*/"current": /g' | dnl + sed 's/"limit": [[0-9]][[0-9]]*/"limit": /g' | dnl + sed 's/"maximum": [[0-9]][[0-9]]*/"maximum": /g' | dnl + sed 's/"keys": [[0-9]][[0-9]]*/"keys": /g' | dnl + sed 's/"[[0-9]][[0-9]]*": {/"": {/g'], [0], [dnl +{ + "": { + "dump-duration-ms": , + "flows": { + "average": , + "current": , + "limit": , + "maximum": , + "offloaded": 0}, + "revalidators": { + "": { + "keys": }}, + "ufid-enabled": true}} +]) + dnl Delete all datapath flows, and make sure they are gone. AT_CHECK([ovs-appctl dpctl/del-flows]) AT_CHECK([ovs-appctl dpctl/dump-flows --names ], [0], []) From patchwork Tue Apr 7 13:34:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timothy Redaelli X-Patchwork-Id: 2220549 X-Patchwork-Delegate: echaudro@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=BOMxpZ16; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4fqnJq3T1Gz1xy1 for ; Tue, 07 Apr 2026 23:35:53 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id DB8A440658; Tue, 7 Apr 2026 13:35:51 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id zA4NcnBLFJSW; Tue, 7 Apr 2026 13:35:51 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org EEDF540624 Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=BOMxpZ16 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id EEDF540624; Tue, 7 Apr 2026 13:35:50 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id D8B5FC04FB; Tue, 7 Apr 2026 13:35:50 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 4BE48C04FB for ; Tue, 7 Apr 2026 13:35:49 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 90DF240643 for ; Tue, 7 Apr 2026 13:35:34 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id ioPbNy1vC1s3 for ; Tue, 7 Apr 2026 13:35:33 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=tredaelli@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp2.osuosl.org 87F4240654 Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org 87F4240654 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp2.osuosl.org (Postfix) with ESMTPS id 87F4240654 for ; Tue, 7 Apr 2026 13:35:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775568932; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=XweFcPn6jYf9vi1mWfpg0W4v1wiumKf5xPm0il8E0vM=; b=BOMxpZ169/JkAEOvvbqdqbgf0wPiiBYMnS+1vdvvFD2EynN/qM8lHMPaolPIYvGTEiJw45 j9b8bxvtB0vVtLsW+d1xwIqB5vb+/w/K2D3A8V2GS64US7wkOQE/NqZZ8B/VaZ47+C7v8Q SKg3xmLgoRDr9M1Uvbg8yFY0FiiQ/qY= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-369-h0TYXp8EMbiCmJY_F9cC_w-1; Tue, 07 Apr 2026 09:35:29 -0400 X-MC-Unique: h0TYXp8EMbiCmJY_F9cC_w-1 X-Mimecast-MFC-AGG-ID: h0TYXp8EMbiCmJY_F9cC_w_1775568928 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3BC4D18005B2; Tue, 7 Apr 2026 13:35:28 +0000 (UTC) Received: from aldebaran.char-dominant.ts.net (unknown [10.44.34.48]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0083A1955BCC; Tue, 7 Apr 2026 13:35:26 +0000 (UTC) To: dev@openvswitch.org Date: Tue, 7 Apr 2026 15:34:30 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: VE9Et4l0iNDTiy7TwCMEdKHGFwYkkX-2k3z8LcFMqPo_1775568928 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 3/5] dpif-netdev: Add JSON output to pmd-sleep-show. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Timothy Redaelli via dev From: Timothy Redaelli Reply-To: Timothy Redaelli Cc: i.maximets@ovn.org Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" When --format json is passed to ovs-appctl, pmd-sleep-show returns a JSON object with "default-max-sleep-us" and a "threads" object. Each thread is keyed by a stable identifier ("pmd-cNN" derived from the core ID) and contains "core", "max-sleep-us", and "numa" fields. The "pmd-cNN" key format is chosen over the full thread name ("pmd-cNN/id:XXX") because the thread ID suffix is dynamic and would make the key unstable across restarts. The implementation is split into separate text and JSON helper functions. Example output: {"default-max-sleep-us": 100, "threads": {"pmd-c03": {"core": 3, "max-sleep-us": 100, "numa": 1}}} Signed-off-by: Timothy Redaelli --- lib/dpif-netdev.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++ tests/pmd.at | 19 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 9df05c4c2..32e1dc938 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -65,6 +65,7 @@ #include "odp-execute.h" #include "odp-util.h" #include "openvswitch/dynamic-string.h" +#include "openvswitch/json.h" #include "openvswitch/list.h" #include "openvswitch/match.h" #include "openvswitch/ofp-parse.h" @@ -1353,6 +1354,41 @@ dpif_netdev_pmd_rebalance(struct unixctl_conn *conn, int argc, ds_destroy(&reply); } +static struct json * +pmd_info_sleep_show_json(struct dp_netdev *dp, + struct dp_netdev_pmd_thread **pmd_list, size_t n) +{ + struct json *json_result = json_object_create(); + struct json *json_threads = json_object_create(); + + json_object_put(json_result, "default-max-sleep-us", + json_integer_create(dp->pmd_max_sleep_default)); + + for (size_t i = 0; i < n; i++) { + struct dp_netdev_pmd_thread *pmd = pmd_list[i]; + uint64_t max_sleep; + + if (!pmd || pmd->core_id == NON_PMD_CORE_ID) { + continue; + } + + struct json *json_pmd = json_object_create(); + char *key = xasprintf("pmd-c%02u", pmd->core_id); + + atomic_read_relaxed(&pmd->max_sleep, &max_sleep); + json_object_put(json_pmd, "core", + json_integer_create(pmd->core_id)); + json_object_put(json_pmd, "max-sleep-us", + json_integer_create(max_sleep)); + json_object_put(json_pmd, "numa", + json_integer_create(pmd->numa_id)); + json_object_put_nocopy(json_threads, key, json_pmd); + } + + json_object_put(json_result, "threads", json_threads); + return json_result; +} + static void pmd_info_show_sleep(struct ds *reply, unsigned core_id, int numa_id, uint64_t pmd_max_sleep) @@ -1420,6 +1456,18 @@ 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; + } + for (size_t i = 0; i < n; i++) { struct dp_netdev_pmd_thread *pmd = pmd_list[i]; if (!pmd) { diff --git a/tests/pmd.at b/tests/pmd.at index 8254ac3b0..702c9b950 100644 --- a/tests/pmd.at +++ b/tests/pmd.at @@ -1303,6 +1303,25 @@ pmd thread numa_id 8 core_id 5: max sleep: 1 us ]) +dnl Check JSON output. +AT_CHECK([ovs-appctl --format json --pretty dpif-netdev/pmd-sleep-show], [0], [dnl +{ + "default-max-sleep-us": 1, + "threads": { + "pmd-c00": { + "core": 0, + "max-sleep-us": 1, + "numa": 0}, + "pmd-c03": { + "core": 3, + "max-sleep-us": 1, + "numa": 1}, + "pmd-c05": { + "core": 5, + "max-sleep-us": 1, + "numa": 8}}} +]) + dnl Check high value max sleep get_log_next_line_num AT_CHECK([ovs-vsctl set open_vswitch . other_config:pmd-sleep-max="10000"]) From patchwork Tue Apr 7 13:34:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timothy Redaelli X-Patchwork-Id: 2220550 X-Patchwork-Delegate: echaudro@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=HIy1vZql; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4fqnK40BCKz1xy1 for ; Tue, 07 Apr 2026 23:36:07 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id E3010811A3; Tue, 7 Apr 2026 13:36:05 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id CM2Yz9aIFLcW; Tue, 7 Apr 2026 13:36:04 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org C8C178116C Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=HIy1vZql Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id C8C178116C; Tue, 7 Apr 2026 13:36:04 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id A89D8C04FB; Tue, 7 Apr 2026 13:36:04 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) by lists.linuxfoundation.org (Postfix) with ESMTP id B528FC04FA for ; Tue, 7 Apr 2026 13:36:03 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 543EA60D6A for ; Tue, 7 Apr 2026 13:35:47 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id tqPm3a7kxLL7 for ; Tue, 7 Apr 2026 13:35:46 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=tredaelli@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org 2EAC760D68 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 2EAC760D68 Authentication-Results: smtp3.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=HIy1vZql Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id 2EAC760D68 for ; Tue, 7 Apr 2026 13:35:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775568945; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=khNl9SGoy5RVn3wjYm6IQYprF2iG5E+hLgVFqDGcNnA=; b=HIy1vZqlQFUoduxwq6e91Vn6htiVMcdoXPVYgsqVnGNRGQ9KKU+HYyEQjIK05WI4evDgLI MN59hNuwb2a+FMM3OLcoT4nRr5yQ47Yyeb79v4Dv1ZeyXfrZ5BdEHMfdAaaeOa0RW7FlaI 54QipNEaKDlQm+KtsJhOnl+tLBPCOSM= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-671-pA8nIifNNm-KqO5RN3yTHg-1; Tue, 07 Apr 2026 09:35:41 -0400 X-MC-Unique: pA8nIifNNm-KqO5RN3yTHg-1 X-Mimecast-MFC-AGG-ID: pA8nIifNNm-KqO5RN3yTHg_1775568938 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E469B195609E; Tue, 7 Apr 2026 13:35:37 +0000 (UTC) Received: from aldebaran.char-dominant.ts.net (unknown [10.44.34.48]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 7EF4719560A6; Tue, 7 Apr 2026 13:35:36 +0000 (UTC) To: dev@openvswitch.org Date: Tue, 7 Apr 2026 15:34:32 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: V69xfGejrqq0KL5bprkxuKW2yNLLc3B_fNdp_3sxxs4_1775568938 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 4/5] dpif-netdev: Add JSON output to pmd-stats-show. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Timothy Redaelli via dev From: Timothy Redaelli Reply-To: Timothy Redaelli Cc: i.maximets@ovn.org Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" 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 --- lib/dpif-netdev.c | 143 ++++++++++++++++++++++++++++++++++++++++++---- tests/pmd.at | 73 +++++++++++++++++++++++ 2 files changed, 205 insertions(+), 11 deletions(-) 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 core_id : 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"/g' | dnl + sed 's/"core": [[0-9]][[0-9]]*/"core": /g' | dnl + sed 's/"count": [[0-9]][[0-9]]*/"count": /g' | dnl + sed 's/"percentage": [[0-9.]][[0-9.]]*/"percentage": /g' | dnl + sed 's/"cycles-per-packet": [[0-9.]][[0-9.]]*/"cycles-per-packet": /g' | dnl + sed 's/"packets-per-output-batch": [[0-9.]][[0-9.]]*/"packets-per-output-batch": /g' | dnl + sed 's/"processing-cycles-per-packet": [[0-9.]][[0-9.]]*/"processing-cycles-per-packet": /g'], [0], [dnl +{ + "main": { + "averages": { + "cycles-per-packet": , + "datapath-passes-per-packet": 0.0, + "packets-per-output-batch": , + "processing-cycles-per-packet": , + "subtable-lookups-per-megaflow-hit": 0.0}, + "core": null, + "cycles": { + "idle": { + "count": , + "percentage": }, + "processing": { + "count": , + "percentage": }}, + "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": { + "averages": { + "cycles-per-packet": , + "datapath-passes-per-packet": 1.0, + "packets-per-output-batch": , + "processing-cycles-per-packet": , + "subtable-lookups-per-megaflow-hit": 0.0}, + "core": , + "cycles": { + "idle": { + "count": , + "percentage": }, + "processing": { + "count": , + "percentage": }}, + "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 From patchwork Tue Apr 7 13:34:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timothy Redaelli X-Patchwork-Id: 2220551 X-Patchwork-Delegate: echaudro@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q8GXly6G; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4fqnKC1yTLz1xy1 for ; Tue, 07 Apr 2026 23:36:15 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id E225840681; Tue, 7 Apr 2026 13:36:13 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id I8yLjAYuJwW0; Tue, 7 Apr 2026 13:36:12 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org AE7954065E Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q8GXly6G Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id AE7954065E; Tue, 7 Apr 2026 13:36:12 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 8D2B1C04FB; Tue, 7 Apr 2026 13:36:12 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 1E28BC04FB for ; Tue, 7 Apr 2026 13:36:11 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id BAC6E81191 for ; Tue, 7 Apr 2026 13:35:56 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id h6qvGZRd2whO for ; Tue, 7 Apr 2026 13:35:55 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=tredaelli@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp1.osuosl.org 94BF08116C Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 94BF08116C Authentication-Results: smtp1.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q8GXly6G Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp1.osuosl.org (Postfix) with ESMTPS id 94BF08116C for ; Tue, 7 Apr 2026 13:35:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775568954; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=U6Bsce6vb3np8diFg6HayUx1hHNt6FJwaNXDRvELEGs=; b=Q8GXly6G5lq+lFNGP2piAd+c2tNQ7GL2Hf+KmG7vPwsv367C4kz8nJdMpL8O1LnL9FZjVl pyrewNDzbSJqgr9r5dw7tXZekp2i4H2YZDQrHFRbmpDybueNTC0cD9OTQpnnS/1CJYC3NO YBi6MqqeBs6MjTrKUS/eHUdm6r9q8Fo= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-558-oheYl3dUPMyzhO8DNZScHw-1; Tue, 07 Apr 2026 09:35:51 -0400 X-MC-Unique: oheYl3dUPMyzhO8DNZScHw-1 X-Mimecast-MFC-AGG-ID: oheYl3dUPMyzhO8DNZScHw_1775568950 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 43A411956065; Tue, 7 Apr 2026 13:35:50 +0000 (UTC) Received: from aldebaran.char-dominant.ts.net (unknown [10.44.34.48]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E48071955BCC; Tue, 7 Apr 2026 13:35:48 +0000 (UTC) To: dev@openvswitch.org Date: Tue, 7 Apr 2026 15:34:34 +0200 Message-ID: <1fdde3bfd9bac1f24ec54d9feae9c704ee92df07.1775568640.git.tredaelli@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 8p4w3DwfuTqNF_zOeW7X15N3xY6yVIO_ACKBB8OUhF4_1775568950 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v2 5/5] dpctl: Add JSON output to dpctl/show. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Timothy Redaelli via dev From: Timothy Redaelli Reply-To: Timothy Redaelli Cc: i.maximets@ovn.org Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" When --format json is passed to ovs-appctl, dpctl/show returns a JSON object keyed by datapath name. Each entry contains "flows", "lookups" (hit/lost/missed), and "ports" (keyed by port name, each with "port-number" and "type"). Optional sections "masks" and "cache" are present as null when the datapath does not support them. Port "config" and "statistics" sub-objects are included when applicable. The output_format field is added to struct dpctl_params so the handler can select the appropriate callback (show_dpif or show_dpif_json). The JSON accumulator is stored in dpctl_params.json, and the reply is sent by dpctl_unixctl_handler based on whether json is set. On error, the JSON object is discarded and a text error reply is sent. Example output: {"ovs-system": {"flows": 0, "lookups": {"hit": 0, "lost": 0, "missed": 0}, "masks": null, "cache": null, "ports": {"br0": {"port-number": 0, "type": "internal"}}}} Signed-off-by: Timothy Redaelli --- NEWS | 4 + lib/dpctl.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++++- lib/dpctl.h | 11 +++ tests/dpctl.at | 17 +++++ 4 files changed, 232 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 1a3044cbf..da49c67b3 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,9 @@ Post-v3.7.0 -------------------- + - ovs-appctl: + * Added JSON output support (--format json) for fdb/stats-show, + upcall/show, dpif-netdev/pmd-sleep-show, dpif-netdev/pmd-stats-show, + and dpctl/show commands. - Userspace datapath: * ARP/ND lookups for native tunnel are now rate limited. The holdout timer can be configured with 'tnl/neigh/retrans_time'. diff --git a/lib/dpctl.c b/lib/dpctl.c index 752168b5a..c7d6ca9e3 100644 --- a/lib/dpctl.c +++ b/lib/dpctl.c @@ -49,6 +49,7 @@ #include "smap.h" #include "sset.h" #include "timeval.h" +#include "openvswitch/json.h" #include "unixctl.h" #include "util.h" #include "openvswitch/ofp-flow.h" @@ -635,6 +636,185 @@ show_dpif_cache(struct dpif *dpif, struct dpctl_params *dpctl_p) show_dpif_cache__(dpif, dpctl_p); } +static void +show_dpif_json(struct dpif *dpif, struct dpctl_params *dpctl_p) +{ + struct json *json_dps = dpctl_p->json; + struct json *json_dp = json_object_create(); + struct dpif_dp_stats stats; + + if (!dpif_get_dp_stats(dpif, &stats)) { + uint64_t n_pkts = stats.n_hit + stats.n_missed; + struct json *json_lookups = json_object_create(); + + json_object_put(json_lookups, "hit", + json_integer_create(stats.n_hit)); + json_object_put(json_lookups, "lost", + json_integer_create(stats.n_lost)); + json_object_put(json_lookups, "missed", + json_integer_create(stats.n_missed)); + json_object_put(json_dp, "flows", json_integer_create(stats.n_flows)); + json_object_put(json_dp, "lookups", json_lookups); + + if (stats.n_masks != UINT32_MAX) { + double avg = n_pkts ? (double) stats.n_mask_hit / n_pkts : 0.0; + struct json *json_masks = json_object_create(); + + json_object_put(json_masks, "hit", + json_integer_create(stats.n_mask_hit)); + json_object_put(json_masks, "hit-per-packet", + json_real_create(avg)); + json_object_put(json_masks, "total", + json_integer_create(stats.n_masks)); + json_object_put(json_dp, "masks", json_masks); + } else { + json_object_put(json_dp, "masks", json_null_create()); + } + + if (stats.n_cache_hit != UINT64_MAX) { + double avg_hits = n_pkts + ? (double) stats.n_cache_hit / n_pkts * 100 : 0.0; + struct json *json_cache = json_object_create(); + + json_object_put(json_cache, "hit", + json_integer_create(stats.n_cache_hit)); + json_object_put(json_cache, "hit-rate-percentage", + json_real_create(avg_hits)); + json_object_put(json_dp, "cache", json_cache); + } else { + json_object_put(json_dp, "cache", json_null_create()); + } + } else { + json_object_put(json_dp, "cache", json_null_create()); + json_object_put(json_dp, "flows", json_null_create()); + json_object_put(json_dp, "lookups", json_null_create()); + json_object_put(json_dp, "masks", json_null_create()); + } + + uint32_t nr_caches; + if (!dpif_cache_get_supported_levels(dpif, &nr_caches) && nr_caches > 0) { + struct json *json_caches = json_object_create(); + + for (int i = 0; i < nr_caches; i++) { + const char *name; + uint32_t size; + + if (dpif_cache_get_name(dpif, i, &name) || + dpif_cache_get_size(dpif, i, &size)) { + continue; + } + struct json *json_c = json_object_create(); + + json_object_put(json_c, "size", json_integer_create(size)); + json_object_put(json_caches, name, json_c); + } + json_object_put(json_dp, "caches", json_caches); + } + + struct json *json_ports = json_object_create(); + struct dpif_port_dump dump; + struct dpif_port dpif_port; + odp_port_t *port_nos = NULL; + size_t allocated_port_nos = 0, n_port_nos = 0; + + DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) { + if (n_port_nos >= allocated_port_nos) { + port_nos = x2nrealloc(port_nos, &allocated_port_nos, + sizeof *port_nos); + } + port_nos[n_port_nos++] = dpif_port.port_no; + } + + if (port_nos) { + qsort(port_nos, n_port_nos, sizeof *port_nos, compare_port_nos); + } + + for (int i = 0; i < n_port_nos; i++) { + struct netdev *netdev; + + if (dpif_port_query_by_number(dpif, port_nos[i], &dpif_port, true)) { + continue; + } + + struct json *json_port = json_object_create(); + + json_object_put(json_port, "port-number", + json_integer_create(odp_to_u32(dpif_port.port_no))); + json_object_put_string(json_port, "type", dpif_port.type); + + if (strcmp(dpif_port.type, "system")) { + int error = netdev_open(dpif_port.name, dpif_port.type, &netdev); + + if (!error) { + struct smap config; + + smap_init(&config); + if (!netdev_get_config(netdev, &config) && + smap_count(&config) > 0) { + json_object_put(json_port, "config", + smap_to_json(&config)); + } + smap_destroy(&config); + netdev_close(netdev); + } + } + + if (dpctl_p->print_statistics) { + struct netdev_stats s; + int error = netdev_open(dpif_port.name, dpif_port.type, &netdev); + + if (!error) { + error = netdev_get_stats(netdev, &s); + netdev_close(netdev); + if (!error) { + struct json *json_stats = json_object_create(); + + json_object_put(json_stats, "collisions", + json_integer_create(s.collisions)); + json_object_put(json_stats, "rx-bytes", + json_integer_create(s.rx_bytes)); + json_object_put(json_stats, "rx-dropped", + json_integer_create(s.rx_dropped)); + json_object_put(json_stats, "rx-errors", + json_integer_create(s.rx_errors)); + json_object_put(json_stats, "rx-frame-errors", + json_integer_create(s.rx_frame_errors)); + json_object_put(json_stats, "rx-over-errors", + json_integer_create(s.rx_over_errors)); + json_object_put(json_stats, "rx-packets", + json_integer_create(s.rx_packets)); + json_object_put(json_stats, "tx-aborted-errors", + json_integer_create(s.tx_aborted_errors)); + json_object_put(json_stats, "tx-bytes", + json_integer_create(s.tx_bytes)); + json_object_put(json_stats, "tx-carrier-errors", + json_integer_create(s.tx_carrier_errors)); + json_object_put(json_stats, "tx-dropped", + json_integer_create(s.tx_dropped)); + json_object_put(json_stats, "tx-errors", + json_integer_create(s.tx_errors)); + json_object_put(json_stats, "tx-packets", + json_integer_create(s.tx_packets)); + json_object_put(json_stats, "upcall-errors", + json_integer_create(s.upcall_errors)); + json_object_put(json_stats, "upcall-packets", + json_integer_create(s.upcall_packets)); + json_object_put(json_port, "statistics", json_stats); + } + } + } else { + json_object_put(json_port, "statistics", json_null_create()); + } + + json_object_put(json_ports, dpif_port.name, json_port); + dpif_port_destroy(&dpif_port); + } + + free(port_nos); + json_object_put(json_dp, "ports", json_ports); + json_object_put(json_dps, dpif_name(dpif), json_dp); +} + static void show_dpif(struct dpif *dpif, struct dpctl_params *dpctl_p) { @@ -833,6 +1013,15 @@ static int dpctl_show(int argc, const char *argv[], struct dpctl_params *dpctl_p) { int error, lasterror = 0; + dps_for_each_cb cb; + + if (dpctl_p->output_format == UNIXCTL_OUTPUT_FMT_JSON) { + dpctl_p->json = json_object_create(); + cb = show_dpif_json; + } else { + cb = show_dpif; + } + if (argc > 1) { int i; for (i = 1; i < argc; i++) { @@ -841,7 +1030,7 @@ dpctl_show(int argc, const char *argv[], struct dpctl_params *dpctl_p) error = parsed_dpif_open(name, false, &dpif); if (!error) { - show_dpif(dpif, dpctl_p); + cb(dpif, dpctl_p); dpif_close(dpif); } else { dpctl_error(dpctl_p, error, "opening datapath %s failed", @@ -850,7 +1039,7 @@ dpctl_show(int argc, const char *argv[], struct dpctl_params *dpctl_p) } } } else { - lasterror = dps_for_each(dpctl_p, show_dpif); + lasterror = dps_for_each(dpctl_p, cb); } return lasterror; @@ -3153,6 +3342,7 @@ dpctl_unixctl_handler(struct unixctl_conn *conn, int argc, const char *argv[], .is_appctl = true, .output = dpctl_unixctl_print, .aux = &ds, + .output_format = unixctl_command_get_output_format(conn), }; /* Parse options (like getopt). Unfortunately it does @@ -3220,7 +3410,14 @@ dpctl_unixctl_handler(struct unixctl_conn *conn, int argc, const char *argv[], error = handler(argc, argv, &dpctl_p) != 0; } - if (error) { + if (dpctl_p.json) { + if (error) { + json_destroy(dpctl_p.json); + unixctl_command_reply_error(conn, ds_cstr(&ds)); + } else { + unixctl_command_reply_json(conn, dpctl_p.json); + } + } else if (error) { unixctl_command_reply_error(conn, ds_cstr(&ds)); } else { unixctl_command_reply(conn, ds_cstr(&ds)); diff --git a/lib/dpctl.h b/lib/dpctl.h index 9d0052152..fe475471e 100644 --- a/lib/dpctl.h +++ b/lib/dpctl.h @@ -19,6 +19,9 @@ #include #include "compiler.h" +#include "unixctl.h" + +struct json; struct dpctl_params { /* True if it is called by ovs-appctl command. */ @@ -51,6 +54,14 @@ struct dpctl_params { /* 'usage' (if != NULL) gets called for the "help" command. */ void (*usage)(void *aux); + + /* Output format requested by the caller. */ + enum unixctl_output_fmt output_format; + + /* JSON object for accumulating structured output. Command handlers + * set this to a non-NULL value when producing JSON output; the caller + * uses it to decide whether to send a JSON or text reply. */ + struct json *json; }; int dpctl_run_command(int argc, const char *argv[], diff --git a/tests/dpctl.at b/tests/dpctl.at index a87f67f98..5e1d20e00 100644 --- a/tests/dpctl.at +++ b/tests/dpctl.at @@ -25,6 +25,23 @@ dummy@br0: flows: 0 port 0: br0 (dummy-internal) ]) +dnl Check dpctl/show JSON output. +AT_CHECK([ovs-appctl --format json --pretty dpctl/show dummy@br0], [0], [dnl +{ + "dummy@br0": { + "cache": null, + "flows": 0, + "lookups": { + "hit": 0, + "lost": 0, + "missed": 0}, + "masks": null, + "ports": { + "br0": { + "port-number": 0, + "statistics": null, + "type": "dummy-internal"}}}} +]) AT_CHECK([ovs-appctl dpctl/add-if dummy@br0 vif1.0,type=dummy,port_no=5]) AT_CHECK([ovs-appctl dpctl/show dummy@br0], [0], [dnl dummy@br0: