From patchwork Tue Apr 20 00:31:07 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakub Kicinski X-Patchwork-Id: 425076 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8899FC433ED for ; Tue, 20 Apr 2021 00:31:19 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5A7E2613AC for ; Tue, 20 Apr 2021 00:31:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232468AbhDTAbs (ORCPT ); Mon, 19 Apr 2021 20:31:48 -0400 Received: from mail.kernel.org ([198.145.29.99]:45356 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230116AbhDTAbr (ORCPT ); Mon, 19 Apr 2021 20:31:47 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 625EF613AE; Tue, 20 Apr 2021 00:31:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1618878676; bh=vEgjPMtOy94tyc/fzcF7/13s6+VdePTIJsmZnBRhse4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bQynq+JIFumQesqnPWI5UabKGHcVTuL0zq9kEUeEdoNr+r6GP+6h/cvvh9KJ2RDI+ hYa6VHjGkCEGqXILyKQXdhLHJQdyuHDMeA+Nlto1EGKJkWb4WVePw/hFTddI7MmS7I lw2c4T0BE+1b4Ib974Cy5VtJ5G1TV6je7tIFrTr3y/5fIUby+sNmRM9jBJbEOT2w2h qXROBC4ACmbDRXiImq7QS+qXI1yVGQ6qCCw2JQqI7wBsXXDgM0YGy0zAm1gAslY7tt GizkpHk8W/mGhmJUCeGzdllp1OYruol+XGxeTzD85YqBcu0RxhWoGcjQXV+xQs2Us7 ISNAycventdLg== From: Jakub Kicinski To: mkubecek@suse.cz Cc: netdev@vger.kernel.org, idosch@idosch.org, Jakub Kicinski Subject: [PATCH ethtool-next 2/7] json: improve array print API Date: Mon, 19 Apr 2021 17:31:07 -0700 Message-Id: <20210420003112.3175038-3-kuba@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210420003112.3175038-1-kuba@kernel.org> References: <20210420003112.3175038-1-kuba@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org In ethtool when we print an array we usually have a label (non-JSON) and a key (JSON), because arrays are most often printed entry-per-line (unlike iproute2 where the output has multiple params on a line to cater well to multi-interface dumps). Use this knowledge in the json array API to make it simpler to use. At the same time (similarly to open_json_object()) do not require specifying output type. Users can pass an empty string if they want nothing printed for non-JSON output. Signed-off-by: Jakub Kicinski --- json_print.c | 20 ++++++++++---------- json_print.h | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/json_print.c b/json_print.c index 56d5b4337e49..4f62767bdbc9 100644 --- a/json_print.c +++ b/json_print.c @@ -73,15 +73,15 @@ void close_json_object(void) /* * Start json array or string array using * the provided string as json key (if not null) - * or as array delimiter in non-json context. + * or array delimiter in non-json context. */ -void open_json_array(enum output_type type, const char *str) +void open_json_array(const char *key, const char *str) { - if (_IS_JSON_CONTEXT(type)) { - if (str) - jsonw_name(_jw, str); + if (is_json_context()) { + if (key) + jsonw_name(_jw, key); jsonw_start_array(_jw); - } else if (_IS_FP_CONTEXT(type)) { + } else { printf("%s", str); } } @@ -89,12 +89,12 @@ void open_json_array(enum output_type type, const char *str) /* * End json array or string array */ -void close_json_array(enum output_type type, const char *str) +void close_json_array(const char *delim) { - if (_IS_JSON_CONTEXT(type)) + if (is_json_context()) jsonw_end_array(_jw); - else if (_IS_FP_CONTEXT(type)) - printf("%s", str); + else + printf("%s", delim); } /* diff --git a/json_print.h b/json_print.h index cc0c2ea19b59..df15314bafe2 100644 --- a/json_print.h +++ b/json_print.h @@ -37,8 +37,8 @@ void fflush_fp(void); void open_json_object(const char *str); void close_json_object(void); -void open_json_array(enum output_type type, const char *delim); -void close_json_array(enum output_type type, const char *delim); +void open_json_array(const char *key, const char *str); +void close_json_array(const char *delim); void print_nl(void); From patchwork Tue Apr 20 00:31:09 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakub Kicinski X-Patchwork-Id: 425075 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0F1A4C433ED for ; Tue, 20 Apr 2021 00:31:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D956D6113C for ; Tue, 20 Apr 2021 00:31:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232930AbhDTAbv (ORCPT ); Mon, 19 Apr 2021 20:31:51 -0400 Received: from mail.kernel.org ([198.145.29.99]:45376 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230382AbhDTAbr (ORCPT ); Mon, 19 Apr 2021 20:31:47 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 39202613C1; Tue, 20 Apr 2021 00:31:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1618878677; bh=Vv4eNI5EmcVg6W1ff3uhAWU37NRElg8zmyTXumU0ONk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=e7r4E09CudrdbzQf46huPPLUr8XWfkbBwSBHBFnhuep1ZAIVRW8n7cV4sErGlA7dP t/DGKCgd7dPU3FviYIqHYJHHipmk2no0tlafPm3irptn56hg3dD8nQpFRtFGG0Bh8f 8BY8ftAsveP4opj1LeFQdRUBICdJobnraRWqf8QMeHqZhl3ZQ5LgmkTJEN1W47Mk3s m9K/hS2r3CYD2ZsHroAqvLQwqFL6OwEnUnUxth4yg4o2kLC92kOcBVwQUFEGY6Ff+/ FOYYSC+roLbDZ/zRr5gO3alAbbYLiWPwrNDEAVCX6rvmNVDEqxqJMrmwBhMkwfne8m sDgwxCflqckew== From: Jakub Kicinski To: mkubecek@suse.cz Cc: netdev@vger.kernel.org, idosch@idosch.org, Jakub Kicinski Subject: [PATCH ethtool-next 4/7] netlink: fec: support displaying statistics Date: Mon, 19 Apr 2021 17:31:09 -0700 Message-Id: <20210420003112.3175038-5-kuba@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210420003112.3175038-1-kuba@kernel.org> References: <20210420003112.3175038-1-kuba@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Similar implementation-wise to pause stats. The difference is that stats are reported as arrays (first entry being total, and remaining ones per-lane). # ethtool -I --show-fec eth0 FEC parameters for eth0: Configured FEC encodings: None Active FEC encoding: None Statistics: corrected_blocks: 256 Lane 0: 255 Lane 1: 1 uncorrectable_blocks: 145 Lane 0: 128 Lane 1: 17 # ethtool --json -I --show-fec eth0 [ { "ifname": "eth0", "config": [ "None" ], "active": [ "None" ], "statistics": { "corrected_blocks": { "total": 256, "lanes": [ 255,1 ] }, "uncorrectable_blocks": { "total": 145, "lanes": [ 128,17 ] } } } ] Signed-off-by: Jakub Kicinski --- netlink/fec.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/netlink/fec.c b/netlink/fec.c index bc9e120ce1be..f2659199c157 100644 --- a/netlink/fec.c +++ b/netlink/fec.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -40,6 +41,79 @@ fec_mode_walk(unsigned int idx, const char *name, bool val, void *data) print_string(PRINT_ANY, NULL, " %s", name); } +static int fec_show_stats(const struct nlattr *nest) +{ + const struct nlattr *tb[ETHTOOL_A_FEC_STAT_MAX + 1] = {}; + DECLARE_ATTR_TB_INFO(tb); + static const struct { + unsigned int attr; + char *name; + } stats[] = { + { ETHTOOL_A_FEC_STAT_CORRECTED, "corrected_blocks" }, + { ETHTOOL_A_FEC_STAT_UNCORR, "uncorrectable_blocks" }, + { ETHTOOL_A_FEC_STAT_CORR_BITS, "corrected_bits" }, + }; + bool header = false; + unsigned int i; + int ret; + + ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info); + if (ret < 0) + return ret; + + open_json_object("statistics"); + for (i = 0; i < ARRAY_SIZE(stats); i++) { + uint64_t *vals; + int lanes, l; + + if (!tb[stats[i].attr] || + !mnl_attr_get_payload_len(tb[stats[i].attr])) + continue; + + if (!header && !is_json_context()) { + printf("Statistics:\n"); + header = true; + } + + if (mnl_attr_get_payload_len(tb[stats[i].attr]) % 8) { + fprintf(stderr, "malformed netlink message (statistic)\n"); + goto err_close_stats; + } + + vals = mnl_attr_get_payload(tb[stats[i].attr]); + lanes = mnl_attr_get_payload_len(tb[stats[i].attr]) / 8 - 1; + + if (!is_json_context()) { + fprintf(stdout, " %s: %" PRIu64 "\n", + stats[i].name, *vals++); + } else { + open_json_object(stats[i].name); + print_u64(PRINT_JSON, "total", NULL, *vals++); + } + + if (lanes) + open_json_array("lanes", ""); + for (l = 0; l < lanes; l++) { + if (!is_json_context()) + fprintf(stdout, " Lane %d: %" PRIu64 "\n", + l, *vals++); + else + print_u64(PRINT_JSON, NULL, NULL, *vals++); + } + if (lanes) + close_json_array(""); + + close_json_object(); + } + close_json_object(); + + return 0; + +err_close_stats: + close_json_object(); + return -1; +} + int fec_reply_cb(const struct nlmsghdr *nlhdr, void *data) { const struct nlattr *tb[ETHTOOL_A_FEC_MAX + 1] = {}; @@ -106,6 +180,12 @@ int fec_reply_cb(const struct nlmsghdr *nlhdr, void *data) } close_json_array("\n"); + if (tb[ETHTOOL_A_FEC_STATS]) { + ret = fec_show_stats(tb[ETHTOOL_A_FEC_STATS]); + if (ret < 0) + goto err_close_dev; + } + close_json_object(); return MNL_CB_OK; @@ -119,6 +199,7 @@ int nl_gfec(struct cmd_context *ctx) { struct nl_context *nlctx = ctx->nlctx; struct nl_socket *nlsk = nlctx->ethnl_socket; + u32 flags; int ret; if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEC_GET, true)) @@ -129,8 +210,10 @@ int nl_gfec(struct cmd_context *ctx) return 1; } + flags = get_stats_flag(nlctx, ETHTOOL_MSG_FEC_GET, + ETHTOOL_A_FEC_HEADER); ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_FEC_GET, - ETHTOOL_A_FEC_HEADER, 0); + ETHTOOL_A_FEC_HEADER, flags); if (ret < 0) return ret; From patchwork Tue Apr 20 00:31:11 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakub Kicinski X-Patchwork-Id: 425074 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EE881C433ED for ; Tue, 20 Apr 2021 00:31:26 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B87856135F for ; Tue, 20 Apr 2021 00:31:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233146AbhDTAb4 (ORCPT ); Mon, 19 Apr 2021 20:31:56 -0400 Received: from mail.kernel.org ([198.145.29.99]:45398 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232527AbhDTAbs (ORCPT ); Mon, 19 Apr 2021 20:31:48 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 04079613C3; Tue, 20 Apr 2021 00:31:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1618878678; bh=R59v+60BDL16b2yHxTk3Vkvl6f3ZiE2VSh+fzac87yE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ObTH8r9FQj2Z7RbGqP1ippfkJlFHctXjyu3DsAz9OueOjLUdLFaN+cYXSiWCQN42r mz0hnopELJ3KIyjZJi6pCqy2/2w2besrnBAXCdX2BPg6TCDKAnYPmsyb+PTJdONrF2 TYOdXAU7KKxsdFbngavPQ8btA4xY0bTeFtZehJ/v9Wd6NDBa6zZPjro91A9tcHD3Mw P9XkRGPM83trg5RhA1F4Yf/3AA8kR3pwMTZHWd1Bvf7D1UKmz65Xh7Dgsz7xmcWz28 7eVXJ9YK5ajM1Qva4vyvYmwALN3k+c1Vve9oo9F/D8bkKpQwuHd8w8RscTJakldyAO RQQ/pXutgu7Ew== From: Jakub Kicinski To: mkubecek@suse.cz Cc: netdev@vger.kernel.org, idosch@idosch.org, Jakub Kicinski Subject: [PATCH ethtool-next 6/7] netlink: add support for standard stats Date: Mon, 19 Apr 2021 17:31:11 -0700 Message-Id: <20210420003112.3175038-7-kuba@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210420003112.3175038-1-kuba@kernel.org> References: <20210420003112.3175038-1-kuba@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Add support for standard-based stats. Unlike ethtool -S eth0 the new stats should be allow cross-vendor compatibility. The interface depends on bitsets and is rather simple. # ethtool -S eth0 --groups eth-phy eth-mac rmon Stats for eth0: eth-phy-SymbolErrorDuringCarrier: 1 eth-mac-FramesTransmittedOK: 1 eth-mac-FrameTooLongErrors: 1 rmon-etherStatsUndersizePkts: 1 rmon-etherStatsJabbers: 1 rmon-rx-etherStatsPkts64Octets: 1 rmon-rx-etherStatsPkts128to255Octets: 1 rmon-rx-etherStatsPkts1024toMaxOctets: 0 In JSON form stats are grouped and histograms are broken out: # ethtool --json -S eth0 --groups eth-phy eth-mac rmon | jq [ { "ifname": "eth0", "eth-phy": { "SymbolErrorDuringCarrier": 1 }, "eth-mac": { "FramesTransmittedOK": 1, "FrameTooLongErrors": 0 }, "rmon": { "etherStatsUndersizePkts": 1, "etherStatsJabbers": 0, "rx-pktsNtoM": [ { "low": 0, "high": 64, "val": 1 }, { "low": 128, "high": 255, "val": 1 }, { "low": 1024, "high": 0, "val": 0 } ] } } ] This allows for easy querying, e.g. to add up all packets larger than 128 (assuming buckets align): # ethtool --json -S eth0 --groups eth-phy eth-mac rmon | \ jq '.[].rmon."rx-pktsNtoM" | map(select(.low >= 128)) | map(.val) | add' 1 Signed-off-by: Jakub Kicinski Tested-by: Ido Schimmel --- Makefile.am | 1 + ethtool.8.in | 20 +++- ethtool.c | 5 +- netlink/desc-ethtool.c | 39 ++++++ netlink/extapi.h | 4 + netlink/stats.c | 264 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 netlink/stats.c diff --git a/Makefile.am b/Makefile.am index f643a24af97a..75c245653cda 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,6 +36,7 @@ ethtool_SOURCES += \ netlink/features.c netlink/privflags.c netlink/rings.c \ netlink/channels.c netlink/coalesce.c netlink/pause.c \ netlink/eee.c netlink/tsinfo.c netlink/fec.c \ + netlink/stats.c \ netlink/desc-ethtool.c netlink/desc-genlctrl.c \ netlink/desc-rtnl.c netlink/cable_test.c netlink/tunnels.c \ uapi/linux/ethtool_netlink.h \ diff --git a/ethtool.8.in b/ethtool.8.in index ba4e245cdc61..b98c42b8a4c3 100644 --- a/ethtool.8.in +++ b/ethtool.8.in @@ -240,6 +240,12 @@ ethtool \- query or control network driver and hardware settings .HP .B ethtool \-S|\-\-statistics .I devname +.RB [\fB\-\-groups +.RB [\fBeth\-phy\fP] +.RB [\fBeth\-mac\fP] +.RB [\fBeth\-ctrl\fP] +.RN [\fBrmon\fP] +.RB ] .HP .B ethtool \-\-phy\-statistics .I devname @@ -651,8 +657,18 @@ Restarts auto-negotiation on the specified Ethernet device, if auto-negotiation is enabled. .TP .B \-S \-\-statistics -Queries the specified network device for NIC- and driver-specific -statistics. +Queries the specified network device for standard (IEEE, IETF, etc.), or NIC- +and driver-specific statistics. NIC- and driver-specific statistics are +requested when no group of statistics is specified. + +NIC- and driver-specific statistics and standard statistics are independent, +devices may implement either, both or none. There is little commonality between +naming of NIC- and driver-specific statistics across vendors. +.RS 4 +.TP +.B \fB\-\-groups [\fBeth\-phy\fP] [\fBeth\-mac\fP] [\fBeth\-ctrl\fP] [\fBrmon\fP] +Request groups of standard device statistics. +.RE .TP .B \-\-phy\-statistics Queries the specified network device for PHY specific statistics. diff --git a/ethtool.c b/ethtool.c index b07fd9292d77..1b5690f424c8 100644 --- a/ethtool.c +++ b/ethtool.c @@ -5760,7 +5760,10 @@ static const struct option args[] = { { .opts = "-S|--statistics", .func = do_gnicstats, - .help = "Show adapter statistics" + .nlchk = nl_gstats_chk, + .nlfunc = nl_gstats, + .help = "Show adapter statistics", + .xhelp = " [ --groups [eth-phy] [eth-mac] [eth-ctrl] [rmon] ]\n" }, { .opts = "--phy-statistics", diff --git a/netlink/desc-ethtool.c b/netlink/desc-ethtool.c index 9da052a5b8aa..63e91ee7ffcd 100644 --- a/netlink/desc-ethtool.c +++ b/netlink/desc-ethtool.c @@ -325,6 +325,43 @@ static const struct pretty_nla_desc __fec_desc[] = { NLATTR_DESC_U32(ETHTOOL_A_FEC_ACTIVE), }; +static const struct pretty_nla_desc __stats_grp_stat_desc[] = { + NLATTR_DESC_U64(0), NLATTR_DESC_U64(1), NLATTR_DESC_U64(2), + NLATTR_DESC_U64(3), NLATTR_DESC_U64(4), NLATTR_DESC_U64(5), + NLATTR_DESC_U64(6), NLATTR_DESC_U64(7), NLATTR_DESC_U64(8), + NLATTR_DESC_U64(9), NLATTR_DESC_U64(10), NLATTR_DESC_U64(11), + NLATTR_DESC_U64(12), NLATTR_DESC_U64(13), NLATTR_DESC_U64(14), + NLATTR_DESC_U64(15), NLATTR_DESC_U64(16), NLATTR_DESC_U64(17), + NLATTR_DESC_U64(18), NLATTR_DESC_U64(19), NLATTR_DESC_U64(20), + NLATTR_DESC_U64(21), NLATTR_DESC_U64(22), NLATTR_DESC_U64(23), + NLATTR_DESC_U64(24), NLATTR_DESC_U64(25), NLATTR_DESC_U64(26), + NLATTR_DESC_U64(27), NLATTR_DESC_U64(28), NLATTR_DESC_U64(29), +}; + +static const struct pretty_nla_desc __stats_grp_hist_desc[] = { + NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_HIST_BKT_LOW), + NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_HIST_BKT_HI), + NLATTR_DESC_U64(ETHTOOL_A_STATS_GRP_HIST_VAL), +}; + +static const struct pretty_nla_desc __stats_grp_desc[] = { + NLATTR_DESC_INVALID(ETHTOOL_A_STATS_GRP_UNSPEC), + NLATTR_DESC_INVALID(ETHTOOL_A_STATS_GRP_PAD), + NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_ID), + NLATTR_DESC_U32(ETHTOOL_A_STATS_GRP_SS_ID), + NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP_STAT, stats_grp_stat), + NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP_HIST_RX, stats_grp_hist), + NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP_HIST_TX, stats_grp_hist), +}; + +static const struct pretty_nla_desc __stats_desc[] = { + NLATTR_DESC_INVALID(ETHTOOL_A_STATS_UNSPEC), + NLATTR_DESC_INVALID(ETHTOOL_A_STATS_PAD), + NLATTR_DESC_NESTED(ETHTOOL_A_STATS_HEADER, header), + NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GROUPS, bitset), + NLATTR_DESC_NESTED(ETHTOOL_A_STATS_GRP, stats_grp), +}; + const struct pretty_nlmsg_desc ethnl_umsg_desc[] = { NLMSG_DESC_INVALID(ETHTOOL_MSG_USER_NONE), NLMSG_DESC(ETHTOOL_MSG_STRSET_GET, strset), @@ -357,6 +394,7 @@ const struct pretty_nlmsg_desc ethnl_umsg_desc[] = { NLMSG_DESC(ETHTOOL_MSG_TUNNEL_INFO_GET, tunnel_info), NLMSG_DESC(ETHTOOL_MSG_FEC_GET, fec), NLMSG_DESC(ETHTOOL_MSG_FEC_SET, fec), + NLMSG_DESC(ETHTOOL_MSG_STATS_GET, stats), }; const unsigned int ethnl_umsg_n_desc = ARRAY_SIZE(ethnl_umsg_desc); @@ -394,6 +432,7 @@ const struct pretty_nlmsg_desc ethnl_kmsg_desc[] = { NLMSG_DESC(ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY, tunnel_info), NLMSG_DESC(ETHTOOL_MSG_FEC_GET_REPLY, fec), NLMSG_DESC(ETHTOOL_MSG_FEC_NTF, fec), + NLMSG_DESC(ETHTOOL_MSG_STATS_GET_REPLY, stats), }; const unsigned int ethnl_kmsg_n_desc = ARRAY_SIZE(ethnl_kmsg_desc); diff --git a/netlink/extapi.h b/netlink/extapi.h index d6036a39e920..97113eb98ca0 100644 --- a/netlink/extapi.h +++ b/netlink/extapi.h @@ -41,6 +41,8 @@ int nl_cable_test_tdr(struct cmd_context *ctx); int nl_gtunnels(struct cmd_context *ctx); int nl_gfec(struct cmd_context *ctx); int nl_sfec(struct cmd_context *ctx); +bool nl_gstats_chk(struct cmd_context *ctx); +int nl_gstats(struct cmd_context *ctx); int nl_monitor(struct cmd_context *ctx); void nl_monitor_usage(void); @@ -92,6 +94,8 @@ static inline void nl_monitor_usage(void) #define nl_gtunnels NULL #define nl_gfec NULL #define nl_sfec NULL +#define nl_gstats_chk NULL +#define nl_gstats NULL #endif /* ETHTOOL_ENABLE_NETLINK */ diff --git a/netlink/stats.c b/netlink/stats.c new file mode 100644 index 000000000000..e7e69f002cd5 --- /dev/null +++ b/netlink/stats.c @@ -0,0 +1,264 @@ +/* + * stats.c - netlink implementation of stats + * + * Implementation of "ethtool -S " and + */ + +#include +#include +#include +#include +#include + +#include "../internal.h" +#include "../common.h" +#include "netlink.h" +#include "parser.h" +#include "strset.h" + +static int parse_rmon_hist_one(const char *grp_name, const struct nlattr *hist, + const char *dir) +{ + const struct nlattr *tb[ETHTOOL_A_STATS_GRP_HIST_VAL + 1] = {}; + DECLARE_ATTR_TB_INFO(tb); + unsigned long long val; + unsigned int low, hi; + int ret; + + ret = mnl_attr_parse_nested(hist, attr_cb, &tb_info); + if (ret < 0) { + fprintf(stderr, "invalid kernel response - malformed histogram entry\n"); + return 1; + } + + if (!tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW] || + !tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI] || + !tb[ETHTOOL_A_STATS_GRP_HIST_VAL]) { + fprintf(stderr, "invalid kernel response - histogram entry missing attributes\n"); + return 1; + } + + low = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW]); + hi = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI]); + val = mnl_attr_get_u64(tb[ETHTOOL_A_STATS_GRP_HIST_VAL]); + + if (!is_json_context()) { + fprintf(stdout, "%s-%s-etherStatsPkts", dir, grp_name); + + if (low && hi) { + fprintf(stdout, "%uto%uOctets: %llu\n", low, hi, val); + } else if (hi) { + fprintf(stdout, "%uOctets: %llu\n", hi, val); + } else if (low) { + fprintf(stdout, "%utoMaxOctets: %llu\n", low, val); + } else { + fprintf(stderr, "invalid kernel response - bad histogram entry bounds\n"); + return 1; + } + } else { + open_json_object(NULL); + print_uint(PRINT_JSON, "low", NULL, low); + print_uint(PRINT_JSON, "high", NULL, hi); + print_u64(PRINT_JSON, "val", NULL, val); + close_json_object(); + } + + return 0; +} + +static int parse_rmon_hist(const struct nlattr *grp, const char *grp_name, + const char *name, const char *dir, unsigned int type) +{ + const struct nlattr *attr; + + open_json_array(name, ""); + + mnl_attr_for_each_nested(attr, grp) { + if (mnl_attr_get_type(attr) == type && + parse_rmon_hist_one(grp_name, attr, dir)) + goto err_close_rmon; + } + close_json_array(""); + + return 0; + +err_close_rmon: + close_json_array(""); + return 1; +} + +static int parse_grp(struct nl_context *nlctx, const struct nlattr *grp, + const struct stringset *std_str) +{ + const struct nlattr *tb[ETHTOOL_A_STATS_GRP_SS_ID + 1] = {}; + DECLARE_ATTR_TB_INFO(tb); + bool hist_rx = false, hist_tx = false; + const struct stringset *stat_str; + const struct nlattr *attr, *stat; + const char *std_name, *name; + unsigned int ss_id, id, s; + unsigned long long val; + int ret; + + ret = mnl_attr_parse_nested(grp, attr_cb, &tb_info); + if (ret < 0) + return 1; + + if (!tb[ETHTOOL_A_STATS_GRP_ID]) + return 1; + if (!tb[ETHTOOL_A_STATS_GRP_SS_ID]) + return 0; + + id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_ID]); + ss_id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_SS_ID]); + + stat_str = global_stringset(ss_id, nlctx->ethnl2_socket); + + std_name = get_string(std_str, id); + open_json_object(std_name); + + mnl_attr_for_each_nested(attr, grp) { + switch (mnl_attr_get_type(attr)) { + case ETHTOOL_A_STATS_GRP_STAT: + break; + case ETHTOOL_A_STATS_GRP_HIST_RX: + hist_rx = true; + continue; + case ETHTOOL_A_STATS_GRP_HIST_TX: + hist_tx = true; + continue; + default: + continue; + } + + stat = mnl_attr_get_payload(attr); + ret = mnl_attr_validate(stat, MNL_TYPE_U64); + if (ret) { + fprintf(stderr, "invalid kernel response - bad statistic entry\n"); + goto err_close_grp; + } + s = mnl_attr_get_type(stat); + name = get_string(stat_str, s); + if (!name || !name[0]) + continue; + + if (!is_json_context()) + fprintf(stdout, "%s-%s: ", std_name, name); + + val = mnl_attr_get_u64(stat); + print_u64(PRINT_ANY, name, "%llu\n", val); + } + + if (hist_rx) + parse_rmon_hist(grp, std_name, "rx-pktsNtoM", "rx", + ETHTOOL_A_STATS_GRP_HIST_RX); + if (hist_tx) + parse_rmon_hist(grp, std_name, "tx-pktsNtoM", "tx", + ETHTOOL_A_STATS_GRP_HIST_TX); + + close_json_object(); + + return 0; + +err_close_grp: + close_json_object(); + return 1; +} + +static int stats_reply_cb(const struct nlmsghdr *nlhdr, void *data) +{ + const struct nlattr *tb[ETHTOOL_A_STATS_MAX + 1] = {}; + DECLARE_ATTR_TB_INFO(tb); + struct nl_context *nlctx = data; + const struct stringset *std_str; + const struct nlattr *attr; + bool silent; + int err_ret; + int ret; + + silent = nlctx->is_dump || nlctx->is_monitor; + err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR; + ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info); + if (ret < 0) + return err_ret; + nlctx->devname = get_dev_name(tb[ETHTOOL_A_STATS_HEADER]); + if (!dev_ok(nlctx)) + return err_ret; + + ret = netlink_init_ethnl2_socket(nlctx); + if (ret < 0) + return err_ret; + std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket); + + if (silent) + print_nl(); + + open_json_object(NULL); + + print_string(PRINT_ANY, "ifname", "Standard stats for %s:\n", + nlctx->devname); + + mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) { + if (mnl_attr_get_type(attr) == ETHTOOL_A_STATS_GRP) { + ret = parse_grp(nlctx, attr, std_str); + if (ret) + goto err_close_dev; + } + } + + close_json_object(); + + return MNL_CB_OK; + +err_close_dev: + close_json_object(); + return err_ret; +} + +static const struct bitset_parser_data stats_parser_data = { + .no_mask = true, + .force_hex = false, +}; + +static const struct param_parser stats_params[] = { + { + .arg = "--groups", + .type = ETHTOOL_A_STATS_GROUPS, + .handler = nl_parse_bitset, + .handler_data = &stats_parser_data, + .min_argc = 1, + }, + {} +}; + +int nl_gstats(struct cmd_context *ctx) +{ + struct nl_context *nlctx = ctx->nlctx; + struct nl_socket *nlsk = nlctx->ethnl_socket; + int ret; + + ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_STATS_GET, + ETHTOOL_A_STATS_HEADER, 0); + if (ret < 0) + return ret; + + nlctx->cmd = "-S"; + nlctx->argp = ctx->argp; + nlctx->argc = ctx->argc; + nlctx->devname = ctx->devname; + nlsk = nlctx->ethnl_socket; + + ret = nl_parser(nlctx, stats_params, NULL, PARSER_GROUP_NONE, NULL); + if (ret < 0) + return 1; + + new_json_obj(ctx->json); + ret = nlsock_send_get_request(nlsk, stats_reply_cb); + delete_json_obj(); + return ret; +} + +bool nl_gstats_chk(struct cmd_context *ctx) +{ + return ctx->argc; +}