Revert OL modifications

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-20 04:17:29 -04:00
parent 8542ed072d
commit e87b44ee0d
19 changed files with 3 additions and 8731 deletions

View File

@ -1,56 +0,0 @@
From e1c81c356a176fa244b3b32a2b98362d75fd2842 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
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 <sagar.sagar@oracle.com>
Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2180/commits/43025e54a97cb8aa2a3cc955a9fb1d0cb3f39cf4
Orabug: 38526351
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,170 +0,0 @@
From 975ec9e98e8ccbfbb2bfe5652920b3b3cd5208fe Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
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 <sagar.sagar@oracle.com>
Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2180/commits/81393407ec8d31642f637ab25eb3f9c082dab821
Orabug: 38526351
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,267 +0,0 @@
From a02f695771e7534a90391fe243badb47b7b2371d Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
Orabug: 38526328
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/a02f695771e7534a90391fe243badb47b7b2371d
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,29 +0,0 @@
From 64cfffa8777d349d58aac07684abe0d834695e22 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
Orabug: 38526381
Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2200/commits/64cfffa8777d349d58aac07684abe0d834695e22
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,220 +0,0 @@
From 8e6a5ac18c06f813bad74494134ad5ac2202684e Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
Co-authored-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
Orabug: 38526450
Cherry-pick-commit: https://github.com/orasagar/pcp/commit/8e6a5ac18c06f813bad74494134ad5ac2202684e
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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 {

View File

@ -1,495 +0,0 @@
From b09b0435b4fafd56a5930de2adeeea4c7f45ea57 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
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 <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,45 +0,0 @@
From d2e92db5d6ee2a07e52ff72da471976aafaeb7ec Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
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 <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,33 +0,0 @@
From 9703b9c63e27b541e32de56e1d482f85e4e548f4 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
---
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 >/dev/null 2>&1
+ if xz -3 --block-size=10MiB </dev/null >/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

View File

@ -1,38 +0,0 @@
From c80dc758eb0eb27fafa0b594d6c2aa1f4c3803fa Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
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 <sourav.ss.sharma@oracle.com>
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/85671f8874d7d4b4e57eb45bfe295de92b95415c
Orabug: 38724866
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,575 +0,0 @@
From ca8fbd2a827e3e7caa72331e003f93523c9f5241 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
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 <sourav.ss.sharma@oracle.com>
---
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

View File

@ -1,217 +0,0 @@
From e138ce29c6220377728b263b8c8cd8b0794cfcc2 Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
---
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

View File

@ -1,486 +0,0 @@
From e02b8ba31512a3e4448330b28b734f010b157dce Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
---
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/<hostname>
+and
+.IR $PCP_REMOTE_ARCHIVE_DIR/<hostname>
+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 ...
+ # <directory>|<archive>
+ #
+ 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

View File

@ -1,328 +0,0 @@
From cc127dfca5ab71fcbd34ed27be8844e3aa2be0c3 Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
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 <sagar.sagar@oracle.com>
---
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

View File

@ -1,364 +0,0 @@
From c79976edb317272dcc8ae9352778d5d335763f10 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Wed, 25 Feb 2026 12:45:43 +0000
Subject: [PATCH] Add interval and count support for nfsiostat tool
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/02d9b5a7b3c2ea16de2665c1f8cd6b81843fee15
Orabug: 39068870
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
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

View File

@ -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 <el-errata_ww@oracle.com> - 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 <redhat-ymir-agent@redhat.com> - 6.3.7-8.4
- Fix CVE-2026-16524 command injection in linux_sockets PMDA (RHEL-213668)