diff --git a/SOURCES/1001-Merge-branch-mohith-kumar-thummaluru-rocestat-pmda.patch b/SOURCES/1001-Merge-branch-mohith-kumar-thummaluru-rocestat-pmda.patch deleted file mode 100644 index 9cadcee..0000000 --- a/SOURCES/1001-Merge-branch-mohith-kumar-thummaluru-rocestat-pmda.patch +++ /dev/null @@ -1,1664 +0,0 @@ -From ed8474266464ab5ae182c347ce93e0d4efe181c9 Mon Sep 17 00:00:00 2001 -From: Nathan Scott -Date: Fri, 18 Apr 2025 11:30:35 +1000 -Subject: [PATCH 1/2] Merge branch 'mohith-kumar-thummaluru-rocestat-pmda' - -Orabug: 38109263 - -(cherry picked from commit 316878cba09758c2640ed718fb7f398b557b6eb2) -Signed-off-by: Mohith Kumar Thummaluru ---- - build/rpm/pcp.spec.in | 22 +- - build/rpm/redhat.spec | 27 +- - qa/1993 | 87 ++++ - qa/1993.out | 21 + - qa/GNUmakefile | 2 +- - qa/GNUmakefile.install | 2 +- - qa/group | 2 + - qa/rocestat/GNUmakefile | 22 + - qa/rocestat/GNUmakefile.install | 20 + - qa/rocestat/README | 29 ++ - qa/rocestat/metrics.list | 49 +++ - qa/rocestat/rocestat.json | 218 ++++++++++ - qa/rocestat/rocestat_test.python | 123 ++++++ - src/pmdas/GNUmakefile | 2 +- - src/pmdas/rocestat/GNUmakefile | 40 ++ - src/pmdas/rocestat/Install | 40 ++ - src/pmdas/rocestat/Remove | 38 ++ - src/pmdas/rocestat/domain.h | 2 + - src/pmdas/rocestat/pmdarocestat.1 | 94 +++++ - src/pmdas/rocestat/pmdarocestat.python | 536 +++++++++++++++++++++++++ - src/pmdas/rocestat/pmns | 1 + - src/pmns/stdpmid.pcp | 2 +- - 22 files changed, 1373 insertions(+), 6 deletions(-) - create mode 100755 qa/1993 - create mode 100644 qa/1993.out - create mode 100644 qa/rocestat/GNUmakefile - create mode 100644 qa/rocestat/GNUmakefile.install - create mode 100644 qa/rocestat/README - create mode 100644 qa/rocestat/metrics.list - create mode 100644 qa/rocestat/rocestat.json - create mode 100755 qa/rocestat/rocestat_test.python - create mode 100644 src/pmdas/rocestat/GNUmakefile - create mode 100644 src/pmdas/rocestat/Install - create mode 100644 src/pmdas/rocestat/Remove - create mode 100644 src/pmdas/rocestat/domain.h - create mode 100644 src/pmdas/rocestat/pmdarocestat.1 - create mode 100644 src/pmdas/rocestat/pmdarocestat.python - create mode 100644 src/pmdas/rocestat/pmns - -diff --git a/build/rpm/pcp.spec.in b/build/rpm/pcp.spec.in -index efec2fbf06..823d006863 100755 ---- a/build/rpm/pcp.spec.in -+++ b/build/rpm/pcp.spec.in -@@ -1787,6 +1787,20 @@ This package contains the PCP Performance Metrics Domain Agent (PMDA) for - collecting metrics from simple network checks. - # end pcp-pmda-netcheck - -+# -+# pcp-pmda-rocestat -+# -+%package pmda-rocestat -+License: GPL-2.0-or-later -+Summary: Performance Co-Pilot (PCP) metrics for nVidia RoCE devices -+URL: https://pcp.io -+Requires: pcp = @package_version@ pcp-libs = @package_version@ -+Requires: python3-pcp -+%description pmda-rocestat -+This package contains the PCP Performance Metrics Domain Agent (PMDA) for -+collecting statistics for nVidia RDMA over Converged Ethernet (RoCE) devices. -+# end pcp-pmda-rocestat -+ - # - # pcp-pmda-openvswitch - # -@@ -2507,6 +2521,7 @@ basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-fil - basic_manifest | keep '(etc/pcp|pmdas)/rabbitmq(/|$)' >pcp-pmda-rabbitmq-files - basic_manifest | keep '(etc/pcp|pmdas)/redis(/|$)' >pcp-pmda-redis-files - basic_manifest | keep '(etc/pcp|pmdas)/resctrl(/|$)|sys-fs-resctrl' >pcp-pmda-resctrl-files -+basic_manifest | keep '(etc/pcp|pmdas)/rocestat(/|$)' >pcp-pmda-rocestat-files - basic_manifest | keep '(etc/pcp|pmdas)/roomtemp(/|$)' >pcp-pmda-roomtemp-files - basic_manifest | keep '(etc/pcp|pmdas)/rsyslog(/|$)' >pcp-pmda-rsyslog-files - basic_manifest | keep '(etc/pcp|pmdas)/samba(/|$)' >pcp-pmda-samba-files -@@ -2544,7 +2559,7 @@ for pmda_package in \ - nutcracker nvidia \ - openmetrics openvswitch oracle \ - pdns perfevent podman postfix postgresql \ -- rabbitmq redis resctrl roomtemp rsyslog \ -+ rabbitmq redis resctrl rocestat roomtemp rsyslog \ - samba sendmail shping slurm smart snmp \ - sockets statsd summary systemd \ - unbound uwsgi \ -@@ -2960,6 +2975,9 @@ done - %preun pmda-rabbitmq - %{pmda_remove "$1" "rabbitmq"} - -+%preun pmda-rocestat -+%{pmda_remove "$1" "rocestat"} -+ - %preun pmda-uwsgi - %{pmda_remove "$1" "uwsgi"} - %endif -@@ -3284,6 +3302,8 @@ fi - - %files pmda-rabbitmq -f pcp-pmda-rabbitmq-files.rpm - -+%files pmda-rocestat -f pcp-pmda-rocestat-files.rpm -+ - %files pmda-uwsgi -f pcp-pmda-uwsgi-files.rpm - - %files export-pcp2graphite -f pcp-export-pcp2graphite-files.rpm -diff --git a/build/rpm/redhat.spec b/build/rpm/redhat.spec -index e1fa1fbcd3..a15f859c9c 100644 ---- a/build/rpm/redhat.spec -+++ b/build/rpm/redhat.spec -@@ -1942,6 +1942,25 @@ collecting metrics from simple network checks. - # end pcp-pmda-netcheck - %endif - -+# -+# pcp-pmda-rocestat -+# -+%package pmda-rocestat -+License: GPL-2.0-or-later -+Summary: Performance Co-Pilot (PCP) metrics for nVidia RoCE devices -+URL: https://pcp.io -+Requires: pcp = %{version}-%{release} pcp-libs = %{version}-%{release} -+%if !%{disable_python3} -+Requires: python3-pcp -+%else -+Requires: %{__python2}-pcp -+%endif -+%description pmda-rocestat -+This package contains the PCP Performance Metrics Domain Agent (PMDA) for -+collecting statistics for nVidia RDMA over Converged Ethernet (RoCE) devices. -+# end pcp-pmda-rocestat -+%endif -+ - %if !%{disable_mongodb} - # - # pcp-pmda-mongodb -@@ -2715,6 +2734,7 @@ basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-fil - basic_manifest | keep '(etc/pcp|pmdas)/rabbitmq(/|$)' >pcp-pmda-rabbitmq-files - basic_manifest | keep '(etc/pcp|pmdas)/redis(/|$)' >pcp-pmda-redis-files - basic_manifest | keep '(etc/pcp|pmdas)/resctrl(/|$)|sys-fs-resctrl' >pcp-pmda-resctrl-files -+basic_manifest | keep '(etc/pcp|pmdas)/rocestat(/|$)' >pcp-pmda-rocestat-files - basic_manifest | keep '(etc/pcp|pmdas)/roomtemp(/|$)' >pcp-pmda-roomtemp-files - basic_manifest | keep '(etc/pcp|pmdas)/rpm(/|$)' >pcp-pmda-rpm-files - basic_manifest | keep '(etc/pcp|pmdas)/rsyslog(/|$)' >pcp-pmda-rsyslog-files -@@ -2753,7 +2773,7 @@ for pmda_package in \ - nutcracker nvidia \ - openmetrics openvswitch oracle \ - pdns perfevent podman postfix postgresql \ -- rabbitmq redis resctrl roomtemp rpm rsyslog \ -+ rabbitmq redis resctrl rocestat roomtemp rpm rsyslog \ - samba sendmail shping slurm smart snmp \ - sockets statsd summary systemd \ - unbound uwsgi \ -@@ -3131,6 +3151,9 @@ exit 0 - %preun pmda-netcheck - %{pmda_remove "$1" "netcheck"} - -+%preun pmda-rocestat -+%{pmda_remove "$1" "rocestat"} -+ - %endif - - %preun pmda-apache -@@ -3475,6 +3498,8 @@ fi - - %files pmda-rabbitmq -f pcp-pmda-rabbitmq-files.rpm - -+%files pmda-rocestat -f pcp-pmda-rocestat-files.rpm -+ - %files pmda-uwsgi -f pcp-pmda-uwsgi-files.rpm - - %files export-pcp2graphite -f pcp-export-pcp2graphite-files.rpm -diff --git a/qa/1993 b/qa/1993 -new file mode 100755 -index 0000000000..5d0af70a1d ---- /dev/null -+++ b/qa/1993 -@@ -0,0 +1,87 @@ -+#!/bin/sh -+# PCP QA Test No. 1993 -+# Testing PCP Rocestat PMDA - install, remove and values. -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+ -+seq=`basename $0` -+echo "QA output created by $seq" -+ -+. ./common.python -+ -+pmda_path="$PCP_PMDAS_DIR/rocestat" -+pmda_script="$pmda_path/pmdarocestat.python" -+test_script="rocestat/rocestat_test.python" -+ -+[ -d $pmda_path ] || _notrun "ROCESTAT PMDA is not installed" -+ -+$python -c "from pcp import pmda" >/dev/null 2>&1 -+[ $? -eq 0 ] || _notrun "python pcp pmda module not installed" -+ -+if [ ! -d /sys/class/infiniband ]; then -+ # Check if Infiniband kernel module is loaded -+ lsmod | grep -q '^ib_core' || _notrun "IB kernel modules are not loaded" -+ _notrun "No RoCE devices detected" -+fi -+ -+status=1 # failure is the default! -+trap "_cleanup_pmda rocestat; exit \$status" 0 1 2 3 15 -+ -+pmdarocestat_filter() -+{ -+ sed \ -+ -e "s/Ran [0-9]* tests in [0-9]*\.[0-9][0-9]*s/Ran X tests in YYYs/" \ -+ -e "s/FAILED (failures=[0-9]*)/FAILED (failures=X)/" \ -+ -e "/^$/d" -+} -+ -+pmdarocestat_remove() -+{ -+ cd $pmda_path -+ echo -+ echo "=== Removing ROCESTAT agent ===" -+ $sudo ./Remove >$tmp.out 2>&1 -+} -+ -+pmdarocestat_install() -+{ -+ cd $pmda_path -+ $sudo ./Remove >/dev/null 2>&1 -+ -+ echo -+ echo "=== Installing ROCESTAT agent ===" -+ $sudo ./Install $tmp.out 2>&1 -+ cd $here -+} -+ -+run_rocestat_test() -+{ -+ echo -+ echo "=== Running ROCESTAT test script ===" -+ $sudo $python $test_script 2>&1 | LC_COLLATE=POSIX sort | pmdarocestat_filter -+} -+ -+# Real QA test starts here -+_prepare_pmda rocestat -+ -+pmdarocestat_install -+ -+run_rocestat_test -+ -+pmdarocestat_remove -+ -+# Success, all done -+status=0 -+exit -diff --git a/qa/1993.out b/qa/1993.out -new file mode 100644 -index 0000000000..70753bdb62 ---- /dev/null -+++ b/qa/1993.out -@@ -0,0 +1,21 @@ -+QA output created by 1993 -+ -+=== Installing ROCESTAT agent === -+ -+=== Running ROCESTAT test script === -+---------------------------------------------------------------------- -+Fetching Rocestat PMDA metrics -+OK -+Ran X tests in YYYs -+test_hw_link_metrics (__main__.RocestatTests) ... ok -+test_hw_mcast_metrics (__main__.RocestatTests) ... ok -+test_hw_metrics (__main__.RocestatTests) ... ok -+test_hw_rcv_metrics (__main__.RocestatTests) ... ok -+test_hw_req_metrics (__main__.RocestatTests) ... ok -+test_hw_resp_metrics (__main__.RocestatTests) ... ok -+test_hw_rnr_metrics (__main__.RocestatTests) ... ok -+test_hw_ucast_metrics (__main__.RocestatTests) ... ok -+test_hw_xmit_metrics (__main__.RocestatTests) ... ok -+test_nic_metrics (__main__.RocestatTests) ... ok -+ -+=== Removing ROCESTAT agent === -diff --git a/qa/GNUmakefile b/qa/GNUmakefile -index e8a055b6e1..a4d6e6a4ef 100644 ---- a/qa/GNUmakefile -+++ b/qa/GNUmakefile -@@ -17,7 +17,7 @@ TESTS = $(shell sed -n -e '/^[0-9][0-9]*:retired/d' -e '/^[0-9][0-9]*:reserved/d - SUBDIRS = src pmdas cisco gluster pconf sadist collectl nfsclient named \ - archives badarchives views qt linux unbound cifs gpfs lustre ganglia \ - java mmv postfix perl json slurm tmparch sheet smart admin hacluster \ -- sockets denki gfs2 farm pdudata -+ sockets denki gfs2 farm pdudata rocestat - - ifeq "$(PMDA_PERFEVENT)" "true" - SUBDIRS += perfevent -diff --git a/qa/GNUmakefile.install b/qa/GNUmakefile.install -index cf7c816562..f7b6b9d174 100644 ---- a/qa/GNUmakefile.install -+++ b/qa/GNUmakefile.install -@@ -21,7 +21,7 @@ ifeq "$(PMDA_PERFEVENT)" "true" - SUBDIRS += perfevent - endif - ifeq "$(HAVE_PYTHON)" "true" --SUBDIRS += secure mic haproxy lio openmetrics -+SUBDIRS += secure mic haproxy lio openmetrics rocestat - endif - - # Before installing the pcpqa policy module we need a PCP build with -diff --git a/qa/group b/qa/group -index 6fd43299d8..137dba39f8 100644 ---- a/qa/group -+++ b/qa/group -@@ -260,6 +260,7 @@ pmda.postgresql - pmda.proc - pmda.redis - pmda.resctrl -+pmda.rocestat - pmda.root - #pmda.rpm # note this group has been retired - pmda.rsyslog -@@ -2215,8 +2216,9 @@ pmcd.pdu - 1990 pcp buddyinfo python local - 1991 pcp netstat python local - 1992 pmda.uwsgi local -+1993 pmda.rocestat local python - 2100 pmproxy local security - 2101 pmda.sockets local security - 2104 libpcp local security - 2105 libpcp pmcd local security pmcd.pdu - 4751 libpcp threads valgrind local pcp helgrind -diff --git a/qa/rocestat/GNUmakefile b/qa/rocestat/GNUmakefile -new file mode 100644 -index 0000000000..9c20dc00bc ---- /dev/null -+++ b/qa/rocestat/GNUmakefile -@@ -0,0 +1,22 @@ -+#!gmake -+ -+TOPDIR = ../.. -+include $(TOPDIR)/src/include/builddefs -+ -+TESTDIR = $(PCP_VAR_DIR)/testsuite/rocestat -+MYFILES = rocestat.json -+PYMODULES = rocestat_test.python -+LDIRT = rocestat_test.py rocestat_test.pyc rocestat_test.pyo -+ -+default default_pcp setup: rocestat_test.py -+ -+install install_pcp: -+ $(INSTALL) -m 755 -d $(TESTDIR) -+ $(INSTALL) -m 644 -f $(MYFILES) $(TESTDIR)/$(MYFILES) -+ $(INSTALL) -m 755 -f $(PYMODULES) $(TESTDIR)/$(PYMODULES) -+ $(INSTALL) -m 644 -f GNUmakefile.install $(TESTDIR)/GNUmakefile -+ -+include $(BUILDRULES) -+ -+%.py : %.python -+ $(LN_S) $< $@ -diff --git a/qa/rocestat/GNUmakefile.install b/qa/rocestat/GNUmakefile.install -new file mode 100644 -index 0000000000..495ef4b1bc ---- /dev/null -+++ b/qa/rocestat/GNUmakefile.install -@@ -0,0 +1,20 @@ -+#!gmake -+ -+ifdef PCP_CONF -+include $(PCP_CONF) -+else -+include $(PCP_DIR)/etc/pcp.conf -+endif -+PATH = $(shell . $(PCP_DIR)/etc/pcp.env; echo $$PATH) -+include $(PCP_INC_DIR)/builddefs -+ -+TESTDIR = $(PCP_VAR_DIR)/testsuite/rocestat -+ -+default default_pcp setup: rocestat_test.py -+ -+install install_pcp: -+ -+include $(BUILDRULES) -+ -+%.py : %.python -+ $(LN_S) $< $@ -diff --git a/qa/rocestat/README b/qa/rocestat/README -new file mode 100644 -index 0000000000..5888671b90 ---- /dev/null -+++ b/qa/rocestat/README -@@ -0,0 +1,29 @@ -+Pre-Requisites -+-------------- -+In order to perform the tests, we'll make some assumptions: -+ -+- The `pcp` Python module is installed and accessible. -+- The `rocestat` PMDA has been installed successfully. -+- The system has RoCE devices available and the required kernel modules (`ib_core`) are loaded. -+- You are able to run the test as the root user (required for PMDA installation and removal). -+ -+What the Test Does -+------------------ -+The test validates the functionality of the `rocestat` PMDA by performing the following steps: -+ -+1. Installs the `rocestat` PMDA. -+2. Fetches metrics defined in `metrics.list` and validates their values against the expected values in `rocestat.json`. -+3. Runs unit tests for each metric cluster (e.g., `hw`, `hw_xmit`, `hw_rcv`) to ensure the PMDA is reporting correct values. -+4. Removes the `rocestat` PMDA after the tests are complete. -+ -+If any of the above checks fail, an exception will be raised, and the test will terminate. -+ -+How to Test -+----------- -+1. Make sure the pre-requisites are in place. -+2. Run the rocestat_test.python script -+ -+Issues -+------ -+- None known at this time -+ -diff --git a/qa/rocestat/metrics.list b/qa/rocestat/metrics.list -new file mode 100644 -index 0000000000..68619da7c4 ---- /dev/null -+++ b/qa/rocestat/metrics.list -@@ -0,0 +1,49 @@ -+rocestat.lane.rx_pause -+rocestat.lane.rx_bytes -+rocestat.lane.tx_bytes -+rocestat.hw.link.link_error_recovery -+rocestat.hw.link.link_downed -+rocestat.hw.link.local_link_integrity_errors -+rocestat.hw.rnr.rnr_nak_retry_err -+rocestat.hw.resp.resp_local_length_error -+rocestat.hw.resp.resp_cqe_error -+rocestat.hw.resp.resp_cqe_flush_error -+rocestat.hw.resp.resp_remote_access_errors -+rocestat.hw.req.req_remote_invalid_request -+rocestat.hw.req.req_cqe_error -+rocestat.hw.req.req_cqe_flush_error -+rocestat.hw.req.duplicate_request -+rocestat.hw.req.rx_read_requests -+rocestat.hw.req.rx_atomic_requests -+rocestat.hw.req.req_remote_access_errors -+rocestat.hw.req.rx_write_requests -+rocestat.hw.mcast.multicast_rcv_packets -+rocestat.hw.mcast.multicast_xmit_packets -+rocestat.hw.ucast.unicast_rcv_packets -+rocestat.hw.ucast.unicast_xmit_packets -+rocestat.hw.rcv.port_rcv_errors -+rocestat.hw.rcv.port_rcv_remote_physical_errors -+rocestat.hw.rcv.port_rcv_packets -+rocestat.hw.rcv.port_rcv_data -+rocestat.hw.rcv.port_rcv_constraint_errors -+rocestat.hw.rcv.port_rcv_switch_relay_errors -+rocestat.hw.xmit.port_xmit_data -+rocestat.hw.xmit.port_xmit_constraint_errors -+rocestat.hw.xmit.port_xmit_wait -+rocestat.hw.xmit.port_xmit_packets -+rocestat.hw.xmit.port_xmit_discards -+rocestat.hw.roce_slow_restart_trans -+rocestat.hw.roce_slow_restart_cnps -+rocestat.hw.roce_slow_restart -+rocestat.hw.roce_adp_retrans_to -+rocestat.hw.clear_counters -+rocestat.hw.local_ack_timeout_err -+rocestat.hw.lifespan -+rocestat.hw.implied_nak_seq_err -+rocestat.hw.packet_seq_err -+rocestat.hw.roce_adp_retrans -+rocestat.hw.out_of_buffer -+rocestat.hw.out_of_sequence -+rocestat.hw.VL15_dropped -+rocestat.hw.excessive_buffer_overrun_errors -+rocestat.hw.symbol_error -\ No newline at end of file -diff --git a/qa/rocestat/rocestat.json b/qa/rocestat/rocestat.json -new file mode 100644 -index 0000000000..e5c6da7a5b ---- /dev/null -+++ b/qa/rocestat/rocestat.json -@@ -0,0 +1,218 @@ -+{ -+ "lane": { -+ "rx_pause": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "rx_bytes": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "tx_bytes": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw_link": { -+ "link_error_recovery": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "link_downed": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "local_link_integrity_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw_rnr": { -+ "rnr_nak_retry_err": { -+ "mlx5_0_1": 3, -+ "mlx5_1_1": 1 -+ } -+ }, -+ "hw_resp": { -+ "resp_local_length_error": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "resp_cqe_error": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "resp_cqe_flush_error": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "resp_remote_access_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw_req": { -+ "req_remote_invalid_request": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "req_cqe_error": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "req_cqe_flush_error": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "duplicate_request": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "rx_read_requests": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "rx_atomic_requests": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "req_remote_access_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "rx_write_requests": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw_mcast": { -+ "multicast_rcv_packets": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "multicast_xmit_packets": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw_ucast": { -+ "unicast_rcv_packets": { -+ "mlx5_0_1": 2454, -+ "mlx5_1_1": 2469 -+ }, -+ "unicast_xmit_packets": { -+ "mlx5_0_1": 2496, -+ "mlx5_1_1": 2478 -+ } -+ }, -+ "hw_rcv": { -+ "port_rcv_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "port_rcv_remote_physical_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "port_rcv_packets": { -+ "mlx5_0_1": 2454, -+ "mlx5_1_1": 2469 -+ }, -+ "port_rcv_data": { -+ "mlx5_0_1": 57781, -+ "mlx5_1_1": 53893 -+ }, -+ "port_rcv_constraint_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "port_rcv_switch_relay_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw_xmit": { -+ "port_xmit_data": { -+ "mlx5_0_1": 67939, -+ "mlx5_1_1": 56182 -+ }, -+ "port_xmit_constraint_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "port_xmit_wait": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "port_xmit_packets": { -+ "mlx5_0_1": 2496, -+ "mlx5_1_1": 2478 -+ }, -+ "port_xmit_discards": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ }, -+ "hw": { -+ "roce_slow_restart_trans": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "roce_slow_restart_cnps": { -+ "mlx5_0_1": 3, -+ "mlx5_1_1": 1 -+ }, -+ "roce_slow_restart": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "roce_adp_retrans_to": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "clear_counters": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "local_ack_timeout_err": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "lifespan": { -+ "mlx5_0_1": 10, -+ "mlx5_1_1": 10 -+ }, -+ "implied_nak_seq_err": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "packet_seq_err": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "roce_adp_retrans": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "out_of_buffer": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "out_of_sequence": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "VL15_dropped": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "excessive_buffer_overrun_errors": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ }, -+ "symbol_error": { -+ "mlx5_0_1": 0, -+ "mlx5_1_1": 0 -+ } -+ } -+} -\ No newline at end of file -diff --git a/qa/rocestat/rocestat_test.python b/qa/rocestat/rocestat_test.python -new file mode 100755 -index 0000000000..5645761efc ---- /dev/null -+++ b/qa/rocestat/rocestat_test.python -@@ -0,0 +1,123 @@ -+#!/usr/bin/env pmpython -+ -+import os -+import json -+from pcp import pmapi -+import cpmapi as c_api -+import unittest -+import ctypes -+ -+ -+def load_metrics_from_file(filepath): -+ """Load metrics from a file and organize them by cluster.""" -+ clusters = {} -+ with open(filepath, 'r') as f: -+ for line in f: -+ line = line.strip() -+ if not line or line.startswith('#'): -+ continue -+ cluster, metric = line.split('.', 1) -+ clusters.setdefault(cluster, []).append(metric) -+ return clusters -+ -+ -+def fetch_metric_data(ctx, cluster, metric): -+ """Fetch metric data for a given cluster and metric.""" -+ # print(f"Fetching data for metric: {cluster}.{metric}") -+ pmid = ctx.pmLookupName([f"{cluster}.{metric}"])[0] -+ desc = ctx.pmLookupDescs([pmid])[0] -+ -+ pmid_array = (ctypes.c_uint * 1)(pmid) -+ results = ctx.pmFetch(pmid_array) -+ -+ # Handle instance domains -+ if desc.contents.indom == c_api.PM_INDOM_NULL: -+ print(f"No instances for metric: {cluster}.{metric}") -+ return {} # No instances for this metric -+ -+ instances = ctx.pmGetInDom(desc)[1] -+ metric_data = {} -+ for i, instance in enumerate(instances): -+ atom = ctx.pmExtractValue( -+ results.contents.get_valfmt(0), -+ results.contents.get_vlist(0, i), -+ desc.contents.type, -+ c_api.PM_TYPE_U64 -+ ) -+ metric_data[instance] = atom.ul -+ # print(f"Metric: {cluster}.{metric}, Instance: {instance}, Value: {atom.ul}") -+ -+ return metric_data -+ -+ -+class RocestatTests(unittest.TestCase): -+ """Unit tests for Rocestat PMDA metrics.""" -+ -+ @classmethod -+ def setUpClass(cls): -+ """Set up the test environment and fetch all metrics.""" -+ print("\nFetching Rocestat PMDA metrics\n") -+ cls.ctx = pmapi.pmContext() -+ cls.cluster_data = {} -+ -+ for cluster, metrics in metric_clusters.items(): -+ cls.cluster_data[cluster] = { -+ metric: fetch_metric_data(cls.ctx, cluster, metric) -+ for metric in metrics -+ } -+ -+ def _test_cluster_metrics(self, cluster): -+ """Test all metrics in a given cluster.""" -+ metrics = self.cluster_data.get(cluster, {}) -+ -+ for metric, instances in metrics.items(): -+ self.assertTrue(instances, f"No instances found for metric: {metric} in cluster: {cluster}") -+ for instance, value in instances.items(): -+ with self.subTest(cluster=cluster, metric=metric, instance=instance): -+ expected_value = expected_values.get(cluster, {}).get(metric, {}).get(instance) -+ self.assertIsNotNone(expected_value, f"Expected value for {metric} (instance {instance}) in {cluster} is missing") -+ self.assertEqual(value, expected_value, f"{metric} (instance {instance}) in {cluster} does not match expected value") -+ -+ def test_hw_metrics(self): -+ self._test_cluster_metrics("hw") -+ -+ def test_hw_xmit_metrics(self): -+ self._test_cluster_metrics("hw_xmit") -+ -+ def test_hw_rcv_metrics(self): -+ self._test_cluster_metrics("hw_rcv") -+ -+ def test_hw_ucast_metrics(self): -+ self._test_cluster_metrics("hw_ucast") -+ -+ def test_hw_mcast_metrics(self): -+ self._test_cluster_metrics("hw_mcast") -+ -+ def test_hw_req_metrics(self): -+ self._test_cluster_metrics("hw_req") -+ -+ def test_hw_resp_metrics(self): -+ self._test_cluster_metrics("hw_resp") -+ -+ def test_hw_rnr_metrics(self): -+ self._test_cluster_metrics("hw_rnr") -+ -+ def test_hw_link_metrics(self): -+ self._test_cluster_metrics("hw_link") -+ -+ def test_nic_metrics(self): -+ self._test_cluster_metrics("nic") -+ -+ -+if __name__ == '__main__': -+ # Load metrics and expected values -+ script_dir = os.path.dirname(__file__) -+ metrics_file = os.path.join(script_dir, "metrics.list") -+ expected_values_file = os.path.join(script_dir, "rocestat.json") -+ -+ metric_clusters = load_metrics_from_file(metrics_file) -+ with open(expected_values_file, 'r') as f: -+ expected_values = json.load(f) -+ -+ # Run tests -+ unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(RocestatTests)) -diff --git a/src/pmdas/GNUmakefile b/src/pmdas/GNUmakefile -index b906d4193e..4b0bbba397 100644 ---- a/src/pmdas/GNUmakefile -+++ b/src/pmdas/GNUmakefile -@@ -39,7 +39,7 @@ PLPMDAS = bonding netfilter zimbra postgresql \ - PYPMDAS = bcc gluster zswap unbound mic haproxy \ - json libvirt lio openmetrics elasticsearch \ - bpftrace mssql netcheck rabbitmq openvswitch \ -- nfsclient mongodb uwsgi -+ nfsclient mongodb uwsgi rocestat - - SUBDIRS = $(CPMDAS) $(PLPMDAS) $(PYPMDAS) - LDIRT = pmcd.conf -diff --git a/src/pmdas/rocestat/GNUmakefile b/src/pmdas/rocestat/GNUmakefile -new file mode 100644 -index 0000000000..fd036e0531 ---- /dev/null -+++ b/src/pmdas/rocestat/GNUmakefile -@@ -0,0 +1,40 @@ -+TOPDIR = ../../.. -+include $(TOPDIR)/src/include/builddefs -+ -+IAM = rocestat -+PYSCRIPT = pmda$(IAM).python -+LDIRT = domain.h root pmns $(IAM).log -+DOMAIN = GLUSTER -+ -+PMDAADMDIR = $(PCP_PMDASADM_DIR)/$(IAM) -+PMDATMPDIR = $(PCP_PMDAS_DIR)/$(IAM) -+ -+MAN_SECTION = 1 -+MAN_PAGES = pmda$(IAM).$(MAN_SECTION) -+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION) -+ -+default_pcp default: build-me -+ -+include $(BUILDRULES) -+ -+ifeq "$(HAVE_PYTHON)" "true" -+build-me: check_domain -+install_pcp install: default -+ $(INSTALL) -m 755 -d $(PMDAADMDIR) -+ $(INSTALL) -m 755 -d $(PMDATMPDIR) -+ $(INSTALL) -m 755 -t $(PMDATMPDIR) Install Remove $(PYSCRIPT) $(PMDAADMDIR) -+ @$(INSTALL_MAN) -+else -+build-me: -+install_pcp install: -+ @$(INSTALL_MAN) -+endif -+ -+check_domain: ../../pmns/stdpmid -+ $(DOMAIN_PYTHONRULE) -+ -+check:: $(PYSCRIPT) -+ $(PYLINT) $^ -+ -+check:: $(MAN_PAGES) -+ $(MANLINT) $^ -diff --git a/src/pmdas/rocestat/Install b/src/pmdas/rocestat/Install -new file mode 100644 -index 0000000000..cb66717036 ---- /dev/null -+++ b/src/pmdas/rocestat/Install -@@ -0,0 +1,40 @@ -+#!/bin/sh -+# __author__ = "Mohith Kumar Thummaluru" -+# __email__ = "mohith.k.kumar.thummaluru@oracle.com" -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+# Install the Rocestat PMDA -+# -+ -+. $PCP_DIR/etc/pcp.env -+. $PCP_SHARE_DIR/lib/pmdaproc.sh -+ -+iam=rocestat -+python_opt=true -+daemon_opt=false -+ -+if [[ ! -d /sys/class/infiniband ]]; then -+ if ! lsmod | grep -q '^ib_core'; then -+ echo "IB kernel modules are not loaded" >&2 -+ else -+ echo "No IB devices detected" >&2 -+ fi -+ status=1 -+ exit -+fi -+ -+pmdaSetup -+pmdaInstall -+exit -diff --git a/src/pmdas/rocestat/Remove b/src/pmdas/rocestat/Remove -new file mode 100644 -index 0000000000..00f773b5d7 ---- /dev/null -+++ b/src/pmdas/rocestat/Remove -@@ -0,0 +1,38 @@ -+#!/bin/sh -+# __author__ = "Mohith Kumar Thummaluru" -+# __email__ = "mohith.k.kumar.thummaluru@oracle.com" -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+# Remove the rocestat PMDA -+# -+ -+# source the PCP configuration environment variables -+. $PCP_DIR/etc/pcp.env -+ -+# Get the common procedures and variable assignments -+# -+. $PCP_SHARE_DIR/lib/pmdaproc.sh -+ -+# The name of the PMDA -+# -+iam=rocestat -+ -+# Do it -+# -+pmdaSetup -+pmdaRemove -+ -+ -+exit -diff --git a/src/pmdas/rocestat/domain.h b/src/pmdas/rocestat/domain.h -new file mode 100644 -index 0000000000..a2bcb16fda ---- /dev/null -+++ b/src/pmdas/rocestat/domain.h -@@ -0,0 +1,2 @@ -+#define ROCESTAT 254 -+ -diff --git a/src/pmdas/rocestat/pmdarocestat.1 b/src/pmdas/rocestat/pmdarocestat.1 -new file mode 100644 -index 0000000000..28a55f19c6 ---- /dev/null -+++ b/src/pmdas/rocestat/pmdarocestat.1 -@@ -0,0 +1,94 @@ -+'\"macro stdmacro -+.\" -+.\" Copyright (c) 2025 Oracle and/or its affiliates. -+.\" DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+.\" -+.\" This program is free software; you can redistribute it and/or modify it -+.\" under the terms of the GNU General Public License as published by the -+.\" Free Software Foundation; either version 2 of the License, or (at your -+.\" option) any later version. -+.\" -+.\" This program is distributed in the hope that it will be useful, but -+.\" WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+.\" or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+.\" for more details. -+.\" -+.\" -+.TH PMDAROCESTAT 1 "PCP" "Performance Co-Pilot" "General Commands Manual" -+.SH NAME -+.B pmdarocestat -+\- Performance Metrics Domain Agent (PMDA) for RoCE devices -+ -+.SH DESCRIPTION -+The -+.B Rocestat PMDA -+(Performance Metrics Domain Agent) is a Performance Co-Pilot (PCP) module that collects -+and exports performance statistics for RDMA over Converged Ethernet (RoCE) devices. It provides insights into -+network performance, error conditions, and congestion events, aiding in the diagnosis and monitoring of RoCE-based -+communication. -+ -+This PMDA reports software-aggregated InfiniBand port statistics, including received/transmitted bytes and -+packets, link errors, and congestion-related drops, helping to identify potential bottlenecks and failures. -+Additionally, it includes hardware-level counters, which track low-level transmission metrics, duplicate requests, -+NAKs, and physical/constraint errors, offering a deeper view into the underlying transport reliability and efficiency. -+ -+Furthermore, Rocestat PMDA collects priority-based lane metrics from ethtool -S , filtering -+statistics related to priority lanes in RoCE traffic. These metrics provide visibility into traffic distribution -+across lanes, helping diagnose congestion hotspots and optimize workload balancing across different lanes -+ -+By integrating Rocestat PMDA into a PCP monitoring environment, users can efficiently analyze RoCE -+network behavior, detect performance anomalies, and optimize high-speed RDMA workloads in data center and HPC -+environments. -+ -+.SH INSTALLATION -+To install the Rocestat PMDA, follow these steps: -+.RS -+.nf -+# cd $PCP_PMDAS_DIR/rocestat -+# ./Install -+.fi -+.RE -+ -+To verify that the PMDA is running: -+.RS -+.nf -+$ pminfo -t rocestat -+.fi -+.RE -+ -+.SH USAGE -+To query Rocestat metrics, use the following command: -+.RS -+.nf -+$ pminfo rocestat -+.fi -+.RE -+To retrieve specific metric values: -+.RS -+.nf -+$ pmval rocestat.hw.rcv.port_rcv_packets -+.fi -+.RE -+ -+.SH FILES -+.TP -+.I $PCP_PMDAS_DIR/rocestat/Install -+Installation script for Rocestat PMDA. -+.TP -+.I $PCP_PMDAS_DIR/rocestat/Remove -+Uninstallation script. -+.TP -+.I $PCP_LOG_DIR/pmcd/rocestat.log -+Log file for Rocestat PMDA events and errors. -+ -+.SH PCP ENVIRONMENT -+Environment variables with the prefix PCP_ are used to parameterize the file and -+directory names used by PCP. On each installation, the file /etc/pcp.conf contains -+the local values for these variables. The $PCP_CONF variable may be used to specify -+an alternative configuration file, as described in pcp.conf(5). -+ -+.SH SEE ALSO -+.BR PCPIntro (1), -+.BR pmcd (1) -+.BR PMDA (3), -+.BR pminfo (1), -diff --git a/src/pmdas/rocestat/pmdarocestat.python b/src/pmdas/rocestat/pmdarocestat.python -new file mode 100644 -index 0000000000..9b852d398f ---- /dev/null -+++ b/src/pmdas/rocestat/pmdarocestat.python -@@ -0,0 +1,536 @@ -+# __author__ = "Mohith Kumar Thummaluru" -+# __email__ = "mohith.k.kumar.thummaluru@oracle.com" -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+# pylint: disable=no-self-use, consider-using-f-string, too-many-instance-attributes -+# pylint: disable=c-extension-no-member, consider-iterating-dictionary -+# pylint: disable=too-many-locals, too-many-arguments, too-many-lines, unused-argument, line-too-long -+# pylint: disable=too-many-return-statements, too-many-branches, too-many-statements, bare-except -+""" -+Python implementation of the "rocestat" Performance Metrics Domain Agent. -+""" -+import subprocess -+import os -+import re -+import cpmapi as c_api -+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom -+from pcp.pmapi import pmUnits -+from ctypes import c_int, c_ulonglong, POINTER, cast, Structure -+ -+lane_mapping = { -+ 0: 'Default(TCP) - 0', -+ 1: 'Normal Large - 1', -+ 2: 'VIP Small - 2', -+ 3: 'VIP Large - 3', -+ 4: 'Normal Small - 4', -+ 5: 'VIP Small - 5', -+ 6: 'Unused - 6', -+ 7: 'CNP - 7', -+} -+ -+RoceStatHW = [ -+ ("symbol_error", c_ulonglong), -+ ("excessive_buffer_overrun_errors", c_ulonglong), -+ ("VL15_dropped", c_ulonglong), -+ ("out_of_sequence", c_ulonglong), -+ ("out_of_buffer", c_ulonglong), -+ ("roce_adp_retrans", c_ulonglong), -+ ("packet_seq_err", c_ulonglong), -+ ("implied_nak_seq_err", c_ulonglong), -+ ("lifespan", c_ulonglong), -+ ("local_ack_timeout_err", c_ulonglong), -+ ("clear_counters", c_ulonglong), -+ ("roce_adp_retrans_to", c_ulonglong), -+ ("roce_slow_restart", c_ulonglong), -+ ("roce_slow_restart_cnps", c_ulonglong), -+ ("roce_slow_restart_trans", c_ulonglong) -+ ] -+ -+RoceStatXmit = [ -+ ("port_xmit_discards", c_ulonglong), -+ ("port_xmit_packets", c_ulonglong), -+ ("port_xmit_wait", c_ulonglong), -+ ("port_xmit_constraint_errors", c_ulonglong), -+ ("port_xmit_data", c_ulonglong) -+ ] -+ -+RoceStatRcv = [ -+ ("port_rcv_switch_relay_errors", c_ulonglong), -+ ("port_rcv_constraint_errors", c_ulonglong), -+ ("port_rcv_data", c_ulonglong), -+ ("port_rcv_packets", c_ulonglong), -+ ("port_rcv_remote_physical_errors", c_ulonglong), -+ ("port_rcv_errors", c_ulonglong) -+ ] -+ -+RoceStatUcast = [ -+ ("unicast_xmit_packets", c_ulonglong), -+ ("unicast_rcv_packets", c_ulonglong) -+ ] -+ -+RoceStatMcast = [ -+ ("multicast_xmit_packets", c_ulonglong), -+ ("multicast_rcv_packets", c_ulonglong) -+ ] -+ -+RoceStatReq = [ -+ ("rx_write_requests", c_ulonglong), -+ ("req_remote_access_errors", c_ulonglong), -+ ("rx_atomic_requests", c_ulonglong), -+ ("rx_read_requests", c_ulonglong), -+ ("duplicate_request", c_ulonglong), -+ ("req_cqe_flush_error", c_ulonglong), -+ ("req_cqe_error", c_ulonglong), -+ ("req_remote_invalid_request", c_ulonglong) -+ ] -+ -+RoceStatResp = [ -+ ("resp_remote_access_errors", c_ulonglong), -+ ("resp_cqe_flush_error", c_ulonglong), -+ ("resp_cqe_error", c_ulonglong), -+ ("resp_local_length_error", c_ulonglong) -+ ] -+ -+RoceStatRnr = [ -+ ("rnr_nak_retry_err", c_ulonglong) -+ ] -+ -+RoceStatLink = [ -+ ("local_link_integrity_errors", c_ulonglong), -+ ("link_downed", c_ulonglong), -+ ("link_error_recovery", c_ulonglong), -+ ] -+ -+class RoceDev(Structure): -+ ''' Statistic values per-mlnx-device (mlnx device indom cache lookup) ''' -+ _fields_ = RoceStatHW + RoceStatXmit + RoceStatRcv -+ _fields_ += RoceStatUcast + RoceStatMcast -+ _fields_ += RoceStatReq + RoceStatResp -+ _fields_ += RoceStatRnr + RoceStatLink -+ -+class NicStats(Structure): -+ _fields_ = [ -+ ("tx_bytes", c_ulonglong), -+ ("rx_bytes", c_ulonglong), -+ ("rx_pause", c_ulonglong), -+ ] -+ -+class NICSTATS: -+ def __init__(self): -+ self.up_interfaces = [] -+ self.ethtool_stats = {} -+ -+ def get_up_interfaces(self): -+ """Get the names of all interfaces that are in the Up state.""" -+ result = subprocess.Popen(['/usr/bin/ibdev2netdev'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) -+ output, _ = result.communicate() -+ -+ if result.returncode != 0: -+ return [] -+ -+ # Regular expression to find interfaces and their status -+ pattern = re.compile(r'(\S+) \(Up\)') -+ self.up_interfaces = pattern.findall(output) -+ return self.up_interfaces -+ -+ def get_ethtool_stats(self, interface): -+ """Get the ethtool -S output for the specified interface and filter stats containing 'prio'.""" -+ result = subprocess.Popen(['ethtool', '-S', interface], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) -+ output, _ = result.communicate() -+ -+ if result.returncode != 0: -+ return {} -+ -+ # Filter the stats to include only those with 'prio' in their names -+ prio_stats = {} -+ for line in output.splitlines(): -+ if 'prio' in line: -+ if 'bytes' in line or 'pause' in line: -+ key, value = line.strip().split(':', 1) -+ value = int(value) -+ if value > 0: -+ prio_stats[key] = value -+ -+ return prio_stats -+ -+ def process_prio_stats(self, prio_stats): -+ """Process the prio stats to group them by lanes.""" -+ all_lanes = [f"lane{i}" for i in range(0,8)] -+ -+ lanes = {lane:{} for lane in all_lanes} -+ -+ for key, value in prio_stats.items(): -+ # Extract the lane number from the key -+ match = re.match(r'(rx|tx)_prio(\d+)_(pause|bytes)', key) -+ if match: -+ direction, lane, key = match.groups() -+ -+ lane = "lane" + str(lane) -+ if (direction =='rx' and key.endswith("pause")) or key.endswith("bytes"): -+ lanes[lane][f'{direction}_{key}'] = value -+ -+ for lane, sub_dict in lanes.items(): -+ metrics = ['rx_bytes', 'tx_bytes', 'rx_pause'] -+ for metric in metrics: -+ if metric not in sub_dict.keys(): -+ lanes[lane][metric] = 0 -+ return lanes -+ -+ def collect_stats(self): -+ """Collect ethtool stats for all Up interfaces.""" -+ self.get_up_interfaces() -+ for interface in self.up_interfaces: -+ stats = self.get_ethtool_stats(interface) -+ lanes_stats = self.process_prio_stats(stats) -+ self.ethtool_stats[interface] = lanes_stats -+ return self.convert_dict(self.ethtool_stats) -+ -+ def convert_dict(self, input_dict): -+ output_dict = {} -+ for key1, sub_dict in input_dict.items(): -+ if isinstance(sub_dict, dict): -+ for key2, value in sub_dict.items(): -+ new_key = f"{key1}_{key2}" -+ output_dict[new_key] = value -+ return output_dict -+ -+class MLNXCOUNTERS(): -+ hw_counter_hash = {} -+ hw_counter_path = [] -+ hw_counter = [] -+ first_hw_qry = True -+ -+ def get_hw_counter_names(self, base_path='/sys/class/infiniband'): -+ counter_paths = [] -+ -+ # Check if base path exists -+ if not os.path.exists(base_path): -+ return counter_paths -+ # raise FileNotFoundError(f"Base path {base_path} does not exist.") -+ -+ # Traverse each device directory in the base path -+ for device in os.listdir(base_path): -+ device_path = os.path.join(base_path, device) -+ ports_path = os.path.join(device_path, 'ports') -+ -+ # Check if ports directory exists -+ if os.path.exists(ports_path): -+ # Traverse each port directory in the device's ports directory -+ for port in os.listdir(ports_path): -+ port_path = os.path.join(ports_path, port) -+ -+ # Check and collect counter file paths -+ counters_path = os.path.join(port_path, 'counters') -+ if os.path.exists(counters_path): -+ for counter_file in os.listdir(counters_path): -+ counter_paths.append(os.path.join(counters_path, counter_file)) -+ -+ # Check and collect hw_counters file paths -+ hw_counters_path = os.path.join(port_path, 'hw_counters') -+ if os.path.exists(hw_counters_path): -+ for hw_counter_file in os.listdir(hw_counters_path): -+ counter_paths.append(os.path.join(hw_counters_path, hw_counter_file)) -+ -+ return counter_paths -+ -+ def find_device_status(self, device): -+ infiniband_base = "/sys/class/infiniband" -+ ports_dir = os.path.join(infiniband_base, device, "ports") -+ -+ if not os.path.isdir(ports_dir): -+ return False -+ -+ try: -+ ports = os.listdir(ports_dir) -+ except FileNotFoundError: -+ return False -+ -+ for port in ports: -+ port_dir = os.path.join(infiniband_base, device, "ports", str(port)) -+ -+ if os.path.isfile(os.path.join(port_dir, "phys_state")): -+ with open(os.path.join(port_dir, "phys_state"), 'r') as f: -+ phys_state = f.read().strip() -+ if "LinkUp" in phys_state: -+ return True -+ -+ return False -+ -+ def get_active_devices(self): -+ -+ active_devices = [] -+ visit = set() -+ -+ for path in self.hw_counter: -+ token = path.split('/') -+ if len(token) > 8: -+ mlx = token[4] -+ if mlx not in visit: -+ status = self.find_device_status(mlx) -+ if status is True: -+ active_devices.append(mlx) -+ self.hw_counter_path.append(path.strip()) -+ visit.add(mlx) -+ return active_devices -+ -+ # initialize the hardware counters -+ # self.hw_counter_hash['mlnx5_0'][1]['np_cnp_sent'] = -+ # where mlnx5_0 is device, 1 is port, np_cnp_sent is the counter -+ def init_hw_counter(self, active_devices): -+ port_keys = {} -+ port_keys['1'] = 0 -+ port_keys['2'] = 0 -+ for i in self.hw_counter: -+ token = i.split('/') -+ if len(token) > 8: -+ mlx = token[4] -+ port = token[6] -+ if mlx not in active_devices: -+ continue -+ self.hw_counter_path.append(i.strip()) -+ counter = token[8].strip() -+ if mlx not in self.hw_counter_hash: -+ self.hw_counter_hash[mlx] = {} -+ if port not in self.hw_counter_hash[mlx]: -+ self.hw_counter_hash[mlx][port] = {} -+ self.hw_counter_hash[mlx][port][counter] = 0 -+ port_keys[port] += 1 -+ -+ # get_hw_counters probes the counter periodically and updates the counter stats -+ def get_hw_counters(self, counter_path, hw_counter_vals): -+ for path in counter_path: -+ with open(path, 'r') as fread: -+ ret = fread.read().strip() -+ -+ token = path.split('/') -+ mlx, port, counter = token[4], token[6], token[8] -+ hw_counter_vals[mlx][port][counter] = int(ret) -+ -+ return hw_counter_vals -+ -+ def convert_dict(self, input_dict): -+ output_dict = {} -+ for key1, sub_dict in input_dict.items(): -+ if isinstance(sub_dict, dict): -+ for key2, value in sub_dict.items(): -+ new_key = f"{key1}_{key2}" -+ output_dict[new_key] = value -+ return output_dict -+ -+ def fetch_mlnx_counters(self): -+ self.hw_counter = self.get_hw_counter_names() -+ active_device = self.get_active_devices() -+ -+ self.init_hw_counter(active_device) -+ -+ self.hw_counter_hash = self.get_hw_counters(self.hw_counter_path, self.hw_counter_hash) -+ return self.convert_dict(self.hw_counter_hash) -+ -+ -+class ROCESTATPMDA(PMDA): -+ """ -+ A ROCESTAT Performance Metrics Domain Agent. -+ """ -+ -+ # Cluster definitions -+ CLUSTER_HW = 1 -+ CLUSTER_HW_XMIT = 2 -+ CLUSTER_HW_RCV = 3 -+ CLUSTER_HW_UCAST = 4 -+ CLUSTER_HW_MCAST = 5 -+ CLUSTER_HW_REQ = 6 -+ CLUSTER_HW_RESP = 7 -+ CLUSTER_HW_RNR = 8 -+ CLUSTER_HW_LINK = 9 -+ -+ CLUSTER_NIC = 10 -+ -+ mlnx_counters = {} -+ nic_stats = {} -+ -+ def register_metrics(self, name): -+ """ -+ Register Metrics -+ """ -+ pm_type_32 = c_api.PM_TYPE_U32 -+ pm_type_64 = c_api.PM_TYPE_U64 -+ -+ inst_sem = c_api.PM_SEM_INSTANT -+ -+ pm_units = pmUnits(0,0,0,0,0,0) -+ byte_units = pmUnits(1,0,0,c_api.PM_SPACE_BYTE,0,0) -+ count_units = pmUnits(0,0,1,0,0,0) -+ -+ # Registering MLNX HW Counters -+ self.mlnx_indom = self.indom(0) -+ self.add_indom(pmdaIndom(self.mlnx_indom, self.mlnx_counters)) -+ -+ hw_metrics = [(f"{name}.hw.{dev_attr[0]}", self.CLUSTER_HW, dev_attr[1]) for dev_attr in RoceStatHW] -+ hw_metrics += [(f"{name}.hw.xmit.{dev_attr[0]}", self.CLUSTER_HW_XMIT, dev_attr[1]) for dev_attr in RoceStatXmit] -+ hw_metrics += [(f"{name}.hw.rcv.{dev_attr[0]}", self.CLUSTER_HW_RCV, dev_attr[1]) for dev_attr in RoceStatRcv] -+ hw_metrics += [(f"{name}.hw.ucast.{dev_attr[0]}", self.CLUSTER_HW_UCAST, dev_attr[1]) for dev_attr in RoceStatUcast] -+ hw_metrics += [(f"{name}.hw.mcast.{dev_attr[0]}", self.CLUSTER_HW_MCAST, dev_attr[1]) for dev_attr in RoceStatMcast] -+ hw_metrics += [(f"{name}.hw.req.{dev_attr[0]}", self.CLUSTER_HW_REQ, dev_attr[1]) for dev_attr in RoceStatReq] -+ hw_metrics += [(f"{name}.hw.resp.{dev_attr[0]}", self.CLUSTER_HW_RESP, dev_attr[1]) for dev_attr in RoceStatResp] -+ hw_metrics += [(f"{name}.hw.rnr.{dev_attr[0]}", self.CLUSTER_HW_RNR, dev_attr[1]) for dev_attr in RoceStatRnr] -+ hw_metrics += [(f"{name}.hw.link.{dev_attr[0]}", self.CLUSTER_HW_LINK, dev_attr[1]) for dev_attr in RoceStatLink] -+ -+ for idx, (metric_name, metric_cluster, metric_type) in enumerate(hw_metrics, 0): -+ if metric_type == c_int: -+ metric_type = pm_type_32 -+ elif metric_type == c_ulonglong: -+ metric_type = pm_type_64 -+ -+ if "data" in metric_name: -+ pm_units = byte_units -+ else: -+ pm_units = count_units -+ -+ self.add_metric(metric_name, -+ pmdaMetric(self.pmid(metric_cluster, idx), -+ metric_type, -+ self.mlnx_indom, inst_sem, pm_units)) -+ -+ # Register Per Lane NIC stats -+ self.nic_indom = self.indom(1) -+ self.add_indom(pmdaIndom(self.nic_indom, self.nic_stats)) -+ -+ nic_metric = [f"{name}.lane.{dev_attr[0]}" for dev_attr in NicStats._fields_] -+ -+ for idx, metric_name in enumerate(nic_metric, 0): -+ if "bytes" in metric_name: -+ pm_units = byte_units -+ else: -+ pm_units = count_units -+ -+ self.add_metric(metric_name, -+ pmdaMetric(self.pmid(self.CLUSTER_NIC, idx), -+ pm_type_64, -+ self.nic_indom, inst_sem, pm_units)) -+ -+ def rocestat_fetch_mlnx_counters(self, item, inst): -+ -+ voidp = self.inst_lookup(self.mlnx_indom, inst) -+ -+ if voidp is None: -+ return [c_api.PM_ERR_INST, 0] -+ -+ cache = cast(voidp, POINTER(RoceDev)) -+ item_lookup = [dev_attr[0] for dev_attr in RoceDev._fields_] -+ dev = cache.contents -+ -+ if 0 <= item < len(RoceDev._fields_): -+ return [getattr(dev, item_lookup[item]), 1] -+ else: -+ return [c_api.PM_ERR_INST, 0] -+ -+ def rocestat_fetch_nic_stats(self, item, inst): -+ -+ voidp = self.inst_lookup(self.nic_indom, inst) -+ -+ if voidp is None: -+ return [c_api.PM_ERR_INST, 0] -+ -+ cache = cast(voidp, POINTER(NicStats)) -+ item_lookup = [dev_attr[0] for dev_attr in NicStats._fields_] -+ nic = cache.contents -+ -+ if 0 <= item < len(NicStats._fields_): -+ return [getattr(nic, item_lookup[item]), 1] -+ else: -+ return [c_api.PM_ERR_INST, 0] -+ -+ def refresh_mlnx_counters(self): -+ ''' Refresh the values and instances for mlnx devices''' -+ try: -+ roce_stat = MLNXCOUNTERS() -+ hw_cnts = roce_stat.fetch_mlnx_counters() -+ except: -+ self.log("failed to fetch mlnx counters") -+ -+ for device, dict_counters in hw_cnts.items(): -+ dev = RoceDev() -+ for k,v in dict_counters.items(): -+ setattr(dev, k, v) -+ -+ self.mlnx_counters[device] = dev -+ -+ def refresh_nic_stats(self): -+ try: -+ collector = NICSTATS() -+ nic_stats = collector.collect_stats() -+ except: -+ self.log("failed to fetch nic stats") -+ -+ for nic_lane, dict_stats in nic_stats.items(): -+ nic = NicStats() -+ for k,v in dict_stats.items(): -+ setattr(nic, k, v) -+ -+ self.nic_stats[nic_lane] = nic -+ -+ def refresh(self): -+ try: -+ self.refresh_mlnx_counters() -+ try: -+ self.refresh_nic_stats() -+ except: -+ self.log("Nic stats refresh failed") -+ except: -+ self.log("mlnx counters refresh failed") -+ -+ def roce_stat_fetch(self): -+ ''' Called once per "fetch" PDU ''' -+ self.refresh() -+ self.replace_indom(self.mlnx_indom, self.mlnx_counters) -+ self.replace_indom(self.nic_indom, self.nic_stats) -+ -+ def roce_fetch_callback(self, cluster, item, inst): -+ """ -+ Main fetch callback -+ """ -+ -+ if self.CLUSTER_HW <= cluster <= self.CLUSTER_HW_LINK: -+ return self.rocestat_fetch_mlnx_counters(item, inst) -+ -+ if cluster == self.CLUSTER_NIC: -+ return self.rocestat_fetch_nic_stats(item, inst) -+ -+ return [c_api.PM_ERR_PMID, 0] -+ -+ def __init__(self, name, domain, user, logfile): -+ -+ super().__init__(name, domain, logfile) -+ if user is not None: -+ self.set_user(user) -+ -+ self.connect_pmcd() -+ -+ self.pmda_name = name -+ -+ self.register_metrics(name) -+ self.set_fetch(self.roce_stat_fetch) -+ self.set_fetch_callback(self.roce_fetch_callback) -+ -+ @classmethod -+ def in_pmda_setup(cls): -+ """Check if PMDA is in setup state""" -+ return os.environ.get('PCP_PYTHON_DOMAIN') or os.environ.get('PCP_PYTHON_PMNS') -+ -+if __name__ == "__main__": -+ pmda = ROCESTATPMDA(name='rocestat', domain=254, user="root", logfile='rocestat.log') -+ -+ # Now enter the main loop -+ pmda.run() -diff --git a/src/pmdas/rocestat/pmns b/src/pmdas/rocestat/pmns -new file mode 100644 -index 0000000000..6eb997b7e1 ---- /dev/null -+++ b/src/pmdas/rocestat/pmns -@@ -0,0 +1 @@ -+rocestat 254:*:* -diff --git a/src/pmns/stdpmid.pcp b/src/pmns/stdpmid.pcp -index 41b2dfe51d..9a8e4d5bfa 100644 ---- a/src/pmns/stdpmid.pcp -+++ b/src/pmns/stdpmid.pcp -@@ -156,7 +156,7 @@ TRIVIAL 250 - FORQA 251 - QA_TEST_PERL 252 - SIMPLE 253 --### FREE SLOT 254 ### -+ROCESTAT 254 - MEMORY_PYTHON 255 - ### MORE FREE SLOTS 256..510 ### - # --- -2.43.5 diff --git a/SOURCES/1002-Merge-branch-pcp-rocestat-of-https-github.com-mohith.patch b/SOURCES/1002-Merge-branch-pcp-rocestat-of-https-github.com-mohith.patch deleted file mode 100644 index 750c2ad..0000000 --- a/SOURCES/1002-Merge-branch-pcp-rocestat-of-https-github.com-mohith.patch +++ /dev/null @@ -1,1093 +0,0 @@ -From 35303835eceaf98e7fc45b9d1c6f478881a0be6c Mon Sep 17 00:00:00 2001 -From: Nathan Scott -Date: Tue, 24 Jun 2025 07:41:45 +1000 -Subject: [PATCH 2/2] Merge branch 'pcp-rocestat' of - https://github.com/mohith-kumar-thummaluru/pcp into - mohith-kumar-thummaluru-pcp-rocestat - -Orabug: 38109331 - -(cherry picked from commit 858814e52563652591d3b7bd6db900f7465e3549) -Signed-off-by: Mohith Kumar Thummaluru ---- - qa/1994 | 48 ++++ - qa/1994.out | 415 +++++++++++++++++++++++++++++++ - qa/archives/GNUmakefile | 2 +- - qa/archives/mk.rocestat | 79 ++++++ - qa/group | 2 + - src/pcp/GNUmakefile | 1 + - src/pcp/rocestat/GNUmakefile | 44 ++++ - src/pcp/rocestat/pcp-rocestat.1 | 78 ++++++ - src/pcp/rocestat/pcp-rocestat.py | 315 +++++++++++++++++++++++ - 9 files changed, 983 insertions(+), 1 deletion(-) - create mode 100755 qa/1994 - create mode 100644 qa/1994.out - create mode 100755 qa/archives/mk.rocestat - create mode 100755 src/pcp/rocestat/GNUmakefile - create mode 100755 src/pcp/rocestat/pcp-rocestat.1 - create mode 100755 src/pcp/rocestat/pcp-rocestat.py - -diff --git a/qa/1994 b/qa/1994 -new file mode 100755 -index 0000000000..51bf85d5fa ---- /dev/null -+++ b/qa/1994 -@@ -0,0 +1,48 @@ -+ -+#!/bin/sh -+# PCP QA Test No. 1994 -+# Exercise various pcp-rocestat(1) command options. -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+ -+seq=`basename $0` -+echo "QA output created by $seq" -+ -+. ./common.python -+ -+status=1 # failure is the default! -+trap "cd $here; rm -rf $tmp.*; exit \$status" 0 1 2 3 15 -+ -+pcp_rocestat="$PCP_BINADM_DIR/pcp-rocestat" -+test -x $pcp_rocestat || _notrun "No pcp-rocestat(1) installed" -+pcp_rocestat="$python $pcp_rocestat" -+ -+# real QA test starts here -+echo && echo pcp-rocestat output : Display default output -+PCP_ARCHIVE="archives/pcp-rocestat" PCP_HOSTZONE=1 PCP_ORIGIN=1 $pcp_rocestat -s 2 -+ -+archive_first="-a archives/pcp-rocestat -z -O +1" -+ -+echo && echo pcp-rocestat output : Display all network statistics for nVidia RoCE devices. -+pcp $archive_first rocestat -s 2 -+ -+echo && echo pcp-rocestat output : Display hardware counter statistics for nVidia RoCE devices. -+pcp $archive_first rocestat --hw_stats -s 2 -+ -+echo && echo pcp-rocestat output : Display per lane statistics for nVidia RoCE devices. -+pcp $archive_first rocestat --lane_stats -s 2 -+ -+status=0 -+exit -diff --git a/qa/1994.out b/qa/1994.out -new file mode 100644 -index 0000000000..449e066325 ---- /dev/null -+++ b/qa/1994.out -@@ -0,0 +1,415 @@ -+QA output created by 1994 -+ -+pcp-rocestat output : Display default output -+zzz <06/19/25 23:21:52> -+ Counter mlx5_0_1 mlx5_0_2 -+ link_error_recovery 0(0) 0(0) -+ link_downed 0(0) 0(0) -+ local_link_integrity_errors 0(0) 0(0) -+ rnr_nak_retry_err 0(0) 0(0) -+ resp_local_length_error 0(0) 0(0) -+ resp_cqe_error 0(0) 0(0) -+ resp_cqe_flush_error 0(0) 0(0) -+ resp_remote_access_errors 0(0) 0(0) -+ req_remote_invalid_request 0(0) 0(0) -+ req_cqe_error 0(0) 0(0) -+ req_cqe_flush_error 0(0) 0(0) -+ duplicate_request 0(0) 0(0) -+ rx_read_requests 0(0) 0(0) -+ rx_atomic_requests 0(0) 0(0) -+ req_remote_access_errors 0(0) 0(0) -+ rx_write_requests 0(0) 0(0) -+ multicast_rcv_packets 0(0) 0(0) -+ multicast_xmit_packets 0(0) 0(0) -+ unicast_rcv_packets 12406304(0) 6484416(0) -+ unicast_xmit_packets 11679454(0) 6478121(0) -+ port_rcv_errors 0(0) 0(0) -+ port_rcv_remote_physical_errors 0(0) 0(0) -+ port_rcv_packets 12406304(0) 6484618(0) -+ port_rcv_data 267669766(0) 271326623(0) -+ port_rcv_constraint_errors 0(0) 0(0) -+ port_rcv_switch_relay_errors 0(0) 0(0) -+ port_xmit_data 247591758(0) 270392723(0) -+ port_xmit_constraint_errors 0(0) 0(0) -+ port_xmit_wait 0(0) 0(0) -+ port_xmit_packets 11679454(0) 6478253(0) -+ port_xmit_discards 0(0) 0(0) -+ roce_slow_restart_trans 0(0) 0(0) -+ roce_slow_restart_cnps 0(0) 0(0) -+ roce_slow_restart 0(0) 0(0) -+ roce_adp_retrans_to 0(0) 0(0) -+ clear_counters 0(0) 0(0) -+ local_ack_timeout_err 0(0) 0(0) -+ lifespan 10(0) 10(0) -+ implied_nak_seq_err 0(0) 0(0) -+ packet_seq_err 0(0) 0(0) -+ roce_adp_retrans 0(0) 0(0) -+ out_of_buffer 2412(0) 340(0) -+ out_of_sequence 0(0) 0(0) -+ VL15_dropped 0(0) 0(0) -+ excessive_buffer_overrun_errors 0(0) 0(0) -+ symbol_error 0(0) 0(0) -+ -+ Netdev Queue tx_bytes rx_bytes tx/s(Gbps) rx/s(Gbps) pause pause_delta -+ re0 Default(TCP) - 0 0 0 0 0 0 0 -+ re0 Normal Large - 1 0 0 0 0 0 0 -+ re0 VIP Small - 2 0 0 0 0 0 0 -+ re0 VIP Large - 3 0 0 0 0 0 0 -+ re0 Normal Small - 4 0 0 0 0 0 0 -+ re0 VIP Small - 5 0 0 0 0 0 0 -+ re0 Unused - 6 0 0 0 0 0 0 -+ re0 CNP - 7 0 0 0 0 0 0 -+ re1 Default(TCP) - 0 0 0 0 0 0 0 -+ re1 Normal Large - 1 0 0 0 0 0 0 -+ re1 VIP Small - 2 0 0 0 0 0 0 -+ re1 VIP Large - 3 0 0 0 0 0 0 -+ re1 Normal Small - 4 0 0 0 0 0 0 -+ re1 VIP Small - 5 0 0 0 0 0 0 -+ re1 Unused - 6 0 0 0 0 0 0 -+ re1 CNP - 7 0 0 0 0 0 0 -+ -+zzz <06/19/25 23:21:53> -+ Counter mlx5_0_1 mlx5_0_2 -+ link_error_recovery 0(0) 0(0) -+ link_downed 0(0) 0(0) -+ local_link_integrity_errors 0(0) 0(0) -+ rnr_nak_retry_err 0(0) 0(0) -+ resp_local_length_error 0(0) 0(0) -+ resp_cqe_error 0(0) 0(0) -+ resp_cqe_flush_error 0(0) 0(0) -+ resp_remote_access_errors 0(0) 0(0) -+ req_remote_invalid_request 0(0) 0(0) -+ req_cqe_error 0(0) 0(0) -+ req_cqe_flush_error 0(0) 0(0) -+ duplicate_request 0(0) 0(0) -+ rx_read_requests 0(0) 0(0) -+ rx_atomic_requests 0(0) 0(0) -+ req_remote_access_errors 0(0) 0(0) -+ rx_write_requests 0(0) 0(0) -+ multicast_rcv_packets 0(0) 0(0) -+ multicast_xmit_packets 0(0) 0(0) -+ unicast_rcv_packets 12406304(0) 6484416(0) -+ unicast_xmit_packets 11679454(0) 6478121(0) -+ port_rcv_errors 0(0) 0(0) -+ port_rcv_remote_physical_errors 0(0) 0(0) -+ port_rcv_packets 12406304(0) 6484618(0) -+ port_rcv_data 267669766(0) 271326623(0) -+ port_rcv_constraint_errors 0(0) 0(0) -+ port_rcv_switch_relay_errors 0(0) 0(0) -+ port_xmit_data 247591758(0) 270392723(0) -+ port_xmit_constraint_errors 0(0) 0(0) -+ port_xmit_wait 0(0) 0(0) -+ port_xmit_packets 11679454(0) 6478253(0) -+ port_xmit_discards 0(0) 0(0) -+ roce_slow_restart_trans 0(0) 0(0) -+ roce_slow_restart_cnps 0(0) 0(0) -+ roce_slow_restart 0(0) 0(0) -+ roce_adp_retrans_to 0(0) 0(0) -+ clear_counters 0(0) 0(0) -+ local_ack_timeout_err 0(0) 0(0) -+ lifespan 10(0) 10(0) -+ implied_nak_seq_err 0(0) 0(0) -+ packet_seq_err 0(0) 0(0) -+ roce_adp_retrans 0(0) 0(0) -+ out_of_buffer 2412(0) 340(0) -+ out_of_sequence 0(0) 0(0) -+ VL15_dropped 0(0) 0(0) -+ excessive_buffer_overrun_errors 0(0) 0(0) -+ symbol_error 0(0) 0(0) -+ -+ Netdev Queue tx_bytes rx_bytes tx/s(Gbps) rx/s(Gbps) pause pause_delta -+ re0 Default(TCP) - 0 0 0 0 0 0 0 -+ re0 Normal Large - 1 0 0 0 0 0 0 -+ re0 VIP Small - 2 0 0 0 0 0 0 -+ re0 VIP Large - 3 0 0 0 0 0 0 -+ re0 Normal Small - 4 0 0 0 0 0 0 -+ re0 VIP Small - 5 0 0 0 0 0 0 -+ re0 Unused - 6 0 0 0 0 0 0 -+ re0 CNP - 7 0 0 0 0 0 0 -+ re1 Default(TCP) - 0 0 0 0 0 0 0 -+ re1 Normal Large - 1 0 0 0 0 0 0 -+ re1 VIP Small - 2 0 0 0 0 0 0 -+ re1 VIP Large - 3 0 0 0 0 0 0 -+ re1 Normal Small - 4 0 0 0 0 0 0 -+ re1 VIP Small - 5 0 0 0 0 0 0 -+ re1 Unused - 6 0 0 0 0 0 0 -+ re1 CNP - 7 0 0 0 0 0 0 -+ -+ -+pcp-rocestat output : Display all network statistics for nVidia RoCE devices. -+zzz <06/19/25 23:21:52> -+ Counter mlx5_0_1 mlx5_0_2 -+ link_error_recovery 0(0) 0(0) -+ link_downed 0(0) 0(0) -+ local_link_integrity_errors 0(0) 0(0) -+ rnr_nak_retry_err 0(0) 0(0) -+ resp_local_length_error 0(0) 0(0) -+ resp_cqe_error 0(0) 0(0) -+ resp_cqe_flush_error 0(0) 0(0) -+ resp_remote_access_errors 0(0) 0(0) -+ req_remote_invalid_request 0(0) 0(0) -+ req_cqe_error 0(0) 0(0) -+ req_cqe_flush_error 0(0) 0(0) -+ duplicate_request 0(0) 0(0) -+ rx_read_requests 0(0) 0(0) -+ rx_atomic_requests 0(0) 0(0) -+ req_remote_access_errors 0(0) 0(0) -+ rx_write_requests 0(0) 0(0) -+ multicast_rcv_packets 0(0) 0(0) -+ multicast_xmit_packets 0(0) 0(0) -+ unicast_rcv_packets 12406304(0) 6484416(0) -+ unicast_xmit_packets 11679454(0) 6478121(0) -+ port_rcv_errors 0(0) 0(0) -+ port_rcv_remote_physical_errors 0(0) 0(0) -+ port_rcv_packets 12406304(0) 6484618(0) -+ port_rcv_data 267669766(0) 271326623(0) -+ port_rcv_constraint_errors 0(0) 0(0) -+ port_rcv_switch_relay_errors 0(0) 0(0) -+ port_xmit_data 247591758(0) 270392723(0) -+ port_xmit_constraint_errors 0(0) 0(0) -+ port_xmit_wait 0(0) 0(0) -+ port_xmit_packets 11679454(0) 6478253(0) -+ port_xmit_discards 0(0) 0(0) -+ roce_slow_restart_trans 0(0) 0(0) -+ roce_slow_restart_cnps 0(0) 0(0) -+ roce_slow_restart 0(0) 0(0) -+ roce_adp_retrans_to 0(0) 0(0) -+ clear_counters 0(0) 0(0) -+ local_ack_timeout_err 0(0) 0(0) -+ lifespan 10(0) 10(0) -+ implied_nak_seq_err 0(0) 0(0) -+ packet_seq_err 0(0) 0(0) -+ roce_adp_retrans 0(0) 0(0) -+ out_of_buffer 2412(0) 340(0) -+ out_of_sequence 0(0) 0(0) -+ VL15_dropped 0(0) 0(0) -+ excessive_buffer_overrun_errors 0(0) 0(0) -+ symbol_error 0(0) 0(0) -+ -+ Netdev Queue tx_bytes rx_bytes tx/s(Gbps) rx/s(Gbps) pause pause_delta -+ re0 Default(TCP) - 0 0 0 0 0 0 0 -+ re0 Normal Large - 1 0 0 0 0 0 0 -+ re0 VIP Small - 2 0 0 0 0 0 0 -+ re0 VIP Large - 3 0 0 0 0 0 0 -+ re0 Normal Small - 4 0 0 0 0 0 0 -+ re0 VIP Small - 5 0 0 0 0 0 0 -+ re0 Unused - 6 0 0 0 0 0 0 -+ re0 CNP - 7 0 0 0 0 0 0 -+ re1 Default(TCP) - 0 0 0 0 0 0 0 -+ re1 Normal Large - 1 0 0 0 0 0 0 -+ re1 VIP Small - 2 0 0 0 0 0 0 -+ re1 VIP Large - 3 0 0 0 0 0 0 -+ re1 Normal Small - 4 0 0 0 0 0 0 -+ re1 VIP Small - 5 0 0 0 0 0 0 -+ re1 Unused - 6 0 0 0 0 0 0 -+ re1 CNP - 7 0 0 0 0 0 0 -+ -+zzz <06/19/25 23:21:53> -+ Counter mlx5_0_1 mlx5_0_2 -+ link_error_recovery 0(0) 0(0) -+ link_downed 0(0) 0(0) -+ local_link_integrity_errors 0(0) 0(0) -+ rnr_nak_retry_err 0(0) 0(0) -+ resp_local_length_error 0(0) 0(0) -+ resp_cqe_error 0(0) 0(0) -+ resp_cqe_flush_error 0(0) 0(0) -+ resp_remote_access_errors 0(0) 0(0) -+ req_remote_invalid_request 0(0) 0(0) -+ req_cqe_error 0(0) 0(0) -+ req_cqe_flush_error 0(0) 0(0) -+ duplicate_request 0(0) 0(0) -+ rx_read_requests 0(0) 0(0) -+ rx_atomic_requests 0(0) 0(0) -+ req_remote_access_errors 0(0) 0(0) -+ rx_write_requests 0(0) 0(0) -+ multicast_rcv_packets 0(0) 0(0) -+ multicast_xmit_packets 0(0) 0(0) -+ unicast_rcv_packets 12406304(0) 6484416(0) -+ unicast_xmit_packets 11679454(0) 6478121(0) -+ port_rcv_errors 0(0) 0(0) -+ port_rcv_remote_physical_errors 0(0) 0(0) -+ port_rcv_packets 12406304(0) 6484618(0) -+ port_rcv_data 267669766(0) 271326623(0) -+ port_rcv_constraint_errors 0(0) 0(0) -+ port_rcv_switch_relay_errors 0(0) 0(0) -+ port_xmit_data 247591758(0) 270392723(0) -+ port_xmit_constraint_errors 0(0) 0(0) -+ port_xmit_wait 0(0) 0(0) -+ port_xmit_packets 11679454(0) 6478253(0) -+ port_xmit_discards 0(0) 0(0) -+ roce_slow_restart_trans 0(0) 0(0) -+ roce_slow_restart_cnps 0(0) 0(0) -+ roce_slow_restart 0(0) 0(0) -+ roce_adp_retrans_to 0(0) 0(0) -+ clear_counters 0(0) 0(0) -+ local_ack_timeout_err 0(0) 0(0) -+ lifespan 10(0) 10(0) -+ implied_nak_seq_err 0(0) 0(0) -+ packet_seq_err 0(0) 0(0) -+ roce_adp_retrans 0(0) 0(0) -+ out_of_buffer 2412(0) 340(0) -+ out_of_sequence 0(0) 0(0) -+ VL15_dropped 0(0) 0(0) -+ excessive_buffer_overrun_errors 0(0) 0(0) -+ symbol_error 0(0) 0(0) -+ -+ Netdev Queue tx_bytes rx_bytes tx/s(Gbps) rx/s(Gbps) pause pause_delta -+ re0 Default(TCP) - 0 0 0 0 0 0 0 -+ re0 Normal Large - 1 0 0 0 0 0 0 -+ re0 VIP Small - 2 0 0 0 0 0 0 -+ re0 VIP Large - 3 0 0 0 0 0 0 -+ re0 Normal Small - 4 0 0 0 0 0 0 -+ re0 VIP Small - 5 0 0 0 0 0 0 -+ re0 Unused - 6 0 0 0 0 0 0 -+ re0 CNP - 7 0 0 0 0 0 0 -+ re1 Default(TCP) - 0 0 0 0 0 0 0 -+ re1 Normal Large - 1 0 0 0 0 0 0 -+ re1 VIP Small - 2 0 0 0 0 0 0 -+ re1 VIP Large - 3 0 0 0 0 0 0 -+ re1 Normal Small - 4 0 0 0 0 0 0 -+ re1 VIP Small - 5 0 0 0 0 0 0 -+ re1 Unused - 6 0 0 0 0 0 0 -+ re1 CNP - 7 0 0 0 0 0 0 -+ -+ -+pcp-rocestat output : Display hardware counter statistics for nVidia RoCE devices. -+zzz <06/19/25 23:21:52> -+ Counter mlx5_0_1 mlx5_0_2 -+ link_error_recovery 0(0) 0(0) -+ link_downed 0(0) 0(0) -+ local_link_integrity_errors 0(0) 0(0) -+ rnr_nak_retry_err 0(0) 0(0) -+ resp_local_length_error 0(0) 0(0) -+ resp_cqe_error 0(0) 0(0) -+ resp_cqe_flush_error 0(0) 0(0) -+ resp_remote_access_errors 0(0) 0(0) -+ req_remote_invalid_request 0(0) 0(0) -+ req_cqe_error 0(0) 0(0) -+ req_cqe_flush_error 0(0) 0(0) -+ duplicate_request 0(0) 0(0) -+ rx_read_requests 0(0) 0(0) -+ rx_atomic_requests 0(0) 0(0) -+ req_remote_access_errors 0(0) 0(0) -+ rx_write_requests 0(0) 0(0) -+ multicast_rcv_packets 0(0) 0(0) -+ multicast_xmit_packets 0(0) 0(0) -+ unicast_rcv_packets 12406304(0) 6484416(0) -+ unicast_xmit_packets 11679454(0) 6478121(0) -+ port_rcv_errors 0(0) 0(0) -+ port_rcv_remote_physical_errors 0(0) 0(0) -+ port_rcv_packets 12406304(0) 6484618(0) -+ port_rcv_data 267669766(0) 271326623(0) -+ port_rcv_constraint_errors 0(0) 0(0) -+ port_rcv_switch_relay_errors 0(0) 0(0) -+ port_xmit_data 247591758(0) 270392723(0) -+ port_xmit_constraint_errors 0(0) 0(0) -+ port_xmit_wait 0(0) 0(0) -+ port_xmit_packets 11679454(0) 6478253(0) -+ port_xmit_discards 0(0) 0(0) -+ roce_slow_restart_trans 0(0) 0(0) -+ roce_slow_restart_cnps 0(0) 0(0) -+ roce_slow_restart 0(0) 0(0) -+ roce_adp_retrans_to 0(0) 0(0) -+ clear_counters 0(0) 0(0) -+ local_ack_timeout_err 0(0) 0(0) -+ lifespan 10(0) 10(0) -+ implied_nak_seq_err 0(0) 0(0) -+ packet_seq_err 0(0) 0(0) -+ roce_adp_retrans 0(0) 0(0) -+ out_of_buffer 2412(0) 340(0) -+ out_of_sequence 0(0) 0(0) -+ VL15_dropped 0(0) 0(0) -+ excessive_buffer_overrun_errors 0(0) 0(0) -+ symbol_error 0(0) 0(0) -+ -+ -+zzz <06/19/25 23:21:53> -+ Counter mlx5_0_1 mlx5_0_2 -+ link_error_recovery 0(0) 0(0) -+ link_downed 0(0) 0(0) -+ local_link_integrity_errors 0(0) 0(0) -+ rnr_nak_retry_err 0(0) 0(0) -+ resp_local_length_error 0(0) 0(0) -+ resp_cqe_error 0(0) 0(0) -+ resp_cqe_flush_error 0(0) 0(0) -+ resp_remote_access_errors 0(0) 0(0) -+ req_remote_invalid_request 0(0) 0(0) -+ req_cqe_error 0(0) 0(0) -+ req_cqe_flush_error 0(0) 0(0) -+ duplicate_request 0(0) 0(0) -+ rx_read_requests 0(0) 0(0) -+ rx_atomic_requests 0(0) 0(0) -+ req_remote_access_errors 0(0) 0(0) -+ rx_write_requests 0(0) 0(0) -+ multicast_rcv_packets 0(0) 0(0) -+ multicast_xmit_packets 0(0) 0(0) -+ unicast_rcv_packets 12406304(0) 6484416(0) -+ unicast_xmit_packets 11679454(0) 6478121(0) -+ port_rcv_errors 0(0) 0(0) -+ port_rcv_remote_physical_errors 0(0) 0(0) -+ port_rcv_packets 12406304(0) 6484618(0) -+ port_rcv_data 267669766(0) 271326623(0) -+ port_rcv_constraint_errors 0(0) 0(0) -+ port_rcv_switch_relay_errors 0(0) 0(0) -+ port_xmit_data 247591758(0) 270392723(0) -+ port_xmit_constraint_errors 0(0) 0(0) -+ port_xmit_wait 0(0) 0(0) -+ port_xmit_packets 11679454(0) 6478253(0) -+ port_xmit_discards 0(0) 0(0) -+ roce_slow_restart_trans 0(0) 0(0) -+ roce_slow_restart_cnps 0(0) 0(0) -+ roce_slow_restart 0(0) 0(0) -+ roce_adp_retrans_to 0(0) 0(0) -+ clear_counters 0(0) 0(0) -+ local_ack_timeout_err 0(0) 0(0) -+ lifespan 10(0) 10(0) -+ implied_nak_seq_err 0(0) 0(0) -+ packet_seq_err 0(0) 0(0) -+ roce_adp_retrans 0(0) 0(0) -+ out_of_buffer 2412(0) 340(0) -+ out_of_sequence 0(0) 0(0) -+ VL15_dropped 0(0) 0(0) -+ excessive_buffer_overrun_errors 0(0) 0(0) -+ symbol_error 0(0) 0(0) -+ -+ -+ -+pcp-rocestat output : Display per lane statistics for nVidia RoCE devices. -+zzz <06/19/25 23:21:52> -+ Netdev Queue tx_bytes rx_bytes tx/s(Gbps) rx/s(Gbps) pause pause_delta -+ re0 Default(TCP) - 0 0 0 0 0 0 0 -+ re0 Normal Large - 1 0 0 0 0 0 0 -+ re0 VIP Small - 2 0 0 0 0 0 0 -+ re0 VIP Large - 3 0 0 0 0 0 0 -+ re0 Normal Small - 4 0 0 0 0 0 0 -+ re0 VIP Small - 5 0 0 0 0 0 0 -+ re0 Unused - 6 0 0 0 0 0 0 -+ re0 CNP - 7 0 0 0 0 0 0 -+ re1 Default(TCP) - 0 0 0 0 0 0 0 -+ re1 Normal Large - 1 0 0 0 0 0 0 -+ re1 VIP Small - 2 0 0 0 0 0 0 -+ re1 VIP Large - 3 0 0 0 0 0 0 -+ re1 Normal Small - 4 0 0 0 0 0 0 -+ re1 VIP Small - 5 0 0 0 0 0 0 -+ re1 Unused - 6 0 0 0 0 0 0 -+ re1 CNP - 7 0 0 0 0 0 0 -+ -+zzz <06/19/25 23:21:53> -+ Netdev Queue tx_bytes rx_bytes tx/s(Gbps) rx/s(Gbps) pause pause_delta -+ re0 Default(TCP) - 0 0 0 0 0 0 0 -+ re0 Normal Large - 1 0 0 0 0 0 0 -+ re0 VIP Small - 2 0 0 0 0 0 0 -+ re0 VIP Large - 3 0 0 0 0 0 0 -+ re0 Normal Small - 4 0 0 0 0 0 0 -+ re0 VIP Small - 5 0 0 0 0 0 0 -+ re0 Unused - 6 0 0 0 0 0 0 -+ re0 CNP - 7 0 0 0 0 0 0 -+ re1 Default(TCP) - 0 0 0 0 0 0 0 -+ re1 Normal Large - 1 0 0 0 0 0 0 -+ re1 VIP Small - 2 0 0 0 0 0 0 -+ re1 VIP Large - 3 0 0 0 0 0 0 -+ re1 Normal Small - 4 0 0 0 0 0 0 -+ re1 VIP Small - 5 0 0 0 0 0 0 -+ re1 Unused - 6 0 0 0 0 0 0 -+ re1 CNP - 7 0 0 0 0 0 0 -+ -diff --git a/qa/archives/GNUmakefile b/qa/archives/GNUmakefile -index a26d1b77ba..523812a2e8 100644 ---- a/qa/archives/GNUmakefile -+++ b/qa/archives/GNUmakefile -@@ -77,7 +77,7 @@ SCRIPTS = mk.diff mk.gap mk.mysql mk.schizo mk.foo+ \ - mk.log-derived mk.vmstat mk.rep mk.procsched mk.netstat \ - mk.ipcs mk.value-test mk.rank-pred mk.cputime mk.meminfo \ - mk.sample-labels mk.pmproxy mk.mmv.help mk.slabinfo mk.zoneinfo \ -- mk.atop-sar mk.pmstat -+ mk.atop-sar mk.pmstat mk.rocestat - - CONFIGS = config.verify config.shping YWhcCi.toium.config \ - kenj-pc-2.config reduce.config uwrap.config wrap.config \ -diff --git a/qa/archives/mk.rocestat b/qa/archives/mk.rocestat -new file mode 100755 -index 0000000000..b074c7706f ---- /dev/null -+++ b/qa/archives/mk.rocestat -@@ -0,0 +1,79 @@ -+#!/bin/sh -+# -+# remake the pcp-netstat archive ... -+# this archive is intended to be checked in and not remade, this script is -+# simply a record of how it was created -+# -+ -+. /etc/pcp.env -+ -+tmp=/var/tmp/$$ -+rm -f $tmp.* -+trap "rm -f $tmp.*; exit 0" 0 1 2 3 15 -+ -+cat <>$tmp.config -+ -+log mandatory on 5 sec { -+ rocestat.hw.link.link_error_recovery -+ rocestat.hw.link.link_downed -+ rocestat.hw.link.local_link_integrity_errors -+ rocestat.hw.rnr.rnr_nak_retry_err -+ rocestat.hw.resp.resp_local_length_error -+ rocestat.hw.resp.resp_cqe_error -+ rocestat.hw.resp.resp_cqe_flush_error -+ rocestat.hw.resp.resp_remote_access_errors -+ rocestat.hw.req.req_remote_invalid_request -+ rocestat.hw.req.req_cqe_error -+ rocestat.hw.req.req_cqe_flush_error -+ rocestat.hw.req.duplicate_request -+ rocestat.hw.req.rx_read_requests -+ rocestat.hw.req.rx_atomic_requests -+ rocestat.hw.req.req_remote_access_errors -+ rocestat.hw.req.rx_write_requests -+ rocestat.hw.mcast.multicast_rcv_packets -+ rocestat.hw.mcast.multicast_xmit_packets -+ rocestat.hw.ucast.unicast_rcv_packets -+ rocestat.hw.ucast.unicast_xmit_packets -+ rocestat.hw.rcv.port_rcv_errors -+ rocestat.hw.rcv.port_rcv_remote_physical_errors -+ rocestat.hw.rcv.port_rcv_packets -+ rocestat.hw.rcv.port_rcv_data -+ rocestat.hw.rcv.port_rcv_constraint_errors -+ rocestat.hw.rcv.port_rcv_switch_relay_errors -+ rocestat.hw.xmit.port_xmit_data -+ rocestat.hw.xmit.port_xmit_constraint_errors -+ rocestat.hw.xmit.port_xmit_wait -+ rocestat.hw.xmit.port_xmit_packets -+ rocestat.hw.xmit.port_xmit_discards -+ rocestat.hw.roce_slow_restart_trans -+ rocestat.hw.roce_slow_restart_cnps -+ rocestat.hw.roce_slow_restart -+ rocestat.hw.roce_adp_retrans_to -+ rocestat.hw.clear_counters -+ rocestat.hw.local_ack_timeout_err -+ rocestat.hw.lifespan -+ rocestat.hw.implied_nak_seq_err -+ rocestat.hw.packet_seq_err -+ rocestat.hw.roce_adp_retrans -+ rocestat.hw.out_of_buffer -+ rocestat.hw.out_of_sequence -+ rocestat.hw.VL15_dropped -+ rocestat.hw.excessive_buffer_overrun_errors -+ rocestat.hw.symbol_error -+ rocestat.lane.tx_bytes -+ rocestat.lane.rx_bytes -+ rocestat.lane.rx_pause -+} -+End-of-File -+ -+rm -f pcp-rocestat.0.xz pcp-rocestat.meta.xz pcp-rocestat.index.xz -+ -+if pmlogger -s 10 -c $tmp.config pcp-rocestat -+then -+ xz pcp-rocestat.0 -+ xz pcp-rocestat.meta -+ xz pcp-rocestat.index -+else -+ echo "Argh: pmlogger failed ..." -+ cat pmlogger.log -+fi -diff --git a/qa/group b/qa/group -index 137dba39f8..b2b9ad1aee 100644 ---- a/qa/group -+++ b/qa/group -@@ -383,6 +383,7 @@ meminfo - mpstat - netstat - ps -+rocestat - slabinfo - - # full test search -@@ -2217,8 +2218,9 @@ pmcd.pdu - 1991 pcp netstat python local - 1992 pmda.uwsgi local - 1993 pmda.rocestat local python -+1994 pcp rocestat python local - 2100 pmproxy local security - 2101 pmda.sockets local security - 2104 libpcp local security - 2105 libpcp pmcd local security pmcd.pdu - 4751 libpcp threads valgrind local pcp helgrind -diff --git a/src/pcp/GNUmakefile b/src/pcp/GNUmakefile -index 09361a4103..da292695d3 100644 ---- a/src/pcp/GNUmakefile -+++ b/src/pcp/GNUmakefile -@@ -33,6 +33,7 @@ SUBDIRS = \ - pidstat \ - ps \ - python \ -+ rocestat \ - shping \ - slabinfo \ - ss \ -diff --git a/src/pcp/rocestat/GNUmakefile b/src/pcp/rocestat/GNUmakefile -new file mode 100755 -index 0000000000..0ead2d8eb7 ---- /dev/null -+++ b/src/pcp/rocestat/GNUmakefile -@@ -0,0 +1,44 @@ -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+ -+TOPDIR = ../../.. -+include $(TOPDIR)/src/include/builddefs -+ -+TARGET = pcp-rocestat -+SCRIPT = $(TARGET).py -+MAN_SECTION = 1 -+MAN_PAGES = $(TARGET).$(MAN_SECTION) -+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION) -+ -+default: $(SCRIPT) $(MAN_PAGES) -+ -+include $(BUILDRULES) -+ -+install: default -+ifeq "$(HAVE_PYTHON)" "true" -+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BINADM_DIR)/$(TARGET) -+ @$(INSTALL_MAN) -+endif -+ -+default_pcp : default -+ -+install_pcp : install -+ -+check:: $(SCRIPT) -+ $(PYLINT) $^ -+ -+check :: $(MAN_PAGES) -+ $(MANLINT) $^ -+ -diff --git a/src/pcp/rocestat/pcp-rocestat.1 b/src/pcp/rocestat/pcp-rocestat.1 -new file mode 100755 -index 0000000000..a4b8ede416 ---- /dev/null -+++ b/src/pcp/rocestat/pcp-rocestat.1 -@@ -0,0 +1,78 @@ -+'\"! tbl | mmdoc -+'\"macro stdmacro -+.\" -+.\" Copyright (c) 2025 Oracle and/or its affiliates. -+.\" DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+.\" -+.\" This program is free software; you can redistribute it and/or modify it -+.\" under the terms of the GNU General Public License as published by the -+.\" Free Software Foundation; either version 2 of the License, or (at your -+.\" option) any later version. -+.\" -+.\" This program is distributed in the hope that it will be useful, but -+.\" WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+.\" or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+.\" for more details. -+.\" -+.TH PCP-ROCESTAT 1 "PCP" "Performance Co-Pilot" -+.SH NAME -+\f3pcp-rocestat\f1 \- Reports statistics for nVidia RoCE devices. -+.SH SYNOPSIS -+\f3pcp\f1 -+[\f2pcp\ options\f1] -+\f3rocestat\f1 -+\f2[--hw_stats|--lane_stats]\f2 -+[\f3\-s\f1 \f2samples\f1] -+[\f3\-a\f1 \f2archive\f1] -+[\f3\-S\f1 \f2start_time\f1] -+[\f3\-T\f1 \f2end_time\f1] -+ -+.SH DESCRIPTION -+The -+.B pcp-rocestat -+tool for viewing nVidia RoCE device counters and monitoring real-time throughput on local interfaces. -+This tool utilizes ethtool counters for each device and reports throughput per priority queue. -+By default -+.B pcp-rocestat -+reports live data for the local host. It has the capabilities to analyze the data on the archives as well. -+.SH OPTIONS -+.TP -+\fB\-\-hw_stats\fP -+Display hardware counters -+.TP -+\fB\-\-lane_stats\fP -+Display lane stats -+.TP -+\fB\-a\fP, \fB\-\-archive\fP -+Specify the archive to examine -+.TP -+\fB\-s\fP, \fB\-\-samples\fP -+Specify number of samples -+.TP -+\fB\-S\fP, \fB\-\-START TIME\fP -+Filter the samples from the archive from the given time -+.TP -+\fB\-T\fP, \fB\-\-END TIME\fP -+Filter the samples from the archive till the given time -+.TP -+\fB\-V\fR, \fB\-\-version\fR -+Display version number and exit. -+.TP -+\fB\-?\fR, \fB\-\-help\fR -+Display usage message and exit. -+.SH PCP ENVIRONMENT -+Environment variables with the prefix \fBPCP_\fP are used to parameterize -+the file and directory names used by PCP. -+On each installation, the -+file \fI/etc/pcp.conf\fP contains the local values for these variables. -+The \fB$PCP_CONF\fP variable may be used to specify an alternative -+configuration file, as described in \fBpcp.conf\fP(5). -+.PP -+For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3). -+.SH SEE ALSO -+.BR PCPIntro (1), -+.BR pcp (1), -+.BR pmParseInterval (3) -+and -+.BR environ (7). -+ -diff --git a/src/pcp/rocestat/pcp-rocestat.py b/src/pcp/rocestat/pcp-rocestat.py -new file mode 100755 -index 0000000000..79bf98a7f3 ---- /dev/null -+++ b/src/pcp/rocestat/pcp-rocestat.py -@@ -0,0 +1,315 @@ -+#!/usr/bin/pmpython -+# -+# Copyright (c) 2025 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+# pylint: disable=bad-whitespace,too-many-arguments,too-many-lines, bad-continuation, line-too-long -+# pylint: disable=redefined-outer-name,unnecessary-lambda, wildcard-import, unused-wildcard-import -+# -+from datetime import datetime -+from pcp import pmapi -+from pcp import pmcc -+import sys -+import time -+ -+ROCESTAT_HW_METRICS = [ -+ 'rocestat.hw.link.link_error_recovery', -+ 'rocestat.hw.link.link_downed', -+ 'rocestat.hw.link.local_link_integrity_errors', -+ 'rocestat.hw.rnr.rnr_nak_retry_err', -+ 'rocestat.hw.resp.resp_local_length_error', -+ 'rocestat.hw.resp.resp_cqe_error', -+ 'rocestat.hw.resp.resp_cqe_flush_error', -+ 'rocestat.hw.resp.resp_remote_access_errors', -+ 'rocestat.hw.req.req_remote_invalid_request', -+ 'rocestat.hw.req.req_cqe_error', -+ 'rocestat.hw.req.req_cqe_flush_error', -+ 'rocestat.hw.req.duplicate_request', -+ 'rocestat.hw.req.rx_read_requests', -+ 'rocestat.hw.req.rx_atomic_requests', -+ 'rocestat.hw.req.req_remote_access_errors', -+ 'rocestat.hw.req.rx_write_requests', -+ 'rocestat.hw.mcast.multicast_rcv_packets', -+ 'rocestat.hw.mcast.multicast_xmit_packets', -+ 'rocestat.hw.ucast.unicast_rcv_packets', -+ 'rocestat.hw.ucast.unicast_xmit_packets', -+ 'rocestat.hw.rcv.port_rcv_errors', -+ 'rocestat.hw.rcv.port_rcv_remote_physical_errors', -+ 'rocestat.hw.rcv.port_rcv_packets', -+ 'rocestat.hw.rcv.port_rcv_data', -+ 'rocestat.hw.rcv.port_rcv_constraint_errors', -+ 'rocestat.hw.rcv.port_rcv_switch_relay_errors', -+ 'rocestat.hw.xmit.port_xmit_data', -+ 'rocestat.hw.xmit.port_xmit_constraint_errors', -+ 'rocestat.hw.xmit.port_xmit_wait', -+ 'rocestat.hw.xmit.port_xmit_packets', -+ 'rocestat.hw.xmit.port_xmit_discards', -+ 'rocestat.hw.roce_slow_restart_trans', -+ 'rocestat.hw.roce_slow_restart_cnps', -+ 'rocestat.hw.roce_slow_restart', -+ 'rocestat.hw.roce_adp_retrans_to', -+ 'rocestat.hw.clear_counters', -+ 'rocestat.hw.local_ack_timeout_err', -+ 'rocestat.hw.lifespan', -+ 'rocestat.hw.implied_nak_seq_err', -+ 'rocestat.hw.packet_seq_err', -+ 'rocestat.hw.roce_adp_retrans', -+ 'rocestat.hw.out_of_buffer', -+ 'rocestat.hw.out_of_sequence', -+ 'rocestat.hw.VL15_dropped', -+ 'rocestat.hw.excessive_buffer_overrun_errors', -+ 'rocestat.hw.symbol_error', -+] -+ -+ROCESTAT_HW_METRICS_DESC = [ -+ 'link_error_recovery', -+ 'link_downed', -+ 'local_link_integrity_errors', -+ 'rnr_nak_retry_err', -+ 'resp_local_length_error', -+ 'resp_cqe_error', -+ 'resp_cqe_flush_error', -+ 'resp_remote_access_errors', -+ 'req_remote_invalid_request', -+ 'req_cqe_error', -+ 'req_cqe_flush_error', -+ 'duplicate_request', -+ 'rx_read_requests', -+ 'rx_atomic_requests', -+ 'req_remote_access_errors', -+ 'rx_write_requests', -+ 'multicast_rcv_packets', -+ 'multicast_xmit_packets', -+ 'unicast_rcv_packets', -+ 'unicast_xmit_packets', -+ 'port_rcv_errors', -+ 'port_rcv_remote_physical_errors', -+ 'port_rcv_packets', -+ 'port_rcv_data', -+ 'port_rcv_constraint_errors', -+ 'port_rcv_switch_relay_errors', -+ 'port_xmit_data', -+ 'port_xmit_constraint_errors', -+ 'port_xmit_wait', -+ 'port_xmit_packets', -+ 'port_xmit_discards', -+ 'roce_slow_restart_trans', -+ 'roce_slow_restart_cnps', -+ 'roce_slow_restart', -+ 'roce_adp_retrans_to', -+ 'clear_counters', -+ 'local_ack_timeout_err', -+ 'lifespan', -+ 'implied_nak_seq_err', -+ 'packet_seq_err', -+ 'roce_adp_retrans', -+ 'out_of_buffer', -+ 'out_of_sequence', -+ 'VL15_dropped', -+ 'excessive_buffer_overrun_errors', -+ 'symbol_error', -+] -+ -+ROCESTAT_PER_LANE_METRICS = ['rocestat.lane.tx_bytes', 'rocestat.lane.rx_bytes', 'rocestat.lane.rx_pause'] -+ -+ -+class RoceStatOptions(pmapi.pmOptions): -+ context = None -+ timefmt = "%H:%M:%S" -+ samples = 0 -+ hw_stats_flag = False -+ lane_stats_flag = False -+ -+ def extra_options(self, opt, optarg, index): -+ if opt == "hw_stats": -+ RoceStatOptions.hw_stats_flag = True -+ -+ elif opt == "lane_stats": -+ RoceStatOptions.lane_stats_flag = True -+ -+ def __init__(self): -+ pmapi.pmOptions.__init__(self, "a:s:S:T:t:") -+ self.pmSetLongOption("hw_stats", 0, "", "", "Display hw counters") -+ self.pmSetLongOption("lane_stats", 0, "", "", "Display lane stats") -+ self.pmSetLongOptionStart() -+ self.pmSetLongOptionFinish() -+ self.pmSetLongOption("samples", 1, "s", "COUNT", -+ "Number of samples to collect") -+ self.pmSetLongOptionArchive() -+ self.pmSetLongOptionHelp() -+ -+ self.pmSetOptionCallback(self.extra_options) -+ -+ -+class ROCeStatReport: -+ prev_counter_data = None -+ def __init__(self, samples): -+ self.samples = samples -+ -+ def report(self, manager): -+ group = manager["rocestat"] -+ -+ opts.pmGetOptionSamples() -+ -+ timestamp = manager.pmLocaltime(group.timestamp.tv_sec) -+ time_string = time.strftime("%x", timestamp.struct_time()) + " " -+ t_s = group.contextCache.pmLocaltime(int(group.timestamp)) -+ time_string += time.strftime(RoceStatOptions.timefmt, t_s.struct_time()) -+ print("zzz <%s>" % (time_string)) -+ hw_stats_flag = RoceStatOptions.hw_stats_flag -+ lane_stats_flag = RoceStatOptions.lane_stats_flag -+ -+ # HW Counters -+ if hw_stats_flag or (hw_stats_flag == lane_stats_flag): -+ header = ["Counter"] -+ for val in group[ROCESTAT_HW_METRICS[0]].netValues: -+ if not val[1].endswith("_delta"): -+ header.append(val[1]) -+ card_idxs = {} -+ for i in range(1, len(header)): -+ card_idxs[header[i]] = i -+ prev_counter_data = self.prev_counter_data -+ curr_counter_data = {} -+ curr_counter_data["ts"] = datetime.now() -+ for card in card_idxs: -+ curr_counter_data[card] = {} -+ -+ for metric_name in ROCESTAT_HW_METRICS_DESC: -+ metric_idx = ROCESTAT_HW_METRICS_DESC.index(metric_name) -+ metric = ROCESTAT_HW_METRICS[metric_idx] -+ for val in group[metric].netValues: -+ card_name, value = val[1], val[2] -+ curr_counter_data[card_name][metric_name] = value -+ -+ counters = [] -+ for counter in ROCESTAT_HW_METRICS_DESC: -+ row = [counter] + ["" for _ in range(len(card_idxs.keys()))] -+ for card, card_data in curr_counter_data.items(): -+ if card == "ts": -+ continue -+ card_idx = card_idxs[card] -+ count = card_data[counter] -+ if prev_counter_data is not None and card in prev_counter_data: -+ count_delta = count - prev_counter_data[card][counter] -+ time_diff = (curr_counter_data["ts"] - prev_counter_data["ts"]).total_seconds() -+ count_delta /= time_diff -+ if "data" in counter: -+ count_delta = self.convert_to_gbps(count_delta) -+ count_delta = round(count_delta, 2) -+ if int(count_delta) == count_delta: -+ count_delta = int(count_delta) -+ else: -+ count_delta = 0 -+ row[card_idx] = f"{count}({count_delta})" -+ counters.append(row) -+ self.prev_counter_data = curr_counter_data -+ -+ space = 5 + max(len(counter) -+ for counter in ROCESTAT_HW_METRICS_DESC) -+ print_format = f"%{space}s " + \ -+ " ".join(["%25s" for _ in range(len(header)-1)]) -+ if len(header) > 1: -+ print(print_format % tuple(header)) -+ for counter in counters: -+ print(print_format % tuple(counter)) -+ print() -+ -+ # Per lane stats -+ if lane_stats_flag or (lane_stats_flag == hw_stats_flag): -+ lane_stats = {} -+ for metric in ROCESTAT_PER_LANE_METRICS: -+ idx = 0 -+ try: -+ val = group[metric].netValues -+ except IndexError: -+ idx += 1 -+ continue -+ -+ for elem in val: -+ lane = elem[1] -+ value = elem[2] -+ -+ if lane not in lane_stats: -+ lane_stats[lane] = { -+ m: 0 for m in ROCESTAT_PER_LANE_METRICS} -+ lane_stats[lane][metric] = value -+ -+ if lane_stats: -+ print("%10s %20s %12s %12s %12s %12s %12s %12s" % ( -+ "Netdev", "Queue", "tx_bytes", "rx_bytes", "tx/s(Gbps)", "rx/s(Gbps)", "pause", "pause_delta")) -+ for lane, metrics in lane_stats.items(): -+ netdev, queue = self.split_lane(lane) -+ tx_bw_gbps = self.convert_to_gbps( -+ metrics.get('rocestat.lane.tx_bytes_bw', 0)) -+ rx_bw_gbps = self.convert_to_gbps( -+ metrics.get('rocestat.lane.rx_bytes_bw', 0)) -+ print("%10s %20s %12s %12s %12s %12s %12s %12s" % ( -+ netdev, queue, -+ metrics.get('tx_bytes', 0), -+ metrics.get('rx_bytes', 0), -+ tx_bw_gbps, -+ rx_bw_gbps, -+ metrics.get('rx_pause', 0), -+ metrics.get('rx_pause_delta', 0) -+ )) -+ print() -+ -+ def split_lane(self, lane): -+ parts = lane.split('_') -+ if len(parts) == 2: -+ netdev = parts[0] -+ queue_id = parts[1].replace("lane", "") -+ -+ queue_name = { -+ "0": "Default(TCP) - 0", -+ "1": "Normal Large - 1", -+ "2": "VIP Small - 2", -+ "3": "VIP Large - 3", -+ "4": "Normal Small - 4", -+ "5": "VIP Small - 5", -+ "6": "Unused - 6", -+ "7": "CNP - 7" -+ }.get(queue_id, f"Queue - {queue_id}") -+ -+ return netdev, queue_name -+ return lane, "Unknown Queue" -+ -+ def convert_to_gbps(self, value): -+ if value == 0: -+ return 0 -+ return value / 1000000000 -+ -+ -+if __name__ == '__main__': -+ try: -+ opts = RoceStatOptions() -+ mngr = pmcc.MetricGroupManager.builder(opts, sys.argv) -+ RoceStatOptions.context = mngr.type -+ missing = mngr.checkMissingMetrics( -+ ROCESTAT_HW_METRICS + ROCESTAT_PER_LANE_METRICS) -+ if missing is not None: -+ sys.stderr.write( -+ 'Error: not all required metrics are available\nMissing %s\n' % missing) -+ sys.exit(1) -+ mngr["rocestat"] = ROCESTAT_HW_METRICS + ROCESTAT_PER_LANE_METRICS -+ mngr.printer = ROCeStatReport(opts.samples) -+ sts = mngr.run() -+ sys.exit(sts) -+ -+ except pmapi.pmErr as error: -+ sys.stderr.write("%s %s\n" % (error.progname(), error.message())) -+ except pmapi.pmUsageErr as usage: -+ usage.message() -+ sys.exit(1) -+ except KeyboardInterrupt: -+ pass --- -2.43.5 diff --git a/SOURCES/1003-pcp-meminfo-additional-metrics-added-for-mem.util-to.patch b/SOURCES/1003-pcp-meminfo-additional-metrics-added-for-mem.util-to.patch deleted file mode 100644 index 9c554e7..0000000 --- a/SOURCES/1003-pcp-meminfo-additional-metrics-added-for-mem.util-to.patch +++ /dev/null @@ -1,56 +0,0 @@ -From e1c81c356a176fa244b3b32a2b98362d75fd2842 Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Thu, 9 Oct 2025 20:30:48 +0530 -Subject: [PATCH] pcp-meminfo: additional metrics added for mem.util to show - newly added kernel metrics by following commit - 1c559713ebc5c02759de59013066c3d29e4395e4 - -Signed-off-by: sagar sagar - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2180/commits/43025e54a97cb8aa2a3cc955a9fb1d0cb3f39cf4 - -Orabug: 38526351 - -Signed-off-by: Sourav Sharma - ---- - src/pcp/meminfo/pcp-meminfo.py | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py -index 103aa52..bae8931 100755 ---- a/src/pcp/meminfo/pcp-meminfo.py -+++ b/src/pcp/meminfo/pcp-meminfo.py -@@ -57,10 +57,14 @@ METRICS = ["mem.physmem", - "mem.util.vmallocTotal", - "mem.util.vmallocUsed", - "mem.util.vmallocChunk", -+ "mem.util.percpu", - "mem.util.corrupthardware", - "mem.util.anonhugepages", - "mem.vmstat.nr_shmem_hugepages", - "mem.vmstat.nr_shmem_pmdmapped", -+ "mem.util.filehugepages", -+ "mem.util.filepmdmapped", -+ "mem.util.cmatotal", - "mem.zoneinfo.nr_free_cma", - "mem.util.hugepagesTotal", - "mem.util.hugepagesFree", -@@ -105,10 +109,14 @@ METRICS_DESC = ["MemTotal", - "VmallocTotal", - "VmallocUsed", - "VmallocChunk", -+ "Percpu", - "HardwareCorrupted", - "AnonHugePages", - "ShmemHugePages", - "ShmemPmdMapped", -+ "FileHugePages", -+ "FilePmdMapped", -+ "CmaTotal", - "CmaFree", - "HugePages_Total_NO_kb", - "HugePages_Free_NO_kb", --- -2.43.7 - diff --git a/SOURCES/1004-meminfo-added-kreclaimable-and-hugtlb-metrics.patch b/SOURCES/1004-meminfo-added-kreclaimable-and-hugtlb-metrics.patch deleted file mode 100644 index 7ed4496..0000000 --- a/SOURCES/1004-meminfo-added-kreclaimable-and-hugtlb-metrics.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 975ec9e98e8ccbfbb2bfe5652920b3b3cd5208fe Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Thu, 9 Oct 2025 20:44:30 +0530 -Subject: [PATCH] meminfo: Added mem.util.kreclaimable and mem.util.hugetlb - metrics for meminfo - -updated pcp-meminfo tool as well two show these metrics -data - -Signed-off-by: sagar sagar - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2180/commits/81393407ec8d31642f637ab25eb3f9c082dab821 - -Orabug: 38526351 - -Signed-off-by: Sourav Sharma - ---- - src/pcp/meminfo/pcp-meminfo.py | 4 ++++ - src/pmdas/linux/help | 3 +++ - src/pmdas/linux/pmda.c | 20 ++++++++++++++++++++ - src/pmdas/linux/proc_meminfo.c | 2 ++ - src/pmdas/linux/proc_meminfo.h | 2 ++ - src/pmdas/linux/root_linux | 2 ++ - 6 files changed, 33 insertions(+) - -diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py -index bae8931..8492859 100755 ---- a/src/pcp/meminfo/pcp-meminfo.py -+++ b/src/pcp/meminfo/pcp-meminfo.py -@@ -44,6 +44,7 @@ METRICS = ["mem.physmem", - "mem.util.anonpages", - "mem.util.mapped", - "mem.util.shared", -+ "mem.util.kreclaimable", - "mem.util.slab", - "mem.util.slabReclaimable", - "mem.util.slabUnreclaimable", -@@ -71,6 +72,7 @@ METRICS = ["mem.physmem", - "mem.util.hugepagesRsvd", - "mem.util.hugepagesSurp", - "hinv.hugepagesize", -+ "mem.util.hugetlb", - "mem.util.directMap4k", - "mem.util.directMap2M", - "mem.util.directMap1G"] -@@ -96,6 +98,7 @@ METRICS_DESC = ["MemTotal", - "AnonPages", - "Mapped", - "Shmem", -+ "KReclaimable", - "Slab", - "SReclaimable", - "SUnreclaim", -@@ -123,6 +126,7 @@ METRICS_DESC = ["MemTotal", - "HugePages_Rsvd_NO_kb", - "HugePages_Surp_NO_kb", - "Hugepagesize", -+ "Hugetlb", - "DirectMap4k", - "DirectMap2M", - "DirectMap1G"] -diff --git a/src/pmdas/linux/help b/src/pmdas/linux/help -index 0429eeb..7c4b99d 100644 ---- a/src/pmdas/linux/help -+++ b/src/pmdas/linux/help -@@ -993,6 +993,9 @@ corruption of the call stack, allowing the kernel to react to such an - attach in an appropriate fashion. Shadow stacks are often maintained - by the processor hardware and require additional stack memory. - @ mem.util.percpu amount of per CPU allocator memory -+@ mem.util.kreclaimable Kbytes in kernel reclaimable memory, from /proc/meminfo -+Kernel allocations that the kernel will attempt to reclaim under memory pressure. -+@ mem.util.hugetlb the total amount of memory (in kB), consumed by huge pages of all sizes. - - @ mem.numa.util.total per-node total memory - @ mem.numa.util.free per-node free memory -diff --git a/src/pmdas/linux/pmda.c b/src/pmdas/linux/pmda.c -index d7c05c2..399f7f6 100644 ---- a/src/pmdas/linux/pmda.c -+++ b/src/pmdas/linux/pmda.c -@@ -1196,6 +1196,16 @@ static pmdaMetric metrictab[] = { - { PMDA_PMID(CLUSTER_MEMINFO,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, - PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, - -+/* mem.util.kreclaimable */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_MEMINFO,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.util.hugetlb */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_MEMINFO,76), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ - /* mem.numa.util.total */ - { NULL, - { PMDA_PMID(CLUSTER_NUMA_MEMINFO,0), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -@@ -8755,6 +8765,16 @@ linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) - return 0; /* no values available */ - atom->ull = proc_meminfo.Percpu; - break; -+ case 75: /* mem.util.kreclaimable (in kbytes) */ -+ if (!MEMINFO_VALID_VALUE(proc_meminfo.KReclaimable)) -+ return 0; /* no values available */ -+ atom->ull = proc_meminfo.KReclaimable; -+ break; -+ case 76: /* mem.util.hugetlb (in kbytes) */ -+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Hugetlb)) -+ return 0; /* no values available */ -+ atom->ull = proc_meminfo.Hugetlb; -+ break; - default: - return PM_ERR_PMID; - } -diff --git a/src/pmdas/linux/proc_meminfo.c b/src/pmdas/linux/proc_meminfo.c -index 7d9f5ce..8ae2936 100644 ---- a/src/pmdas/linux/proc_meminfo.c -+++ b/src/pmdas/linux/proc_meminfo.c -@@ -54,6 +54,7 @@ static struct { - { "AnonPages", &moff.AnonPages }, - { "Mapped", &moff.Mapped }, - { "Shmem", &moff.Shmem }, -+ { "KReclaimable", &moff.KReclaimable }, - { "Slab", &moff.Slab }, - { "SReclaimable", &moff.SlabReclaimable }, - { "SUnreclaim", &moff.SlabUnreclaimable }, -@@ -87,6 +88,7 @@ static struct { - { "HugePages_Rsvd", &moff.HugepagesRsvd }, - { "HugePages_Surp", &moff.HugepagesSurp }, - { "Hugepagesize", &moff.Hugepagesize }, -+ { "Hugetlb", &moff.Hugetlb }, - { "DirectMap4k", &moff.directMap4k }, - { "DirectMap2M", &moff.directMap2M }, - { "DirectMap1G", &moff.directMap1G }, -diff --git a/src/pmdas/linux/proc_meminfo.h b/src/pmdas/linux/proc_meminfo.h -index 321594c..17e504f 100644 ---- a/src/pmdas/linux/proc_meminfo.h -+++ b/src/pmdas/linux/proc_meminfo.h -@@ -51,6 +51,7 @@ typedef struct { - int64_t Writeback; - int64_t Mapped; - int64_t Shmem; -+ int64_t KReclaimable; - int64_t Slab; - int64_t SlabReclaimable; - int64_t SlabUnreclaimable; -@@ -84,6 +85,7 @@ typedef struct { - int64_t HugepagesRsvd; - int64_t HugepagesSurp; - int64_t Hugepagesize; -+ int64_t Hugetlb; - int64_t directMap4k; - int64_t directMap2M; - int64_t directMap1G; -diff --git a/src/pmdas/linux/root_linux b/src/pmdas/linux/root_linux -index 38e63c3..ee5d7ad 100644 ---- a/src/pmdas/linux/root_linux -+++ b/src/pmdas/linux/root_linux -@@ -746,6 +746,8 @@ mem.util { - zswapped 60:1:72 - shadowcallstack 60:1:73 - percpu 60:1:74 -+ kreclaimable 60:1:75 -+ hugetlb 60:1:76 - } - - mem.numa { --- -2.43.7 - diff --git a/SOURCES/1005-fix-mpstat-showing-inconsistent-values.patch b/SOURCES/1005-fix-mpstat-showing-inconsistent-values.patch deleted file mode 100644 index 8cc31c2..0000000 --- a/SOURCES/1005-fix-mpstat-showing-inconsistent-values.patch +++ /dev/null @@ -1,267 +0,0 @@ -From a02f695771e7534a90391fe243badb47b7b2371d Mon Sep 17 00:00:00 2001 -From: sagar sagar -Date: Thu, 28 Aug 2025 19:37:54 +0530 -Subject: [PATCH] pcp-mpstat: refactor to use vuser/vnice metrics and unify CPU - utilization calculation - -Switch all references in MPSTAT_METRICS from cpu.user/cpu.nice to the newer cpu.vuser/cpu.vnice metrics -for both kernel.all and kernel.percpu to be inline with mpstat tool. -Refactor all per-metric calculation methods in CoreCpuUtil to delegate to a new _compute_metric helper, -eliminating redundant code. -Optimize CPU count retrieval by caching the result of hinv.ncpu in the CoreCpuUtil class. -Remove unused or redundant calls to cpu_online, clarifying the core class interface. -Simplify and centralize the per-metric computation logic by using self._all_or_percpu() for proper metric -path construction and handling instance (per-core vs global) logic in a single location. - -Signed-off-by: sagar sagar - -Orabug: 38526328 - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/a02f695771e7534a90391fe243badb47b7b2371d - -Signed-off-by: Sourav Sharma - ---- - src/pcp/mpstat/pcp-mpstat.py | 184 ++++++++--------------------------- - 1 file changed, 42 insertions(+), 142 deletions(-) - -diff --git a/src/pcp/mpstat/pcp-mpstat.py b/src/pcp/mpstat/pcp-mpstat.py -index 89f308ac39..fed1b91ae1 100755 ---- a/src/pcp/mpstat/pcp-mpstat.py -+++ b/src/pcp/mpstat/pcp-mpstat.py -@@ -22,14 +22,14 @@ - import time - MPSTAT_METRICS = ['kernel.uname.nodename', 'kernel.uname.release', 'kernel.uname.sysname', - 'kernel.uname.machine', 'hinv.map.cpu_num', 'hinv.ncpu', 'hinv.cpu.online', -- 'kernel.all.cpu.user', -- 'kernel.all.cpu.nice', 'kernel.all.cpu.sys', 'kernel.all.cpu.wait.total', -- 'kernel.all.cpu.irq.hard', 'kernel.all.cpu.irq.soft', 'kernel.all.cpu.steal', -- 'kernel.all.cpu.guest', 'kernel.all.cpu.guest_nice', 'kernel.all.cpu.idle', -- 'kernel.percpu.cpu.user', 'kernel.percpu.cpu.nice', 'kernel.percpu.cpu.sys', -- 'kernel.percpu.cpu.wait.total', 'kernel.percpu.cpu.irq.hard', 'kernel.percpu.cpu.irq.soft', -- 'kernel.percpu.cpu.steal', 'kernel.percpu.cpu.guest','kernel.percpu.cpu.guest_nice', -- 'kernel.percpu.cpu.idle', 'kernel.all.intr', 'kernel.percpu.intr'] -+ 'kernel.all.cpu.vuser', 'kernel.all.cpu.vnice', 'kernel.all.cpu.sys', -+ 'kernel.all.cpu.wait.total', 'kernel.all.cpu.irq.hard', 'kernel.all.cpu.irq.soft', -+ 'kernel.all.cpu.steal', 'kernel.all.cpu.guest', 'kernel.all.cpu.guest_nice', -+ 'kernel.all.cpu.idle','kernel.percpu.cpu.vuser','kernel.percpu.cpu.vnice', -+ 'kernel.percpu.cpu.sys','kernel.percpu.cpu.wait.total', 'kernel.percpu.cpu.irq.hard', -+ 'kernel.percpu.cpu.irq.soft','kernel.percpu.cpu.steal', 'kernel.percpu.cpu.guest', -+ 'kernel.percpu.cpu.guest_nice','kernel.percpu.cpu.idle', 'kernel.all.intr', 'kernel.percpu.intr'] -+ - interrupts_list = [] - soft_interrupts_list = [] - -@@ -126,170 +126,70 @@ def __fetch_previous_values(self,metric,instance): - - class CoreCpuUtil: - def __init__(self, instance, delta_time, metric_repository): -- self.delta_time = delta_time - self.instance = instance -+ self.delta_time = delta_time - self.metric_repository = metric_repository -+ self._total_cpus = None # Cache for performance - - def total_cpus(self): -- return self.metric_repository.current_value('hinv.ncpu', None) -+ if self._total_cpus is None: -+ self._total_cpus = self.metric_repository.current_value('hinv.ncpu', None) -+ return self._total_cpus -+ - def cpu_number(self): - return self.instance - -- def cpu_online(self): -- return self.metric_repository.current_value('hinv.cpu.online', self.instance) -- - def user_time(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.user' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- -- else: -- return None -+ return self._compute_metric('cpu.vuser') - - def nice_time(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.nice' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.vnice') - - def sys_time(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.sys' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.sys') - - def iowait_time(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.wait.total' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.wait.total') - - def irq_hard(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.irq.hard' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.irq.hard') - - def irq_soft(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.irq.soft' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.irq.soft') - - def steal(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.steal' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.steal') - - def guest_time(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.guest' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.guest') - - def guest_nice(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.guest_nice' -- p_time = self.metric_repository.previous_value(metric, self.instance) -- c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -- return None -- return float("%.2f"%(value)) -- else: -- return None -+ return self._compute_metric('cpu.guest_nice') - - def idle_time(self): -- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.idle' -+ return self._compute_metric('cpu.idle') -+ -+ def _compute_metric(self, metric_suffix): -+ metric = f'kernel.{self._all_or_percpu()}.{metric_suffix}' - p_time = self.metric_repository.previous_value(metric, self.instance) - c_time = self.metric_repository.current_value(metric, self.instance) -- if p_time is not None and c_time is not None: -- value = (100*(c_time - p_time))/(1000*self.delta_time) -- if self.instance is None and self.total_cpus() is not None: -- return float("%.2f"%(value/self.total_cpus())) -- else: -- if self.total_cpus() is None: -+ -+ if p_time is None or c_time is None or self.delta_time == 0: -+ return None -+ -+ try: -+ value = (100 * (c_time - p_time)) / (1000 * self.delta_time) -+ if self.instance is None: -+ total = self.total_cpus() -+ if total: -+ value /= total -+ else: - return None -- return float("%.2f"%(value)) -- else: -+ return min (round(value, 2),100) -+ except (ZeroDivisionError, TypeError): - return None - -- def __all_or_percpu(self): -+ def _all_or_percpu(self): - return 'all' if self.instance is None else 'percpu' - - class CpuUtil: -@@ -652,7 +552,7 @@ def get_summary_metrics(self,group): - def report(self,manager): - try: - group = manager['mpstat'] -- if group['kernel.all.cpu.user'].netPrevValues is None: -+ if group['kernel.all.cpu.vuser'].netPrevValues is None: - # need two fetches to report rate converted counter metrics - self.get_summary_metrics(group) - return diff --git a/SOURCES/1006-fix-broken-pipe-error-iostat.patch b/SOURCES/1006-fix-broken-pipe-error-iostat.patch deleted file mode 100644 index 6aa5115..0000000 --- a/SOURCES/1006-fix-broken-pipe-error-iostat.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 64cfffa8777d349d58aac07684abe0d834695e22 Mon Sep 17 00:00:00 2001 -From: sagar sagar -Date: Fri, 2 May 2025 14:54:08 +0530 -Subject: [PATCH] pcp-iostat:fixed broken pipe issue in pcp-iostat utility. - -Signed-off-by: sagar sagar - -Orabug: 38526381 - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2200/commits/64cfffa8777d349d58aac07684abe0d834695e22 - -Signed-off-by: Sourav Sharma - ---- - src/pcp/iostat/pcp-iostat.py | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/src/pcp/iostat/pcp-iostat.py b/src/pcp/iostat/pcp-iostat.py -index 909f2ac70d..ae503ed83f 100755 ---- a/src/pcp/iostat/pcp-iostat.py -+++ b/src/pcp/iostat/pcp-iostat.py -@@ -461,5 +461,7 @@ def __init__(self): - except pmapi.pmUsageErr as usage: - usage.message() - sys.exit(1) -+ except IOError: -+ signal.signal(signal.SIGPIPE, signal.SIG_DFL) - except KeyboardInterrupt: - pass diff --git a/SOURCES/1007-add-missing-numastat-metrics.patch b/SOURCES/1007-add-missing-numastat-metrics.patch deleted file mode 100644 index e664200..0000000 --- a/SOURCES/1007-add-missing-numastat-metrics.patch +++ /dev/null @@ -1,220 +0,0 @@ -From 8e6a5ac18c06f813bad74494134ad5ac2202684e Mon Sep 17 00:00:00 2001 -From: Sagar Sagar -Date: Mon, 22 Sep 2025 15:39:18 +0000 -Subject: [PATCH] Added new metrics for numastat per node - -newly added metrics are follows:- -mem.numa.util.swapCached -mem.numa.util.kreclaimable -mem.numa.util.anonhugepages -mem.numa.util.shmemhugepages -mem.numa.util.shmempmdmapped -mem.numa.util.filehugepages -mem.numa.util.filepmdmapped - -Fixed issue with mem.numa.util.mapped metric where it was not giving correct values. - -Signed-off-by: Sagar Sagar -Co-authored-by: Sourav Sharma - -Orabug: 38526450 - -Cherry-pick-commit: https://github.com/orasagar/pcp/commit/8e6a5ac18c06f813bad74494134ad5ac2202684e - -Signed-off-by: Sourav Sharma - ---- - src/pmdas/linux/help | 7 ++++ - src/pmdas/linux/numa_meminfo.c | 50 +++++++++++++++++------------ - src/pmdas/linux/pmda.c | 58 +++++++++++++++++++++++++++++++++- - src/pmdas/linux/root_linux | 7 ++++ - 4 files changed, 100 insertions(+), 22 deletions(-) - -diff --git a/src/pmdas/linux/help b/src/pmdas/linux/help -index c608d10ba5..482e099d66 100644 ---- a/src/pmdas/linux/help -+++ b/src/pmdas/linux/help -@@ -1030,6 +1030,13 @@ pressure. - @ mem.numa.util.hugepagesTotal per-node total count of hugepages - @ mem.numa.util.hugepagesFree per-node count of free hugepages - @ mem.numa.util.hugepagesSurp per-node count of surplus hugepages -+@ mem.numa.util.swapCached per-node amount of memory in swap cache -+@ mem.numa.util.kreclaimable per-node kernel reclaimable memory -+@ mem.numa.util.anonhugepages per-node amount of memory in anonymous huge pages -+@ mem.numa.util.shmemhugepages per-node amount of shared memory allocated with huge -+@ mem.numa.util.shmempmdmapped per-node shared memory mapped into userspace with hugepages -+@ mem.numa.util.filehugepages per-node page cache (file) pages allocated with hugepages -+@ mem.numa.util.filepmdmapped per-node page cache mapped into userspace with hugepages - @ mem.numa.alloc.hit per-node count of times a task wanted alloc on local node and succeeded - @ mem.numa.alloc.miss per-node count of times a task wanted alloc on local node but got another node - @ mem.numa.alloc.foreign count of times a task on another node alloced on that node, but got this node -diff --git a/src/pmdas/linux/numa_meminfo.c b/src/pmdas/linux/numa_meminfo.c -index 9024962b2e..461bed0965 100644 ---- a/src/pmdas/linux/numa_meminfo.c -+++ b/src/pmdas/linux/numa_meminfo.c -@@ -24,42 +24,50 @@ - - /* sysfs file for numa meminfo */ - static struct linux_table numa_meminfo_table[] = { -- { field: "MemTotal:", maxval: 0x0 }, -- { field: "MemFree:", maxval: 0x0 }, -- { field: "MemUsed:", maxval: 0x0 }, -- { field: "Active:", maxval: 0x0 }, -- { field: "Inactive:", maxval: 0x0 }, -+ { field: "MemTotal:", maxval: 0x0 }, -+ { field: "MemFree:", maxval: 0x0 }, -+ { field: "MemUsed:", maxval: 0x0 }, -+ { field: "SwapCached:", maxval: 0x0 }, -+ { field: "Active:", maxval: 0x0 }, -+ { field: "Inactive:", maxval: 0x0 }, - { field: "Active(anon):", maxval: 0x0 }, - { field: "Inactive(anon):", maxval: 0x0 }, - { field: "Active(file):", maxval: 0x0 }, - { field: "Inactive(file):", maxval: 0x0 }, -- { field: "HighTotal:", maxval: 0x0 }, -- { field: "HighFree:", maxval: 0x0 }, -- { field: "LowTotal:", maxval: 0x0 }, -- { field: "LowFree:", maxval: 0x0 }, -+ { field: "HighTotal:", maxval: 0x0 }, -+ { field: "HighFree:", maxval: 0x0 }, -+ { field: "LowTotal:", maxval: 0x0 }, -+ { field: "LowFree:", maxval: 0x0 }, - { field: "Unevictable:", maxval: 0x0 }, -- { field: "Mlocked:", maxval: 0x0 }, -- { field: "Dirty:", maxval: 0x0 }, -- { field: "Writeback:", maxval: 0x0 }, -- { field: "FilePages:", maxval: 0x0 }, -- { field: "Mapped:", maxval: 0x0 }, -- { field: "AnonPages:", maxval: 0x0 }, -- { field: "Shmem:", maxval: 0x0 }, -+ { field: "Mlocked:", maxval: 0x0 }, -+ { field: "Dirty:", maxval: 0x0 }, -+ { field: "Writeback:", maxval: 0x0 }, -+ { field: "FilePages:", maxval: 0x0 }, -+ { field: "AnonPages:", maxval: 0x0 }, -+ { field: "Shmem:", maxval: 0x0 }, - { field: "KernelStack:", maxval: 0x0 }, -- { field: "PageTables:", maxval: 0x0 }, -+ { field: "PageTables:", maxval: 0x0 }, -+ { field: "SecPageTables:", maxval: 0x0 }, - { field: "NFS_Unstable:", maxval: 0x0 }, -- { field: "Bounce:", maxval: 0x0 }, -+ { field: "Bounce:", maxval: 0x0 }, - { field: "WritebackTmp:", maxval: 0x0 }, -- { field: "Slab:", maxval: 0x0 }, -+ { field: "KReclaimable:", maxval: 0x0 }, -+ { field: "Slab:", maxval: 0x0 }, - { field: "SReclaimable:", maxval: 0x0 }, -- { field: "SUnreclaim:", maxval: 0x0 }, -+ { field: "SUnreclaim:", maxval: 0x0 }, -+ { field: "AnonHugePages:", maxval: 0x0 }, -+ { field: "ShmemHugePages:", maxval: 0x0 }, -+ { field: "ShmemPmdMapped:", maxval: 0x0 }, -+ { field: "FileHugePages:", maxval: 0x0 }, -+ { field: "FilePmdMapped:", maxval: 0x0 }, -+ { field: "Mapped:", maxval: 0x0 }, - { field: "HugePages_Total:", maxval: 0x0 }, - { field: "HugePages_Free:", maxval: 0x0 }, - { field: "HugePages_Surp:", maxval: 0x0 }, - { field: NULL } - }; - --/* sysfs file for numastat */ -+/* sysfs file for numastat */ - static struct linux_table numa_memstat_table[] = { - { field: "numa_hit", maxval: ULONGLONG_MAX }, - { field: "numa_miss", maxval: ULONGLONG_MAX }, -diff --git a/src/pmdas/linux/pmda.c b/src/pmdas/linux/pmda.c -index 1026caf860..b30496b4b8 100644 ---- a/src/pmdas/linux/pmda.c -+++ b/src/pmdas/linux/pmda.c -@@ -1416,6 +1416,42 @@ static pmdaMetric metrictab[] = { - { PMDA_PMID(CLUSTER_NUMA_MEMINFO,41), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, - PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, }, - -+/* mem.numa.util.swapCached */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,42), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.numa.util.kreclaimable */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,43), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.numa.util.anonhugepages */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,44), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.numa.util.shmemhugepages */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,45), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.numa.util.shemempmdmapped */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,46), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.numa.util.filehugepages */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,47), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+/* mem.numa.util.filepmdmapped */ -+ { NULL, -+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,48), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT, -+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, -+ -+ - /* swap.length */ - { NULL, - { PMDA_PMID(CLUSTER_MEMINFO,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, -@@ -9952,7 +9988,27 @@ linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) - sts = linux_table_lookup("HugePages_Surp:", np->meminfo, &atom->ull); - atom->ull *= (proc_meminfo.Hugepagesize << 10); - break; -- -+ case 42: /* mem.numa.util.swapCached */ -+ sts = linux_table_lookup("SwapCached:", np->meminfo, &atom->ull); -+ break; -+ case 43: /* mem.numa.util.kreclaimable */ -+ sts = linux_table_lookup("KReclaimable:", np->meminfo, &atom->ull); -+ break; -+ case 44: /* mem.numa.util.anonhugepages */ -+ sts = linux_table_lookup("AnonHugePages:", np->meminfo, &atom->ull); -+ break; -+ case 45: /* mem.numa.util.shmemhugepages */ -+ sts = linux_table_lookup("ShmemHugePages:", np->meminfo, &atom->ull); -+ break; -+ case 46: /* mem.numa.util.shmempmdmapped */ -+ sts = linux_table_lookup("ShmemPmdMapped:", np->meminfo, &atom->ull); -+ break; -+ case 47: /* mem.numa.util.filehugepages */ -+ sts = linux_table_lookup("FileHugePages:", np->meminfo, &atom->ull); -+ break; -+ case 48: /* mem.numa.util.filepmdmapped */ -+ sts = linux_table_lookup("FilePmdMapped:", np->meminfo, &atom->ull); -+ break; - default: - return PM_ERR_PMID; - } -diff --git a/src/pmdas/linux/root_linux b/src/pmdas/linux/root_linux -index 1add9a43cf..054b77b67d 100644 ---- a/src/pmdas/linux/root_linux -+++ b/src/pmdas/linux/root_linux -@@ -793,6 +793,13 @@ mem.numa.util { - hugepagesTotalBytes 60:36:39 - hugepagesFreeBytes 60:36:40 - hugepagesSurpBytes 60:36:41 -+ swapCached 60:36:42 -+ kreclaimable 60:36:43 -+ anonhugepages 60:36:44 -+ shmemhugepages 60:36:45 -+ shmempmdmapped 60:36:46 -+ filehugepages 60:36:47 -+ filepmdmapped 60:36:48 - } - - mem.numa.alloc { diff --git a/SOURCES/1008-add-numastat-support-for-mn-options.patch b/SOURCES/1008-add-numastat-support-for-mn-options.patch deleted file mode 100644 index cb88be7..0000000 --- a/SOURCES/1008-add-numastat-support-for-mn-options.patch +++ /dev/null @@ -1,495 +0,0 @@ -From b09b0435b4fafd56a5930de2adeeea4c7f45ea57 Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Wed, 8 Oct 2025 13:17:18 +0530 -Subject: [PATCH] Modifies pcp numastat tool with pmcc metric group printer Add - support for -m and -n options in pcp numastat tool like numastat Provides - meminfo data per node - -Orabug: 38526434 - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/e4d513fd503cd3f0e7a2ccab0e0f069acf872427 - -Signed-off-by: Sourav Sharma - ---- - src/pcp/numastat/pcp-numastat.1 | 8 +- - src/pcp/numastat/pcp-numastat.py | 414 +++++++++++++++------ - 2 files changed, 422 insertions(+), 1 deletions(-) - -diff --git a/src/pcp/numastat/pcp-numastat.1 b/src/pcp/numastat/pcp-numastat.1 -index d9165ea86e..65910a4877 100644 ---- a/src/pcp/numastat/pcp-numastat.1 -+++ b/src/pcp/numastat/pcp-numastat.1 -@@ -18,7 +18,7 @@ - \f3pcp-numastat\f1 \- report on NUMA memory allocation - .SH SYNOPSIS - \f3pcp\f1 [\f2pcp\ options\f1] \f3numastat\f1 --[\f3\-Vw?\f1] -+[\fB-V\fR] [\fB-w\fR \fIwidth\fR] [\fB-m\fR][-n\fR] [\fB-?\fR] - .SH DESCRIPTION - .B pcp-numastat - displays NUMA allocation statistics from the kernel memory -@@ -69,6 +69,12 @@ Display the current version of the command. - Limit display to - .IR width . - .TP -+\fB-m\fR, \fB--meminfo\fR -+Display meminfo-like system-wide memory usage. -+.TP -+\fB-n\fR, \fB--numastat\fR -+Display the numastat statistics info. -+.TP - \fB\-?\fR, \fB\-\-help\fR - Display usage message and exit. - .SH NOTES -diff --git a/src/pcp/numastat/pcp-numastat.py b/src/pcp/numastat/pcp-numastat.py -index 46c2fcb97d..2a044c9bbd 100755 ---- a/src/pcp/numastat/pcp-numastat.py -+++ b/src/pcp/numastat/pcp-numastat.py -@@ -17,150 +17,328 @@ - """ Display NUMA memory allocation statistucs """ - - import os -+import signal - import sys -+import time -+ - from pcp import pmapi --from cpmapi import PM_TYPE_U64, PM_CONTEXT_ARCHIVE -+from pcp import pmcc -+from cpmapi import PM_CONTEXT_ARCHIVE - - if sys.version >= '3': - long = int # python2 to python3 portability (no long() in python3) - xrange = range # more back-compat (xrange() is range() in python3) - --class NUMAStat(object): -- """ Gives a short summary of per-node NUMA memory information. -+NUMA_METRICS = [ -+ "mem.numa.alloc.hit", -+ "mem.numa.alloc.miss", -+ "mem.numa.alloc.foreign", -+ "mem.numa.alloc.interleave_hit", -+ "mem.numa.alloc.local_node", -+ "mem.numa.alloc.other_node", -+] - -- Knows about some of the default PCP arguments - can function -- using remote hosts or historical data, using the timezone of -- the metric source, at an offset within an archive, and so on. -- """ -+MEM_METRICS = [ -+ "mem.numa.util.total", -+ "mem.numa.util.free", -+ "mem.numa.util.used", -+ "mem.numa.util.active", -+ "mem.numa.util.inactive", -+ "mem.numa.util.active_anon", -+ "mem.numa.util.inactive_anon", -+ "mem.numa.util.active_file", -+ "mem.numa.util.inactive_file", -+ "mem.numa.util.unevictable", -+ "mem.numa.util.mlocked", -+ "mem.numa.util.dirty", -+ "mem.numa.util.writeback", -+ "mem.numa.util.filePages", -+ "mem.numa.util.mapped", -+ "mem.numa.util.anonpages", -+ "mem.numa.util.shmem", -+ "mem.numa.util.kernelStack", -+ "mem.numa.util.pageTables", -+ "mem.numa.util.NFS_Unstable", -+ "mem.numa.util.bounce", -+ "mem.numa.util.writebackTmp", -+ "mem.numa.util.filehugepages", -+ "mem.numa.util.filepmdmapped", -+ "mem.numa.util.slab", -+ "mem.numa.util.slabReclaimable", -+ "mem.numa.util.slabUnreclaimable", -+ "mem.numa.util.anonhugepages", -+ "mem.numa.util.shmemhugepages", -+ "mem.numa.util.shmempmdmapped", -+ "mem.numa.util.hugepagesTotal", -+ "mem.numa.util.hugepagesFree", -+ "mem.numa.util.hugepagesSurp", -+ "mem.numa.util.swapCached", -+ "mem.numa.util.kreclaimable", -+] - -- def __init__(self): -- """ Construct object - prepare for command line handling """ -- self.opts = self.options() -- self.context = None -- self.width = 0 -+SYS_METRICS = [ -+ 'kernel.uname.nodename', -+ 'kernel.uname.release', -+ 'kernel.uname.sysname', -+ 'kernel.uname.machine', -+ 'hinv.ncpu', -+] -+ -+ALL_METRICS = NUMA_METRICS + MEM_METRICS - -- def resize(self): -+def prefix(metric): -+ last_part = metric.split('.')[-1] -+ result = last_part[0].upper() + last_part[1:] -+ return result -+ -+class MetricRepository: -+ def __init__(self, group): -+ self.group = group -+ self.current_cached_values = {} -+ self.previous_cached_values = {} -+ -+ def _fetch_current_values(self, metric, instance): -+ if instance is not None: -+ return dict( -+ map(lambda x: (x[0].inst, x[2]), self.group[metric].netValues) -+ ) -+ else: -+ if self.group[metric].netValues == []: -+ return None -+ else: -+ return self.group[metric].netValues[0][2] -+ def current_values(self, metric_name): -+ if self.group.get(metric_name, None) is None: -+ return None -+ if self.current_cached_values.get(metric_name, None) is None: -+ self.current_cached_values[ -+ metric_name -+ ] = self._fetch_current_values(metric_name, True) -+ return self.current_cached_values.get(metric_name, None) -+ -+class NUMAStat: -+ -+ def __init__(self, group): -+ self.group = group -+ self.repo = MetricRepository(group) -+ -+ def resize(self, width): - """ Find a suitable display width limit """ -- if self.width == 0: -+ if width == 0: - if not sys.stdout.isatty(): -- self.width = 1000000000 # mimic numastat(1) here -+ width = 1000000000 # mimic numastat(1) here - else: -- # popen() is SAFE, command is a literal string -+ # popen() is SAFE, command is a literal string - (_, width) = os.popen('stty size', 'r').read().split() -- self.width = int(width) -- self.width = int(os.getenv('NUMASTAT_WIDTH', str(self.width))) -- self.width = max(self.width, 32) -+ width = int(width) -+ width = int(os.getenv('NUMASTAT_WIDTH', str(width))) -+ return max(width, 32) - -- def option(self, opt, optarg, index): -- """ Perform setup for an individual command line option """ -- if opt == 'w': -- self.width = int(optarg) -+ def __format_table(self, width, nodes, data): -+ null_output = False -+ if not nodes: -+ null_output = True -+ nodes = [(0, 'Node ')] - -- def options(self): -- """ Setup default command line argument option handling """ -- opts = pmapi.pmOptions() -- opts.pmSetOptionCallback(self.option) -- opts.pmSetShortOptions("w:V?") -- opts.pmSetLongOptionHeader("Options") -- opts.pmSetLongOption("width", 1, 'w', "N", "limit the display width") -- opts.pmSetLongOptionVersion() -- opts.pmSetLongOptionHelp() -- return opts -- -- def extract(self, descs, insts, result): -- """ Extract the set of metric values from a given pmResult """ -- values = [[]] -- for metrics in xrange(len(descs)): -- values.append([]) -- for nodes in xrange(len(insts)): -- if result.contents.get_numval(metrics) > 0: -- atom = self.context.pmExtractValue( -- result.contents.get_valfmt(metrics), -- result.contents.get_vlist(metrics, nodes), -- descs[metrics].contents.type, PM_TYPE_U64) -- values[metrics].append(long(atom.ull)) -- else: -- values[metrics].append(long(0)) -- return values -- -- def execute(self): -- """ Using a PMAPI context (could be either host or archive), -- fetch and report per-node values related to NUMA memory. -- """ -- metrics = ('mem.numa.alloc.hit', 'mem.numa.alloc.miss', -- 'mem.numa.alloc.foreign', 'mem.numa.alloc.interleave_hit', -- 'mem.numa.alloc.local_node', 'mem.numa.alloc.other_node') -- -- pmids = self.context.pmLookupName(metrics) -- descs = self.context.pmLookupDescs(pmids) -- if self.context.type == PM_CONTEXT_ARCHIVE: -- (insts, nodes) = self.context.pmGetInDomArchive(descs[0]) -+ if "numastat" in data: -+ metrics = NUMA_METRICS -+ title = "NUMA memory allocation statistics (pages)" - else: -- (insts, nodes) = self.context.pmGetInDom(descs[0]) -- result = self.context.pmFetch(pmids) -- values = self.extract(descs, insts, result) -- self.context.pmFreeResult(result) -- self.report(metrics, nodes, values) -- -- def report(self, metrics, nodes, values): -- """ Given per-node metric names and values, dump 'em like numastat(1) -- Nodes is a list of strings, values is a list of lists of values. -- """ -- columns = len(nodes) * 16 -- if columns == 0: -- print("No NUMA nodes found, exiting") -- sys.exit(1) -- self.resize() -- maxnodes = int((self.width - 16) / 16) -+ metrics = MEM_METRICS -+ title = "Per-node system memory usage (KB)" -+ -+ total_w = max(42, int(width)) -+ print(title[:total_w]) -+ -+ width = self.resize(width) -+ maxnodes = int((width - 16) / 16) - if maxnodes > len(nodes): # just an initial header suffices -- header = '%-16s' % '' -- for node in nodes: -- header += '%16s' % node -+ header = '%30s' % '' -+ for _, node in nodes: -+ header += '%-12s' % node - print(header) -- for index in xrange(len(metrics)): -- title = self.prefix(metrics[index]) -- self.metric(title, nodes, values[index], maxnodes) -- -- def metric(self, prefix, nodes, values, maxnodes): -- """ Given one metric and its per-node values, produce one or more -- lines of output with the values, each line node-name prefixed -- and with a new node header for each. -- """ -- done = 0 -- while done < len(nodes): -- header = '%-16s' % '' -- window = '%-16s' % prefix -- for index in xrange(maxnodes): -- current = done + index -- if current >= len(nodes): -- break -- header += '%16s' % (nodes[current]) -- window += '%16d' % (values[current]) -- if done > maxnodes or maxnodes <= len(nodes): -- print('%s\n%s' % (header, window)) -- else: -- print('%s' % window) -- done += maxnodes - -- def prefix(self, metric): -- """ Transform the PCP metric names into the reported sub-headings """ -- title = metric[15:] -- if '_' not in title: -- title = 'numa_' + title -- return title -+ for m in metrics: -+ if not null_output: -+ vals = self.repo.current_values(m) -+ done = 0 # reset for each metric -+ -+ # Loop through nodes in chunks of 'maxnodes' -+ while done < len(nodes): -+ header = '%-30s' % '' -+ window = '%-20s : ' % prefix(m) -+ -+ # Slice the range we'll print in this batch -+ chunk = nodes[done:done + maxnodes] -+ -+ for i, ( _, name) in enumerate(chunk): -+ header += '%-12s' % name -+ if not null_output: -+ window += '%12s' % vals[done + i] -+ else: -+ window += '%12s' % "NA" -+ -+ # Print header once per row group (not every metric) -+ if done > maxnodes or maxnodes <= len(nodes): -+ print('%s\n%s' % (header, window)) -+ else: -+ print('%s' % window) -+ done += maxnodes -+ print() -+ -+ def print_mem(self, width, nodes, data): -+ self.__format_table(width, nodes, data) -+ -+ def print_numa(self, width, nodes, data): -+ self.__format_table(width, nodes, data) -+ -+class NumaStatOption(pmapi.pmOptions): -+ context = None -+ timefmt = "%m/%d/%Y %H:%M:%S" -+ width = 0 -+ mem_out = False -+ numa_out = False -+ -+ def override(self,opt): -+ """ Override standard PCP options to match numastat(1) """ -+ if opt == 'n': -+ return True -+ return False -+ -+ def __init__(self): -+ pmapi.pmOptions.__init__(self) -+ self.pmSetShortOptions("w:mV?:n") -+ self.pmSetOptionCallback(self.extraOptions) -+ self.pmSetOverrideCallback(self.override) -+ self.pmSetLongOptionHeader("Numastat options") -+ self.pmSetLongOption("width", 1, 'w', "n", "limit the display width") -+ # Map long options to our non-conflicting short letters -+ self.pmSetLongOption("meminfo", 0, 'm', "", "show meminfo-like system-wide memory usage") -+ self.pmSetLongOption("numastat", 0, 'n', "", "show the numastat statistics info") -+ self.pmSetLongOptionVersion() -+ self.pmSetLongOptionHelp() -+ -+ def extraOptions(self, opt, optarg, index): -+ if opt == 'w': -+ self.width = int(optarg) -+ elif opt == "m": -+ self.mem_out = True -+ elif opt == "n": -+ self.numa_out = True -+ elif opt == "V": -+ pass -+ else: -+ raise pmapi.pmUsageErr() -+ return True -+ -+ def checkoptions(self): -+ if (not self.mem_out) and (not self.numa_out) and (self.width == 0): -+ self.numa_out = True -+ if self.width < 0: -+ return False -+ return True - -- def connect(self): -- """ Establish a PMAPI context to archive, host or local, via args """ -- self.context = pmapi.pmContext.fromOptions(self.opts, sys.argv) -+class NumaStatReport(pmcc.MetricGroupPrinter): -+ machine_info_count = 0 -+ -+ def __init__(self, options): -+ self.options = options -+ self.timestamp = None -+ -+ def __get_timestamp(self, group): -+ ts = group.contextCache.pmLocaltime(int(group.timestamp)) -+ self.timestamp = time.strftime(NumaStatOption.timefmt, ts.struct_time()) -+ return self.timestamp -+ -+ def __get_ncpu(self, group): -+ return group['hinv.ncpu'].netValues[0][2] -+ -+ def print_machine_info(self,group, context): -+ timestamp = context.pmLocaltime(group.timestamp.tv_sec) -+ # Please check strftime(3) for different formatting options. -+ # Also check TZ and LC_TIME environment variables for more -+ # information on how to override the default formatting of -+ # the date display in the header -+ time_string = time.strftime("%x", timestamp.struct_time()) -+ header_string = '' -+ header_string += group['kernel.uname.sysname'].netValues[0][2] + ' ' -+ header_string += group['kernel.uname.release'].netValues[0][2] + ' ' -+ header_string += '(' + group['kernel.uname.nodename'].netValues[0][2] + ') ' -+ header_string += time_string + ' ' -+ header_string += group['kernel.uname.machine'].netValues[0][2] + ' ' -+ print("%s (%s CPU)" % (header_string, self.__get_ncpu(group))) -+ -+ def __discover_nodes(self, group, name): -+ # Build list of online nodes (instance id, instance name) -+ nodes = [] -+ try: -+ for ent in group[name].netValues: -+ inst_id = ent[0].inst -+ inst_name = ent[1] # usually "node0", "node1", ... -+ online = int(ent[2]) != 0 -+ if online: -+ nodes.append((inst_id, inst_name)) -+ except Exception: -+ pass -+ # Sort by instance id (node number) -+ nodes.sort(key=lambda t: t[0]) -+ return nodes -+ -+ def report(self, manager): -+ # Print in a stable order -+ group = manager["sys_info"] -+ try: -+ if not self.machine_info_count: -+ self.print_machine_info(group, manager) -+ self.machine_info_count = 1 -+ except IndexError: -+ return -+ -+ output_numa = ( -+ self.options.numa_out -+ or (not self.options.mem_out and not self.options.numa_out) -+ ) -+ output_mem = self.options.mem_out -+ group = manager["numastat"] -+ nodes = self.__discover_nodes(group, "mem.numa.util.total") -+ timestamp = self.__get_timestamp(group) -+ print("%-20s : %s"%("Timestamp", timestamp)) -+ if output_mem: -+ NUMAStat(group).print_mem(self.options.width, nodes, "meminfo") -+ if output_numa: -+ NUMAStat(group).print_numa(self.options.width, nodes, "numastat") -+ -+ if ( -+ NumaStatOption.context is not PM_CONTEXT_ARCHIVE -+ and self.options.pmGetOptionSamples() is None -+ ): -+ sys.exit(0) - - if __name__ == '__main__': - try: -- NUMASTAT = NUMAStat() -- NUMASTAT.connect() -- NUMASTAT.execute() -+ opts = NumaStatOption() -+ mngr = pmcc.MetricGroupManager.builder(opts, sys.argv) -+ if not opts.checkoptions(): -+ print("Invalid options from command line") -+ raise pmapi.pmUsageErr() -+ NumaStatOption.context = mngr.type -+ -+ missing = mngr.checkMissingMetrics(ALL_METRICS) -+ if missing is not None: -+ sys.stderr.write('Error: not all required metrics are available\nMissing: %s\n' % (missing)) -+ sys.exit(1) -+ -+ mngr["numastat"] = ALL_METRICS -+ mngr["sys_info"] = SYS_METRICS -+ mngr.printer = NumaStatReport(opts) -+ sts = mngr.run() -+ sys.exit(sts) -+ except IOError: -+ signal.signal(signal.SIGPIPE, signal.SIG_DFL) - except pmapi.pmErr as error: -- print("%s: %s" % (error.progname(), error.message())) -+ sys.stderr.write("%s %s\n" % (error.progname(), error.message())) - except pmapi.pmUsageErr as usage: - usage.message() -+ sys.exit(1) - except KeyboardInterrupt: - pass diff --git a/SOURCES/1009-Fixes-higepagesize-metric-value-from-bytes-to-KB.patch b/SOURCES/1009-Fixes-higepagesize-metric-value-from-bytes-to-KB.patch deleted file mode 100644 index a81ebc1..0000000 --- a/SOURCES/1009-Fixes-higepagesize-metric-value-from-bytes-to-KB.patch +++ /dev/null @@ -1,45 +0,0 @@ -From d2e92db5d6ee2a07e52ff72da471976aafaeb7ec Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Fri, 10 Oct 2025 16:39:51 +0530 -Subject: [PATCH] Fixes higepagesize metric value from bytes to KB as per - /proc/meminfo - -[sagar@vbox ~]$ pcp meminfo | grep Hugepagesize ; cat /proc/meminfo | grep Hugepagesize -Hugepagesize : 2097152 kB -Hugepagesize: 2048 kB - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2371/commits/84bf35c442cedab82bdd5d71da6cca21cbf628db - -Orabug: 38527066 - -Signed-off-by: Sourav Sharma ---- - src/pcp/meminfo/pcp-meminfo.py | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py -index 8492859..f956910 100755 ---- a/src/pcp/meminfo/pcp-meminfo.py -+++ b/src/pcp/meminfo/pcp-meminfo.py -@@ -168,6 +168,9 @@ class MeminfoReport(pmcc.MetricGroupPrinter): - units = "" - if METRICS_DESC[idx][-6:] == "_NO_kb": - metric_name = METRICS_DESC[idx][:-6] -+ elif METRICS_DESC[idx] == "Hugepagesize": -+ metric_name = METRICS_DESC[idx] -+ units = "B" - else: - metric_name = METRICS_DESC[idx] - units = "kB" -@@ -203,6 +206,9 @@ class MeminfoReport(pmcc.MetricGroupPrinter): - continue - - metric_name, units = self.getMetricName(idx) -+ if units == "B": -+ val = int(val / 1024) -+ units = "kB" - print("%-17s : %s %s"%(metric_name, val, units)) - - idx += 1 --- -2.43.7 diff --git a/SOURCES/1010-xz-default-compression-changed-to-level-3.patch b/SOURCES/1010-xz-default-compression-changed-to-level-3.patch deleted file mode 100644 index 73a9178..0000000 --- a/SOURCES/1010-xz-default-compression-changed-to-level-3.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 9703b9c63e27b541e32de56e1d482f85e4e548f4 Mon Sep 17 00:00:00 2001 -From: sagar sagar -Date: Fri, 21 Nov 2025 12:35:08 +0530 -Subject: [PATCH OL9 1010/1010] xz default compression changed to level 3 - -to reduce the size of the compressed pcp archives -Orabug: 38674828 -Signed-off-by: sagar sagar ---- - src/pmlogger/pmlogger_daily.sh | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh -index 18fc691..6e73a1f 100755 ---- a/src/pmlogger/pmlogger_daily.sh -+++ b/src/pmlogger/pmlogger_daily.sh -@@ -264,10 +264,10 @@ COMPRESS="" - COMPRESS_CMDLINE="" - if which xz >/dev/null 2>&1 - then -- if xz -0 --block-size=10MiB /dev/null 2>&1 -+ if xz -3 --block-size=10MiB /dev/null 2>&1 - then -- # want minimal overheads, -0 is the same as --fast -- COMPRESS_DEFAULT="xz -0 --block-size=10MiB" -+ # want better compression with minimum overhead -+ COMPRESS_DEFAULT="xz -3 --block-size=10MiB" - else - COMPRESS_DEFAULT=xz - fi --- -2.43.7 - diff --git a/SOURCES/1011-orabug38724866-fix-nfsclient-per-op-parsing.patch b/SOURCES/1011-orabug38724866-fix-nfsclient-per-op-parsing.patch deleted file mode 100644 index 3101e0f..0000000 --- a/SOURCES/1011-orabug38724866-fix-nfsclient-per-op-parsing.patch +++ /dev/null @@ -1,38 +0,0 @@ -From c80dc758eb0eb27fafa0b594d6c2aa1f4c3803fa Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Wed, 3 Dec 2025 20:40:52 +0530 -Subject: [PATCH] pmdanfsclient: fix regex to correctly parse NFS op stats - -The regex used to match NFS operation statistics in /proc/self/mountstats was - missing a capture group, causing parsing failures when an additional field - was present in newer kernel formats. Updated the regex to include the extra - numeric field so that all opstats lines are parsed correctly. - -Signed-off-by: Sourav Sharma - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/85671f8874d7d4b4e57eb45bfe295de92b95415c - -Orabug: 38724866 - -Signed-off-by: Sourav Sharma - ---- - src/pmdas/nfsclient/pmdanfsclient.python | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/pmdas/nfsclient/pmdanfsclient.python b/src/pmdas/nfsclient/pmdanfsclient.python -index 786d958..f08b93a 100644 ---- a/src/pmdas/nfsclient/pmdanfsclient.python -+++ b/src/pmdas/nfsclient/pmdanfsclient.python -@@ -636,7 +636,7 @@ class NFSCLIENTPMDA(PMDA): - line = STATS.readline() - if line == '': - break -- m = re.match(r'\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$', line) -+ m = re.match(r'\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$', line) - if not m: - break - opname = m.group(1).lower() --- -2.43.7 - diff --git a/SOURCES/1012-orabug38817068-Introduce-PCP-implementation-of-nfsiostat.patch b/SOURCES/1012-orabug38817068-Introduce-PCP-implementation-of-nfsiostat.patch deleted file mode 100644 index dc38400..0000000 --- a/SOURCES/1012-orabug38817068-Introduce-PCP-implementation-of-nfsiostat.patch +++ /dev/null @@ -1,575 +0,0 @@ -From ca8fbd2a827e3e7caa72331e003f93523c9f5241 Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Tue, 6 Jan 2026 05:53:08 +0000 -Subject: [PATCH] pcp-nfsiostat: Introduce PCP implementation of nfsiostat - parser - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/c0a0c53b57774dfac0b727e62472fb1ae6065540 - -Orabug: 38817068 - -Signed-off-by: Sourav Sharma ---- - src/pcp/GNUmakefile | 1 + - src/pcp/nfsiostat/GNUmakefile | 43 ++++ - src/pcp/nfsiostat/pcp-nfsiostat.1 | 132 +++++++++++ - src/pcp/nfsiostat/pcp-nfsiostat.py | 346 +++++++++++++++++++++++++++++ - 4 files changed, 522 insertions(+) - create mode 100644 src/pcp/nfsiostat/GNUmakefile - create mode 100644 src/pcp/nfsiostat/pcp-nfsiostat.1 - create mode 100644 src/pcp/nfsiostat/pcp-nfsiostat.py - -diff --git a/src/pcp/GNUmakefile b/src/pcp/GNUmakefile -index da29269..2b74fc0 100644 ---- a/src/pcp/GNUmakefile -+++ b/src/pcp/GNUmakefile -@@ -30,6 +30,7 @@ SUBDIRS = \ - mpstat \ - netstat \ - numastat \ -+ nfsiostat \ - pidstat \ - ps \ - python \ -diff --git a/src/pcp/nfsiostat/GNUmakefile b/src/pcp/nfsiostat/GNUmakefile -new file mode 100644 -index 0000000..d8c5356 ---- /dev/null -+++ b/src/pcp/nfsiostat/GNUmakefile -@@ -0,0 +1,43 @@ -+# -+# Copyright (c) 2023 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+ -+TOPDIR = ../../.. -+include $(TOPDIR)/src/include/builddefs -+ -+TARGET = pcp-nfsiostat -+SCRIPT = $(TARGET).py -+MAN_SECTION = 1 -+MAN_PAGES = $(TARGET).$(MAN_SECTION) -+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION) -+ -+default: $(SCRIPT) $(MAN_PAGES) -+ -+include $(BUILDRULES) -+ -+install: default -+ifeq "$(HAVE_PYTHON)" "true" -+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BINADM_DIR)/$(TARGET) -+ @$(INSTALL_MAN) -+endif -+ -+default_pcp : default -+ -+install_pcp : install -+ -+check:: $(SCRIPT) -+ $(PYLINT) $^ -+ -+check :: $(MAN_PAGES) -+ $(MANLINT) $^ -diff --git a/src/pcp/nfsiostat/pcp-nfsiostat.1 b/src/pcp/nfsiostat/pcp-nfsiostat.1 -new file mode 100644 -index 0000000..b297285 ---- /dev/null -+++ b/src/pcp/nfsiostat/pcp-nfsiostat.1 -@@ -0,0 +1,132 @@ -+'\"! tbl | mmdoc -+'\"macro stdmacro -+.\" -+.\" Man page for pcp-nfsiostat -+.\" Copyright (c) 2023 Oracle and/or its affiliates. -+.\" DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+.\" -+.\" This program is free software; you can redistribute it and/or modify it -+.\" under the terms of the GNU General Public License as published by the -+.\" Free Software Foundation; either version 2 of the License, or (at your -+.\" option) any later version. -+.\" -+.\" This program is distributed in the hope that it will be useful, but -+.\" WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+.\" or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+.\" for more details. -+.\" -+ -+.TH PCP-NFSIOSTAT 1 "PCP" "Performance Co-Pilot" -+ -+.SH NAME -+\f3pcp-nfsiostat\f1 \- Emulate iostat for NFS mount points using /proc/self/mountstats -+ -+.SH SYNOPSIS -+\fBpcp\fP [\fBpcp options\fP] \fBnfsiostat\fP [\fB-s\fP \fBsamples\fP] [\fB-a\fP \fBarchive\fP] [\fB-Z\fP \fB--timezone\fP] [\fB-z\fP \fB--hostzone\fP] [\fB-V\fP \fBVersion\fP] -+ -+.SH DESCRIPTION -+The\fB pcp-nfsiostat \fPcommand reports client-side\fB Network File System (NFS) \fPI/O statistics for each mounted NFS filesystem. For every NFS mountpoint, the output consists of a summary section followed by detailed read and write statistics blocks. These statistics include operation rates, data throughput, latency, retransmissions, queueing delays, and error counts. By default,\fB pcp-nfsiostat \fPmonitors NFS mountpoints on the local host and reports live metrics collected via the \fB PCP NFS PMDA\fP. When an archive is specified, historical metrics are reported instead. -+ -+.SH OUTPUT FORMAT -+Statistics are reported per mounted NFS filesystem using the following layout: -+ -+.nf -+server:/export mounted on /mount/point: -+ -+ ops/s rpc bklog -+ 0.017 0.000 -+ -+read: ops/s kB/s kB/op retrans avg RTT (ms) avg exe (ms) avg queue (ms) errors -+ ... -+ -+write: ops/s kB/s kB/op retrans avg RTT (ms) avg exe (ms) avg queue (ms) errors -+ ... -+.fi -+ -+.SH FIELD DESCRIPTIONS -+.TP -+.B ops/s -+Number of NFS operations performed per second. -+ -+.TP -+.B rpc bklog -+Average number of RPC requests waiting to be transmitted. -+ -+.TP -+.B kB/s -+Kilobytes transferred per second. -+ -+.TP -+.B kB/op -+Average number of kilobytes transferred per operation. -+ -+.TP -+.B retrans -+Number of RPC retransmissions and the retransmission percentage. -+ -+.TP -+.B avg RTT (ms) -+Average round-trip time in milliseconds for RPC requests. -+ -+.TP -+.B avg exe (ms) -+Average execution time in milliseconds spent servicing requests on the server. -+ -+.TP -+.B avg queue (ms) -+Average time in milliseconds spent waiting in the RPC transmission queue. -+ -+.TP -+.B errors -+Number of failed operations and failure percentage. -+ -+.SH OPTIONS -+.TP -+.BR \-a ", " \-\-archive " " I archive -+Fetch NFS I/O statistics from the specified PCP archive. -+ -+.TP -+.BR \-s ", " \-\-samples " " I samples -+Number of samples to collect before exiting. -+ -+.TP -+.BR \-z ", " \-\-hostzone -+Set the reporting timezone to the local timezone of the metrics source. -+ -+.TP -+.BR \-Z ", " \-\-timezone " " I tz -+Set the reporting timezone. -+ -+.TP -+.BR \-V ", " \-\-version -+Display version information and exit. -+ -+.TP -+.BR \-? ", " \-\-help -+Display usage information and exit. -+ -+.SH NOTES -+.B pcp-nfsiostat -+reports client-side NFS statistics collected from the kernel and exposed -+via the PCP NFS PMDA. The output format and metrics are similar to those -+reported by the -+.BR nfsiostat (1) -+tool from the nfs-utils package. -+ -+.SH PCP ENVIRONMENT -+Environment variables with the prefix \fBPCP_\fP are used to parameterize -+the file and directory names used by PCP. -+On each installation, the -+file \fI/etc/pcp.conf\fP contains the local values for these variables. -+The \fB$PCP_CONF\fP variable may be used to specify an alternative -+configuration file, as described in -+.BR pcp.conf (5). -+ -+For environment variables affecting PCP tools, see -+.BR pmGetOptions (3). -+ -+.SH SEE ALSO -+.BR PCPIntro (1), -+.BR pcp (1), -+.BR nfsiostat (1), -+.BR environ (7). -diff --git a/src/pcp/nfsiostat/pcp-nfsiostat.py b/src/pcp/nfsiostat/pcp-nfsiostat.py -new file mode 100644 -index 0000000..0b0376d ---- /dev/null -+++ b/src/pcp/nfsiostat/pcp-nfsiostat.py -@@ -0,0 +1,346 @@ -+#!/usr/bin/pmpython -+# -+# Copyright (c) 2023 Oracle and/or its affiliates. -+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -+# -+# This program is free software; you can redistribute it and/or modify it -+# under the terms of the GNU General Public License as published by the -+# Free Software Foundation; either version 2 of the License, or (at your -+# option) any later version. -+# -+# This program is distributed in the hope that it will be useful, but -+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -+# for more details. -+# -+# pylint: disable=bad-whitespace,too-many-lines,bad-continuation -+# pylint: disable=too-many-arguments,too-many-positional-arguments -+# pylint: disable=redefined-outer-name,unnecessary-lambda -+# -+ -+import signal -+import sys -+import time -+from pcp import pmapi, pmcc -+from cpmapi import PM_CONTEXT_ARCHIVE -+ -+SYS_METRICS= ["kernel.uname.sysname","kernel.uname.release", -+ "kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"] -+NFSIOSTAT_METRICS = ["nfsclient.mountpoint","nfsclient.export","nfsclient.age", -+ "nfsclient.xprt.sends","nfsclient.xprt.backlog_u","nfsclient.ops.read.ops", -+ "nfsclient.ops.read.errors","nfsclient.ops.read.execute","nfsclient.ops.read.rtt", -+ "nfsclient.ops.read.queue","nfsclient.ops.read.bytes_recv","nfsclient.ops.read.bytes_sent", -+ "nfsclient.ops.read.ntrans","nfsclient.ops.write.ops","nfsclient.ops.write.errors", -+ "nfsclient.ops.write.execute","nfsclient.ops.write.rtt","nfsclient.ops.write.queue", -+ "nfsclient.ops.write.bytes_recv","nfsclient.ops.write.bytes_sent","nfsclient.ops.write.ntrans"] -+ALL_METRICS = NFSIOSTAT_METRICS + SYS_METRICS -+ -+def adjust_length(name): -+ return name.ljust(25) -+class ReportingMetricRepository: -+ -+ def __init__(self,group): -+ self.group=group -+ self.current_cached_values = {} -+ -+ def __sorted(self,data): -+ return dict(sorted(data.items(), key=lambda item: item[0].lower())) -+ -+ def __fetch_current_value(self,metric): -+ val=dict(map(lambda x: (x[1], x[2]), self.group[metric].netValues)) -+ val=self.__sorted(val) -+ return dict(val) -+ -+ def current_value(self,metric): -+ if not metric in self.group: -+ return None -+ if self.current_cached_values.get(metric) is None: -+ first_value=self.__fetch_current_value(metric) -+ self.current_cached_values[metric]=first_value -+ return self.current_cached_values[metric] -+ -+class NfsioStatUtil: -+ def __init__(self,metrics_repository): -+ self.__metric_repository=metrics_repository -+ self.report=ReportingMetricRepository(self.__metric_repository) -+ -+ def mount_point(self): -+ return self.report.current_value('nfsclient.mountpoint') -+ -+ def mount_share(self): -+ return self.report.current_value('nfsclient.export') -+ -+ def mount_share_keys(self): -+ data = self.report.current_value('nfsclient.export') -+ return data.keys() -+ -+ def sample_time(self): -+ return self.report.current_value('nfsclient.age') -+ -+ def xprt_sends(self): -+ return self.report.current_value('nfsclient.xprt.sends') -+ -+ def xprt_backlog(self): -+ return self.report.current_value('nfsclient.xprt.backlog_u') -+ -+ def readops(self): -+ return self.report.current_value('nfsclient.ops.read.ops') -+ -+ def readerrors(self): -+ return self.report.current_value('nfsclient.ops.read.errors') -+ -+ def readexecute(self): -+ return self.report.current_value('nfsclient.ops.read.execute') -+ -+ def readrtt(self): -+ return self.report.current_value('nfsclient.ops.read.rtt') -+ -+ def readqueue(self): -+ return self.report.current_value('nfsclient.ops.read.queue') -+ -+ def readbytesrecv(self): -+ return self.report.current_value('nfsclient.ops.read.bytes_recv') -+ -+ def readbytessent(self): -+ return self.report.current_value('nfsclient.ops.read.bytes_sent') -+ -+ def readntrans(self): -+ return self.report.current_value('nfsclient.ops.read.ntrans') -+ -+ def writeops(self): -+ return self.report.current_value('nfsclient.ops.write.ops') -+ -+ def writeerrors(self): -+ return self.report.current_value('nfsclient.ops.write.errors') -+ -+ def writeexecute(self): -+ return self.report.current_value('nfsclient.ops.write.execute') -+ -+ def writertt(self): -+ return self.report.current_value('nfsclient.ops.write.rtt') -+ -+ def writequeue(self): -+ return self.report.current_value('nfsclient.ops.write.queue') -+ -+ def writebytesrecv(self): -+ return self.report.current_value('nfsclient.ops.write.bytes_recv') -+ -+ def writebytessent(self): -+ return self.report.current_value('nfsclient.ops.write.bytes_sent') -+ -+ def writentrans(self): -+ return self.report.current_value('nfsclient.ops.write.ntrans') -+ -+class NfsiostatReport(pmcc.MetricGroupPrinter): -+ def __init__(self,opts,group): -+ self.opts = opts -+ self.group = group -+ self.samples = opts.samples -+ self.context = opts.context -+ -+ def __get_ncpu(self, group): -+ return group['hinv.ncpu'].netValues[0][2] -+ -+ def __print_machine_info(self, context): -+ timestamp = self.group.pmLocaltime(context.timestamp.tv_sec) -+ # Please check strftime(3) for different formatting options. -+ # Also check TZ and LC_TIME environment variables for more -+ # information on how to override the default formatting of -+ # the date display in the header -+ time_string = time.strftime("%m/%d/%Y %H:%M:%S", timestamp.struct_time()) -+ header_string = '' -+ header_string += context['kernel.uname.sysname'].netValues[0][2] + ' ' -+ header_string += context['kernel.uname.release'].netValues[0][2] + ' ' -+ header_string += '(' + context['kernel.uname.nodename'].netValues[0][2] + ') ' -+ header_string += time_string + ' ' -+ header_string += context['kernel.uname.machine'].netValues[0][2] + ' ' -+ print("%s (%s CPU)" % (header_string, self.__get_ncpu(context))) -+ -+ def __print_values(self,timestamp, nfsstatus): -+ n_shares = nfsstatus.mount_share_keys() -+ mountshare = nfsstatus.mount_share() -+ mountpoint = nfsstatus.mount_point() -+ sampletime = nfsstatus.sample_time() -+ sends = nfsstatus.xprt_sends() -+ backlog = nfsstatus.xprt_backlog() -+ readops = nfsstatus.readops() -+ readerrors = nfsstatus.readerrors() -+ readexecute = nfsstatus.readexecute() -+ readrtt = nfsstatus.readrtt() -+ readqueue = nfsstatus.readqueue() -+ readbytesrecv = nfsstatus.readbytesrecv() -+ readbytessent = nfsstatus.readbytessent() -+ readntrans = nfsstatus.readntrans() -+ writeops = nfsstatus.writeops() -+ writeerrors = nfsstatus.writeerrors() -+ writeexecute = nfsstatus.writeexecute() -+ writertt = nfsstatus.writertt() -+ writequeue = nfsstatus.writequeue() -+ writebytesrecv = nfsstatus.writebytesrecv() -+ writebytessent = nfsstatus.writebytessent() -+ writentrans = nfsstatus.writentrans() -+ -+ print("%-18s:%s"%("Timestamp", timestamp)) -+ print() -+ -+ for name in n_shares: -+ # read -+ r_kilobytes = (readbytessent[name] + readbytesrecv[name]) / 1024 -+ if sampletime[name] > 0: -+ ops_per_sample = sends[name] / sampletime[name] -+ ops_per_sample_read = readops[name] / sampletime[name] -+ r_kilobytes_per_sample = r_kilobytes / sampletime[name] -+ else: -+ ops_per_sample = 0.0 -+ ops_per_sample_read = 0.0 -+ r_kilobytes_per_sample = 0.0 -+ -+ r_retrans = readntrans[name] - readops[name] -+ if readops[name] > 0: -+ r_kilobytes_per_op = r_kilobytes / readops[name] -+ r_retrans_percent = (r_retrans * 100) / readops[name] -+ r_rtt_per_op = readrtt[name] / readops[name] -+ r_exe_per_op = readexecute[name] / readops[name] -+ r_queued_for_per_op = readqueue[name] / readops[name] -+ r_errs_percent = (readerrors[name] * 100) / readops[name] -+ else: -+ r_kilobytes_per_op = 0.0 -+ r_retrans_percent = 0.0 -+ r_rtt_per_op = 0.0 -+ r_exe_per_op = 0.0 -+ r_queued_for_per_op = 0.0 -+ r_errs_percent = 0.0 -+ -+ # write -+ w_kilobytes = (writebytessent[name] + writebytesrecv[name]) / 1024 -+ if sampletime[name] > 0: -+ ops_per_sample_write = writeops[name] / sampletime[name] -+ w_kilobytes_per_sample = w_kilobytes / sampletime[name] -+ else: -+ ops_per_sample_write = 0.0 -+ w_kilobytes_per_sample = 0.0 -+ -+ w_retrans = writentrans[name] - writeops[name] -+ if writeops[name] > 0: -+ w_kilobytes_per_op = w_kilobytes / writeops[name] -+ w_retrans_percent = (w_retrans * 100) / writeops[name] -+ w_rtt_per_op = writertt[name] / writeops[name] -+ w_exe_per_op = writeexecute[name] / writeops[name] -+ w_queued_for_per_op = writequeue[name] / writeops[name] -+ w_errs_percent = (writeerrors[name] * 100) / writeops[name] -+ else: -+ w_kilobytes_per_op = 0.0 -+ w_retrans_percent = 0.0 -+ w_rtt_per_op = 0.0 -+ w_exe_per_op = 0.0 -+ w_queued_for_per_op = 0.0 -+ w_errs_percent = 0.0 -+ -+ print(f"{mountshare[name]} mounted on {mountpoint[name]}:") -+ -+ print(f"{'':14}ops/s{'':7}rpc bklog") -+ print(f"{ops_per_sample:19.3f}{backlog[name]:16.3f}") -+ print() -+ print( -+ "read: " -+ "ops/s kB/s kB/op retrans " -+ "avg RTT (ms) avg exe (ms) avg queue (ms) errors" -+ ) -+ print( -+ f"{'':19}" -+ f"{ops_per_sample_read:5.3f}" -+ f"{r_kilobytes_per_sample:12.3f}" -+ f"{r_kilobytes_per_op:13.3f} " -+ f"{int(r_retrans):2d} ({r_retrans_percent:2.1f}%)" -+ f"{r_rtt_per_op:15.3f}" -+ f"{r_exe_per_op:15.3f}" -+ f"{r_queued_for_per_op:17.3f} " -+ f"{int(readerrors[name]):4d} ({r_errs_percent:2.1f}%)" -+ ) -+ -+ print( -+ "write: " -+ "ops/s kB/s kB/op retrans " -+ "avg RTT (ms) avg exe (ms) avg queue (ms) errors" -+ ) -+ -+ print( -+ f"{'':19}" -+ f"{ops_per_sample_write:5.3f}" -+ f"{w_kilobytes_per_sample:12.3f}" -+ f"{w_kilobytes_per_op:13.3f} " -+ f"{int(w_retrans):2d} ({w_retrans_percent:2.1f}%)" -+ f"{w_rtt_per_op:15.3f}" -+ f"{w_exe_per_op:15.3f}" -+ f"{w_queued_for_per_op:17.3f} " -+ f"{int(writeerrors[name]):4d} ({w_errs_percent:2.1f}%)" -+ ) -+ print() -+ -+ def print_report(self,group,timestamp, manager_nfsiostat): -+ def __print_nfs_status(): -+ nfsstatus = NfsioStatUtil(manager_nfsiostat) -+ if nfsstatus.mount_share(): -+ try: -+ self.__print_machine_info(group) -+ self.__print_values(timestamp, nfsstatus) -+ except IndexError: -+ print("Incorrect machine info due to some missing metrics") -+ return -+ else: -+ pass -+ -+ if self.context != PM_CONTEXT_ARCHIVE and self.samples is None: -+ __print_nfs_status() -+ sys.exit(0) -+ elif self.context == PM_CONTEXT_ARCHIVE and self.samples is None: -+ __print_nfs_status() -+ elif self.samples >=1: -+ __print_nfs_status() -+ self.samples-=1 -+ else: -+ pass -+ -+ def report(self, manager): -+ group = manager["sysinfo"] -+ self.samples = self.opts.pmGetOptionSamples() -+ t_s = group.contextCache.pmLocaltime(int(group.timestamp)) -+ timestamp = time.strftime(NfsiostatOptions.timefmt, t_s.struct_time()) -+ self.print_report(group,timestamp,manager['nfsiostat']) -+ -+class NfsiostatOptions(pmapi.pmOptions): -+ timefmt = "%m/%d/%Y %H:%M:%S" -+ def __init__(self): -+ pmapi.pmOptions.__init__(self, "a:s:Z:zV?") -+ self.pmSetLongOptionHeader("General options") -+ self.pmSetLongOptionHostZone() -+ self.pmSetLongOptionTimeZone() -+ self.pmSetLongOptionHelp() -+ self.pmSetLongOptionSamples() -+ self.pmSetLongOptionVersion() -+ self.samples=None -+ self.context=None -+ -+if __name__ == '__main__': -+ try: -+ opts = NfsiostatOptions() -+ mngr = pmcc.MetricGroupManager.builder(opts,sys.argv) -+ opts.context=mngr.type -+ missing = mngr.checkMissingMetrics(ALL_METRICS) -+ if missing is not None: -+ sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing) -+ sys.exit(1) -+ mngr["nfsiostat"] = ALL_METRICS -+ mngr["sysinfo"] = SYS_METRICS -+ mngr.printer = NfsiostatReport(opts,mngr) -+ sts = mngr.run() -+ sys.exit(sts) -+ except pmapi.pmErr as error: -+ sys.stderr.write('%s\n' % (error.message())) -+ except pmapi.pmUsageErr as usage: -+ usage.message() -+ sys.exit(1) -+ except IOError: -+ signal.signal(signal.SIGPIPE, signal.SIG_DFL) -+ except KeyboardInterrupt: -+ pass --- -2.43.7 - diff --git a/SOURCES/1013-pmlogger_janitor-fix-not-to-terminate-unauthorized-p.patch b/SOURCES/1013-pmlogger_janitor-fix-not-to-terminate-unauthorized-p.patch deleted file mode 100644 index 45b975d..0000000 --- a/SOURCES/1013-pmlogger_janitor-fix-not-to-terminate-unauthorized-p.patch +++ /dev/null @@ -1,217 +0,0 @@ -From e138ce29c6220377728b263b8c8cd8b0794cfcc2 Mon Sep 17 00:00:00 2001 -From: Sagar Sagar -Date: Tue, 3 Mar 2026 10:10:25 +0000 -Subject: [PATCH OL9 1013/1016] pmlogger_janitor fix not to terminate - unauthorized process - -- Fix pmlogger_janitor.sh to verify pid for active pmlogger before kill/removing map files -- Update process type detection to support *BSD ps output syntax -- Refactor pmlogger_check.sh to load janitor env vars more robustly - -upstream :- 358b684619d786acc93092824bc24ba69c5d5dbc - f305a22730a614535098c4f85c05de37eadc790e - -[Orabug:38598244] -Signed-off-by: sagar sagar ---- - src/pmlogger/pmlogger_check.sh | 26 +++++++++--- - src/pmlogger/pmlogger_farm.defaults | 7 ---- - src/pmlogger/pmlogger_janitor.sh | 62 +++++++++++++++++++++++++---- - src/pmlogger/utilproc.sh | 14 +++++++ - 4 files changed, 89 insertions(+), 20 deletions(-) - -diff --git a/src/pmlogger/pmlogger_check.sh b/src/pmlogger/pmlogger_check.sh -index 3593cea..87a9e72 100755 ---- a/src/pmlogger/pmlogger_check.sh -+++ b/src/pmlogger/pmlogger_check.sh -@@ -23,9 +23,6 @@ - - PMLOGGER="$PCP_BINADM_DIR/pmlogger" - PMLOGCONF="$PCP_BINADM_DIR/pmlogconf" --PMLOGGERENVS="$PCP_SYSCONFIG_DIR/pmlogger" --PMLOGGERFARMENVS="$PCP_SYSCONFIG_DIR/pmlogger_farm" --PMLOGGERZEROCONFENVS="$PCP_SHARE_DIR/zeroconf/pmlogger" - - # error messages should go to stderr, not the GUI notifiers - # -@@ -1004,7 +1001,7 @@ END { print m }'` - if [ "X$primary" = Xy ] - then - # User configuration takes precedence over pcp-zeroconf -- envs=`grep -h ^PMLOGGER "$PMLOGGERZEROCONFENVS" "$PMLOGGERENVS" 2>/dev/null` -+ envs=`grep -h ^PMLOGGER "$PCP_SHARE_DIR/zeroconf/pmlogger" "$PCP_SYSCONFIG_DIR/pmlogger" 2>/dev/null` - args="-P $args" - iam=" primary" - # clean up port-map, just in case -@@ -1023,7 +1020,7 @@ END { print m }'` - continue - fi - else -- envs=`grep -h ^PMLOGGER "$PMLOGGERFARMENVS" 2>/dev/null` -+ envs=`grep -h ^PMLOGGER "$PCP_SYSCONFIG_DIR/pmlogger_farm" 2>/dev/null` - args="-h $host $args" - iam="" - fi -@@ -1191,9 +1188,26 @@ fi - # because the legitimate pmloggers, like the primary pmlogger, may - # not be included in the "test" control file(s). - # -+if [ -z "${PMLOGGER_CHECK_SKIP_JANITOR+is_set}" ] -+then -+ check=`grep '^PMLOGGER_CHECK_SKIP_JANITOR=' $PCP_SYSCONFIG_DIR/pmlogger` -+ if [ -n "$check" ] -+ then -+ eval $check -+ fi -+fi -+if [ -z "${PMLOGGER_JANITOR_ARGS+is_set}" ] -+then -+ check=`grep '^PMLOGGER_JANITOR_ARGS=' $PCP_SYSCONFIG_DIR/pmlogger` -+ if [ -n "$check" ] -+ then -+ eval $check -+ fi -+fi - if [ "$CONTROL" = "$PCP_PMLOGGERCONTROL_PATH" -a "$PMLOGGER_CHECK_SKIP_JANITOR" != "yes" ] - then -- $PCP_BINADM_DIR/pmlogger_janitor $daily_args -+ args="$daily_args" -+ $PCP_BINADM_DIR/pmlogger_janitor $args - fi - - [ -f $tmp/err ] && status=1 -diff --git a/src/pmlogger/pmlogger_farm.defaults b/src/pmlogger/pmlogger_farm.defaults -index 10a0132..d752dda 100644 ---- a/src/pmlogger/pmlogger_farm.defaults -+++ b/src/pmlogger/pmlogger_farm.defaults -@@ -26,10 +26,3 @@ - # and checking. - # PMLOGGER_CHECK_SKIP_LOGCONF=yes - --# By default pmlogger_check(1) will run pmlogger_janitor to check for --# pmlogger(1) badness caused by processes and/or files that were once --# managed from the control files but have become detached from those --# control files. --# Setting PMLOGGER_CHECK_SKIP_JANITOR to yes disables pmlogger_janitor --# and maybe useful for QA or special testing --# PMLOGGER_CHECK_SKIP_JANITOR=yes -diff --git a/src/pmlogger/pmlogger_janitor.sh b/src/pmlogger/pmlogger_janitor.sh -index e111031..d7fb8d0 100755 ---- a/src/pmlogger/pmlogger_janitor.sh -+++ b/src/pmlogger/pmlogger_janitor.sh -@@ -77,7 +77,7 @@ _cleanup() - lockfile=`cat $tmp/lock 2>/dev/null` - [ -n "$lockfile" ] && rm -f "$lockfile" - rm -rf $tmp -- $VERY_VERBOSE && echo "End: `date '+%F %T.%N'`" -+ $VERBOSE && echo "End [janitor]: `_datestamp` status=$status" - } - - trap "_cleanup; exit \$status" 0 1 2 3 15 -@@ -242,11 +242,8 @@ else - exec 3>&2 1>"$MYPROGLOG" 2>&1 - fi - --if $VERY_VERBOSE --then -- echo "Start: `date '+%F %T.%N'`" -- _pstree_all $$ --fi -+$VERBOSE && echo "Start [janitor]: `_datestamp`" -+$VERY_VERBOSE && _pstree_all $$ - - # if SaveLogs exists in the $PCP_LOG_DIR/pmlogger directory and is writeable - # then save $MYPROGLOG there as well with a unique name that contains the date -@@ -660,7 +657,13 @@ END { print m }'` - if [ -n "$pid" ] - then - # found matching pmlogger ... cull this one from -- $VERY_VERBOSE && echo "[$controlfile:$line] match PID $pid, nothing to be done" -+ if $VERY_VERBOSE -+ then -+ echo "[$controlfile:$line] match PID $pid, nothing to be done" -+ elif $VERBOSE -+ then -+ echo "Pass 3: PID $pid matches control [$controlfile:$line], nothing to be done" -+ fi - sed <$tmp/loggers >$tmp/tmp -e "/^$pid /d" - mv $tmp/tmp $tmp/loggers - fi -@@ -679,6 +682,51 @@ then - | while read file - do - pid=`echo "$file" | sed -e "s@$PCP_TMP_DIR/pmlogger/@@"` -+ # sanity checks -+ # 1. does this process exist? -+ # 2. is it really pmlogger? -+ # if "no" to either case, remove this (stale) mapfile -+ # and move on ... -+ # -+ if $PCP_PS_PROG -p "$pid" >$tmp/tmp 2>&1 -+ then -+ # ps(1) -p output should be something like this ... -+ # PID TTY TIME CMD -+ # 14298 ? 00:00:00 pmlogger -+ # or this (for *BSD) -+ # PID TT STAT TIME COMMAND -+ # 22839 1 S 0:00.04 /usr/libexec/pcp/bin/pmlogger -N -P ... -+ # -+ if sed -n -e 2p <$tmp/tmp | grep -E -q '( pmlogger$)|(/bin/pmlogger )' -+ then -+ : OK -+ else -+ if $VERBOSE -+ then -+ cat $tmp/tmp -+ echo "Warning: PID $pid is not a pmlogger process, removing $file" -+ fi -+ if $SHOWME -+ then -+ echo "+ rm $file" -+ else -+ rm -f "$file" -+ fi -+ continue -+ fi -+ else -+ if $VERBOSE -+ then -+ echo "Warning: PID $pid has vanished, removing $file" -+ fi -+ if $SHOWME -+ then -+ echo "+ rm $file" -+ else -+ rm -f "$file" -+ fi -+ continue -+ fi - # timing window here, file may have gone away between - # find(1) and awk(1), so just ignore any errors ... - # -diff --git a/src/pmlogger/utilproc.sh b/src/pmlogger/utilproc.sh -index 318b4a1..a0b62c9 100644 ---- a/src/pmlogger/utilproc.sh -+++ b/src/pmlogger/utilproc.sh -@@ -254,3 +254,17 @@ END { exit sts }' - return $__sts - fi - } -+ -+# current time to the highest precision available from date(1) and -+# strftime(3) -+# -+_datestamp() -+{ -+ if [ `date +%N` = N ] -+ then -+ # no %N, %T is the best we can do -+ date +'%F %T' -+ else -+ date +'%F %T.%N' -+ fi -+} -\ No newline at end of file --- -2.43.7 - diff --git a/SOURCES/1014-pcp-ps-implement-sort-option-to-allow-sorting-by-cpu.patch b/SOURCES/1014-pcp-ps-implement-sort-option-to-allow-sorting-by-cpu.patch deleted file mode 100644 index d93a62e..0000000 --- a/SOURCES/1014-pcp-ps-implement-sort-option-to-allow-sorting-by-cpu.patch +++ /dev/null @@ -1,1278 +0,0 @@ -From b4dd24f2fe7c84044cf1c5c2495b95efe48fdecd Mon Sep 17 00:00:00 2001 -From: Sagar Sagar -Date: Tue, 3 Mar 2026 10:11:06 +0000 -Subject: [PATCH OL9 1014/1016] pcp-ps implement --sort option to allow sorting - by %cpu, %mem - -- Implements the '--sort' option in 'pcp ps' include sorting by '%cpu' and '%mem'. - -upstream ref:- https://github.com/performancecopilot/pcp/pull/2436/ - -[Orabug:38719615] -Signed-off-by: sagar sagar ---- - src/pcp/ps/pcp-ps.1 | 20 +- - src/pcp/ps/pcp-ps.py | 920 +++++++++++++++++++++++++------------------ - 2 files changed, 557 insertions(+), 383 deletions(-) - -diff --git a/src/pcp/ps/pcp-ps.1 b/src/pcp/ps/pcp-ps.1 -index 8c44e67..42d1d5f 100755 ---- a/src/pcp/ps/pcp-ps.1 -+++ b/src/pcp/ps/pcp-ps.1 -@@ -1,5 +1,4 @@ --'\"! tbl | mmdoc --'\"macro stdmacro -+'\" t - .\" - .\" Copyright (c) 2022 Oracle and/or its affiliates. - .\" DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -@@ -28,6 +27,8 @@ - [\f3\-P\f1 \f2pid1,pid2..\f1] - [\f3\-p\f1 \f2pid1,pid2..\f1] - [\f3\-o\f1 \f2col1,col2... or ALL\f1] -+[\f3\-O\f1 \f2%mem,%cpu\f1] -+[\f3\-d\f1] - [\f3\-Z\f1 \f2timezone\f1] - [\f3\-z\f1] - [\f3\-?\f1] -@@ -244,6 +245,21 @@ WCHAN and COMMAND. - T} - .TE - .TP -+.BR \-O\ \fI%mem,%cpu\fR ", " \fB\-\-sort=\fI%mem,%cpu\fR -+Sort the output processes by the specified column. -+This sorting can now be used in conjunction with the -+.BR \-o -+option to sort on any user-specified output column (e.g., -o pid,%mem -O %mem). -+The column given to -O/--sort must appear in the -o list. -+If the sorting column is missing from the output format, an error will be shown. -+(In case of an unsupported or missing field name, sorting will default to %cpu.) -+.TP -+.BR \-d\, -+debug mode -+.br -+Enable debug mode, which will cause debug messages to be written to -+stdout. -+.TP - .BR \-Z \ \fItimezone\fR ", "\fB\-\-timezone=\fItimezone\fR - By default, - .B pcp-ps -diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py -index d31fc9a..c8f2e9d 100755 ---- a/src/pcp/ps/pcp-ps.py -+++ b/src/pcp/ps/pcp-ps.py -@@ -60,107 +60,86 @@ class NoneHandlingPrinterDecorator: - class ReportingMetricRepository: - def __init__(self, group): - self.group = group -- self.current_cached_values = {} -- self.previous_cached_values = {} -- -- def __fetch_current_values(self, metric, instance): -- if instance: -- return dict(map(lambda x: (x[0].inst, x[2]), self.group[metric].netValues)) -- else: -- return self.group[metric].netValues[0][2] -- -- def __fetch_previous_values(self, metric, instance): -- if instance: -- return dict(map(lambda x: (x[0].inst, x[2]), self.group[metric].netPrevValues)) -- else: -- return self.group[metric].netPrevValues[0][2] -- -- def current_value(self, metric, instance): -- if not metric in self.group: -- return None -- if instance: -- if self.current_cached_values.get(metric, None) is None: -- lst = self.__fetch_current_values(metric, instance) -- self.current_cached_values[metric] = lst -- -- return self.current_cached_values[metric].get(instance, None) -- else: -- if self.current_cached_values.get(metric, None) is None: -- self.current_cached_values[metric] = self.__fetch_current_values(metric, instance) -- return self.current_cached_values.get(metric, None) -- -- def previous_value(self, metric, instance): -- if not metric in self.group: -- return None -- if instance: -- if self.previous_cached_values.get(metric, None) is None: -- lst = self.__fetch_previous_values(metric, instance) -- self.previous_cached_values[metric] = lst -- -- return self.previous_cached_values[metric].get(instance, None) -- else: -- if self.previous_cached_values.get(metric, None) is None: -- self.previous_cached_values[metric] = self.__fetch_previous_values(metric, instance) -- return self.previous_cached_values.get(metric, None) -+ self._current_cache = {} -+ self._previous_cache = {} -+ -+ def _fetch_values(self, metric, use_previous=False): -+ """Fetch values - always returns a dictionary.""" -+ if metric not in self.group: -+ return {} -+ attr = "netPrevValues" if use_previous else "netValues" -+ values = getattr(self.group[metric], attr, []) -+ return {x[0].inst: x[2] for x in values} if values else {} -+ -+ def _get_values_dict(self, metric, use_previous=False): -+ """Get cached dictionary of all values for a metric.""" -+ cache = self._previous_cache if use_previous else self._current_cache -+ if metric not in cache: -+ cache[metric] = self._fetch_values(metric, use_previous) -+ return cache[metric] -+ -+ def current_value(self, metric, instance=None): -+ """Get current value. Returns single value if instance given, else returns dict.""" -+ values_dict = self._get_values_dict(metric, use_previous=False) -+ if instance is not None: -+ return values_dict.get(instance) -+ return values_dict -+ -+ def previous_value(self, metric, instance=None): -+ """Get previous value. Returns single value if instance given, else returns dict.""" -+ values_dict = self._get_values_dict(metric, use_previous=True) -+ if instance is not None: -+ return values_dict.get(instance) -+ return values_dict - - def current_values(self, metric_name): -- if self.group.get(metric_name, None) is None: -- return None -- if self.current_cached_values.get(metric_name, None) is None: -- self.current_cached_values[metric_name] = self.__fetch_current_values(metric_name, True) -- return self.current_cached_values.get(metric_name, None) -+ """Get all current values for a metric (returns dict).""" -+ return self._get_values_dict(metric_name, use_previous=False) - - def previous_values(self, metric_name): -- if self.group.get(metric_name, None) is None: -- return None -- if self.previous_cached_values.get(metric_name, None) is None: -- self.previous_cached_values[metric_name] = self.__fetch_previous_values(metric_name, True) -- return self.previous_cached_values.get(metric_name, None) -+ """Get all previous values for a metric (returns dict).""" -+ return self._get_values_dict(metric_name, use_previous=True) - - - class ProcessFilter: -+ """ -+ Optimized filtering of processes based on user-provided options. -+ Precomputes filter predicates at instantiation to minimize overhead per process, -+ and leverages generator expressions with all() for efficiency. -+ """ - def __init__(self, options): - self.options = options -+ self._predicates = [] -+ flag = getattr(options, "universal_flag", None) -+ # Username filter -+ if flag == "username" and getattr(options, "filtered_process_user", None) is not None: -+ required_user = options.filtered_process_user.strip() -+ self._predicates.append(lambda proc: proc.user_name().strip() == required_user) -+ # PID filter -+ if flag == "pid" and getattr(options, "pid_list", None) is not None: -+ try: -+ pids = set(int(pid) for pid in options.pid_list) -+ self._predicates.append(lambda proc: int(proc.pid()) in pids) -+ except Exception: -+ self._predicates.append(lambda proc: False) -+ # PPID filter -+ if flag == "ppid" and getattr(options, "ppid_list", None) is not None: -+ try: -+ ppids = set(int(ppid) for ppid in options.ppid_list) -+ self._predicates.append(lambda proc: int(proc.ppid()) in ppids) -+ except Exception: -+ self._predicates.append(lambda proc: False) -+ # Command name filter -+ if flag == "command" and getattr(options, "command_list", None) is not None: -+ commands = set(cmd.strip() for cmd in options.command_list if cmd is not None) -+ self._predicates.append(lambda proc: (proc.process_name() or "").strip() in commands) -+ # If no filter flag: no filter -+ if not getattr(options, "filter_flag", False) or not self._predicates: -+ self._predicates.append(lambda proc: True) - - def filter_processes(self, processes): -- return filter(lambda p: self.__predicate(p), processes) -- -- def __predicate(self, process): -- if self.options.filter_flag: -- return bool(self.__matches_process_username(process) -- and self.__matches_process_pid(process) -- and self.__matches_process_name(process) -- and self.__matches_process_ppid(process)) -- else: -- return True -- -- def __matches_process_username(self, process): -- if self.options.username_filter_flag is True and self.options.filtered_process_user is not None: -- return process.user_name().strip() == self.options.filtered_process_user.strip() -- else: -- return True -- -- def __matches_process_pid(self, process): -- if self.options.pid_filter_flag: -- if self.options.pid_list is not None: -- pid = int(process.pid()) -- return bool(pid in self.options.pid_list) -- return True -- -- def __matches_process_ppid(self, process): -- if self.options.ppid_filter_flag: -- if self.options.ppid_list is not None: -- ppid = int(process.ppid()) -- return bool(ppid in self.options.ppid_list) -- return True -- -- def __matches_process_name(self, process): -- name = process.process_name() -- if self.options.command_filter_flag is True and self.options.command_list is not None and name is not None: -- return name.strip() in self.options.command_list -- else: -- return True -- -+ """Yield processes matching all active predicates.""" -+ return (proc for proc in processes if all(pred(proc) for pred in self._predicates)) - - class ProcessStatusUtil: - def __init__(self, instance, manager, delta_time, metrics_repository): -@@ -169,106 +148,88 @@ class ProcessStatusUtil: - self.__delta_time = delta_time - self.__metric_repository = metrics_repository - -+ def __get_value(self, metric, instance=None): -+ return self.__metric_repository.current_value(metric, instance) -+ def __get_previous_value(self, metric, instance=None): -+ return self.__metric_repository.previous_value(metric, instance) -+ - def pid(self): -- data = str(self.__metric_repository.current_value('proc.psinfo.pid', self.instance)) -- if len(str(data)) < 8: -- whitespace = 8 - len(str(data)) -- res = data.ljust(whitespace + len(str(data)), ' ') -- return res -- else: -- return data -+ data = str(self.__get_value('proc.psinfo.pid', self.instance)) -+ return data.ljust(8) if len(data) < 8 else data -+ - - def ppid(self): -- data = str(self.__metric_repository.current_value('proc.psinfo.ppid', self.instance)) -- if len(str(data)) < 8: -- whitespace = 8 - len(str(data)) -- res = data.ljust(whitespace + len(str(data)), ' ') -- return res -- else: -- return data -+ data = str(self.__get_value('proc.psinfo.ppid', self.instance)) -+ return data.ljust(8) if len(data) < 8 else data - - def user_name(self): -- data = self.__metric_repository.current_value('proc.id.uid_nm', self.instance)[:10] -- if len(data) < 10: -- whitespace = 10 - len(data) -- res = data.ljust(whitespace + len(data), ' ') -- return res -- else: -+ data = self.__get_value('proc.id.uid_nm', self.instance) -+ if data is None: - return data -+ return data.ljust(10) if len(data) < 10 else data[:10] - - def process_name(self): -- try: -- data = self.__metric_repository.current_value('proc.psinfo.cmd', self.instance)[:20] -- if len(data) < 20: -- whitespace = 20 - len(data) -- res = data.ljust(whitespace + len(data), ' ') -- return res -- else: -- return data -- except TypeError: -- data = '-' -+ data = self.__get_value('proc.psinfo.cmd', self.instance) -+ if data is None: -+ return '-' -+ return data.ljust(20) if len(data) < 20 else data[:20] -+ -+ def process_name_with_args(self, flag=False): -+ data = self.__get_value('proc.psinfo.psargs', self.instance) -+ if data is None: - return data -- -- def process_name_with_args(self,flag = False): -- if flag is True: -- data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance) -- else: -- data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance)[:30] -- if len(data) < 30: -- whitespace = 30 - len(data) -- res = data.ljust(whitespace + len(data), ' ') -- return res -+ max_length = 30 -+ if not flag: -+ return data[:max_length].ljust(max_length) - else: - return data - - def process_name_with_args_last(self): - return self.process_name_with_args(True) - def vsize(self): -- return self.__metric_repository.current_value('proc.psinfo.vsize', self.instance) -+ return self.__get_value('proc.psinfo.vsize', self.instance) - - def rss(self): -- return self.__metric_repository.current_value('proc.psinfo.rss', self.instance) -- -+ return self.__get_value('proc.psinfo.rss', self.instance) - def mem(self): -- total_mem = self.__metric_repository.current_value('mem.physmem', None) -- rss = self.__metric_repository.current_value('proc.psinfo.rss', self.instance) -+ total_mem = self.__get_value('mem.physmem', None) -+ if isinstance(total_mem, dict): -+ total_mem = next(iter(total_mem.values()), None) -+ rss = self.__get_value('proc.psinfo.rss', self.instance) - if total_mem is not None and rss is not None: - return float("%.2f" % (100 * float(rss) / total_mem)) - else: - return None - - def s_name(self): -- return self.__metric_repository.current_value('proc.psinfo.sname', self.instance) -+ return self.__get_value('proc.psinfo.sname', self.instance) - - def cpu_number(self): -- return self.__metric_repository.current_value('proc.psinfo.processor', self.instance) -+ return self.__get_value('proc.psinfo.processor', self.instance) - - def system_percent(self): -- c_systemtime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance) -- p_systemtime = self.__metric_repository.previous_value('proc.psinfo.stime', self.instance) -+ c_systemtime = self.__get_value('proc.psinfo.stime', self.instance) -+ p_systemtime = self.__get_previous_value('proc.psinfo.stime', self.instance) - if c_systemtime is not None and p_systemtime is not None: -- percent_of_time = 100 * float(c_systemtime - p_systemtime) / float(1000 * self.__delta_time) -+ system_time_diff = float(c_systemtime - p_systemtime) -+ delta_time_in_ms = float(1000 * self.__delta_time) -+ percent_of_time = 100 * system_time_diff / delta_time_in_ms - return float("%.2f" % percent_of_time) - else: -- return None -+ return '-' - - def wchan_s(self): -- process = self.__metric_repository.current_value('proc.psinfo.wchan_s', self.instance) -- if process is None: -- process = '-' -- return process -- elif len(process) < 30: -- whitespace = 30 - len(process) -- res = process.ljust(whitespace + len(process), ' ') -- return res -- return process[:30] -+ process = self.__get_value('proc.psinfo.wchan_s', self.instance) -+ if process is None or process == "0" or process == '': -+ return '-' + ' ' * 29 -+ return process.ljust(30) if len(process) < 30 else process[:30] - - def priority(self): -- return self.__metric_repository.current_value('proc.psinfo.priority', self.instance) -+ return self.__get_value('proc.psinfo.priority', self.instance) - - def user_percent(self): -- c_usertime = self.__metric_repository.current_value('proc.psinfo.utime', self.instance) -- p_usertime = self.__metric_repository.previous_value('proc.psinfo.utime', self.instance) -+ c_usertime = self.__get_value('proc.psinfo.utime', self.instance) -+ p_usertime = self.__get_previous_value('proc.psinfo.utime', self.instance) - if c_usertime is not None and p_usertime is not None: - percent_of_time = 100 * float(c_usertime - p_usertime) / float(1000 * self.__delta_time) - return float("%.2f" % percent_of_time) -@@ -276,8 +237,8 @@ class ProcessStatusUtil: - return None - - def guest_percent(self): -- c_guesttime = self.__metric_repository.current_value('proc.psinfo.guest_time', self.instance) -- p_guesttime = self.__metric_repository.previous_value('proc.psinfo.guest_time', self.instance) -+ c_guesttime = self.__get_value('proc.psinfo.guest_time', self.instance) -+ p_guesttime = self.__get_previous_value('proc.psinfo.guest_time', self.instance) - if c_guesttime is not None and p_guesttime is not None: - percent_of_time = 100 * float(c_guesttime - p_guesttime) / float(1000 * self.__delta_time) - return float("%.2f" % percent_of_time) -@@ -291,8 +252,8 @@ class ProcessStatusUtil: - return None - - def stime(self): -- c_systime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance) -- p_systime = self.__metric_repository.previous_value('proc.psinfo.stime', self.instance) -+ c_systime = self.__get_value('proc.psinfo.stime', self.instance) -+ p_systime = self.__get_previous_value('proc.psinfo.stime', self.instance) - # sometimes the previous_value seems to be Nonetype, not sure why - if p_systime is None: # print a '?' here - return '?' -@@ -300,21 +261,28 @@ class ProcessStatusUtil: - return c_systime - p_systime - - def start(self): -- s_time = self.__metric_repository.current_value('proc.psinfo.start_time', self.instance) -+ s_time = self.__get_value('proc.psinfo.start_time', self.instance) - group = self.manager['psstat'] -- kernel_boottime = group['kernel.all.boottime'].netValues[0][2] -- ts = group.contextCache.pmLocaltime(int(kernel_boottime + (s_time / 1000))) -- if group.timestamp.tv_sec - (kernel_boottime + s_time / 1000) >= 24*60*60: -- # started one day or more ago, use MmmDD HH:MM -+ try: -+ kernel_boottime = group['kernel.all.boottime'].netValues[0][2] -+ except (KeyError, IndexError, TypeError, AttributeError): -+ return '?' -+ if s_time is None or kernel_boottime is None: -+ return '?' -+ ts_val = kernel_boottime + (s_time / 1000.0) -+ try: -+ ts = group.contextCache.pmLocaltime(int(ts_val)) -+ except Exception: -+ return '?' -+ if group.timestamp.tv_sec - ts_val >= 24*60*60: - return time.strftime("%b%d %H:%M", ts.struct_time()) - else: -- # started less than one day ago, use HH:MM:SS - return time.strftime("%H:%M:%S", ts.struct_time()) - - - def total_time(self): -- c_usertime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance) -- p_guesttime = self.__metric_repository.previous_value('proc.psinfo.utime', self.instance) -+ c_usertime = self.__get_value('proc.psinfo.stime', self.instance) -+ p_guesttime = self.__get_previous_value('proc.psinfo.utime', self.instance) - timefmt = "%H:%M:%S" - if c_usertime and p_guesttime is not None: - total_time = (c_usertime / 1000) + (p_guesttime / 1000) -@@ -323,29 +291,28 @@ class ProcessStatusUtil: - return time.strftime(timefmt, time.gmtime(total_time)) - - def tty_name(self): -- return self.__metric_repository.current_value('proc.psinfo.ttyname', self.instance) -+ return self.__get_value('proc.psinfo.ttyname', self.instance) - - def user_id(self): -- return self.__metric_repository.current_value('proc.id.uid', self.instance) -+ return self.__get_value('proc.id.uid', self.instance) - - def start_time(self): -- return self.__metric_repository.current_value('proc.psinfo.start_time', self.instance) -+ return self.__get_value('proc.psinfo.start_time', self.instance) - - def func_state(self): -- s_name = self.__metric_repository.current_value('proc.psinfo.sname', self.instance) -+ s_name = self.__get_value('proc.psinfo.sname', self.instance) - if s_name == 'R': -- return 'N/A' -+ return '-' - elif s_name is None: -- return '?' -+ return '-' - else: - return self.wchan_s() - - def policy(self): -- policy_int = self.__metric_repository.current_value('proc.psinfo.policy', self.instance) -- if policy_int is not None and policy_int <= len(SCHED_POLICY): -- # return policy_int -+ policy_int = self.__get_value('proc.psinfo.policy', self.instance) -+ if isinstance(policy_int, int) and 0 <= policy_int < len(SCHED_POLICY): - return SCHED_POLICY[policy_int] -- return None -+ return '?' - - - PIDINFO_PAIR = {"%cpu": ('%CPU', ProcessStatusUtil.system_percent), -@@ -353,16 +320,16 @@ PIDINFO_PAIR = {"%cpu": ('%CPU', ProcessStatusUtil.system_percent), - "start": ("START\t", ProcessStatusUtil.start), - "time": ("TIME\t", ProcessStatusUtil.total_time), - "cls": ("CLS", ProcessStatusUtil.policy), -- "cmd": ("Command\t\t\t", ProcessStatusUtil.process_name), -+ "cmd": ("Command\t\t", ProcessStatusUtil.process_name), - "args": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args), -- "args_last": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args_last), -+ "args_last": ("Command", ProcessStatusUtil.process_name_with_args_last), - "pid": ("PID\t", ProcessStatusUtil.pid), - "ppid": ("PPID\t", ProcessStatusUtil.ppid), - "pri": ("PRI", ProcessStatusUtil.priority), - "state": ("S", ProcessStatusUtil.s_name), - "rss": ("RSS", ProcessStatusUtil.rss), - "rtprio": ("RTPRIO", ProcessStatusUtil.priority), -- "tty": ("TTY\t", ProcessStatusUtil.tty_name), -+ "tty": ("TTY", ProcessStatusUtil.tty_name), - "pname": ("Pname\t\t", ProcessStatusUtil.process_name), - "vsize": ("VSZ", ProcessStatusUtil.vsize), - "uname": ("USER\t", ProcessStatusUtil.user_name), -@@ -376,8 +343,8 @@ class ProcessStatus: - self.__metric_repository = metric_repository - - def get_processes(self, delta_time): -- return map(lambda pid: -- (ProcessStatusUtil(pid, self.__manager, delta_time, self.__metric_repository)), self.__pids()) -+ # Use generator expression for lazy evaluation and reduced memory usage -+ return (ProcessStatusUtil(pid, self.__manager, delta_time, self.__metric_repository) for pid in self.__pids()) - - def __pids(self): - pid_dict = self.__metric_repository.current_values('proc.psinfo.pid') -@@ -397,65 +364,114 @@ class DynamicProcessReporter: - self.processStatOptions.colum_list.index(key) == len(self.processStatOptions.colum_list) - 1 - def print_report(self, timestamp, header_indentation, value_indentation): - -- # when the print count is exhausted exit the program gracefully -- # we can't use break here because it's being called by the run manager -- if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE: -- if self.processStatOptions.print_count == 0: -- sys.exit(0) -+ # Exit logic for non-archive context -+ if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE and self.processStatOptions.print_count == 0: -+ sys.exit(0) -+ -+ # Sorting validations -+ sorting_idx = None -+ if self.processStatOptions.sorting_flag: -+ if self.processStatOptions.filterstate == "ALL": -+ if self.processStatOptions.sorting_order == '%mem': -+ sorting_idx = 7 -+ elif self.processStatOptions.sorting_order == '%cpu': -+ sorting_idx = 8 - else: -- self.processStatOptions.print_count -= 1 -- -- if self.processStatOptions.filterstate is not None: -- self.printer("Timestamp" + header_indentation + -- "USER\t\tPID\t\tPPID\t\tPRI\t%CPU\t%MEM\tVSZ\tRSS\tS\tSTARTED\t\tTIME\t\tWCHAN\t\t\t\tCommand") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ sorting_idx = next((idx for idx, key in enumerate(self.processStatOptions.colum_list) -+ if key == self.processStatOptions.sorting_order), -+ None) -+ if sorting_idx is None: -+ raise ValueError("Sorting order not found in output columns") -+ # Adjust for timestamp column -+ sorting_idx += 1 -+ -+ # Always compute process list ONCE -+ processes = self.process_filter.filter_processes( -+ self.process_report.get_processes(self.delta_time) -+ ) -+ -+ output_list = [] -+ header = None -+ -+ # -------- PATH 1: With filterstate -------- # -+ if self.processStatOptions.filterstate == "ALL": -+ header = ( -+ "Timestamp\tUSER\t\tPID\t\tPPID\t\tPRI\t%CPU\t%MEM\tVSZ" -+ "\tRSS\tS\tSTARTED\t\tTIME\t\t" -+ "WCHAN\t\t\t\tCommand" -+ ) -+ -+ # Precompute format string -+ # fmt = ( -+ # "{ts}{indent}{user}\t{pid}\t{ppid}\t{pri}\t{cpu}\t{mem}\t" -+ # "{vsz}\t{rss}\t{s}\t{started}\t{time}\t{wchan}\t{cmd}" -+ # ) - for process in processes: -- total_percent = process.total_percent() -- current_process_pid = process.pid() -- current_process_sname = process.s_name() -- if process.wchan_s() is not None: -- wchan = process.wchan_s() -- else: -- wchan = '-' -- key = (current_process_sname, current_process_pid) -- if key in process_state_info: -- process_state_info[key] = process_state_info[key] + self.delta_time -- else: -- process_state_info[key] = self.delta_time -- process_name = process.process_name_with_args() -- if 7 < len(wchan) < 15: -- self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t%s" % -- (timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(), -- process.priority(), total_percent, process.system_percent(), process.vsize(), -- process.rss(), current_process_sname, process.start(), process.total_time(), wchan, -- process_name)) -- elif len(wchan) >= 15: -- self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % -- (timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(), -- process.priority(), total_percent, process.system_percent(), process.vsize(), -- process.rss(), current_process_sname, process.start(), process.total_time(), wchan, -- process_name)) -- else: -- self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t\t%s" % -- (timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(), -- process.priority(), total_percent, process.system_percent(), process.vsize(), -- process.rss(), current_process_sname, process.start(), process.total_time(), wchan, -- process_name)) -+ # Maintain state info -+ key = (process.s_name(), process.pid()) -+ process_state_info[key] = process_state_info.get(key, 0) + self.delta_time -+ row = [timestamp] -+ row.extend([ -+ process.user_name(), -+ process.pid(), -+ process.ppid(), -+ process.priority(), -+ process.total_percent(), -+ process.system_percent(), -+ process.vsize(), -+ process.rss(), -+ process.s_name(), -+ process.start(), -+ process.total_time(), -+ process.wchan_s(), -+ process.process_name_with_args_last()[:45] -+ ]) -+ output_list.append( -+ "\t".join( -+ str(x) if x is not None else '' for x in row -+ ) -+ ) -+ -+ # -------- PATH 2: Customized column list -------- # - elif self.processStatOptions.colum_list is not None: -- header = "Timestamp" + '\t' -+ -+ header = "Timestamp\t" - for key in self.processStatOptions.colum_list: - if key in PIDINFO_PAIR: -- header += PIDINFO_PAIR[key][0] + '\t\t' -- print(header) -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ header += PIDINFO_PAIR[key][0] + "\t" -+ - for process in processes: -- data_to_print = timestamp + '\t' -+ row = [timestamp] - for key in self.processStatOptions.colum_list: - if self._is_last_and_args(key): -- data_to_print += str(PIDINFO_PAIR["args_last"][1](process)) + '\t\t' -+ row.append(str(PIDINFO_PAIR["args_last"][1](process))) - elif key in PIDINFO_PAIR: -- data_to_print += str(PIDINFO_PAIR[key][1](process)) + '\t\t' -- print(data_to_print) -+ row.append(str(PIDINFO_PAIR[key][1](process))) -+ # print(row) -+ output_list.append("\t".join(str(x) if x is not None else '' for x in row)) -+ -+ # -------- PATH 3: Invalid filterstate or column list -------- # -+ # This should never happen, but just in case -+ else: -+ raise ValueError("No valid filterstate or column list provided") -+ -+ # Sorting logic -+ if self.processStatOptions.sorting_flag and sorting_idx is not None: -+ output_list.sort( -+ key=lambda x: ( -+ sorting_idx, -+ float('inf') if ( -+ len(x.split()) <= sorting_idx or -+ not x.split()[sorting_idx].replace('.', '', 1).isdigit() -+ ) else float(x.split()[sorting_idx]) -+ ), -+ reverse=True -+ ) -+ -+ # --------- Print output --------- # -+ self.printer(header) -+ self.printer('\n'.join(output_list)) -+ - - - class ProcessStatusReporter: -@@ -467,92 +483,110 @@ class ProcessStatusReporter: - self.processStatOptions = processStatOptions - - def print_report(self, timestamp, header_indentation, value_indentation): -- -- # when the print count is exhausted exit the program gracefully -- # we can't use break here because it's being called by the run manager -- if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE: -- if self.processStatOptions.print_count == 0: -- sys.exit(0) -- else: -- self.processStatOptions.print_count -= 1 -- -- if self.processStatOptions.show_all_process: -- self.printer("Timestamp" + header_indentation + "PID\t\t\tTTY\tTIME\t\tCMD") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ if self.processStatOptions.debug_mode: -+ print("option selected: %s" % self.processStatOptions.universal_flag) -+ print("filer option status: %s" % self.processStatOptions.filterstate) -+ FORMAT_MAP = { -+ "empty_arg" : "Timestamp" + header_indentation + "PID\t\tTIME\t\tCMD", -+ "all" : "Timestamp" + header_indentation + "PID\t\t\tTTY\tTIME\t\tCMD", -+ "user": "Timestamp" + header_indentation + "USERNAME\tPID\t\t%CPU\t%MEM\tVSZ\tRSS\t" + -+ "TTY\tSTAT\tTIME\t\tSTART\t\tCOMMAND", -+ "pid": "Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD", -+ "ppid": "Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD", -+ "username": "Timestamp" + header_indentation + "USERNAME\t\tPID\t\t%CPU\t%MEM\tVSZ\tRSS\t" + -+ "TTY\tSTAT\t\tTIME\t\tSTART\t\tCOMMAND", -+ "command": "Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD" -+ } -+ selected_flag = self.processStatOptions.universal_flag -+ header = FORMAT_MAP.get(selected_flag, FORMAT_MAP["all"]) -+ # Bulk buffer for all rows, print all at once for efficiency -+ output_rows = [] -+ cpu_idx , mem_idx = 0, 0 -+ processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ def safe_str(val): -+ return '' if val is None else str(val) -+ if selected_flag == "all": - for process in processes: -- command = process.process_name_with_args(True) -- ttyname = process.tty_name() -- self.printer("%s%s%s\t\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ttyname, -- process.total_time(), command)) -- elif self.processStatOptions.empty_arg_flag: -- self.printer("Timestamp" + header_indentation + "PID\t\tTIME\t\tCMD") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ output_rows.append("%s%s%s\t\t%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.pid()), -+ safe_str(process.tty_name()), safe_str(process.total_time()), -+ safe_str(process.process_name_with_args(True)))) -+ elif selected_flag == "empty_arg": - for process in processes: -- pid = process.pid() -- command = process.process_name() -- self.printer("%s%s%s\t%s\t%s" % (timestamp, value_indentation, pid, -- process.total_time(), command)) -- elif self.processStatOptions.pid_filter_flag: -- self.printer("Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ output_rows.append("%s%s%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.pid()), -+ safe_str(process.total_time()), safe_str(process.process_name()))) -+ elif selected_flag == "pid": - for process in processes: -- pid = process.pid() -- command = process.process_name() -- ttyname = process.tty_name() -- self.printer("%s%s%s\t%s\t%s\t%s\t%s" % (timestamp, value_indentation, pid, process.ppid(), -- ttyname, process.total_time(), command)) -- elif self.processStatOptions.ppid_filter_flag: -- self.printer("Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ output_rows.append("%s%s%s\t%s\t%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.pid()), safe_str(process.ppid()), -+ safe_str(process.tty_name()), safe_str(process.total_time()), safe_str(process.process_name()))) -+ elif selected_flag == "ppid": - for process in processes: -- ppid = process.ppid() -- command = process.process_name() -- ttyname = process.tty_name() -- self.printer("%s%s%s\t%s\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ppid, ttyname, -- process.total_time(), command)) -- elif self.processStatOptions.username_filter_flag: -- self.printer("Timestamp" + header_indentation + "USERNAME\t\tPID\t\t%CPU\t%MEM\tVSZ\tRSS\t" + -- "TTY\tSTAT\t\tTIME\t\tSTART\t\tCOMMAND") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ output_rows.append("%s%s%s\t%s\t%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.pid()), -+ safe_str(process.ppid()), safe_str(process.tty_name()), -+ safe_str(process.total_time()), safe_str(process.process_name()))) -+ elif selected_flag == "username": - for process in processes: -- self.printer("%s%s%s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % ( -- timestamp, value_indentation, process.user_name(), process.pid(), -- process.system_percent(), process.total_percent(), process.vsize(), process.rss(), -- process.tty_name(), process.ppid(), process.total_time(), process.start(), -- process.process_name())) -- -- elif self.processStatOptions.user_oriented_format: -- self.printer("Timestamp" + header_indentation + "USERNAME\tPID\t\t%CPU\t%MEM\tVSZ\tRSS\t" + -- "TTY\tSTAT\tTIME\t\tSTART\t\tCOMMAND") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ output_rows.append("%s%s%s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.user_name()), -+ safe_str(process.pid()),safe_str(process.system_percent()), safe_str(process.total_percent()), -+ safe_str(process.vsize()), safe_str(process.rss()), -+ safe_str(process.tty_name()), safe_str(process.ppid()), safe_str(process.total_time()), -+ safe_str(process.start()), -+ safe_str(process.process_name()))) -+ cpu_idx = 5 -+ mem_idx = 6 -+ elif selected_flag == "user": - for process in processes: -- self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % ( -- timestamp, value_indentation, process.user_name(), process.pid(), -- process.system_percent(), process.total_percent(), process.vsize(), process.rss(), -- process.tty_name(), process.s_name(), process.total_time(), process.start(), -- process.process_name())) -- -- elif self.processStatOptions.command_filter_flag: -- self.printer("Timestamp" + header_indentation + "PID\t\tPPID\t\tTTY\tTIME\t\tCMD") -- processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time)) -+ output_rows.append("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.user_name()), -+ safe_str(process.pid()), -+ safe_str(process.system_percent()), safe_str(process.total_percent()), -+ safe_str(process.vsize()), safe_str(process.rss()), -+ safe_str(process.tty_name()), safe_str(process.s_name()), -+ safe_str(process.total_time()), safe_str(process.start()), -+ safe_str(process.process_name()))) -+ cpu_idx = 5 -+ mem_idx = 6 -+ elif selected_flag == "command": - for process in processes: -- ppid = process.ppid() -- command = process.process_name() -- ttyname = process.tty_name() -- self.printer("%s%s%s\t%s\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ppid, ttyname, -- process.total_time(), command)) -- -+ output_rows.append("%s%s%s\t%s\t%s\t%s\t%s" % ( -+ safe_str(timestamp), safe_str(value_indentation), safe_str(process.pid()), -+ safe_str(process.ppid()), safe_str(process.tty_name()), -+ safe_str(process.total_time()), safe_str(process.process_name()))) -+ else: # default fallback, print nothing extra -+ pass -+ if self.processStatOptions.sorting_flag: -+ if cpu_idx == 0 and mem_idx == 0: -+ raise ValueError("Sorting indices not set for selected flag, " -+ "please remove sorting flag or choose another flag for output") -+ if self.processStatOptions.sorting_order == '%cpu': -+ output_rows.sort( -+ key=lambda x: float(x.split()[cpu_idx]) if x.split()[cpu_idx].replace('.', '', 1).isdigit() -+ else float('-inf'), -+ reverse=True) -+ elif self.processStatOptions.sorting_order == '%mem': -+ output_rows.sort( -+ key=lambda x: float(x.split()[mem_idx]) if x.split()[mem_idx].replace('.', '', 1).isdigit() -+ else float('-inf'), -+ reverse=True) -+ if output_rows: -+ self.printer(header) -+ self.printer('\n'.join(output_rows)) - - class ProcessStatReport(pmcc.MetricGroupPrinter): - Machine_info_count = 0 - group = None -- def __init__(self, group=None): -+ def __init__(self, group=None, options = None): - self.group = group -+ self.processStatOptions = options - - def timeStampDelta(self): - s = self.group.timestamp.tv_sec - self.group.prevTimestamp.tv_sec -- u = self.group.timestamp.tv_usec - self.group.prevTimestamp.tv_usec -- return s + u / 1000000.0 -+ n = self.group.timestamp.tv_nsec - self.group.prevTimestamp.tv_nsec -+ return s + n / 1000000000.0 - - def print_machine_info(self,context): - timestamp = context.pmLocaltime(self.group.timestamp.tv_sec) -@@ -573,70 +607,85 @@ class ProcessStatReport(pmcc.MetricGroupPrinter): - return group['hinv.ncpu'].netValues[0][2] - - def __print_report(self, manager,timestamp, header_indentation, value_indentation,interval_in_seconds): -+ if self.processStatOptions.debug_mode: -+ print("Printing standard report") - metric_repository = ReportingMetricRepository(self.group) - process_report = ProcessStatus(manager, metric_repository) -- process_filter = ProcessFilter(ProcessStatOptions) -+ process_filter = ProcessFilter(self.processStatOptions) - stdout = StdoutPrinter() - printdecorator = NoneHandlingPrinterDecorator(stdout) - report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, -- printdecorator.Print, ProcessStatOptions) -+ printdecorator.Print, self.processStatOptions) - report.print_report(timestamp, header_indentation, value_indentation) - def __print_dynamic_report(self, manager,timestamp, header_indentation, value_indentation,interval_in_seconds): -+ if self.processStatOptions.debug_mode: -+ print("Printing dynamic report") - metric_repository = ReportingMetricRepository(self.group) - process_report = ProcessStatus(manager, metric_repository) -- process_filter = ProcessFilter(ProcessStatOptions) -+ process_filter = ProcessFilter(self.processStatOptions) - stdout = StdoutPrinter() - printdecorator = NoneHandlingPrinterDecorator(stdout) - report = DynamicProcessReporter(process_report, process_filter, interval_in_seconds, -- printdecorator.Print, ProcessStatOptions) -+ printdecorator.Print, self.processStatOptions) - report.print_report(timestamp, header_indentation, value_indentation) - def __get_timestamp(self): - ts = self.group.contextCache.pmLocaltime(int(self.group.timestamp)) -- timestamp = time.strftime(ProcessStatOptions.timefmt, ts.struct_time()) -+ timestamp = time.strftime(self.processStatOptions.timefmt, ts.struct_time()) - return timestamp - - def report(self, manager): - try: - if self.group['proc.psinfo.utime'].netPrevValues is None: -- # need two fetches to report rate converted counter metrics -- return -+ return False # Not ready, skip increment - if not self.group['hinv.ncpu'].netValues or not self.group['kernel.uname.sysname'].netValues: -- return -+ return False - try: - if not self.Machine_info_count: - self.print_machine_info(manager) - self.Machine_info_count = 1 - except IndexError: -- return -+ if self.processStatOptions.debug_mode: -+ print("IndexError while printing machine info") -+ return False -+ if self.processStatOptions.debug_mode: -+ print("Starting report generation") -+ print("Need to print samples: %s" % self.processStatOptions.print_count) -+ if self.processStatOptions.print_count == 0: -+ if self.processStatOptions.debug_mode: -+ print("Print count exhausted, exiting") -+ sys.exit(0) - timestamp = self.__get_timestamp() - interval_in_seconds = self.timeStampDelta() - header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " " - value_indentation = ((len(header_indentation) + 9) - len(timestamp)) * " " - - # Doing this for one single print instance in case there is no count specified -- if ProcessStatOptions.print_count is None: -- ProcessStatOptions.print_count = 1 -+ if self.processStatOptions.print_count is None \ -+ and self.processStatOptions.context is not PM_CONTEXT_ARCHIVE: -+ self.processStatOptions.print_count = 1 - # ================================================================ -- if ProcessStatOptions.selective_colum_flag: -+ if self.processStatOptions.selective_colum_flag: -+ if self.processStatOptions.debug_mode: -+ print("Selective column flag is set") - self.__print_dynamic_report(manager,timestamp, header_indentation, - value_indentation, interval_in_seconds) - else: - self.__print_report(manager,timestamp, header_indentation, value_indentation, interval_in_seconds) -+ if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE: -+ self.processStatOptions.print_count -= 1 -+ return True # Data was printed - finally: - sys.stdout.flush() - - - class ProcessStatOptions(pmapi.pmOptions): -- show_all_process = False -- command_filter_flag = False -- ppid_filter_flag = False -- pid_filter_flag = False -- username_filter_flag = False -+ universal_flag = None - selective_colum_flag = False - filter_flag = False -- user_oriented_format = False -- empty_arg_flag = False - filterstate = None -+ debug_mode = False -+ sorting_flag = False -+ sorting_order = None - timefmt = "%H:%M:%S" - print_count = None - colum_list = [] -@@ -647,7 +696,7 @@ class ProcessStatOptions(pmapi.pmOptions): - context = None - - def __init__(self): -- pmapi.pmOptions.__init__(self, "t:c:e::p:ukVZ:z?:o:P:l:U:k") -+ pmapi.pmOptions.__init__(self, "t:c:e::p:ukVZ:z?:o:P:l:U:k:O:d") - self.pmSetOptionCallback(self.extraOptions) - self.pmSetOverrideCallback(self.override) - self.options() -@@ -662,108 +711,214 @@ class ProcessStatOptions(pmapi.pmOptions): - "Select the process by process ID") - self.pmSetLongOption("", 1, "P", "[ppid1,ppid2,...]", "Select the process by process parent ID") - self.pmSetLongOption("", 1, "U", "[User Name]", "Select the process by user name") -- self.pmSetLongOption("", 1, "o", "[col1,col2,... Or ALL]", "User -defined format " + -- "USE -o [all] or -B [col1, col2 , ...]" + -- "\n\t\t\tsupported user defined colums are command, wchan, started, Time, pid, ppid, " -- "%mem, pri, user, %cpu and S " -- "\n\t\t\tALL option shows USER,PID,PPID,PRI,%CPU,%MEM,VSZ,RSS,S,STARTED,TIME,WCHAN and " -- "Command") -+ self.pmSetLongOption( -+ "", -+ 1, -+ "o", -+ "[col1,col2,... Or ALL]", -+ ( -+ "User -defined format USE -o [all] or -B [col1, col2 , ...]\n" -+ "\t\t\tsupported user defined colums are command, wchan, started, Time, pid, ppid, " -+ "%mem, pri, user, %cpu and S\n" -+ "\t\t\tALL option shows USER,PID,PPID,PRI,%CPU,%MEM,VSZ,RSS,S,STARTED,TIME,WCHAN and\n" -+ "Command" -+ ), -+ ) - self.pmSetLongOptionText("\tCOL\tHEADER \tDESCRIPTION") - self.pmSetLongOptionText("\t%cpu\t%CPU \tcpu utilization of the process") - self.pmSetLongOptionText("\t%mem\t%MEM \tphysical memory on the machine expressed as a percentage") - self.pmSetLongOptionText("\tstart\tSTART \ttime the command started") - self.pmSetLongOptionText("\ttime\tTIME \taccumulated cpu time, user + system") -- self.pmSetLongOptionText("\tcls\tCLS \tscheduling class of the process") -- self.pmSetLongOptionText("\tcmd\tCMD\tsee args. (alias args, command).") -- self.pmSetLongOptionText("\tpid\tPID \tthe process ID") -- self.pmSetLongOptionText("\tppid\tPPID\tparent process ID") -- self.pmSetLongOptionText("\tpri\tPRI \tpriority of the process") -- self.pmSetLongOptionText("\tstate\tS \tsee s") -- self.pmSetLongOptionText("\trss\tRSS \tthe non-swapped physical memory that a task has used") -- self.pmSetLongOptionText("\trtprio\tRTPRIO \trealtime priority") -- self.pmSetLongOptionText("\tpname\tPname\tProcess name") -- # self.pmSetLongOptionText("\ttime\tTIME \tcumulative CPU time") -- self.pmSetLongOptionText("\ttty\tTT \tcontrolling tty (terminal)") -- self.pmSetLongOptionText("\tuid\tUID \tsee euid") -- self.pmSetLongOptionText("\tvsize\tVSZ \tsee vsz") -- self.pmSetLongOptionText("\tuname\tUSER \tsee euser") -- self.pmSetLongOptionText("\twchan\tWCHAN \tname of the kernel function in which the process is sleeping") -- self.pmSetLongOption("", 0, 'u', "", "Display user-oriented format") -+ self.pmSetLongOptionText( -+ "\tcls\tCLS \tscheduling class of the process" -+ ) -+ self.pmSetLongOptionText( -+ "\tcmd\tCMD\tsee args. (alias args, command)." -+ ) -+ self.pmSetLongOptionText( -+ "\tpid\tPID \tthe process ID" -+ ) -+ self.pmSetLongOptionText( -+ "\tppid\tPPID\tparent process ID" -+ ) -+ self.pmSetLongOptionText( -+ "\tpri\tPRI \tpriority of the process" -+ ) -+ self.pmSetLongOptionText( -+ "\tstate\tS \tsee s" -+ ) -+ self.pmSetLongOptionText( -+ "\trss\tRSS \tthe non-swapped physical memory that a task has used" -+ ) -+ self.pmSetLongOptionText( -+ "\trtprio\tRTPRIO \trealtime priority" -+ ) -+ self.pmSetLongOptionText( -+ "\tpname\tPname\tProcess name" -+ ) -+ self.pmSetLongOptionText( -+ "\ttty\tTT \tcontrolling tty (terminal)" -+ ) -+ self.pmSetLongOptionText( -+ "\tuid\tUID \tsee euid" -+ ) -+ self.pmSetLongOptionText( -+ "\tvsize\tVSZ \tsee vsz" -+ ) -+ self.pmSetLongOptionText( -+ "\tuname\tUSER \tsee euser" -+ ) -+ self.pmSetLongOptionText( -+ "\twchan\tWCHAN \tname of the kernel function in which the process is sleeping" -+ ) -+ self.pmSetLongOption("", 0, 'u', "", -+ "Display user-oriented format" -+ ) -+ self.pmSetLongOption( -+ "sort", 1, "O", "%cpu,%mem", -+ "sort the process list by %cpu or %mem values " -+ ) -+ self.pmSetLongOption("", 0, "d", "", "enable debug mode") - self.pmSetLongOptionVersion() - self.pmSetLongOptionTimeZone() - self.pmSetLongOptionHostZone() - self.pmSetLongOptionHelp() - - def override(self, opts): -- ProcessStatOptions.print_count = self.pmGetOptionSamples() -+ self.print_count = self.pmGetOptionSamples() - # """Override standard Pcp-ps option to show all process """ -- return bool(opts in ['p', 'c', 'o', 'P', 'U']) -+ return bool(opts in ['p', 'c', 'o', 'P', 'U', 'O', 'd']) - - def extraOptions(self, opts, optarg, index): -- if opts == 'e': -- ProcessStatOptions.show_all_process = True -- elif opts == 'c': -- ProcessStatOptions.command_filter_flag = True -- ProcessStatOptions.filter_flag = True -+ -+ def handle_e(): -+ if self.debug_mode: -+ print("e option selected") -+ self.universal_flag = "all" -+ -+ def handle_c(): -+ if self.debug_mode: -+ print("command option selected") -+ self.universal_flag = "command" -+ self.command_filter_flag = True -+ self.filter_flag = True - try: - if optarg is not None: -- ProcessStatOptions.command_list += optarg.replace(',', ' ').split(' ') -+ self.command_list += optarg.replace(',', ' ').split(' ') -+ if self.debug_mode: -+ print("Command List: %s" % self.command_list) - except ValueError: - print("Invalid command Id List: use comma separated pids without whitespaces") - sys.exit(1) -- elif opts == 'p': -- ProcessStatOptions.filter_flag = True -- ProcessStatOptions.pid_filter_flag = True -+ def handle_d(): -+ print("Debug mode selected") -+ self.debug_mode = True -+ -+ def handle_p(): -+ if self.debug_mode: -+ print("pid option selected") -+ self.universal_flag = "pid" -+ self.filter_flag = True -+ self.pid_filter_flag = True - try: - if optarg is not None: - dummy_list = optarg.replace(',', ' ').split(' ') -- ProcessStatOptions.pid_list += [int(x) for x in dummy_list] -+ self.pid_list += [int(x) for x in dummy_list] - except ValueError: - print("Invalid pid Id List: use comma separated pids without whitespaces") - sys.exit(1) -- elif opts == 'P': -- ProcessStatOptions.filter_flag = True -- ProcessStatOptions.ppid_filter_flag = True -+ -+ def handle_P(): -+ if self.debug_mode: -+ print("ppid option selected") -+ self.universal_flag = "ppid" -+ self.filter_flag = True -+ self.ppid_filter_flag = True - try: - if optarg is not None: - dummy_list = optarg.replace(',', ' ').split(' ') -- ProcessStatOptions.ppid_list += [int(x) for x in dummy_list] -+ self.ppid_list += [int(x) for x in dummy_list] - except ValueError: - print("Invalid ppid Id List: use comma separated pids without whitespaces") - sys.exit(1) -- elif opts == 'u': -- ProcessStatOptions.user_oriented_format = True -- elif opts == 'o': -- ProcessStatOptions.selective_colum_flag = True -+ -+ def handle_u(): -+ if self.debug_mode: -+ print("User-oriented format option selected") -+ self.universal_flag = "user" -+ self.user_oriented_format = True -+ -+ def handle_o(): -+ if self.debug_mode: -+ print("User-defined format option selected") -+ self.selective_colum_flag = True - try: - if optarg.upper() == "ALL": -- ProcessStatOptions.filterstate = optarg.upper() -+ self.filterstate = optarg.upper() - else: - dummy_list = optarg.replace(',', ' ').split(' ') -+ if self.debug_mode: -+ print("Custom Column List: %s" % dummy_list) - for key in dummy_list: - if key.lower() in PIDINFO_PAIR: -- ProcessStatOptions.colum_list.append(key.lower()) -+ self.colum_list.append(key.lower()) - else: - raise ValueError - except ValueError: -- print("Invalid ppid Id List: Either column name is not correct " -- "or use comma separated column names without whitespaces") -+ print("Invalid Column List: incorrect name or improper comma-separated format") - sys.exit(1) -- elif opts == 'U': -- ProcessStatOptions.username_filter_flag = True -- ProcessStatOptions.filter_flag = True -- ProcessStatOptions.filtered_process_user = optarg -- elif opts is None: -- ProcessStatOptions.show_all_process = True -- -- @staticmethod -- def checkOptions(): -- if ProcessStatOptions.selective_colum_flag or \ -- ProcessStatOptions.filter_flag or \ -- ProcessStatOptions.user_oriented_format: -+ -+ def handle_U(): -+ if self.debug_mode: -+ print("username option selected") -+ self.universal_flag = "username" -+ self.username_filter_flag = True -+ self.filter_flag = True -+ self.filtered_process_user = optarg -+ -+ def handle_none(): -+ if self.debug_mode: -+ print("No option selected, defaulting to show all process") -+ self.universal_flag = "all" -+ # self.show_all_process = True -+ -+ def handle_O(): -+ if self.debug_mode: -+ print("Sorting option selected") -+ self.sorting_flag = True -+ if optarg.lower() not in ['%cpu', '%mem']: -+ print("Invalid sorting option, defaulting to %cpu") -+ self.sorting_order = '%cpu' # default to %cpu -+ return -+ self.sorting_order = optarg.lower() -+ -+ # Dispatch map simulating switch-case -+ dispatch = { -+ 'e': handle_e, -+ 'c': handle_c, -+ 'p': handle_p, -+ 'P': handle_P, -+ 'u': handle_u, -+ 'o': handle_o, -+ 'U': handle_U, -+ 'O': handle_O, -+ 'd': handle_d, -+ None: handle_none, -+ } -+ # Execute handler or fallback to error -+ handler = dispatch.get(opts) -+ if handler: -+ handler() -+ else: -+ print("Unknown option: %s" % opts) -+ sys.exit(1) -+ def checkOptions(self): -+ if self.universal_flag is not None : - return True - else: -- ProcessStatOptions.empty_arg_flag = True -+ if self.debug_mode: -+ print("No filtering option selected, defaulting to empty argument mode") -+ self.universal_flag = "empty_arg" - return True - - -@@ -779,7 +934,7 @@ if __name__ == "__main__": - sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing) - sys.exit(1) - manager['psstat'] = PSSTAT_METRICS -- manager.printer = ProcessStatReport(manager['psstat']) -+ manager.printer = ProcessStatReport(manager['psstat'],opts) - sts = manager.run() - sys.exit(sts) - except pmapi.pmErr as pmerror: -@@ -787,6 +942,9 @@ if __name__ == "__main__": - except pmapi.pmUsageErr as usage: - usage.message() - sys.exit(1) -+ except ValueError as e: -+ sys.stderr.write("%s\n" % str(e)) -+ sys.exit(1) - except IOError: - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - except KeyboardInterrupt: --- -2.43.7 - diff --git a/SOURCES/1015-pmlogger_daily-d-disk-option-for-archive-space-limit.patch b/SOURCES/1015-pmlogger_daily-d-disk-option-for-archive-space-limit.patch deleted file mode 100644 index 84dbb7c..0000000 --- a/SOURCES/1015-pmlogger_daily-d-disk-option-for-archive-space-limit.patch +++ /dev/null @@ -1,486 +0,0 @@ -From e02b8ba31512a3e4448330b28b734f010b157dce Mon Sep 17 00:00:00 2001 -From: Sagar Sagar -Date: Tue, 3 Mar 2026 10:11:49 +0000 -Subject: [PATCH OL9 1015/1016] pmlogger_daily -d/--disk option for archive - space limit - -Added -d/--disk option in pmlogger_daily for archive size limit -Doc: Explain $PCP_SPACELIMIT env var for disk usage in PCP archives -Clarify archive retention/purging with space limits and env/CLI opts -utilproc.sh: Refactor _convert_to_kb(), unit checks, env - -upstream ref:- 7b7c38c16c6904960ede400efa5225732935b830 - -[orabug:38757778] -Signed-off-by: sagar sagar ---- - man/man1/pmlogger_daily.1 | 83 +++++++++++++- - src/pmlogger/pmlogger_daily.sh | 193 ++++++++++++++++++++++++++++++++- - src/pmlogger/utilproc.sh | 83 ++++++++++++++ - 3 files changed, 357 insertions(+), 2 deletions(-) - -diff --git a/man/man1/pmlogger_daily.1 b/man/man1/pmlogger_daily.1 -index 347fcfe..f0f0742 100644 ---- a/man/man1/pmlogger_daily.1 -+++ b/man/man1/pmlogger_daily.1 -@@ -20,6 +20,7 @@ - .B $PCP_BINADM_DIR/pmlogger_daily - [\f3\-DEfKMNoprRVzZ?\f1] - [\f3\-c\f1 \f2control\f1] -+[\f3\-d\f1 \f2fssize\f1] - [\f3\-k\f1 \f2time\f1] - [\f3\-l\f1 \f2logfile\f1] - [\f3\-m\f1 \f2addresses\f1] -@@ -120,6 +121,72 @@ Do not perform the conditional - .BR pmlogger_daily_report (1) - processing as described below. - .TP 5 -+\fB\-d\fR \fIfssize\fR, \fB\-\-disk\fR=\fIfssize\fR -+This option enforces a maximum total file system space usage per host directory for PCP archive files stored under each -+.IR $PCP_ARCHIVE_DIR/ -+and -+.IR $PCP_REMOTE_ARCHIVE_DIR/ -+location. The -+.I fssize -+must be specified as an integer with a suffix -+.B K -+or -+.B k -+for kilobytes, -+.B M -+or -+.B m -+for megabytes, -+.B G -+or -+.B g -+for gigabytes (e.g. 500m or 10G). -+As a special case, -+.I fssize -+may be the keyword -+.B unlimited -+to prevent any file system space limit enforcement (this is the default -+behaviour). -+.RS -+.PP -+Alternatively, the file system space limit may be set by exporting the -+.B $PCP_SPACELIMIT -+environment variable, which is handled analogously to -+.BR $PCP_CULLAFTER . -+If both -+.B $PCP_SPACELIMIT -+and -+.B \-d -+are provided and specify different values then the value from -+the environment variable is used and a warning is issued. -+.PP -+After normal daily log processing (compression, merging, culling by age, etc.) -+completes, -+if the total file system space consumed by any -+.BR pmlogger (1) -+instance exceeds -+.IR fssize , -+then additional archive files will be purged (i.e. removed), -+oldest first, until -+the file system usage is reduced to be not more than -+.IR fssize . -+This purging operation never deletes archive files with today's date, -+and so always preserves the most recent archives required by active -+.BR pmlogger (1) -+processes. -+.PP -+Enforcement is only performed when -+.B pmlogger_daily -+runs (typically once a day), not continuously. -+As a result, the total file system usage for any -+.BR pmlogger (1) -+instance may exceed a specified maximum between -+.B pmlogger_daily -+executions (for example, as new archives are created -+or when archives are uncompressed for merging or -+prior to asynchronous archive compression). -+.RE -+.TP 5 - \fB\-E\fR, \fB\-\-expunge\fR - This option causes - .B pmlogger_daily -@@ -458,7 +525,7 @@ maximizes the diagnostic capabilities for debugging. - .TP 5 - \fB\-x\fR \fItime\fR, \fB\-\-compress\-after\fR=\fItime\fR - Archive data files can optionally be compressed after some period --to conserve disk space. -+to conserve file system space. - This is particularly useful for large numbers of - .B pmlogger - processes under the control of -@@ -876,6 +943,20 @@ if this file exists, then this is treated as equivalent to using - on the command line and the file will be removed once all rewriting - has been done. - .SH PCP ENVIRONMENT -+.TP 5 -+.B $PCP_SPACELIMIT -+If set, specifies a maximum allowed total file system space (in kilobytes, or with optional K/M/G suffix as for the \-d option) for each per-host archive directory under -+.I $PCP_ARCHIVE_DIR -+or -+.I $PCP_REMOTE_ARCHIVE_DIR . -+This value is used by -+.B pmlogger_daily -+when enforcing archive retention limits for file system space, unless the -+.B \-d -+option is provided in which case the command line flag overrides the environment variable and a warning is issued. Enforcement occurs after each run of -+.B pmlogger_daily -+and only applies at that time—not continuously. -+.PP - Environment variables with the prefix \fBPCP_\fP are used to parameterize - the file and directory names used by PCP. - On each installation, the -diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh -index 6e73a1f..bb22df8 100755 ---- a/src/pmlogger/pmlogger_daily.sh -+++ b/src/pmlogger/pmlogger_daily.sh -@@ -38,6 +38,7 @@ PROGLOG=$PCP_LOG_DIR/pmlogger/$prog.log - MYPROGLOG=$PROGLOG.$$ - USE_SYSLOG=true - -+localhost=`hostname || echo localhost` - # optional begin logging to $PCP_LOG_DIR/NOTICES - # - if $PCP_LOG_RC_SCRIPTS -@@ -339,6 +340,7 @@ Options: - -E,--expunge expunge metrics with metadata inconsistencies when merging archives - -f,--force force actions (intended for QA, not production) - -k=TIME,--discard=TIME remove archives after TIME (format DD[:HH[:MM]]) -+ -d=fssize,--disk=fssize set maximum disk usage for archives for each pmlogger instance - -K compress, but no other changes - -l=FILE,--logfile=FILE send important diagnostic messages to FILE - -m=ADDRs,--mail=ADDRs send daily NOTICES entries to email addresses -@@ -384,6 +386,8 @@ EXPUNGE="" - FORCE=false - KILL=pmsignal - DO_DAILY_REPORT=true -+SPACELIMIT_CMDLINE="" -+SPACELIMIT_DEFAULT="unlimited" - - ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"` - [ $? != 0 ] && exit 1 -@@ -399,6 +403,24 @@ do - ;; - -D) DO_DAILY_REPORT=false - ;; -+ -d) SPACELIMIT_CMDLINE="$2" -+ shift -+ if [ -n "$PCP_SPACELIMIT" -a "$PCP_SPACELIMIT" != "$SPACELIMIT_CMDLINE" ] -+ then -+ echo "Warning: -d value ($SPACELIMIT_CMDLINE) ignored because \$PCP_SPACELIMIT ($PCP_SPACELIMIT) set in environment" -+ SPACELIMIT_CMDLINE="" -+ continue -+ fi -+ if [ "$SPACELIMIT_CMDLINE" != unlimited ] -+ then -+ if ! _convert_to_kb "$SPACELIMIT_CMDLINE" >/dev/null -+ then -+ echo "Error: -d value ($SPACELIMIT_CMDLINE) not valid" -+ $NOERROR || status=1 -+ exit -+ fi -+ fi -+ ;; - -E) EXPUNGE="-E" - ;; - -f) FORCE=true -@@ -763,6 +785,44 @@ _warning() - echo "Warning: $@" - } - -+# Given a list of candidate directories, calculate the total -+# size below them in Kbytes -+# -+_calculate_total_size() -+{ -+ du -sk "$@" 2>$tmp/cts_err \ -+ | awk >$tmp/cts_out ' -+BEGIN { kb = 0 } -+ { kb += $1 } -+END { print kb }' >$tmp/cts_out -+ if [ -s $tmp/cts_err ] && $VERY_VERBOSE -+ then -+ echo >&2 "Warning: _calculate_total_size: du -sk $@ produced errors ..." -+ cat >&2 $tmp/cts_err -+ fi -+ $VERY_VERBOSE && echo >&2 "Info: _calculate_total_size -> `cat $tmp/cts_out` Kbytes" -+ cat $tmp/cts_out -+} -+ -+# Calculate the total size of the archive with basename $1 -+# -+_calculate_archive_size() -+{ -+ du -sk "$1".* 2>$tmp/cts_err \ -+ | awk >$tmp/cts_out ' -+BEGIN { kb = 0 } -+ { kb += $1 } -+END { print kb }' >$tmp/cts_out -+ if [ -s $tmp/cts_err ] && $VERY_VERBOSE -+ then -+ echo >&2 "Warning: _calculate_archive_size: du -sk $1.* produced errors ..." -+ cat >&2 $tmp/cts_err -+ fi -+ $VERY_VERBOSE && echo >&2 "Info: _calculate_archive_size $1 -> `cat $tmp/cts_out` Kbytes" -+ cat $tmp/cts_out -+} -+ -+ - _skipping() - { - echo "$prog: Warning: $@" -@@ -1070,6 +1130,86 @@ BEGIN { seenslash = 0; lastc = ""; inshell = 0; nesting = 0; out = "" } - END { print out }' - } - -+# Check disk space allocation for one pmlogger instance, and if more -+# than $SPACELIMIT Kbytes, purge files to try and get the space -+# allocated to be not more than $SPACELIMIT Kbytes. -+# -+# Oldest files are purged first and the most recent archives for today -+# (including those required by active pmlogger (1) processes are -+# never deleted. -+# -+# On entry, $find_dirs is a list of one or more directories holding -+# archives for a pmlogger instance, set in _callback_log_control(), -+# and $host is the host name field from the control line, set in -+# _parse_log_control(). -+# -+_do_purge() -+{ -+ SPACELIMIT="$PCP_SPACELIMIT" -+ [ -z "$SPACELIMIT" ] && SPACELIMIT="$SPACELIMIT_CMDLINE" -+ [ -z "$SPACELIMIT" ] && SPACELIMIT="$SPACELIMIT_DEFAULT" -+ [ "$SPACELIMIT" = unlimited ] && return -+ if ! _convert_to_kb "$SPACELIMIT" >$tmp/tmp -+ then -+ _warning "skipping purging because of invalid space limit" -+ return -+ fi -+ SPACELIMIT=`cat $tmp/tmp` -+ $VERY_VERBOSE && echo >&2 "SPACELIMIT=$SPACELIMIT" -+ __total_size=`_calculate_total_size $find_dirs` -+ if [ "$__total_size" -le "$SPACELIMIT" ] -+ then -+ $VERBOSE && echo "Info: No purging required for $host, archives ($__total_size Kbytes) <= size limit ($SPACELIMIT Kbytes)" -+ return -+ fi -+ $VERBOSE && echo "Info: archives for $host ($__total_size Kbytes) >= size limit ($SPACELIMIT Kbytes)" -+ -+ # algorithm to find archive basenames borrowed from _do_merge() -+ # output is in this format ... -+ # | -+ # -+ TODAY=`date +%Y%m%d` -+ find $find_dirs -maxdepth 1 -type f \ -+ | sed -n \ -+ -e '/\(.*\)\/\([12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.meta.*\)/s//\1|\2/p' \ -+ -e '/\(.*\)\/\([12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \ -+ -e '/\(.*\)\/\([12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]-[0-9][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \ -+ -e '/\(.*\)\/\([0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.meta.*\)/s//\1|\2/p' \ -+ -e '/\(.*\)\/\([0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \ -+ -e '/\(.*\)\/\([0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]-[0-9][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \ -+ | sort -t'|' -n -k2,2 \ -+ | $PCP_AWK_PROG -F'|' ' -+$2 == "'$TODAY'" { next } -+$2 ~ /^'$TODAY'/ { next } -+ { print }' >$tmp/purge_candidates -+ if [ ! -s $tmp/purge_candidates ] -+ then -+ $VERBOSE && echo "Info: No candidates to purge." -+ return -+ fi -+ -+ sed -e 's/|/ /' <$tmp/purge_candidates \ -+ | while read __dir __file -+ do -+ [ "$__dir" != "." ] && __file="$__dir/$__file" -+ __arch_size=`_calculate_archive_size "$__file"` -+ if $SHOWME -+ then -+ echo "+ rm `echo $__file.**`" -+ __total_size=`expr $__total_size - $__arch_size` -+ else -+ if rm "$__file".* -+ then -+ $VERBOSE && echo "Info: purge $__file, reclaims $__arch_size Kbytes" -+ __total_size=`expr $__total_size - $__arch_size` -+ else -+ $VERBOSE && echo "Warning: rm $__file.* failed" -+ fi -+ fi -+ [ "$__total_size" -le "$SPACELIMIT" ] && break -+ done -+} -+ - # replace `....` in $orig_dir by * ($orig_dir is on stdin) - # - _unbackquote() -@@ -1277,7 +1417,50 @@ s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p - $VERBOSE && echo "Using \$PCP_AUTOSAVE_DIR: $PCP_AUTOSAVE_DIR" - fi - ;; -- -+ 'export PCP_SPACELIMIT;'*) -+ _old_value="$PCP_SPACELIMIT" -+ _check=`echo "$_cmd" | sed -e 's/.*=//' -e 's/ *$//'` -+ if [ -n "$_check" ] -+ then -+ if [ "$_check" = unlimited ] -+ then -+ # no conversion -+ : -+ else -+ # check syntax & convert to canonical Kbytes -+ # -+ _kb=`_convert_to_kb "$_check"` -+ if [ $? != 0 ] -+ then -+ _warning "\$PCP_SPACELIMIT value ($_check) is invalid. Must be a positive integer and a unit (e.g. 100M)" -+ _cmd='' -+ else -+ $SHOWME && echo "+ $_cmd (normalized to $_kb Kbytes)" -+ # need to put back the "K" units here -+ # # because it will get re-processed by -+ # _convert_to_kb() later -+ # -+ _cmd=`echo "$_cmd" | sed -e "s/=.*/=${_kb}K/"` -+ fi -+ fi -+ if [ -n "$_cmd" ] -+ then -+ echo eval $_cmd >>$tmp/_cmd -+ eval $_cmd -+ if [ -n "$_old_value" -a "$_old_value" != "$PCP_SPACELIMIT" ] -+ then -+ _warning "\$PCP_SPACELIMIT ($PCP_SPACELIMIT) reset from control file, previous value ($_old_value) ignored" -+ fi -+ if [ -n "$PCP_SPACELIMIT" -a -n "$SPACELIMIT_CMDLINE" -a "$PCP_SPACELIMIT" != "$SPACELIMIT_CMDLINE" ] -+ then -+ _warning "\$PCP_SPACELIMIT ($PCP_SPACELIMIT) reset from control file, -d value ($SPACELIMIT_CMDLINE) ignored" -+ SPACELIMIT_CMDLINE="" -+ fi -+ fi -+ else -+ _warning "\$PCP_SPACELIMIT from control file missing a value, will be ignored" -+ fi -+ ;; - *) - $SHOWME && echo "+ $cmd" - echo eval $cmd >>$tmp/cmd -@@ -2164,6 +2347,14 @@ p - fi - fi - -+ # if space limit specified, potentially purge old archives if -+ # space limit exceeded -+ # -+ if [ -n "$PCP_SPACELIMIT" -o -n "$SPACELIMIT_CMDLINE" ] -+ then -+ _do_purge -+ fi -+ - _unlock "$dir" - done - } -diff --git a/src/pmlogger/utilproc.sh b/src/pmlogger/utilproc.sh -index a0b62c9..5a939f9 100644 ---- a/src/pmlogger/utilproc.sh -+++ b/src/pmlogger/utilproc.sh -@@ -255,6 +255,89 @@ END { exit sts }' - fi - } - -+# Converts a size string like 10G, 100M, 100K to integer KBytes for limit -+# checks. -+# Usage: _convert_to_kb size_string -+# Outputs: integer KBytes on stdout, returns 0 if OK, 1 if error -+_convert_to_kb() -+{ -+ __input="$1" -+ __num= -+ __unit= -+ __kb= -+ if [ -z "$__input" ] -+ then -+ echo "Error: _convert_to_kb(): missing argument" >&2 -+ return 1 -+ fi -+ __num=`echo "$__input" | sed -E 's/^([0-9]+)\s*([a-zA-Z]*)$/\1/'` -+ case "$__num" -+ in -+ "" ) -+ echo "Error: _convert_to_kb(): argument '$__input' does not start with a number" >&2 -+ return 1 -+ ;; -+ *[!0-9]* ) -+ echo "Error: _convert_to_kb(): argument '$__input' has a non-numeric value" >&2 -+ return 1 -+ ;; -+ 0 ) -+ echo "Error: _convert_to_kb(): argument '$__input' resolves to zero (not allowed)" >&2 -+ return 1 -+ ;; -+ esac -+ __unit=`echo "$__input" | sed -E 's/^([0-9]+)\s*([a-zA-Z]*)$/\2/'` -+ case "$__unit" -+ in -+ G|g|M|m|K|k) -+ ;; -+ '') -+ echo "Error: _convert_to_kb(): missing unit after '$__num'" >&2 -+ return 1 -+ ;; -+ *) -+ echo "Error: _convert_to_kb(): invalid unit '$__unit'" >&2 -+ return 1 -+ ;; -+ esac -+ -+ MAX_INT32=2147483647 -+ -+ case "$__unit" -+ in -+ G|g) -+ # Check before multiplying -+ __max=`expr $MAX_INT32 / \( 1024 \* 1024 \)` -+ if [ $__num -gt $__max ] -+ then -+ echo "Error: overflow, $__num Gbytes too large for Kbytes in a 32-bit signed int" >&2 -+ return 1 -+ fi -+ __kb=`expr $__num \* 1024 \* 1024` -+ ;; -+ M|m) -+ # Check before multiplying -+ __max=`expr $MAX_INT32 / 1024` -+ if [ $__num -gt $__max ] -+ then -+ echo "Error: overflow, $__num Mbytes too large for Kbytes in a 32-bit signed int" >&2 -+ return 1 -+ fi -+ __kb=`expr $__num \* 1024` -+ ;; -+ K|k) -+ if [ $__num -gt $MAX_INT32 ] -+ then -+ echo "Error: overflow, $__num Kbytes too large for 32-bit signed int" >&2 -+ return 1 -+ fi -+ __kb=$__num -+ ;; -+ esac -+ echo "$__kb" -+ return 0 -+} -+ - # current time to the highest precision available from date(1) and - # strftime(3) - # --- -2.43.7 - diff --git a/SOURCES/1016-pcp-system-tools-restore-backward-compatibility-with.patch b/SOURCES/1016-pcp-system-tools-restore-backward-compatibility-with.patch deleted file mode 100644 index b01541b..0000000 --- a/SOURCES/1016-pcp-system-tools-restore-backward-compatibility-with.patch +++ /dev/null @@ -1,328 +0,0 @@ -From cc127dfca5ab71fcbd34ed27be8844e3aa2be0c3 Mon Sep 17 00:00:00 2001 -From: Sagar Sagar -Date: Tue, 3 Mar 2026 10:13:09 +0000 -Subject: [PATCH OL9 1016/1016] pcp-system-tools: restore backward - compatibility with older pmapi versions - -Commit 0a37ed0 introduced support for pmapi version 4, but upgrading to the latest pmapi is not currently desired. -This change updates pcp-iostat, mpstat, pidstat, and ps to fall back to tv_usec when tv_nsec is unavailable -during timestamp delta calculation, ensuring compatibility with older pmapi versions and collectors that only expose tv_usec - -- optimized code in pcp-ps for dynamic sorting -- fixed sorting colum indexes in -u case - -[Orabug: 38719615] -Signed-off-by: Sagar Sagar ---- - src/pcp/iostat/pcp-iostat.py | 15 +++- - src/pcp/mpstat/pcp-mpstat.py | 13 +++- - src/pcp/pidstat/pcp-pidstat.py | 13 +++- - src/pcp/ps/pcp-ps.py | 123 ++++++++++++------------------- - src/pcp/tapestat/pcp-tapestat.py | 15 +++- - 5 files changed, 95 insertions(+), 84 deletions(-) - -diff --git a/src/pcp/iostat/pcp-iostat.py b/src/pcp/iostat/pcp-iostat.py -index ae503ed..11a7245 100755 ---- a/src/pcp/iostat/pcp-iostat.py -+++ b/src/pcp/iostat/pcp-iostat.py -@@ -56,9 +56,18 @@ class IostatReport(pmcc.MetricGroupPrinter): - Hcount = 0 - def timeStampDelta(self, group): - s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec -- u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -- # u may be negative here, calculation is still correct. -- return s + u / 1000000.0 -+ # pmapi timestamps may provide sub-second resolution via tv_nsec (nanoseconds) -+ # or tv_usec (microseconds) depending on the collector. Prefer nanoseconds -+ # when available, but gracefully fall back to microseconds to avoid -+ if hasattr(group.timestamp, 'tv_nsec') and hasattr(group.prevTimestamp, 'tv_nsec'): -+ n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec -+ # n may be negative here, calculation is still correct. -+ return s + n / 1000000000.0 -+ elif hasattr(group.timestamp, 'tv_usec') and hasattr(group.prevTimestamp, 'tv_usec'): -+ u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -+ return s + u / 1000000.0 -+ # it should not reach here -+ return s - - def instlist(self, group, name): - return dict(map(lambda x: (x[1], x[2]), group[name].netValues)).keys() -diff --git a/src/pcp/mpstat/pcp-mpstat.py b/src/pcp/mpstat/pcp-mpstat.py -index f398e95..fb6efb9 100755 ---- a/src/pcp/mpstat/pcp-mpstat.py -+++ b/src/pcp/mpstat/pcp-mpstat.py -@@ -503,8 +503,17 @@ class MpstatReport(pmcc.MetricGroupPrinter): - - def timeStampDelta(self, group): - s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec -- u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -- return s + u / 1000000.0 -+ # pmapi timestamps may provide sub-second resolution via tv_nsec (nanoseconds) -+ # or tv_usec (microseconds) depending on the collector. Prefer nanoseconds -+ # when available, but gracefully fall back to microseconds to avoid -+ if hasattr(group.timestamp, 'tv_nsec') and hasattr(group.prevTimestamp, 'tv_nsec'): -+ n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec -+ return s + n / 1000000000.0 -+ elif hasattr(group.timestamp, 'tv_usec') and hasattr(group.prevTimestamp, 'tv_usec'): -+ u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -+ return s + u / 1000000.0 -+ # it should not reach here -+ return s - - def print_machine_info(self,group, context): - self.get_summary_metrics(group) -diff --git a/src/pcp/pidstat/pcp-pidstat.py b/src/pcp/pidstat/pcp-pidstat.py -index 285aa51..b7613e1 100755 ---- a/src/pcp/pidstat/pcp-pidstat.py -+++ b/src/pcp/pidstat/pcp-pidstat.py -@@ -925,8 +925,17 @@ class PidstatReport(pmcc.MetricGroupPrinter): - - def timeStampDelta(self, group): - s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec -- u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -- return s + u / 1000000.0 -+ # pmapi timestamps may provide sub-second resolution via tv_nsec (nanoseconds) -+ # or tv_usec (microseconds) depending on the collector. Prefer nanoseconds -+ # when available, but gracefully fall back to microseconds to avoid -+ if hasattr(group.timestamp, 'tv_nsec') and hasattr(group.prevTimestamp, 'tv_nsec'): -+ n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec -+ return s + n / 1000000000.0 -+ elif hasattr(group.timestamp, 'tv_usec') and hasattr(group.prevTimestamp, 'tv_usec'): -+ u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -+ return s + u / 1000000.0 -+ # it should not reach here -+ return s - - def print_machine_info(self,group, context): - timestamp = context.pmLocaltime(group.timestamp.tv_sec) -diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py -index c8f2e9d..35f34b5 100755 ---- a/src/pcp/ps/pcp-ps.py -+++ b/src/pcp/ps/pcp-ps.py -@@ -249,7 +249,7 @@ class ProcessStatusUtil: - if self.user_percent() is not None and self.guest_percent() is not None and self.system_percent() is not None: - return float("%.2f" % (self.user_percent() + self.guest_percent() + self.system_percent())) - else: -- return None -+ return 0.0 - - def stime(self): - c_systime = self.__get_value('proc.psinfo.stime', self.instance) -@@ -359,6 +359,20 @@ class DynamicProcessReporter: - self.printer = printer - self.processStatOptions = processStatOptions - -+ def __sort_by_idx(self, output_list, sorting_idx, reverse=True): -+ # Rows are tab-delimited; splitting on whitespace breaks when -+ # command/args contain spaces and shifts sortable column indexes. -+ return sorted( -+ output_list, -+ key=lambda row: ( -+ float(row.split('\t')[sorting_idx].strip()) -+ if sorting_idx < len(row.split('\t')) -+ and row.split('\t')[sorting_idx].strip().replace('.', '', 1).replace('-', '', 1).isdigit() -+ else float('-inf') -+ ), -+ reverse=reverse -+ ) -+ - def _is_last_and_args(self, key): - return (key == "args") and \ - self.processStatOptions.colum_list.index(key) == len(self.processStatOptions.colum_list) - 1 -@@ -371,19 +385,15 @@ class DynamicProcessReporter: - # Sorting validations - sorting_idx = None - if self.processStatOptions.sorting_flag: -- if self.processStatOptions.filterstate == "ALL": -- if self.processStatOptions.sorting_order == '%mem': -- sorting_idx = 7 -- elif self.processStatOptions.sorting_order == '%cpu': -- sorting_idx = 8 -- else: -- sorting_idx = next((idx for idx, key in enumerate(self.processStatOptions.colum_list) -- if key == self.processStatOptions.sorting_order), -- None) -- if sorting_idx is None: -- raise ValueError("Sorting order not found in output columns") -- # Adjust for timestamp column -- sorting_idx += 1 -+ # For dynamic output, sorting key must be present in selected columns. -+ if self.processStatOptions.sorting_order not in self.processStatOptions.colum_list: -+ raise ValueError("Sorting order not found in output columns") -+ -+ # Find sorting column index and adjust for Timestamp at position 0. -+ sorting_idx = next((idx for idx, key in enumerate(self.processStatOptions.colum_list) -+ if key == self.processStatOptions.sorting_order), None) -+ # to account for Timestamp colum -+ sorting_idx += 1 - - # Always compute process list ONCE - processes = self.process_filter.filter_processes( -@@ -393,47 +403,8 @@ class DynamicProcessReporter: - output_list = [] - header = None - -- # -------- PATH 1: With filterstate -------- # -- if self.processStatOptions.filterstate == "ALL": -- header = ( -- "Timestamp\tUSER\t\tPID\t\tPPID\t\tPRI\t%CPU\t%MEM\tVSZ" -- "\tRSS\tS\tSTARTED\t\tTIME\t\t" -- "WCHAN\t\t\t\tCommand" -- ) -- -- # Precompute format string -- # fmt = ( -- # "{ts}{indent}{user}\t{pid}\t{ppid}\t{pri}\t{cpu}\t{mem}\t" -- # "{vsz}\t{rss}\t{s}\t{started}\t{time}\t{wchan}\t{cmd}" -- # ) -- for process in processes: -- # Maintain state info -- key = (process.s_name(), process.pid()) -- process_state_info[key] = process_state_info.get(key, 0) + self.delta_time -- row = [timestamp] -- row.extend([ -- process.user_name(), -- process.pid(), -- process.ppid(), -- process.priority(), -- process.total_percent(), -- process.system_percent(), -- process.vsize(), -- process.rss(), -- process.s_name(), -- process.start(), -- process.total_time(), -- process.wchan_s(), -- process.process_name_with_args_last()[:45] -- ]) -- output_list.append( -- "\t".join( -- str(x) if x is not None else '' for x in row -- ) -- ) -- -- # -------- PATH 2: Customized column list -------- # -- elif self.processStatOptions.colum_list is not None: -+ # -------- Dynamic column list path -------- # -+ if self.processStatOptions.colum_list is not None: - - header = "Timestamp\t" - for key in self.processStatOptions.colum_list: -@@ -450,23 +421,14 @@ class DynamicProcessReporter: - # print(row) - output_list.append("\t".join(str(x) if x is not None else '' for x in row)) - -- # -------- PATH 3: Invalid filterstate or column list -------- # -+ # -------- Invalid column list -------- # - # This should never happen, but just in case - else: - raise ValueError("No valid filterstate or column list provided") - - # Sorting logic - if self.processStatOptions.sorting_flag and sorting_idx is not None: -- output_list.sort( -- key=lambda x: ( -- sorting_idx, -- float('inf') if ( -- len(x.split()) <= sorting_idx or -- not x.split()[sorting_idx].replace('.', '', 1).isdigit() -- ) else float(x.split()[sorting_idx]) -- ), -- reverse=True -- ) -+ output_list = self.__sort_by_idx(output_list, sorting_idx, reverse=True) - - # --------- Print output --------- # - self.printer(header) -@@ -531,25 +493,25 @@ class ProcessStatusReporter: - for process in processes: - output_rows.append("%s%s%s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % ( - safe_str(timestamp), safe_str(value_indentation), safe_str(process.user_name()), -- safe_str(process.pid()),safe_str(process.system_percent()), safe_str(process.total_percent()), -+ safe_str(process.pid()),safe_str(process.system_percent()), safe_str(process.mem()), - safe_str(process.vsize()), safe_str(process.rss()), - safe_str(process.tty_name()), safe_str(process.ppid()), safe_str(process.total_time()), - safe_str(process.start()), - safe_str(process.process_name()))) -- cpu_idx = 5 -- mem_idx = 6 -+ cpu_idx = 3 -+ mem_idx = 4 - elif selected_flag == "user": - for process in processes: - output_rows.append("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % ( - safe_str(timestamp), safe_str(value_indentation), safe_str(process.user_name()), - safe_str(process.pid()), -- safe_str(process.system_percent()), safe_str(process.total_percent()), -+ safe_str(process.system_percent()), safe_str(process.mem()), - safe_str(process.vsize()), safe_str(process.rss()), - safe_str(process.tty_name()), safe_str(process.s_name()), - safe_str(process.total_time()), safe_str(process.start()), - safe_str(process.process_name()))) -- cpu_idx = 5 -- mem_idx = 6 -+ cpu_idx = 3 -+ mem_idx = 4 - elif selected_flag == "command": - for process in processes: - output_rows.append("%s%s%s\t%s\t%s\t%s\t%s" % ( -@@ -585,8 +547,17 @@ class ProcessStatReport(pmcc.MetricGroupPrinter): - - def timeStampDelta(self): - s = self.group.timestamp.tv_sec - self.group.prevTimestamp.tv_sec -- n = self.group.timestamp.tv_nsec - self.group.prevTimestamp.tv_nsec -- return s + n / 1000000000.0 -+ # pmapi timestamps may provide sub-second resolution via tv_nsec (nanoseconds) -+ # or tv_usec (microseconds) depending on the collector. Prefer nanoseconds -+ # when available, but gracefully fall back to microseconds to avoid -+ if hasattr(self.group.timestamp, 'tv_nsec') and hasattr(self.group.prevTimestamp, 'tv_nsec'): -+ n = self.group.timestamp.tv_nsec - self.group.prevTimestamp.tv_nsec -+ return s + n / 1000000000.0 -+ elif hasattr(self.group.timestamp, 'tv_usec') and hasattr(self.group.prevTimestamp, 'tv_usec'): -+ u = self.group.timestamp.tv_usec - self.group.prevTimestamp.tv_usec -+ return s + u / 1000000.0 -+ # it should not reach here -+ return s - - def print_machine_info(self,context): - timestamp = context.pmLocaltime(self.group.timestamp.tv_sec) -@@ -855,6 +826,10 @@ class ProcessStatOptions(pmapi.pmOptions): - try: - if optarg.upper() == "ALL": - self.filterstate = optarg.upper() -+ self.colum_list = [ -+ "uname", "pid", "ppid", "pri", "%cpu", "%mem", -+ "vsize", "rss", "state", "start", "time", "wchan", "args" -+ ] - else: - dummy_list = optarg.replace(',', ' ').split(' ') - if self.debug_mode: -diff --git a/src/pcp/tapestat/pcp-tapestat.py b/src/pcp/tapestat/pcp-tapestat.py -index 38dc3ee..e8c93dc 100755 ---- a/src/pcp/tapestat/pcp-tapestat.py -+++ b/src/pcp/tapestat/pcp-tapestat.py -@@ -68,9 +68,18 @@ class TapestatReport(pmcc.MetricGroupPrinter): - Hcount = 0 - def timeStampDelta(self, group): - s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec -- u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -- # u may be negative here, calculation is still correct. -- return s + u / 1000000.0 -+ # pmapi timestamps may provide sub-second resolution via tv_nsec (nanoseconds) -+ # or tv_usec (microseconds) depending on the collector. Prefer nanoseconds -+ # when available, but gracefully fall back to microseconds to avoid -+ if hasattr(group.timestamp, 'tv_nsec') and hasattr(group.prevTimestamp, 'tv_nsec'): -+ n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec -+ # n may be negative here, calculation is still correct. -+ return s + n / 1000000000.0 -+ elif hasattr(group.timestamp, 'tv_usec') and hasattr(group.prevTimestamp, 'tv_usec'): -+ u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec -+ return s + u / 1000000.0 -+ # it should not reach here -+ return s - def instlist(self, group, name): - return dict(map(lambda x: (x[1], x[2]), group[name].netValues)).keys() - --- -2.43.7 - diff --git a/SOURCES/1017-orabug39068870-adds-interval-option-in-nfsiostat.patch b/SOURCES/1017-orabug39068870-adds-interval-option-in-nfsiostat.patch deleted file mode 100644 index eb311f2..0000000 --- a/SOURCES/1017-orabug39068870-adds-interval-option-in-nfsiostat.patch +++ /dev/null @@ -1,364 +0,0 @@ -From c79976edb317272dcc8ae9352778d5d335763f10 Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Wed, 25 Feb 2026 12:45:43 +0000 -Subject: [PATCH] Add interval and count support for nfsiostat tool - -Signed-off-by: Sourav Sharma - -Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/02d9b5a7b3c2ea16de2665c1f8cd6b81843fee15 - -Orabug: 39068870 - -Signed-off-by: Sourav Sharma - ---- - src/pcp/nfsiostat/pcp-nfsiostat.py | 269 ++++++++++++++++------------- - 1 file changed, 145 insertions(+), 124 deletions(-) - -diff --git a/src/pcp/nfsiostat/pcp-nfsiostat.py b/src/pcp/nfsiostat/pcp-nfsiostat.py -index 0b0376d..e19fa41 100644 ---- a/src/pcp/nfsiostat/pcp-nfsiostat.py -+++ b/src/pcp/nfsiostat/pcp-nfsiostat.py -@@ -22,7 +22,7 @@ import signal - import sys - import time - from pcp import pmapi, pmcc --from cpmapi import PM_CONTEXT_ARCHIVE -+from cpmapi import PM_CONTEXT_ARCHIVE, PM_MODE_FORW - - SYS_METRICS= ["kernel.uname.sysname","kernel.uname.release", - "kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"] -@@ -40,98 +40,54 @@ def adjust_length(name): - class ReportingMetricRepository: - - def __init__(self,group): -- self.group=group -- self.current_cached_values = {} -- -- def __sorted(self,data): -- return dict(sorted(data.items(), key=lambda item: item[0].lower())) -- -- def __fetch_current_value(self,metric): -- val=dict(map(lambda x: (x[1], x[2]), self.group[metric].netValues)) -- val=self.__sorted(val) -- return dict(val) -- -- def current_value(self,metric): -- if not metric in self.group: -- return None -- if self.current_cached_values.get(metric) is None: -- first_value=self.__fetch_current_value(metric) -- self.current_cached_values[metric]=first_value -- return self.current_cached_values[metric] -+ self.group = group -+ self._current_cache = {} -+ self._previous_cache = {} -+ -+ def _fetch_values(self, metric, use_previous=False): -+ """Fetch values - always returns a dictionary.""" -+ if metric not in self.group: -+ return {} -+ attr = "netPrevValues" if use_previous else "netValues" -+ values = getattr(self.group[metric], attr, []) -+ return {x[0].inst: x[2] for x in values} if values else {} -+ -+ def _get_values_dict(self, metric, use_previous=False): -+ """Get cached dictionary of all values for a metric.""" -+ cache = self._previous_cache if use_previous else self._current_cache -+ if metric not in cache: -+ cache[metric] = self._fetch_values(metric, use_previous) -+ return cache[metric] -+ -+ def previous_value(self, metric, instance=None): -+ """Get previous value. Returns single value if instance given, else returns dict.""" -+ values_dict = self._get_values_dict(metric, use_previous=True) -+ if instance is not None: -+ return values_dict.get(instance) -+ return values_dict -+ -+ def current_value(self, metric, instance=None): -+ """Get current value. Returns single value if instance given, else returns dict.""" -+ values_dict = self._get_values_dict(metric, use_previous=False) -+ if instance is not None: -+ return values_dict.get(instance) -+ return values_dict -+ -+ def previous_value(self, metric, instance=None): -+ """Get previous value. Returns single value if instance given, else returns dict.""" -+ values_dict = self._get_values_dict(metric, use_previous=True) -+ if instance is not None: -+ return values_dict.get(instance) -+ return values_dict - - class NfsioStatUtil: - def __init__(self,metrics_repository): - self.__metric_repository=metrics_repository - self.report=ReportingMetricRepository(self.__metric_repository) - -- def mount_point(self): -- return self.report.current_value('nfsclient.mountpoint') -- -- def mount_share(self): -- return self.report.current_value('nfsclient.export') -- -- def mount_share_keys(self): -- data = self.report.current_value('nfsclient.export') -- return data.keys() -- -- def sample_time(self): -- return self.report.current_value('nfsclient.age') -- -- def xprt_sends(self): -- return self.report.current_value('nfsclient.xprt.sends') -- -- def xprt_backlog(self): -- return self.report.current_value('nfsclient.xprt.backlog_u') -- -- def readops(self): -- return self.report.current_value('nfsclient.ops.read.ops') -- -- def readerrors(self): -- return self.report.current_value('nfsclient.ops.read.errors') -- -- def readexecute(self): -- return self.report.current_value('nfsclient.ops.read.execute') -- -- def readrtt(self): -- return self.report.current_value('nfsclient.ops.read.rtt') -- -- def readqueue(self): -- return self.report.current_value('nfsclient.ops.read.queue') -- -- def readbytesrecv(self): -- return self.report.current_value('nfsclient.ops.read.bytes_recv') -- -- def readbytessent(self): -- return self.report.current_value('nfsclient.ops.read.bytes_sent') -- -- def readntrans(self): -- return self.report.current_value('nfsclient.ops.read.ntrans') -- -- def writeops(self): -- return self.report.current_value('nfsclient.ops.write.ops') -- -- def writeerrors(self): -- return self.report.current_value('nfsclient.ops.write.errors') -- -- def writeexecute(self): -- return self.report.current_value('nfsclient.ops.write.execute') -- -- def writertt(self): -- return self.report.current_value('nfsclient.ops.write.rtt') -- -- def writequeue(self): -- return self.report.current_value('nfsclient.ops.write.queue') -- -- def writebytesrecv(self): -- return self.report.current_value('nfsclient.ops.write.bytes_recv') -- -- def writebytessent(self): -- return self.report.current_value('nfsclient.ops.write.bytes_sent') -- -- def writentrans(self): -- return self.report.current_value('nfsclient.ops.write.ntrans') -- - class NfsiostatReport(pmcc.MetricGroupPrinter): -+ machine_info_count = 0 -+ - def __init__(self,opts,group): - self.opts = opts - self.group = group -@@ -156,34 +112,67 @@ class NfsiostatReport(pmcc.MetricGroupPrinter): - header_string += context['kernel.uname.machine'].netValues[0][2] + ' ' - print("%s (%s CPU)" % (header_string, self.__get_ncpu(context))) - -- def __print_values(self,timestamp, nfsstatus): -- n_shares = nfsstatus.mount_share_keys() -- mountshare = nfsstatus.mount_share() -- mountpoint = nfsstatus.mount_point() -- sampletime = nfsstatus.sample_time() -- sends = nfsstatus.xprt_sends() -- backlog = nfsstatus.xprt_backlog() -- readops = nfsstatus.readops() -- readerrors = nfsstatus.readerrors() -- readexecute = nfsstatus.readexecute() -- readrtt = nfsstatus.readrtt() -- readqueue = nfsstatus.readqueue() -- readbytesrecv = nfsstatus.readbytesrecv() -- readbytessent = nfsstatus.readbytessent() -- readntrans = nfsstatus.readntrans() -- writeops = nfsstatus.writeops() -- writeerrors = nfsstatus.writeerrors() -- writeexecute = nfsstatus.writeexecute() -- writertt = nfsstatus.writertt() -- writequeue = nfsstatus.writequeue() -- writebytesrecv = nfsstatus.writebytesrecv() -- writebytessent = nfsstatus.writebytessent() -- writentrans = nfsstatus.writentrans() -+ # -------------------------------------------------------- -+ -+ def __collect(self, nfs): -+ return { -+ metric: nfs.report.current_value(metric) -+ for metric in NFSIOSTAT_METRICS -+ } -+ -+ # -------------------------------------------------------- -+ -+ def __delta(self, new: dict, nfs): -+ delta = {} -+ old = { -+ metric: nfs.report.previous_value(metric) -+ for metric in NFSIOSTAT_METRICS -+ } -+ -+ for metric in new: -+ delta[metric] = {} -+ -+ for inst in new[metric]: -+ new_val = new[metric][inst] -+ old_val = old.get(metric, {}).get(inst, 0) -+ -+ # If value is numeric → subtract -+ if isinstance(new_val, (int, float)): -+ delta[metric][inst] = new_val - old_val -+ else: -+ # If string → just copy (no subtraction) -+ delta[metric][inst] = new_val -+ -+ return delta -+ -+ def __print_values(self,timestamp, delta): -+ -+ sampletime = delta["nfsclient.age"] -+ readops = delta["nfsclient.ops.read.ops"] -+ writeops = delta["nfsclient.ops.write.ops"] -+ readbytesrecv = delta["nfsclient.ops.read.bytes_recv"] -+ writebytesrecv = delta["nfsclient.ops.write.bytes_recv"] -+ mountpoint = delta["nfsclient.mountpoint"] -+ mountshare = delta["nfsclient.export"] -+ sends = delta["nfsclient.xprt.sends"] -+ backlog = delta["nfsclient.xprt.backlog_u"] -+ readerrors = delta["nfsclient.ops.read.errors"] -+ readexecute = delta["nfsclient.ops.read.execute"] -+ readrtt = delta["nfsclient.ops.read.rtt"] -+ readqueue = delta["nfsclient.ops.read.queue"] -+ readbytessent = delta["nfsclient.ops.read.bytes_sent"] -+ readntrans = delta["nfsclient.ops.read.ntrans"] -+ writeerrors = delta["nfsclient.ops.write.errors"] -+ writeexecute = delta["nfsclient.ops.write.execute"] -+ writertt = delta["nfsclient.ops.write.rtt"] -+ writequeue = delta["nfsclient.ops.write.queue"] -+ writebytessent = delta["nfsclient.ops.write.bytes_sent"] -+ writentrans = delta["nfsclient.ops.write.ntrans"] - - print("%-18s:%s"%("Timestamp", timestamp)) - print() - -- for name in n_shares: -+ for name in mountshare: - # read - r_kilobytes = (readbytessent[name] + readbytesrecv[name]) / 1024 - if sampletime[name] > 0: -@@ -277,13 +266,24 @@ class NfsiostatReport(pmcc.MetricGroupPrinter): - ) - print() - -- def print_report(self,group,timestamp, manager_nfsiostat): -+ def get_timestamp(self, group): -+ t_s = group.contextCache.pmLocaltime(int(group.timestamp)) -+ timestamp = time.strftime(NfsiostatOptions.timefmt, t_s.struct_time()) -+ return timestamp -+ -+ def print_report(self,group, manager_nfsiostat, mgr): - def __print_nfs_status(): -- nfsstatus = NfsioStatUtil(manager_nfsiostat) -- if nfsstatus.mount_share(): -+ timestamp = self.get_timestamp(group) -+ nfs = NfsioStatUtil(manager_nfsiostat) -+ if nfs.report.current_value("nfsclient.export"): - try: -- self.__print_machine_info(group) -- self.__print_values(timestamp, nfsstatus) -+ if self.machine_info_count == 0: -+ self.__print_machine_info(group) -+ self.machine_info_count = 1 -+ current = self.__collect(nfs) -+ diff_dict = self.__delta(current, nfs) -+ self. __print_values(timestamp, diff_dict) -+ - except IndexError: - print("Incorrect machine info due to some missing metrics") - return -@@ -292,7 +292,6 @@ class NfsiostatReport(pmcc.MetricGroupPrinter): - - if self.context != PM_CONTEXT_ARCHIVE and self.samples is None: - __print_nfs_status() -- sys.exit(0) - elif self.context == PM_CONTEXT_ARCHIVE and self.samples is None: - __print_nfs_status() - elif self.samples >=1: -@@ -302,30 +301,52 @@ class NfsiostatReport(pmcc.MetricGroupPrinter): - pass - - def report(self, manager): -- group = manager["sysinfo"] - self.samples = self.opts.pmGetOptionSamples() -- t_s = group.contextCache.pmLocaltime(int(group.timestamp)) -- timestamp = time.strftime(NfsiostatOptions.timefmt, t_s.struct_time()) -- self.print_report(group,timestamp,manager['nfsiostat']) -+ self.print_report(manager["sysinfo"] ,manager['nfsiostat'], manager) - - class NfsiostatOptions(pmapi.pmOptions): - timefmt = "%m/%d/%Y %H:%M:%S" -+ uflag = False -+ def checkOptions(self, manager): -+ if NfsiostatOptions.uflag: -+ if manager._options.pmGetOptionInterval(): -+ print("Error: -t incompatible with -u") -+ return False -+ if manager.type != PM_CONTEXT_ARCHIVE: -+ print("Error: -u can only be specified with -a archive") -+ return False -+ return True -+ -+ def extraOptions(self, opt, optarg, index): -+ if opt == "u": -+ NfsiostatOptions.uflag = True -+ - def __init__(self): -- pmapi.pmOptions.__init__(self, "a:s:Z:zV?") -+ pmapi.pmOptions.__init__(self, "a:s:Z:t:uzV?") -+ self.pmSetOptionCallback(self.extraOptions) - self.pmSetLongOptionHeader("General options") - self.pmSetLongOptionHostZone() - self.pmSetLongOptionTimeZone() -- self.pmSetLongOptionHelp() -+ self.pmSetLongOptionArchive() - self.pmSetLongOptionSamples() -+ self.pmSetLongOptionInterval() -+ self.pmSetLongOption("no-interpolation", 0, "u", "", "disable interpolation mode with archives") -+ self.pmSetLongOptionHelp() - self.pmSetLongOptionVersion() -- self.samples=None -- self.context=None -+ self.context = None -+ self.samples = None - - if __name__ == '__main__': - try: - opts = NfsiostatOptions() - mngr = pmcc.MetricGroupManager.builder(opts,sys.argv) - opts.context=mngr.type -+ if not opts.checkOptions(mngr): -+ raise pmapi.pmUsageErr -+ -+ if NfsiostatOptions.uflag: -+ # -u turns off interpolation -+ mngr.pmSetMode(PM_MODE_FORW, mngr._options.pmGetOptionOrigin(), None) - missing = mngr.checkMissingMetrics(ALL_METRICS) - if missing is not None: - sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing) --- -2.43.7 - diff --git a/SOURCES/1018-orabug39096683-introduces-numa-maps-metrics-and-adds-numastat-process-option.patch b/SOURCES/1018-orabug39096683-introduces-numa-maps-metrics-and-adds-numastat-process-option.patch deleted file mode 100644 index 1dd5602..0000000 --- a/SOURCES/1018-orabug39096683-introduces-numa-maps-metrics-and-adds-numastat-process-option.patch +++ /dev/null @@ -1,1286 +0,0 @@ -From 7fde321e47278138f8daee10bb0c30a708471df3 Mon Sep 17 00:00:00 2001 -From: Sourav Sharma -Date: Wed, 25 Mar 2026 05:50:39 +0000 -Subject: [PATCH] Introduces numa_maps metrics inside linux_proc pmda - -Implemented option -p in pcp numastat tool same as numactl numastat tool - - - Add new per-process NUMA maps metrics derived from /proc//numa_maps: proc.numa_maps.huge, proc.numa_maps.heap, proc.numa_maps.stack, - proc.numa_maps.private (per-node usage in MB, exported as nodeN: pairs). - - Extend pcp numastat/pcp-numastat with -p/--process to display per-process NUMA memory usage in a numastat -p-style layout. - - Convert N= counts to MB using system page size (and Hugepagesize where applicable, with fallback). - - How to Test - - - pcp numastat -p (detail view) and pcp numastat -p (summary view) - -Signed-off-by: Sourav Sharma - -Cherry-pick-commit: -https://github.com/sourav-sharma796/pcp/commit/0ea70c7fda4021cb9e35a0d4e26a30dd97409ddb -https://github.com/sourav-sharma796/pcp/commit/95fe35c176c9394ddcef0af5f4d50606308c77d1 -https://github.com/sourav-sharma796/pcp/commit/757c2a1e1c4b51ee3e304f6c3f3038e6030432f3 - -Orabug: 39096683 - -Signed-off-by: Sourav Sharma - ---- - src/pcp/numastat/pcp-numastat.1 | 23 +- - src/pcp/numastat/pcp-numastat.py | 573 ++++++++++++++++++++++++++++++- - src/pmdas/linux_proc/clusters.h | 3 +- - src/pmdas/linux_proc/help | 13 + - src/pmdas/linux_proc/indom.h | 1 + - src/pmdas/linux_proc/pmda.c | 74 ++++ - src/pmdas/linux_proc/pmdaproc.1 | 4 + - src/pmdas/linux_proc/proc_pid.c | 269 +++++++++++++++ - src/pmdas/linux_proc/proc_pid.h | 17 + - src/pmdas/linux_proc/root_proc | 8 + - 10 files changed, 972 insertions(+), 13 deletions(-) - -diff --git a/src/pcp/numastat/pcp-numastat.1 b/src/pcp/numastat/pcp-numastat.1 -index 65910a4..e6a9ada 100644 ---- a/src/pcp/numastat/pcp-numastat.1 -+++ b/src/pcp/numastat/pcp-numastat.1 -@@ -18,7 +18,7 @@ - \f3pcp-numastat\f1 \- report on NUMA memory allocation - .SH SYNOPSIS - \f3pcp\f1 [\f2pcp\ options\f1] \f3numastat\f1 --[\fB-V\fR] [\fB-w\fR \fIwidth\fR] [\fB-m\fR][-n\fR] [\fB-?\fR] -+[\fB-V\fR] [\fB-w\fR \fIwidth\fR] [\fB-m\fR][-n\fR] [\fB\-p\fR [\fIprocess\fR ...]] [\fB-?\fR] - .SH DESCRIPTION - .B pcp-numastat - displays NUMA allocation statistics from the kernel memory -@@ -75,13 +75,32 @@ Display meminfo-like system-wide memory usage. - \fB-n\fR, \fB--numastat\fR - Display the numastat statistics info. - .TP -+\fB\-p\fR, \fB\-\-process\fR -+Display per-process NUMA memory usage from \fBproc.numa_maps\fR metrics. -+Any trailing \fIprocess\fR operands are treated as process IDs (when the -+operand is all digits) or regular expressions for command name matching. -+Matching for command names is performed against the full command line -+(as reported by \fBproc.psinfo.psargs\fR). -+When a single process ID operand is provided and it matches exactly one -+process, a detailed table of Hugepage, Heap, Stack and Private mappings is -+shown (as in \fBnumastat \-p\fR). When multiple process ID operands are -+provided, a per-process summary table is shown (also like -+\fBnumastat \-p\fR) and may be split into multiple node column blocks -+based on the available display width. -+.PP -+In the per-process view, node columns are always shown for all NUMA nodes -+present on the system (as reported by PCP NUMA inventory metrics), even if -+individual \fBproc.numa_maps\fR metric instances omit some nodes. Missing -+per-process metrics are reported as zero. -+.TP - \fB\-?\fR, \fB\-\-help\fR - Display usage message and exit. - .SH NOTES - .B pcp-numastat - is inspired by the - .BR numastat (1) --command and produces exactly the same output. -+command and produces the same output for the system-wide views. The -+per-process view (\fB\-p\fR) follows the \fBnumastat \-p\fR layout. - .SH PCP ENVIRONMENT - Environment variables with the prefix \fBPCP_\fP are used to parameterize - the file and directory names used by PCP. -diff --git a/src/pcp/numastat/pcp-numastat.py b/src/pcp/numastat/pcp-numastat.py -index 2a044c9..decdf12 100755 ---- a/src/pcp/numastat/pcp-numastat.py -+++ b/src/pcp/numastat/pcp-numastat.py -@@ -17,6 +17,7 @@ - """ Display NUMA memory allocation statistucs """ - - import os -+import re - import signal - import sys - import time -@@ -86,6 +87,23 @@ SYS_METRICS = [ - - ALL_METRICS = NUMA_METRICS + MEM_METRICS - -+PROCESS_METRICS = [ -+ "proc.psinfo.pid", -+ "proc.psinfo.cmd", -+ "proc.psinfo.psargs", -+ "proc.numa_maps.hugepage", -+ "proc.numa_maps.heap", -+ "proc.numa_maps.stack", -+ "proc.numa_maps.private", -+] -+ -+PROCESS_NUMA_METRICS = [ -+ ("Huge", "proc.numa_maps.hugepage"), -+ ("Heap", "proc.numa_maps.heap"), -+ ("Stack", "proc.numa_maps.stack"), -+ ("Private", "proc.numa_maps.private"), -+] -+ - def prefix(metric): - last_part = metric.split('.')[-1] - result = last_part[0].upper() + last_part[1:] -@@ -96,6 +114,7 @@ class MetricRepository: - self.group = group - self.current_cached_values = {} - self.previous_cached_values = {} -+ self.current_cached_instance_names = {} - - def _fetch_current_values(self, metric, instance): - if instance is not None: -@@ -192,22 +211,465 @@ class NUMAStat: - def print_numa(self, width, nodes, data): - self.__format_table(width, nodes, data) - -+class ProcessNUMAStat: -+ def __init__(self, group, ignore_pid=None): -+ self.group = group -+ self.repo = MetricRepository(group) -+ self.ignore_pid = ignore_pid -+ -+ def __resize(self, width): -+ """ Find a suitable display width limit (matches NUMAStat.resize) """ -+ if width == 0: -+ if not sys.stdout.isatty(): -+ width = 1000000000 # mimic numastat(1) here -+ else: -+ try: -+ # popen() is SAFE, command is a literal string -+ (_, width) = os.popen('stty size', 'r').read().split() -+ width = int(width) -+ except Exception: -+ width = 80 -+ width = int(os.getenv('NUMASTAT_WIDTH', str(width))) -+ return max(int(width), 32) -+ -+ def __normalize_value(self, value): -+ if value is None: -+ return "" -+ if hasattr(value, "decode"): -+ try: -+ return value.decode("utf-8") -+ except Exception: -+ return value.decode("utf-8", "ignore") -+ return str(value) -+ -+ def __normalize_cmdline(self, value): -+ if value is None: -+ return "" -+ -+ # Convert bytes → string -+ if isinstance(value, bytes): -+ value = value.decode("utf-8", "ignore") -+ -+ # /proc//cmdline uses NULL separators -+ if "\0" in value: -+ parts = value.split("\0") -+ value = " ".join(p for p in parts if p) -+ -+ return value.strip() -+ -+ def __is_missing_command(self, command): -+ normalized = self.__normalize_value(command).strip() -+ return normalized == "" or normalized.lower() == "(null)" -+ -+ def __parse_nodes(self, value): -+ node_values = {} -+ text = self.__normalize_value(value) -+ if not text: -+ return node_values -+ for token in text.split(','): -+ if ':' not in token: -+ continue -+ name, raw_value = token.split(':', 1) -+ name = name.strip() -+ if not name.startswith("node"): -+ continue -+ try: -+ node_id = int(name[4:]) -+ node_values[node_id] = float(raw_value) -+ except (TypeError, ValueError): -+ continue -+ return node_values -+ -+ def __matches_process(self, pid, selectors, haystack): -+ if not selectors: -+ return True -+ haystack = self.__normalize_value(haystack) -+ for selector in selectors: -+ selector_text = self.__normalize_value(selector) -+ if re.fullmatch(r"\d+", selector_text): -+ try: -+ if int(selector_text) == int(pid): -+ return True -+ except (TypeError, ValueError): -+ continue -+ continue -+ if selector_text in haystack: -+ return True -+ return False -+ -+ def __collect_process_categories(self, inst_id, metric_maps): -+ category_values = {} -+ nodes = set() -+ has_data = False -+ for label, metric in PROCESS_NUMA_METRICS: -+ parsed = self.__parse_nodes(metric_maps[metric].get(inst_id, "")) -+ if parsed: -+ has_data = True -+ category_values[label] = parsed -+ nodes.update(parsed.keys()) -+ return category_values, nodes, has_data -+ -+ def __sum_categories(self, category_values): -+ node_totals = {} -+ for values in category_values.values(): -+ for node_id, value in values.items(): -+ node_totals[node_id] = node_totals.get(node_id, 0.0) + value -+ return node_totals -+ -+ def __process_rows(self, selectors, system_nodes=None): -+ rows = [] -+ nodes = set(system_nodes or []) -+ selectors_provided = bool(selectors) -+ requested_pids = set() -+ for selector in selectors or []: -+ selector_text = self.__normalize_value(selector) -+ if re.fullmatch(r"\d+", selector_text): -+ try: -+ requested_pids.add(int(selector_text)) -+ except (TypeError, ValueError): -+ continue -+ pid_map = self.repo.current_values("proc.psinfo.pid") or {} -+ command_map = self.repo.current_values("proc.psinfo.cmd") or {} -+ psargs_map = self.repo.current_values("proc.psinfo.psargs") or {} -+ metric_maps = {} -+ for _, metric in PROCESS_NUMA_METRICS: -+ metric_maps[metric] = self.repo.current_values(metric) or {} -+ -+ # Restrict scanning to processes that are actually present in proc.numa_maps.*. -+ numa_inst_ids = set() -+ for values in metric_maps.values(): -+ try: -+ numa_inst_ids.update(values.keys()) -+ except Exception: -+ continue -+ -+ for inst_id, pid in sorted(pid_map.items(), key=lambda item: item[1]): -+ if inst_id not in numa_inst_ids and int(pid) not in requested_pids: -+ continue -+ if ( -+ self.ignore_pid is not None -+ and int(pid) == int(self.ignore_pid) -+ and int(pid) not in requested_pids -+ ): -+ continue -+ command = command_map.get(inst_id, "") -+ full_command = psargs_map.get(inst_id, command) -+ if self.__is_missing_command(command): -+ if int(pid) in requested_pids: -+ command = "unknown" -+ full_command = "unknown" -+ else: -+ continue -+ # Constrain matching to the same process set and labels as the -+ # proc.numa_maps.* instance domains (e.g. `pminfo -f proc.numa_maps.heap`). -+ match_text = "%s %s" % ( -+ self.__normalize_value(command), -+ self.__normalize_cmdline(full_command), -+ ) -+ if not self.__matches_process(pid, selectors, match_text): -+ continue -+ -+ category_values, category_nodes, has_data = self.__collect_process_categories( -+ inst_id, -+ metric_maps, -+ ) -+ if not has_data and not selectors_provided: -+ continue -+ if not has_data and self.__is_missing_command(command): -+ continue -+ -+ if category_nodes: -+ nodes.update(category_nodes) -+ node_totals = self.__sum_categories(category_values) -+ rows.append((pid, command, node_totals, category_values)) -+ return rows, sorted(nodes) -+ -+ def __node_blocks_for_table(self, nodes, width, pid_col_width): -+ num_col_width = 15 -+ sep = " " -+ col_width = len(sep) + num_col_width -+ -+ width = self.__resize(width) -+ max_cols = int((width - pid_col_width) / col_width) -+ max_cols = max(1, max_cols) -+ max_nodes_no_total = max_cols -+ -+ # If we can only fit one numeric column, there is no room to display -+ # any node column together with a Total column. Emit the nodes first -+ # (one per block), then a final Total-only block. -+ if max_cols <= 1: -+ for node_id in nodes: -+ yield [node_id], False -+ yield [], True -+ return -+ -+ max_nodes_with_total = max_cols - 1 -+ -+ if len(nodes) <= max_nodes_with_total: -+ yield nodes, True -+ return -+ -+ done = 0 -+ while len(nodes) - done > max_nodes_with_total: -+ remaining = len(nodes) - done -+ chunk_size = min(max_nodes_no_total, remaining) -+ -+ # Avoid consuming all remaining nodes in a non-total block (which -+ # would otherwise suppress the Total column entirely when nodes -+ # exactly fill the display width). -+ if remaining <= max_nodes_no_total and remaining - chunk_size == 0: -+ chunk_size = max(1, remaining - 1) -+ -+ chunk = nodes[done:done + chunk_size] -+ if not chunk: -+ break -+ yield chunk, False -+ done += chunk_size -+ -+ yield nodes[done:], True -+ -+ def __print_process_table(self, rows, nodes, width): -+ print("Per-node process memory usage (in MBs)") -+ pid_col_width = max( -+ 16, -+ max( -+ len("%s (%s)" % (pid, self.__normalize_value(command))) -+ for pid, command, _, _ in rows -+ ), -+ ) -+ num_col_width = 15 -+ sep = " " -+ -+ node_totals_all = dict((node_id, 0.0) for node_id in nodes) -+ row_totals = {} -+ grand_total = 0.0 -+ for pid, _, per_node, _ in rows: -+ total = 0.0 -+ for node_id in nodes: -+ value = per_node.get(node_id, 0.0) -+ node_totals_all[node_id] += value -+ total += value -+ row_totals[pid] = total -+ grand_total += total -+ -+ for chunk, include_total in self.__node_blocks_for_table(nodes, width, pid_col_width): -+ header = "%-*s" % (pid_col_width, "PID") -+ for node_id in chunk: -+ header += "%s%*s" % (sep, num_col_width, "Node %d" % node_id) -+ if include_total: -+ header += "%s%*s" % (sep, num_col_width, "Total") -+ print(header) -+ -+ line = "-" * pid_col_width -+ for _ in range(len(chunk) + (1 if include_total else 0)): -+ line += "%s%s" % (sep, "-" * num_col_width) -+ print(line) -+ -+ for pid, command, per_node, _ in rows: -+ label = "%s (%s)" % (pid, self.__normalize_value(command)) -+ row = "%-*s" % (pid_col_width, label) -+ for node_id in chunk: -+ row += "%s%*.2f" % (sep, num_col_width, per_node.get(node_id, 0.0)) -+ if include_total: -+ row += "%s%*.2f" % (sep, num_col_width, row_totals.get(pid, 0.0)) -+ print(row) -+ -+ print(line) -+ total_row = "%-*s" % (pid_col_width, "Total") -+ for node_id in chunk: -+ total_row += "%s%*.2f" % (sep, num_col_width, node_totals_all.get(node_id, 0.0)) -+ if include_total: -+ total_row += "%s%*.2f" % (sep, num_col_width, grand_total) -+ print(total_row) -+ print() -+ -+ def __node_blocks(self, nodes, width): -+ label_width = 16 -+ num_col_width = 15 -+ sep = " " -+ col_width = len(sep) + num_col_width -+ -+ width = self.__resize(width) -+ max_cols = int((width - label_width) / col_width) -+ max_cols = max(1, max_cols) -+ max_nodes_no_total = max_cols -+ -+ # If we can only fit one numeric column, there is no room to display -+ # any node column together with a Total column. Emit the nodes first -+ # (one per block), then a final Total-only block. -+ if max_cols <= 1: -+ for node_id in nodes: -+ yield [node_id], False -+ yield [], True -+ return -+ -+ max_nodes_with_total = max_cols - 1 -+ -+ if len(nodes) <= max_nodes_with_total: -+ yield nodes, True -+ return -+ -+ done = 0 -+ while len(nodes) - done > max_nodes_with_total: -+ remaining = len(nodes) - done -+ chunk_size = min(max_nodes_no_total, remaining) -+ -+ # Avoid consuming all remaining nodes in a non-total block (which -+ # would otherwise suppress the Total column entirely when nodes -+ # exactly fill the display width). -+ if remaining <= max_nodes_no_total and remaining - chunk_size == 0: -+ chunk_size = max(1, remaining - 1) -+ -+ chunk = nodes[done:done + chunk_size] -+ if not chunk: -+ break -+ yield chunk, False -+ done += chunk_size -+ -+ yield nodes[done:], True -+ -+ def __print_process_detail(self, row, nodes, width): -+ pid, command, _, categories = row -+ print("Per-node process memory usage (in MBs) for PID %s (%s)" % -+ (pid, self.__normalize_value(command))) -+ label_width = 16 -+ num_col_width = 15 -+ sep = " " -+ -+ if not nodes: -+ nodes = [0] -+ -+ category_totals = {} -+ node_totals = dict((node_id, 0.0) for node_id in nodes) -+ all_total = 0.0 -+ for label, _ in PROCESS_NUMA_METRICS: -+ values = categories.get(label, {}) -+ total = 0.0 -+ for node_id in nodes: -+ value = values.get(node_id, 0.0) -+ total += value -+ node_totals[node_id] += value -+ category_totals[label] = total -+ all_total += total -+ -+ for chunk, include_total in self.__node_blocks(nodes, width): -+ header = "%-*s" % (label_width, "") -+ for node_id in chunk: -+ header += "%s%*s" % (sep, num_col_width, "Node %d" % node_id) -+ if include_total: -+ header += "%s%*s" % (sep, num_col_width, "Total") -+ print(header) -+ -+ header_line = " " * label_width -+ for _ in range(len(chunk) + (1 if include_total else 0)): -+ header_line += "%s%s" % (sep, "-" * num_col_width) -+ print(header_line) -+ -+ for label, _ in PROCESS_NUMA_METRICS: -+ row = "%-*s" % (label_width, label) -+ values = categories.get(label, {}) -+ for node_id in chunk: -+ row += "%s%*.2f" % (sep, num_col_width, values.get(node_id, 0.0)) -+ if include_total: -+ row += "%s%*.2f" % (sep, num_col_width, category_totals.get(label, 0.0)) -+ print(row) -+ -+ line = "-" * label_width -+ for _ in range(len(chunk) + (1 if include_total else 0)): -+ line += "%s%s" % (sep, "-" * num_col_width) -+ print(line) -+ -+ total_row = "%-*s" % (label_width, "Total") -+ for node_id in chunk: -+ total_row += "%s%*.2f" % (sep, num_col_width, node_totals.get(node_id, 0.0)) -+ if include_total: -+ total_row += "%s%*.2f" % (sep, num_col_width, all_total) -+ print(total_row) -+ print() -+ -+ def print_processes(self, selectors, system_nodes=None, width=0): -+ rows, nodes = self.__process_rows(selectors, system_nodes=system_nodes) -+ if not rows: -+ if selectors: -+ print("No matching processes with NUMA maps data.") -+ else: -+ print("No processes with NUMA maps data.") -+ print() -+ return -+ if not nodes: -+ nodes = [0] -+ selector_texts = [self.__normalize_value(s).strip() for s in (selectors or [])] -+ selectors_are_pids = bool(selector_texts) and all( -+ re.fullmatch(r"\d+", text) for text in selector_texts -+ ) -+ if selectors_are_pids: -+ requested = [] -+ for text in selector_texts: -+ try: -+ requested.append(int(text)) -+ except (TypeError, ValueError): -+ continue -+ -+ rows_by_pid = {} -+ for pid, command, per_node, categories in rows: -+ try: -+ rows_by_pid[int(pid)] = (pid, command, per_node, categories) -+ except (TypeError, ValueError): -+ continue -+ -+ if len(requested) == 1: -+ pid = requested[0] -+ found = rows_by_pid.get(pid) -+ if found is None: -+ print("No matching process with NUMA maps data for PID %s." % pid) -+ print() -+ return -+ self.__print_process_detail(found, nodes, width) -+ return -+ -+ selected_rows = [] -+ for pid in requested: -+ found = rows_by_pid.get(pid) -+ if found is None: -+ print("No matching process with NUMA maps data for PID %s." % pid) -+ print() -+ continue -+ row_pid, command, per_node, categories = found -+ selected_rows.append((row_pid, command, per_node, categories)) -+ if selected_rows: -+ self.__print_process_table(selected_rows, nodes, width) -+ return -+ -+ single_selector = len(selectors or []) == 1 -+ if single_selector and len(rows) == 1: -+ self.__print_process_detail(rows[0], nodes, width) -+ return -+ -+ self.__print_process_table(rows, nodes, width) -+ - class NumaStatOption(pmapi.pmOptions): - context = None - timefmt = "%m/%d/%Y %H:%M:%S" - width = 0 - mem_out = False - numa_out = False -+ process_out = False -+ process_filters = [] - - def override(self,opt): - """ Override standard PCP options to match numastat(1) """ -- if opt == 'n': -+ if opt in ('n', 'p'): - return True - return False - - def __init__(self): - pmapi.pmOptions.__init__(self) -- self.pmSetShortOptions("w:mV?:n") -+ self.width = 0 -+ self.mem_out = False -+ self.numa_out = False -+ self.process_out = False -+ self.process_filters = [] -+ self.pmSetShortOptions("w:mV?np") - self.pmSetOptionCallback(self.extraOptions) - self.pmSetOverrideCallback(self.override) - self.pmSetLongOptionHeader("Numastat options") -@@ -215,6 +677,7 @@ class NumaStatOption(pmapi.pmOptions): - # Map long options to our non-conflicting short letters - self.pmSetLongOption("meminfo", 0, 'm', "", "show meminfo-like system-wide memory usage") - self.pmSetLongOption("numastat", 0, 'n', "", "show the numastat statistics info") -+ self.pmSetLongOption("process", 0, 'p', "", "show per-process NUMA memory usage") - self.pmSetLongOptionVersion() - self.pmSetLongOptionHelp() - -@@ -225,6 +688,8 @@ class NumaStatOption(pmapi.pmOptions): - self.mem_out = True - elif opt == "n": - self.numa_out = True -+ elif opt == "p": -+ self.process_out = True - elif opt == "V": - pass - else: -@@ -232,7 +697,7 @@ class NumaStatOption(pmapi.pmOptions): - return True - - def checkoptions(self): -- if (not self.mem_out) and (not self.numa_out) and (self.width == 0): -+ if (not self.mem_out) and (not self.numa_out) and (not self.process_out): - self.numa_out = True - if self.width < 0: - return False -@@ -284,6 +749,33 @@ class NumaStatReport(pmcc.MetricGroupPrinter): - nodes.sort(key=lambda t: t[0]) - return nodes - -+ def __discover_nodes_all(self, group, name): -+ # Build list of nodes from instances (instance id, instance name) -+ nodes = [] -+ try: -+ for ent in group[name].netValues: -+ inst_id = ent[0].inst -+ inst_name = ent[1] # usually "node0", "node1", ... -+ nodes.append((inst_id, inst_name)) -+ except Exception: -+ pass -+ nodes.sort(key=lambda t: t[0]) -+ return nodes -+ -+ def __node_ids(self, nodes): -+ node_ids = [] -+ for inst_id, inst_name in nodes or []: -+ match = re.match(r"node(\d+)$", str(inst_name)) -+ if match: -+ node_ids.append(int(match.group(1))) -+ continue -+ try: -+ node_ids.append(int(inst_id)) -+ except (TypeError, ValueError): -+ continue -+ node_ids = sorted(set(node_ids)) -+ return node_ids or None -+ - def report(self, manager): - # Print in a stable order - group = manager["sys_info"] -@@ -296,17 +788,54 @@ class NumaStatReport(pmcc.MetricGroupPrinter): - - output_numa = ( - self.options.numa_out -- or (not self.options.mem_out and not self.options.numa_out) - ) - output_mem = self.options.mem_out -- group = manager["numastat"] -- nodes = self.__discover_nodes(group, "mem.numa.util.total") -- timestamp = self.__get_timestamp(group) -+ output_process = self.options.process_out -+ -+ if output_mem or output_numa: -+ group = manager["numastat"] -+ timestamp_group = group -+ elif output_process: -+ group = manager["process_numa"] -+ timestamp_group = group -+ else: -+ return -+ -+ timestamp = self.__get_timestamp(timestamp_group) - print("%-20s : %s"%("Timestamp", timestamp)) - if output_mem: -+ nodes = self.__discover_nodes(group, "mem.numa.util.total") - NUMAStat(group).print_mem(self.options.width, nodes, "meminfo") - if output_numa: -+ nodes = self.__discover_nodes(group, "mem.numa.util.total") - NUMAStat(group).print_numa(self.options.width, nodes, "numastat") -+ if output_process: -+ ignore_pid = None -+ if ( -+ NumaStatOption.context is not PM_CONTEXT_ARCHIVE -+ and not self.options.pmGetOptionHosts() -+ ): -+ ignore_pid = os.getpid() -+ system_nodes = None -+ try: -+ system_nodes = self.__node_ids( -+ self.__discover_nodes(manager["sys_info"], "hinv.node.online") -+ ) -+ except Exception: -+ system_nodes = None -+ if system_nodes is None: -+ try: -+ system_nodes = self.__node_ids( -+ self.__discover_nodes_all(manager["sys_info"], "mem.numa.alloc.hit") -+ ) -+ except Exception: -+ system_nodes = None -+ -+ ProcessNUMAStat(manager["process_numa"], ignore_pid).print_processes( -+ self.options.process_filters, -+ system_nodes=system_nodes, -+ width=self.options.width, -+ ) - - if ( - NumaStatOption.context is not PM_CONTEXT_ARCHIVE -@@ -323,13 +852,37 @@ if __name__ == '__main__': - raise pmapi.pmUsageErr() - NumaStatOption.context = mngr.type - -- missing = mngr.checkMissingMetrics(ALL_METRICS) -+ opts.process_filters = opts.pmGetOperands() -+ if opts.process_filters and not opts.process_out: -+ print("Process selectors require -p/--process option") -+ raise pmapi.pmUsageErr() -+ if not opts.process_filters and opts.process_out: -+ print("Provide pid or process name for -p/--process option") -+ raise pmapi.pmUsageErr() -+ -+ required_metrics = list(SYS_METRICS) -+ if opts.mem_out or opts.numa_out: -+ required_metrics.extend(ALL_METRICS) -+ if opts.process_out: -+ required_metrics.extend(PROCESS_METRICS) -+ required_metrics = list(dict.fromkeys(required_metrics)) -+ -+ missing = mngr.checkMissingMetrics(required_metrics) - if missing is not None: - sys.stderr.write('Error: not all required metrics are available\nMissing: %s\n' % (missing)) - sys.exit(1) - -- mngr["numastat"] = ALL_METRICS -- mngr["sys_info"] = SYS_METRICS -+ sys_info_metrics = list(SYS_METRICS) -+ if mngr.checkMissingMetrics(["hinv.node.online"]) is None: -+ sys_info_metrics.append("hinv.node.online") -+ if mngr.checkMissingMetrics(["mem.numa.alloc.hit"]) is None: -+ sys_info_metrics.append("mem.numa.alloc.hit") -+ -+ if opts.mem_out or opts.numa_out: -+ mngr["numastat"] = ALL_METRICS -+ if opts.process_out: -+ mngr["process_numa"] = PROCESS_METRICS -+ mngr["sys_info"] = sys_info_metrics - mngr.printer = NumaStatReport(opts) - sts = mngr.run() - sys.exit(sts) -diff --git a/src/pmdas/linux_proc/clusters.h b/src/pmdas/linux_proc/clusters.h -index edfd5ad..547c195 100644 ---- a/src/pmdas/linux_proc/clusters.h -+++ b/src/pmdas/linux_proc/clusters.h -@@ -72,8 +72,9 @@ - #define CLUSTER_CGROUP2_IRQ_PRESSURE 76 - #define CLUSTER_PID_FDINFO 77 /* /proc//fdinfo */ - #define CLUSTER_HOTPROC_PID_FDINFO 78 /* /proc//fdinfo */ -+#define CLUSTER_PID_NUMA_MAPS 79 /* /proc//numa_maps */ - - #define MIN_CLUSTER 8 /* first cluster number we use here */ --#define MAX_CLUSTER 79 /* one more than highest cluster number used */ -+#define MAX_CLUSTER 80 /* one more than highest cluster number used */ - - #endif /* _CLUSTERS_H */ -diff --git a/src/pmdas/linux_proc/help b/src/pmdas/linux_proc/help -index 79824bb..c1b2198 100644 ---- a/src/pmdas/linux_proc/help -+++ b/src/pmdas/linux_proc/help -@@ -75,6 +75,19 @@ kernel threads - @ proc.runq.kernel number of kernel threads - Instantaneous number of processes with virtual size of zero (kernel threads) - -+@ proc.numa_maps.hugepage per-node hugepage mapped memory in MB (/proc//numa_maps) -+Hugepage map values are reported as comma-separated node/value pairs, e.g. -+node0:0.00,node1:4.00 where each value is in megabytes. -+@ proc.numa_maps.heap per-node heap mapped memory in MB (/proc//numa_maps) -+Heap map values are reported as comma-separated node/value pairs, e.g. -+node0:0.00,node1:4.00 where each value is in megabytes. -+@ proc.numa_maps.stack per-node stack mapped memory in MB (/proc//numa_maps) -+Stack map values are reported as comma-separated node/value pairs, e.g. -+node0:0.00,node1:4.00 where each value is in megabytes. -+@ proc.numa_maps.private per-node private mapped memory in MB (/proc//numa_maps) -+Private map values are reported as comma-separated node/value pairs, e.g. -+node0:0.00,node1:4.00 where each value is in megabytes. -+ - @ proc.control.all.threads process indom includes threads - If set to one, the process instance domain as reported by pmdaproc - contains all threads as well as the processes that started them. -diff --git a/src/pmdas/linux_proc/indom.h b/src/pmdas/linux_proc/indom.h -index abe2596..c5c00a6 100644 ---- a/src/pmdas/linux_proc/indom.h -+++ b/src/pmdas/linux_proc/indom.h -@@ -62,6 +62,7 @@ extern FILE *proc_statsfile(const char *, char *, int); - - /* Generic globals setup during PMDA startup */ - extern size_t _pm_system_pagesize; -+extern size_t _pm_system_hugepagesize; - extern long _pm_hertz; - - /* -diff --git a/src/pmdas/linux_proc/pmda.c b/src/pmdas/linux_proc/pmda.c -index 0c8eb16..f81e348 100644 ---- a/src/pmdas/linux_proc/pmda.c -+++ b/src/pmdas/linux_proc/pmda.c -@@ -66,6 +66,7 @@ static int autogroup = -1; /* =1 autogroup enabled */ - static unsigned int threads; /* control.all.threads */ - static char * cgroups; /* control.all.cgroups */ - size_t _pm_system_pagesize; -+size_t _pm_system_hugepagesize; - long _pm_hertz; - - /* -@@ -1395,6 +1396,23 @@ static pmdaMetric metrictab[] = { - { NULL, { PMDA_PMID(CLUSTER_PID_FDINFO,12), PM_TYPE_U64, PROC_INDOM, - PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}}, - -+/* -+* numa_maps cluster -+*/ -+ -+/* proc.numa_maps.hugepage */ -+ { NULL, { PMDA_PMID(CLUSTER_PID_NUMA_MAPS,0), PM_TYPE_STRING, PROC_INDOM, -+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}}, -+/* proc.numa_maps.heap */ -+ { NULL, { PMDA_PMID(CLUSTER_PID_NUMA_MAPS,1), PM_TYPE_STRING, PROC_INDOM, -+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}}, -+/* proc.numa_maps.stack */ -+ { NULL, { PMDA_PMID(CLUSTER_PID_NUMA_MAPS,2), PM_TYPE_STRING, PROC_INDOM, -+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}}, -+/* proc.numa_maps.private */ -+ { NULL, { PMDA_PMID(CLUSTER_PID_NUMA_MAPS,3), PM_TYPE_STRING, PROC_INDOM, -+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}}, -+ - }; - - pmInDom -@@ -3534,6 +3552,34 @@ proc_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) - return PM_ERR_PMID; - } - break; -+ case CLUSTER_PID_NUMA_MAPS: -+ if (!have_access) -+ return PM_ERR_PERMISSION; -+ if ((entry = fetch_proc_pid_numa_maps(inst, active_proc_pid, &sts)) == NULL) -+ return sts; -+ if (!(entry->success & PROC_PID_FLAG_NUMA_MAPS)) -+ return 0; -+ -+ switch(item) { -+ case 0: /* proc.numa_maps.hugepage */ -+ atom->cp = proc_strings_lookup(entry->numa_maps.huge_id); -+ break; -+ -+ case 1: /* proc.numa_maps.heap */ -+ atom->cp = proc_strings_lookup(entry->numa_maps.heap_id); -+ break; -+ -+ case 2: /* proc.numa_maps.stack */ -+ atom->cp = proc_strings_lookup(entry->numa_maps.stack_id); -+ break; -+ -+ case 3: /* proc.numa_maps.private */ -+ atom->cp = proc_strings_lookup(entry->numa_maps.private_id); -+ break; -+ default: /* unknown cluster */ -+ return PM_ERR_PMID; -+ } -+ break; - default: /* unknown cluster */ - return PM_ERR_PMID; - } -@@ -3896,6 +3942,30 @@ proc_gidname_lookup(int gid) - return ""; - } - -+static size_t -+proc_hugepagesize(void) -+{ -+ unsigned long huge_page_size_kb = 0; -+ char buf[128]; -+ FILE *fs; -+ -+ if ((fs = fopen("/proc/meminfo", "r")) == NULL) -+ return 0; -+ -+ while (fgets(buf, sizeof(buf), fs)) { -+ if (sscanf(buf, "Hugepagesize: %lu kB", &huge_page_size_kb) == 1) -+ break; -+ } -+ fclose(fs); -+ -+ /* Guard against overflow (CID 502014) */ -+ if (huge_page_size_kb > ULONG_MAX / 1024UL) { -+ return 0; -+ } -+ -+ return huge_page_size_kb * 1024UL; -+} -+ - /* - * Initialise the agent (both daemon and DSO). - */ -@@ -3917,6 +3987,10 @@ proc_init(pmdaInterface *dp) - _pm_system_pagesize = atoi(envpath); - else - _pm_system_pagesize = getpagesize(); -+ if ((envpath = getenv("PROC_HUGEPAGESIZE")) != NULL) -+ _pm_system_hugepagesize = atoi(envpath); -+ else -+ _pm_system_hugepagesize = proc_hugepagesize(); - if ((envpath = getenv("PROC_STATSPATH")) != NULL) - proc_statspath = envpath; - if ((envpath = getenv("PROC_THREADS")) != NULL) -diff --git a/src/pmdas/linux_proc/pmdaproc.1 b/src/pmdas/linux_proc/pmdaproc.1 -index 602b3b6..565120f 100644 ---- a/src/pmdas/linux_proc/pmdaproc.1 -+++ b/src/pmdas/linux_proc/pmdaproc.1 -@@ -34,6 +34,10 @@ The - PMDA exports metrics that measure the memory, processor and - other resource use of each process, as well as summary information - collated across all of the running processes. -+This includes per-process NUMA placement summaries from -+.I /proc//numa_maps -+via the -+.BR proc.numa_maps.*\ metrics. - The PMDA uses credentials passed from the - .BR PMAPI (3) - monitoring tool identifying the user requesting the information, -diff --git a/src/pmdas/linux_proc/proc_pid.c b/src/pmdas/linux_proc/proc_pid.c -index 80a678d..2706f98 100644 ---- a/src/pmdas/linux_proc/proc_pid.c -+++ b/src/pmdas/linux_proc/proc_pid.c -@@ -2530,3 +2530,272 @@ fetch_proc_pid_fdinfo(int id, proc_pid_t *proc_pid, int *sts) - - return (*sts < 0) ? NULL : ep; - } -+ -+#define PROCESS_HUGE_INDEX 0 -+#define PROCESS_HEAP_INDEX 1 -+#define PROCESS_STACK_INDEX 2 -+#define PROCESS_PRIVATE_INDEX 3 -+#define PROCESS_CATEGORY_COUNT 4 -+ -+#define MEGABYTE (1024.0 * 1024.0) -+ -+static const char *process_mem_tokens[] = { -+ "huge", -+ "heap", -+ "stack", -+}; -+ -+typedef struct { -+ char *s; -+ size_t len; -+ size_t cap; -+} strbuf_t; -+ -+typedef struct { -+ int node; -+ double values[PROCESS_CATEGORY_COUNT]; -+} numa_node_totals_t; -+ -+static int -+append_numa_maps_node(strbuf_t *b, int node_num, double value_mb) -+{ -+ char tmp[64]; -+ char *newptr; -+ size_t needed, newcap; -+ int n; -+ -+ n = pmsprintf(tmp, sizeof(tmp), "%snode%d:%.2f", -+ b->len > 0 ? "," : "", node_num, value_mb); -+ if (n < 0 || n >= (int)sizeof(tmp)) -+ return -E2BIG; -+ -+ if (b->s == NULL) { -+ b->cap = 128; -+ b->s = (char *)malloc(b->cap); -+ if (b->s == NULL) -+ return -ENOMEM; -+ b->len = 0; -+ b->s[0] = '\0'; -+ } -+ -+ needed = b->len + (size_t)n + 1; -+ if (needed > b->cap) { -+ newcap = b->cap; -+ while (newcap < needed) -+ newcap *= 2; -+ -+ newptr = (char *)realloc(b->s, newcap); -+ if (newptr == NULL) -+ return -ENOMEM; -+ -+ b->s = newptr; -+ b->cap = newcap; -+ } -+ -+ memcpy(b->s + b->len, tmp, (size_t)n); -+ b->len += (size_t)n; -+ b->s[b->len] = '\0'; -+ return 0; -+} -+ -+static int -+find_or_add_numa_node(numa_node_totals_t **nodes, int *node_count, int node) -+{ -+ numa_node_totals_t *new_nodes; -+ int i; -+ -+ for (i = 0; i < *node_count; i++) { -+ if ((*nodes)[i].node == node) -+ return i; -+ } -+ -+ new_nodes = (numa_node_totals_t *)realloc(*nodes, -+ (*node_count + 1) * sizeof(*new_nodes)); -+ if (new_nodes == NULL) -+ return -ENOMEM; -+ -+ *nodes = new_nodes; -+ (*nodes)[*node_count].node = node; -+ memset((*nodes)[*node_count].values, 0, sizeof((*nodes)[*node_count].values)); -+ (*node_count)++; -+ return *node_count - 1; -+} -+ -+static int -+compare_numa_node_totals(const void *a, const void *b) -+{ -+ const numa_node_totals_t *node_a = (const numa_node_totals_t *)a; -+ const numa_node_totals_t *node_b = (const numa_node_totals_t *)b; -+ -+ return node_a->node - node_b->node; -+} -+ -+static int -+numa_maps_category(const char *line) -+{ -+ char *copy, *tok, *saveptr = NULL; -+ int i; -+ -+ if ((copy = strdup(line)) == NULL) -+ return PROCESS_PRIVATE_INDEX; -+ -+ tok = strtok_r(copy, " \t", &saveptr); -+ while (tok != NULL) { -+ for (i = PROCESS_HUGE_INDEX; i <= PROCESS_STACK_INDEX; i++) { -+ if (strcmp(tok, process_mem_tokens[i]) == 0) { -+ free(copy); -+ return i; -+ } -+ } -+ tok = strtok_r(NULL, " \t", &saveptr); -+ } -+ free(copy); -+ return PROCESS_PRIVATE_INDEX; -+} -+ -+static int -+parse_proc_numa_maps(proc_pid_entry_t *ep, size_t buflen, char *buf) -+{ -+ strbuf_t huge = {0}, heap = {0}, stack = {0}, priv = {0}; -+ numa_node_totals_t *nodes = NULL; -+ double page_size_bytes, huge_page_size_bytes; -+ int node_count = 0; -+ int sts = 0; -+ char *cur = buf; -+ char *end; -+ -+ if (buf == NULL || buflen == 0) -+ return 0; -+ /* -+ * Ensure the proc buffer is NUL-terminated so the string routines below -+ * cannot read past the end. read_proc_entry() allocates len+1 bytes and -+ * uses buflen (len) for the bytes-read value passed here. -+ */ -+ buf[buflen] = '\0'; -+ end = buf + buflen; -+ -+ page_size_bytes = (double)_pm_system_pagesize; -+ if (page_size_bytes <= 0.0) -+ page_size_bytes = 4096.0; -+ -+ huge_page_size_bytes = (double)_pm_system_hugepagesize; -+ if (huge_page_size_bytes <= 0.0) -+ huge_page_size_bytes = 2048 * 1024; // Taking a common hugepagesize value as a fallback -+ -+ while (cur < end && *cur) { -+ char *nl = memchr(cur, '\n', (size_t)(end - cur)); -+ char *tok, *saveptr = NULL; -+ int category; -+ -+ if (nl != NULL) -+ *nl = '\0'; -+ -+ category = numa_maps_category(cur); -+ tok = strtok_r(cur, " \t", &saveptr); -+ while (tok != NULL) { -+ int node, index; -+ double pages, bytes; -+ -+ if (tok[0] == 'N' && sscanf(tok, "N%d=%lf", &node, &pages) == 2) { -+ index = find_or_add_numa_node(&nodes, &node_count, node); -+ if (index < 0) { -+ sts = index; -+ goto cleanup; -+ } -+ -+ if (category == PROCESS_HUGE_INDEX) -+ bytes = pages * huge_page_size_bytes; -+ else -+ bytes = pages * page_size_bytes; -+ -+ nodes[index].values[category] += bytes / MEGABYTE; -+ } -+ tok = strtok_r(NULL, " \t", &saveptr); -+ } -+ -+ if (nl == NULL) -+ break; -+ *nl = '\n'; -+ cur = nl + 1; -+ } -+ -+ if (node_count > 1) -+ qsort(nodes, node_count, sizeof(*nodes), compare_numa_node_totals); -+ -+ for (int i = 0; i < node_count; i++) { -+ sts = append_numa_maps_node(&huge, nodes[i].node, -+ nodes[i].values[PROCESS_HUGE_INDEX]); -+ if (sts < 0) -+ goto cleanup; -+ sts = append_numa_maps_node(&heap, nodes[i].node, -+ nodes[i].values[PROCESS_HEAP_INDEX]); -+ if (sts < 0) -+ goto cleanup; -+ sts = append_numa_maps_node(&stack, nodes[i].node, -+ nodes[i].values[PROCESS_STACK_INDEX]); -+ if (sts < 0) -+ goto cleanup; -+ sts = append_numa_maps_node(&priv, nodes[i].node, -+ nodes[i].values[PROCESS_PRIVATE_INDEX]); -+ if (sts < 0) -+ goto cleanup; -+ } -+ -+ if (huge.s != NULL) -+ ep->numa_maps.huge_id = proc_strings_insert(huge.s); -+ if (heap.s != NULL) -+ ep->numa_maps.heap_id = proc_strings_insert(heap.s); -+ if (stack.s != NULL) -+ ep->numa_maps.stack_id = proc_strings_insert(stack.s); -+ if (priv.s != NULL) -+ ep->numa_maps.private_id = proc_strings_insert(priv.s); -+ -+cleanup: -+ free(nodes); -+ free(huge.s); -+ free(heap.s); -+ free(stack.s); -+ free(priv.s); -+ return sts; -+} -+ -+static int -+refresh_proc_pid_numa_maps(proc_pid_entry_t *ep) -+{ -+ int fd, sts; -+ -+ if (ep->success & PROC_PID_FLAG_NUMA_MAPS) -+ return 0; -+ if ((fd = proc_open("numa_maps", ep)) < 0) -+ return maperr(); -+ ep->numa_maps.huge_id = -1; -+ ep->numa_maps.heap_id = -1; -+ ep->numa_maps.stack_id = -1; -+ ep->numa_maps.private_id = -1; -+ if ((sts = read_proc_entry(fd, &procbuflen, &procbuf)) >= 0) { -+ sts = parse_proc_numa_maps(ep, procbuflen, procbuf); -+ if (sts >= 0) -+ ep->success |= PROC_PID_FLAG_NUMA_MAPS; -+ } -+ close(fd); -+ return sts; -+} -+ -+/* -+ * fetch data from /proc//numa_maps entries for pid -+ */ -+proc_pid_entry_t * -+fetch_proc_pid_numa_maps(int id, proc_pid_t *proc_pid, int *sts) -+{ -+ proc_pid_entry_t *ep = proc_pid_entry_lookup(id, proc_pid); -+ -+ *sts = 0; -+ if (!ep) -+ return NULL; -+ -+ if (!(ep->fetched & PROC_PID_FLAG_NUMA_MAPS)) { -+ *sts = refresh_proc_pid_numa_maps(ep); -+ ep->fetched |= PROC_PID_FLAG_NUMA_MAPS; -+ } -+ return (*sts < 0) ? NULL : ep; -+} -diff --git a/src/pmdas/linux_proc/proc_pid.h b/src/pmdas/linux_proc/proc_pid.h -index f5762aa..145ec8d 100644 ---- a/src/pmdas/linux_proc/proc_pid.h -+++ b/src/pmdas/linux_proc/proc_pid.h -@@ -202,6 +202,16 @@ typedef struct { - uint64_t locked; - } proc_pid_smaps_t; - -+/* -+ * metrics in /proc//numa_maps -+ */ -+typedef struct { -+ int huge_id; -+ int heap_id; -+ int stack_id; -+ int private_id; -+} proc_pid_numa_maps_t; -+ - /* - * metrics in /proc//fdinfo/ - */ -@@ -245,6 +255,7 @@ enum { - PROC_PID_FLAG_EXE = 1<<15, - PROC_PID_FLAG_AUTOGROUP = 1<<16, - PROC_PID_FLAG_FDINFO = 1<<17, -+ PROC_PID_FLAG_NUMA_MAPS = 1<<18, - }; - - typedef struct { -@@ -279,6 +290,9 @@ typedef struct { - /* /proc//smaps_rollup cluster */ - proc_pid_smaps_t smaps; - -+ /* /proc//numa_maps cluster */ -+ proc_pid_numa_maps_t numa_maps; -+ - /* /proc//maps cluster */ - size_t maps_buflen; - -@@ -330,6 +344,9 @@ typedef struct { - /* lookup a proc hash entry */ - extern proc_pid_entry_t *proc_pid_entry_lookup(int, proc_pid_t *); - -+/* fetch a proc//numa_maps entry for pid */ -+extern proc_pid_entry_t *fetch_proc_pid_numa_maps(int, proc_pid_t *, int *); -+ - /* refresh the proc indom, reset all "fetched" flags */ - extern int refresh_proc_pid(proc_pid_t *, proc_runq_t *, int, const char *, const char *, int); - -diff --git a/src/pmdas/linux_proc/root_proc b/src/pmdas/linux_proc/root_proc -index 3e5b50d..16e75dd 100644 ---- a/src/pmdas/linux_proc/root_proc -+++ b/src/pmdas/linux_proc/root_proc -@@ -454,6 +454,14 @@ proc { - smaps PROC:*:* - autogroup PROC:*:* - control -+ numa_maps -+} -+ -+proc.numa_maps { -+ hugepage PROC:79:0 -+ heap PROC:79:1 -+ stack PROC:79:2 -+ private PROC:79:3 - } - - hotproc { --- -2.43.7 - diff --git a/SPECS/pcp.spec b/SPECS/pcp.spec index fcb4db4..580903d 100644 --- a/SPECS/pcp.spec +++ b/SPECS/pcp.spec @@ -1,6 +1,6 @@ Name: pcp Version: 6.3.7 -Release: 8.0.1%{?dist}.4 +Release: 8%{?dist}.4 Summary: System-level performance monitoring and performance management License: GPL-2.0-or-later AND LGPL-2.1-or-later AND CC-BY-3.0 URL: https://pcp.io @@ -37,28 +37,6 @@ Patch19: pcp-6.3.7-CVE-2026-16527.patch # https://github.com/performancecopilot/pcp/commit/c5cbeceb7d3c2af357c04065cdd911efdc270de0 Patch20: pcp-6.3.7-CVE-2026-16524.patch -# Oracle Patches -# rocestat-pmda -Patch1001: 1001-Merge-branch-mohith-kumar-thummaluru-rocestat-pmda.patch -# pcp-rocestat client -Patch1002: 1002-Merge-branch-pcp-rocestat-of-https-github.com-mohith.patch -Patch1003: 1003-pcp-meminfo-additional-metrics-added-for-mem.util-to.patch -Patch1004: 1004-meminfo-added-kreclaimable-and-hugtlb-metrics.patch -Patch1005: 1005-fix-mpstat-showing-inconsistent-values.patch -Patch1006: 1006-fix-broken-pipe-error-iostat.patch -Patch1007: 1007-add-missing-numastat-metrics.patch -Patch1008: 1008-add-numastat-support-for-mn-options.patch -Patch1009: 1009-Fixes-higepagesize-metric-value-from-bytes-to-KB.patch -Patch1010: 1010-xz-default-compression-changed-to-level-3.patch -Patch1011: 1011-orabug38724866-fix-nfsclient-per-op-parsing.patch -Patch1012: 1012-orabug38817068-Introduce-PCP-implementation-of-nfsiostat.patch -Patch1013: 1013-pmlogger_janitor-fix-not-to-terminate-unauthorized-p.patch -Patch1014: 1014-pcp-ps-implement-sort-option-to-allow-sorting-by-cpu.patch -Patch1015: 1015-pmlogger_daily-d-disk-option-for-archive-space-limit.patch -Patch1016: 1016-pcp-system-tools-restore-backward-compatibility-with.patch -Patch1017: 1017-orabug39068870-adds-interval-option-in-nfsiostat.patch -Patch1018: 1018-orabug39096683-introduces-numa-maps-metrics-and-adds-numastat-process-option.patch - %if 0%{?fedora} >= 40 || 0%{?rhel} >= 10 ExcludeArch: %{ix86} %endif @@ -660,7 +638,7 @@ Requires: pcp-export-pcp2spark pcp-export-pcp2xml pcp-export-pcp2zabbix Requires: pcp-pmda-gluster pcp-pmda-zswap pcp-pmda-unbound pcp-pmda-mic Requires: pcp-pmda-libvirt pcp-pmda-lio pcp-pmda-openmetrics pcp-pmda-haproxy Requires: pcp-pmda-lmsensors pcp-pmda-netcheck pcp-pmda-rabbitmq pcp-pmda-uwsgi -Requires: pcp-pmda-openvswitch pcp-pmda-rocestat +Requires: pcp-pmda-openvswitch %endif %if !%{disable_mongodb} Requires: pcp-pmda-mongodb @@ -1692,24 +1670,6 @@ This package contains the PCP Performance Metrics Domain Agent (PMDA) for collecting metrics about the gluster filesystem. # end pcp-pmda-gluster -# -# pcp-pmda-rocestat -# -%package pmda-rocestat -License: GPLv2+ -Summary: Performance Co-Pilot (PCP) metrics for the reporting Nvidia RoCE device metrics -URL: https://pcp.io -Requires: pcp = %{version}-%{release} pcp-libs = %{version}-%{release} -%if !%{disable_python3} -Requires: python3-pcp -%else -Requires: %{__python2}-pcp -%endif -%description pmda-rocestat -This package contains the PCP Performance Metrics Domain Agent (PMDA) for -collecting metrics about the Nvidia RoCE device metrics. -# end pcp-pmda-rocestat - # # pcp-pmda-nfsclient # @@ -2791,7 +2751,6 @@ basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-fil basic_manifest | keep '(etc/pcp|pmdas)/rabbitmq(/|$)' >pcp-pmda-rabbitmq-files basic_manifest | keep '(etc/pcp|pmdas)/redis(/|$)' >pcp-pmda-redis-files basic_manifest | keep '(etc/pcp|pmdas)/resctrl(/|$)|sys-fs-resctrl' >pcp-pmda-resctrl-files -basic_manifest | keep '(etc/pcp|pmdas)/rocestat(/|$)' >pcp-pmda-rocestat-files basic_manifest | keep '(etc/pcp|pmdas)/roomtemp(/|$)' >pcp-pmda-roomtemp-files basic_manifest | keep '(etc/pcp|pmdas)/rpm(/|$)' >pcp-pmda-rpm-files basic_manifest | keep '(etc/pcp|pmdas)/rsyslog(/|$)' >pcp-pmda-rsyslog-files @@ -2830,7 +2789,7 @@ for pmda_package in \ nutcracker nvidia \ openmetrics openvswitch oracle \ pdns perfevent podman postfix postgresql \ - rabbitmq redis resctrl rocestat roomtemp rpm rsyslog \ + rabbitmq redis resctrl roomtemp rpm rsyslog \ samba sendmail shping slurm smart snmp \ sockets statsd summary systemd \ unbound uwsgi \ @@ -3176,9 +3135,6 @@ exit 0 %preun pmda-gluster %{pmda_remove "$1" "gluster"} -%preun pmda-rocestat -%{pmda_remove "$1" "rocestat"} - %preun pmda-zswap %{pmda_remove "$1" "zswap"} @@ -3336,26 +3292,6 @@ done %endif %endif -%post pmda-rocestat -PCP_PMDAS_DIR=%{_pmdasdir} -PCP_SYSCONFIG_DIR=%{_sysconfdir}/sysconfig -PCP_PMCDCONF_PATH=%{_confdir}/pmcd/pmcd.conf - -# Auto-install rocestat PMDA if not already in pmcd.conf -if ! grep -q "rocestat/pmdarocestat" "$PCP_PMCDCONF_PATH"; then - if [ ! -d /sys/class/infiniband ]; then - if ! lsmod | grep -q '^ib_core'; then - echo "Skipping install for PMDA Rocestat (IB kernel modules are not loaded)" >&2 - else - echo "Skipping install for PMDA Rocestat (No IB devices detected)" >&2 - fi - else - cd "$PCP_PMDAS_DIR/rocestat" && \ - chmod +x Install && \ - ./Install < /dev/null - fi -fi - %post PCP_PMNS_DIR=%{_pmnsdir} PCP_LOG_DIR=%{_logsdir} @@ -3552,8 +3488,6 @@ fi %files pmda-gluster -f pcp-pmda-gluster-files.rpm -%files pmda-rocestat -f pcp-pmda-rocestat-files.rpm - %files pmda-zswap -f pcp-pmda-zswap-files.rpm %files pmda-unbound -f pcp-pmda-unbound-files.rpm @@ -3715,24 +3649,6 @@ fi %files zeroconf -f pcp-zeroconf-files.rpm %changelog -* Mon Aug 17 2026 EL Errata - 6.3.7-8.0.1.el9_8.4 -- Adds interval option support in PCP nfsiostat tool [Orabug: 39068870] -- Introduces proc.numa_maps metrics in linux_proc PMDA [Orabug: 39096683] -- adds numastat process option in the tool -- Added support for size based cleanup for pcp archives [Orabug: 38757778] -- Implement sorting option in pcp ps based on %cpu, %mem [Orabug: 38719615] -- Fixed pmlogger incorrectly attempts to terminate -- unauthorized process [Orabug: 38598244] -- Merges new PCP nfsiostat parser in OL [Orabug: 38817068] -- pmdanfsclient: fix regex to correctly parse NFS op stats [Orabug: 38724866] -- pmda/rocestat: skip installation when IB is absent [Orabug: 38742679] -- pmlogger_daily default xz compression changed to level 3 [Orabug: 38674828] -- Backports various pcp bugs and enhancements [Orabug: 38526328] -- [Orabug: 38526351] [Orabug: 38526381] [Orabug: 38526381] -- [Orabug: 38526450] [Orabug: 38527066] -- Add support for rocestat pmda [Orabug: 38109263] -- Add support for rocestat client [Orabug: 38109331] - * Thu Jul 30 2026 RHEL Packaging Agent - 6.3.7-8.4 - Fix CVE-2026-16524 command injection in linux_sockets PMDA (RHEL-213668)