import Oracle_OSS pcp-6.3.7-8.0.1.el9_8.4
This commit is contained in:
parent
98ad073581
commit
8542ed072d
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,56 @@
|
||||
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
|
||||
|
||||
170
SOURCES/1004-meminfo-added-kreclaimable-and-hugtlb-metrics.patch
Normal file
170
SOURCES/1004-meminfo-added-kreclaimable-and-hugtlb-metrics.patch
Normal file
@ -0,0 +1,170 @@
|
||||
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
|
||||
|
||||
267
SOURCES/1005-fix-mpstat-showing-inconsistent-values.patch
Normal file
267
SOURCES/1005-fix-mpstat-showing-inconsistent-values.patch
Normal file
@ -0,0 +1,267 @@
|
||||
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
|
||||
29
SOURCES/1006-fix-broken-pipe-error-iostat.patch
Normal file
29
SOURCES/1006-fix-broken-pipe-error-iostat.patch
Normal file
@ -0,0 +1,29 @@
|
||||
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
|
||||
220
SOURCES/1007-add-missing-numastat-metrics.patch
Normal file
220
SOURCES/1007-add-missing-numastat-metrics.patch
Normal file
@ -0,0 +1,220 @@
|
||||
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 {
|
||||
495
SOURCES/1008-add-numastat-support-for-mn-options.patch
Normal file
495
SOURCES/1008-add-numastat-support-for-mn-options.patch
Normal file
@ -0,0 +1,495 @@
|
||||
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
|
||||
@ -0,0 +1,45 @@
|
||||
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
|
||||
33
SOURCES/1010-xz-default-compression-changed-to-level-3.patch
Normal file
33
SOURCES/1010-xz-default-compression-changed-to-level-3.patch
Normal file
@ -0,0 +1,33 @@
|
||||
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
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
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
|
||||
|
||||
@ -0,0 +1,575 @@
|
||||
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
|
||||
|
||||
@ -0,0 +1,217 @@
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,486 @@
|
||||
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
|
||||
|
||||
@ -0,0 +1,328 @@
|
||||
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
|
||||
|
||||
@ -0,0 +1,364 @@
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
269
SOURCES/pcp-6.3.7-CVE-2026-16524.patch
Normal file
269
SOURCES/pcp-6.3.7-CVE-2026-16524.patch
Normal file
@ -0,0 +1,269 @@
|
||||
From 4412004b8bdc2d9e63a4242a30346f41bd784311 Mon Sep 17 00:00:00 2001
|
||||
From: Nathan Scott <nathans@redhat.com>
|
||||
Date: Thu, 2 Jul 2026 15:41:11 +1000
|
||||
Subject: [PATCH] linux_sockets pmda: fix command injection via
|
||||
network.persocket.filter (CWE-78)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The sockets_check_filter() validation helper returns 1 for safe input
|
||||
and 0 for unsafe input. The guard in sockets_store() tested
|
||||
if (sockets_check_filter(av.cp)) — rejecting safe input and accepting
|
||||
malicious input containing shell metacharacters. The accepted filter
|
||||
was later passed to popen() via shell interpretation, enabling arbitrary
|
||||
command execution as the PMDA process user.
|
||||
|
||||
Fix:
|
||||
- Invert the guard: if (!sockets_check_filter(av.cp))
|
||||
- Replace popen()/pclose() in ss_open_stream() with the libpcp
|
||||
__pmProcessAddArg()/__pmProcessPipe()/__pmProcessPipeClose() API
|
||||
which uses execvp() internally, eliminating shell interpretation
|
||||
of the filter string entirely
|
||||
- Add qa/2101 verifying that valid filters are accepted and shell
|
||||
metacharacters (semicolons, backticks, pipes) are rejected
|
||||
|
||||
Reported-by: Francisco Alisson Bezerra, TIM Security Red Team
|
||||
Reported-by: Lucas Gabriel Alves, TIM Security Red Team
|
||||
Reported-by: Massimiliano Brolli, TIM Security Red Team
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||||
---
|
||||
qa/2101 | 70 +++++++++++++++++++++++++++++
|
||||
qa/2101.out | 15 +++++++
|
||||
qa/group | 1 +
|
||||
src/pmdas/linux_sockets/pmda.c | 8 ++--
|
||||
src/pmdas/linux_sockets/ss_stream.c | 53 ++++++++++++++++++----
|
||||
5 files changed, 133 insertions(+), 14 deletions(-)
|
||||
create mode 100755 qa/2101
|
||||
create mode 100644 qa/2101.out
|
||||
|
||||
diff --git a/qa/2101 b/qa/2101
|
||||
new file mode 100755
|
||||
index 000000000..733b7f70c
|
||||
--- /dev/null
|
||||
+++ b/qa/2101
|
||||
@@ -0,0 +1,70 @@
|
||||
+#!/bin/sh
|
||||
+# PCP QA Test No. 2101
|
||||
+# Verify linux_sockets PMDA filter validation rejects shell metacharacters
|
||||
+# and accepts valid filter expressions (CWE-78 fix verification)
|
||||
+#
|
||||
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
|
||||
+#
|
||||
+
|
||||
+seq=`basename $0`
|
||||
+echo "QA output created by $seq"
|
||||
+
|
||||
+# get standard environment, filters and checks
|
||||
+. ./common.product
|
||||
+. ./common.filter
|
||||
+. ./common.check
|
||||
+
|
||||
+[ $PCP_PLATFORM = linux ] || _notrun "Linux-specific sockets testing"
|
||||
+[ -f $PCP_PMDAS_DIR/sockets/pmdasockets ] || _notrun "sockets PMDA not installed"
|
||||
+
|
||||
+_cleanup()
|
||||
+{
|
||||
+ _cleanup_pmda sockets
|
||||
+ cd $here
|
||||
+ $sudo rm -rf $tmp $tmp.*
|
||||
+}
|
||||
+
|
||||
+status=0 # success is the default!
|
||||
+trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
+
|
||||
+_prepare_pmda sockets
|
||||
+_stop_auto_restart pmcd
|
||||
+
|
||||
+# install the sockets PMDA
|
||||
+cd $PCP_PMDAS_DIR/sockets
|
||||
+$sudo ./Remove >/dev/null 2>&1
|
||||
+$sudo ./Install </dev/null >$tmp.out 2>&1
|
||||
+cat $tmp.out >>$seq_full
|
||||
+
|
||||
+# check the PMDA is alive
|
||||
+pmprobe -v network.persocket.filter >$tmp.probe 2>&1
|
||||
+grep -q 'No PMCD agent' $tmp.probe && _notrun "sockets PMDA failed to install"
|
||||
+
|
||||
+# real QA test starts here
|
||||
+
|
||||
+echo "=== valid filter should be accepted ==="
|
||||
+pmstore network.persocket.filter "sport == 22" 2>&1 \
|
||||
+| grep -q 'Bad input' && echo "FAIL: valid filter rejected" || echo "valid filter accepted"
|
||||
+
|
||||
+echo
|
||||
+echo "=== shell metacharacter semicolon should be rejected ==="
|
||||
+pmstore network.persocket.filter ';id' 2>&1 \
|
||||
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
|
||||
+
|
||||
+echo
|
||||
+echo "=== shell metacharacter backtick should be rejected ==="
|
||||
+pmstore network.persocket.filter '`id`' 2>&1 \
|
||||
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
|
||||
+
|
||||
+echo
|
||||
+echo "=== shell metacharacter pipe should be rejected ==="
|
||||
+pmstore network.persocket.filter '|cat /etc/passwd' 2>&1 \
|
||||
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
|
||||
+
|
||||
+echo
|
||||
+echo "=== shell metacharacter dollar should be rejected ==="
|
||||
+pmstore network.persocket.filter '${IFS}id' 2>&1 \
|
||||
+| grep -q 'Bad input' && echo "metacharacter rejected" || echo "FAIL: metacharacter not rejected"
|
||||
+
|
||||
+# success, all done
|
||||
+exit
|
||||
diff --git a/qa/2101.out b/qa/2101.out
|
||||
new file mode 100644
|
||||
index 000000000..02f9ac565
|
||||
--- /dev/null
|
||||
+++ b/qa/2101.out
|
||||
@@ -0,0 +1,15 @@
|
||||
+QA output created by 2101
|
||||
+=== valid filter should be accepted ===
|
||||
+valid filter accepted
|
||||
+
|
||||
+=== shell metacharacter semicolon should be rejected ===
|
||||
+metacharacter rejected
|
||||
+
|
||||
+=== shell metacharacter backtick should be rejected ===
|
||||
+metacharacter rejected
|
||||
+
|
||||
+=== shell metacharacter pipe should be rejected ===
|
||||
+metacharacter rejected
|
||||
+
|
||||
+=== shell metacharacter dollar should be rejected ===
|
||||
+metacharacter rejected
|
||||
diff --git a/qa/group b/qa/group
|
||||
index a1b2c3d4e..b2c3d4e5f 100644
|
||||
--- a/qa/group
|
||||
+++ b/qa/group
|
||||
@@ -2218,5 +2218,6 @@ pmcd.pdu
|
||||
1992 pmda.uwsgi local
|
||||
2100 pmproxy local security
|
||||
+2101 pmda.sockets local security
|
||||
2104 libpcp local security
|
||||
2105 libpcp pmcd local security pmcd.pdu
|
||||
4751 libpcp threads valgrind local pcp helgrind
|
||||
diff --git a/src/pmdas/linux_sockets/pmda.c b/src/pmdas/linux_sockets/pmda.c
|
||||
index 5a3018d8a..59e69a675 100644
|
||||
--- a/src/pmdas/linux_sockets/pmda.c
|
||||
+++ b/src/pmdas/linux_sockets/pmda.c
|
||||
@@ -162,11 +162,9 @@ sockets_check_filter(const char *string)
|
||||
const char *p;
|
||||
|
||||
for (p = string; *p; p++) {
|
||||
- if (isspace(*p))
|
||||
+ if (isspace(*p) || isalnum(*p))
|
||||
continue;
|
||||
- if (isalnum(*p))
|
||||
- continue;
|
||||
- if (*p == '(' || *p == ')')
|
||||
+ if (strchr("()=!<>:.*/-,", *p) != NULL)
|
||||
continue;
|
||||
return 0; /* disallow */
|
||||
}
|
||||
@@ -191,7 +189,7 @@ sockets_store(pmResult *result, pmdaExt *pmda)
|
||||
case 0: /* network.persocket.filter */
|
||||
if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
|
||||
PM_TYPE_STRING, &av, PM_TYPE_STRING)) >= 0) {
|
||||
- if (sockets_check_filter(av.cp)) {
|
||||
+ if (!sockets_check_filter(av.cp)) {
|
||||
sts = PM_ERR_BADSTORE;
|
||||
free(av.cp);
|
||||
break;
|
||||
diff --git a/src/pmdas/linux_sockets/ss_stream.c b/src/pmdas/linux_sockets/ss_stream.c
|
||||
index 421c65fd1..833fc275a 100644
|
||||
--- a/src/pmdas/linux_sockets/ss_stream.c
|
||||
+++ b/src/pmdas/linux_sockets/ss_stream.c
|
||||
@@ -14,18 +14,19 @@
|
||||
|
||||
#include <pcp/pmapi.h>
|
||||
#include <pcp/pmda.h>
|
||||
+#include <pcp/libpcp.h>
|
||||
#include "ss_stats.h"
|
||||
|
||||
#define SS_OPTIONS "-noemitauO"
|
||||
|
||||
-char *ss_filter = NULL; /* storable: network.persocket.filter */
|
||||
+char *ss_filter; /* storable: network.persocket.filter */
|
||||
+static int using_pipe; /* pipe is normal operation, QA uses files */
|
||||
|
||||
FILE *
|
||||
ss_open_stream()
|
||||
{
|
||||
- FILE *fp;
|
||||
+ FILE *fp = NULL;
|
||||
char *path;
|
||||
- char cmd[MAXPATHLEN];
|
||||
|
||||
if (ss_filter == NULL) {
|
||||
/* pmstore to network.persocket.filter frees this if changing */
|
||||
@@ -38,17 +39,51 @@ ss_open_stream()
|
||||
fp = fopen(path, "r");
|
||||
if (pmDebugOptions.appl0)
|
||||
fprintf(stderr, "ss_open_stream: open PCPQA_PMDA_SOCKETS=%s\n", path);
|
||||
+ using_pipe = 0;
|
||||
} else {
|
||||
+ __pmExecCtl_t *argp = NULL;
|
||||
+ int sts;
|
||||
+
|
||||
if (access((path = "/usr/sbin/ss"), X_OK) != 0) {
|
||||
if (access((path = "/usr/bin/ss"), X_OK) != 0) {
|
||||
fprintf(stderr, "Error: no \"ss\" binary found\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
- pmsprintf(cmd, sizeof(cmd), "%s %s %s", path, SS_OPTIONS, ss_filter);
|
||||
- fp = popen(cmd, "r");
|
||||
+ if ((sts = __pmProcessAddArg(&argp, path)) < 0 ||
|
||||
+ (sts = __pmProcessAddArg(&argp, SS_OPTIONS)) < 0) {
|
||||
+ if (pmDebugOptions.appl0)
|
||||
+ fprintf(stderr, "ss_open_stream: __pmProcessAddArg failed: %s\n",
|
||||
+ pmErrStr(sts));
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ if (ss_filter[0] != '\0') {
|
||||
+ char *s, *tok, *saveptr;
|
||||
+
|
||||
+ if ((s = strdup(ss_filter)) == NULL)
|
||||
+ return NULL;
|
||||
+ for (tok = strtok_r(s, " \t", &saveptr); tok != NULL;
|
||||
+ tok = strtok_r(NULL, " \t", &saveptr)) {
|
||||
+ if ((sts = __pmProcessAddArg(&argp, tok)) < 0) {
|
||||
+ free(s);
|
||||
+ if (pmDebugOptions.appl0)
|
||||
+ fprintf(stderr, "ss_open_stream: __pmProcessAddArg failed: %s\n",
|
||||
+ pmErrStr(sts));
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ }
|
||||
+ free(s);
|
||||
+ }
|
||||
+ if ((sts = __pmProcessPipe(&argp, "r", PM_EXEC_TOSS_NONE, &fp)) < 0) {
|
||||
+ if (pmDebugOptions.appl0)
|
||||
+ fprintf(stderr, "ss_open_stream: __pmProcessPipe failed: %s\n",
|
||||
+ pmErrStr(sts));
|
||||
+ return NULL;
|
||||
+ }
|
||||
if (pmDebugOptions.appl0)
|
||||
- fprintf(stderr, "ss_open_stream: popen %s\n", cmd);
|
||||
+ fprintf(stderr, "ss_open_stream: exec %s %s %s\n",
|
||||
+ path, SS_OPTIONS, ss_filter);
|
||||
+ using_pipe = 1;
|
||||
}
|
||||
|
||||
return fp;
|
||||
@@ -57,8 +92,8 @@ ss_open_stream()
|
||||
void
|
||||
ss_close_stream(FILE *fp)
|
||||
{
|
||||
- if (getenv("PCPQA_PMDA_SOCKETS") != NULL)
|
||||
- fclose(fp);
|
||||
+ if (using_pipe)
|
||||
+ __pmProcessPipeClose(fp);
|
||||
else
|
||||
- pclose(fp);
|
||||
+ fclose(fp);
|
||||
}
|
||||
219
SOURCES/pcp-6.3.7-CVE-2026-16526.patch
Normal file
219
SOURCES/pcp-6.3.7-CVE-2026-16526.patch
Normal file
@ -0,0 +1,219 @@
|
||||
From 8d418f7348a51ea8801e4725a78c8f85bcdf2f7d Mon Sep 17 00:00:00 2001
|
||||
From: Nathan Scott <nathans@redhat.com>
|
||||
Date: Thu, 2 Jul 2026 16:23:26 +1000
|
||||
Subject: [PATCH] libpcp, libpcp_pmda: set FD_CLOEXEC on AF_UNIX sockets
|
||||
(CWE-403)
|
||||
|
||||
The __pmInitSocket() function returns early for AF_UNIX sockets,
|
||||
skipping all subsequent socket hardening including FD_CLOEXEC.
|
||||
This causes the pmdaroot Unix socket fd to be inherited by child
|
||||
processes spawned via popen()/fork(), enabling privilege escalation
|
||||
when combined with the linux_sockets command injection (vuln 3):
|
||||
an attacker's popen() child inherits the pmdaroot fd and can send
|
||||
a PDUROOT_STARTPMDA_REQ to execute commands as root.
|
||||
|
||||
Fix:
|
||||
- Set FD_CLOEXEC on AF_UNIX sockets in __pmInitSocket() before the
|
||||
early return, matching the behavior TCP sockets get via
|
||||
__pmConnectRestoreFlags()
|
||||
- Set FD_CLOEXEC on pmdarootfd in pmdaRootConnect() after connect()
|
||||
succeeds, as belt-and-suspenders for this critical fd
|
||||
- Add qa/src/check_cloexec.c and qa/2104 verifying FD_CLOEXEC is set
|
||||
on sockets created by __pmCreateUnixSocket()
|
||||
|
||||
Note: SO_PEERCRED peer credential verification on the pmdaroot server
|
||||
side is a separate hardening measure to be addressed as a follow-up.
|
||||
|
||||
Reported-by: Francisco Alisson Bezerra, TIM Security Red Team
|
||||
Reported-by: Lucas Gabriel Alves, TIM Security Red Team
|
||||
Reported-by: Massimiliano Brolli, TIM Security Red Team
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||||
---
|
||||
qa/2104 | 31 ++++++++++++++++++++++++++++++
|
||||
qa/2104.out | 2 ++
|
||||
qa/group | 1 +
|
||||
qa/src/GNUlocaldefs | 10 +++++++++-
|
||||
qa/src/check_cloexec.c | 38 +++++++++++++++++++++++++++++++++++++
|
||||
src/libpcp/src/auxconnect.c | 6 +++++-
|
||||
src/libpcp_pmda/src/root.c | 5 ++++-
|
||||
7 files changed, 90 insertions(+), 3 deletions(-)
|
||||
create mode 100755 qa/2104
|
||||
create mode 100644 qa/2104.out
|
||||
create mode 100644 qa/src/check_cloexec.c
|
||||
|
||||
diff --git a/qa/2104 b/qa/2104
|
||||
new file mode 100755
|
||||
index 000000000..0b2b0b602
|
||||
--- /dev/null
|
||||
+++ b/qa/2104
|
||||
@@ -0,0 +1,31 @@
|
||||
+#!/bin/sh
|
||||
+# PCP QA Test No. 2104
|
||||
+# Verify AF_UNIX sockets have FD_CLOEXEC set (CWE-403 fix)
|
||||
+#
|
||||
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
|
||||
+#
|
||||
+
|
||||
+seq=`basename $0`
|
||||
+echo "QA output created by $seq"
|
||||
+
|
||||
+# get standard environment, filters and checks
|
||||
+. ./common.product
|
||||
+. ./common.filter
|
||||
+. ./common.check
|
||||
+
|
||||
+[ -f src/check_cloexec ] || _notrun "check_cloexec not built"
|
||||
+
|
||||
+_cleanup()
|
||||
+{
|
||||
+ cd $here
|
||||
+ $sudo rm -rf $tmp $tmp.*
|
||||
+}
|
||||
+
|
||||
+status=0 # success is the default!
|
||||
+trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
+
|
||||
+# real QA test starts here
|
||||
+src/check_cloexec
|
||||
+
|
||||
+# success, all done
|
||||
+exit
|
||||
diff --git a/qa/2104.out b/qa/2104.out
|
||||
new file mode 100644
|
||||
index 000000000..900a95ec7
|
||||
--- /dev/null
|
||||
+++ b/qa/2104.out
|
||||
@@ -0,0 +1,2 @@
|
||||
+QA output created by 2104
|
||||
+FD_CLOEXEC is set
|
||||
diff --git a/qa/group b/qa/group
|
||||
index d4da513ce..e87583d69 100644
|
||||
--- a/qa/group
|
||||
+++ b/qa/group
|
||||
@@ -2216,4 +2216,5 @@ pmcd.pdu
|
||||
1990 pcp buddyinfo python local
|
||||
1991 pcp netstat python local
|
||||
1992 pmda.uwsgi local
|
||||
+2104 libpcp local security
|
||||
4751 libpcp threads valgrind local pcp helgrind
|
||||
diff --git a/qa/src/GNUlocaldefs b/qa/src/GNUlocaldefs
|
||||
index f83826068..53f0d06c6 100644
|
||||
--- a/qa/src/GNUlocaldefs
|
||||
+++ b/qa/src/GNUlocaldefs
|
||||
@@ -54,7 +54,8 @@ CFILES = disk_test.c exercise.c context_test.c chkoptfetch.c \
|
||||
stampconv.c time_stamp.c archend.c scandata.c wait_for_values.c \
|
||||
dumpstack.c usergroup.c derived_help.c ready-or-not.c cleanmapdir.c \
|
||||
throttle.c throttle_timeout.c y2038.c bigpmcdpmids.c pdu-gadget.c \
|
||||
- newcontext.c
|
||||
+ newcontext.c check_cloexec.c
|
||||
+
|
||||
|
||||
ifeq ($(shell test -f ../localconfig && echo 1), 1)
|
||||
include ../localconfig
|
||||
@@ -574,6 +575,11 @@ sortinst: sortinst.c
|
||||
# --- need libpcp_import
|
||||
#
|
||||
|
||||
+check_cloexec: check_cloexec.c
|
||||
+ rm -f $@
|
||||
+ $(CCF) $(CDEFS) -o $@ $@.c $(LDLIBS)
|
||||
+ $(LINKER_MAKERULE)
|
||||
+
|
||||
check_import: check_import.c
|
||||
rm -f $@
|
||||
$(CCF) $(CDEFS) -o $@ $@.c $(LDLIBS) -lpcp_import
|
||||
@@ -880,6 +886,8 @@ xmktime.o: libpcp.h
|
||||
xxx.o: libpcp.h
|
||||
y2038.o: libpcp.h
|
||||
|
||||
+check_cloexec.o: libpcp.h
|
||||
+
|
||||
bozo:
|
||||
@echo CFILES_TARGETS=$(CFILES_TARGETS)
|
||||
@echo "patsubst ->" $(patsubst %.c,%,$(CFILES_TARGETS))
|
||||
diff --git a/qa/src/check_cloexec.c b/qa/src/check_cloexec.c
|
||||
new file mode 100644
|
||||
index 000000000..ebc438d30
|
||||
--- /dev/null
|
||||
+++ b/qa/src/check_cloexec.c
|
||||
@@ -0,0 +1,38 @@
|
||||
+/*
|
||||
+ * Verify that AF_UNIX sockets created by __pmCreateUnixSocket()
|
||||
+ * have FD_CLOEXEC set.
|
||||
+ */
|
||||
+
|
||||
+#include <pcp/pmapi.h>
|
||||
+#include "libpcp.h"
|
||||
+#include <fcntl.h>
|
||||
+
|
||||
+int
|
||||
+main(int argc, char **argv)
|
||||
+{
|
||||
+ int fd, flags;
|
||||
+
|
||||
+ pmSetProgname(argv[0]);
|
||||
+
|
||||
+ fd = __pmCreateUnixSocket();
|
||||
+ if (fd < 0) {
|
||||
+ fprintf(stderr, "Error: __pmCreateUnixSocket failed: %s\n",
|
||||
+ pmErrStr(fd));
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ flags = fcntl(fd, F_GETFD);
|
||||
+ if (flags < 0) {
|
||||
+ fprintf(stderr, "Error: fcntl F_GETFD failed\n");
|
||||
+ close(fd);
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ if (flags & FD_CLOEXEC)
|
||||
+ printf("FD_CLOEXEC is set\n");
|
||||
+ else
|
||||
+ printf("FAIL: FD_CLOEXEC is NOT set\n");
|
||||
+
|
||||
+ close(fd);
|
||||
+ return 0;
|
||||
+}
|
||||
diff --git a/src/libpcp/src/auxconnect.c b/src/libpcp/src/auxconnect.c
|
||||
index 62f6f5451..67227e3c6 100644
|
||||
--- a/src/libpcp/src/auxconnect.c
|
||||
+++ b/src/libpcp/src/auxconnect.c
|
||||
@@ -516,8 +516,12 @@ __pmInitSocket(int fd, int family)
|
||||
}
|
||||
|
||||
#if defined(HAVE_STRUCT_SOCKADDR_UN)
|
||||
- if (family == AF_UNIX)
|
||||
+ if (family == AF_UNIX) {
|
||||
+ int fdFlags;
|
||||
+ if ((fdFlags = __pmGetFileDescriptorFlags(fd)) >= 0)
|
||||
+ __pmSetFileDescriptorFlags(fd, fdFlags | FD_CLOEXEC);
|
||||
return fd;
|
||||
+ }
|
||||
#endif
|
||||
|
||||
/* Avoid 200 ms delay. This option is not supported for unix domain sockets. */
|
||||
diff --git a/src/libpcp_pmda/src/root.c b/src/libpcp_pmda/src/root.c
|
||||
index 1d3223b57..3950e4f1e 100644
|
||||
--- a/src/libpcp_pmda/src/root.c
|
||||
+++ b/src/libpcp_pmda/src/root.c
|
||||
@@ -32,7 +32,7 @@ pmdaRootConnect(const char *path)
|
||||
char *tmpdir;
|
||||
char socketpath[MAXPATHLEN];
|
||||
char errmsg[PM_MAXERRMSGLEN];
|
||||
- int fd, sts, version, features;
|
||||
+ int fd, sts, version, features, fdFlags;
|
||||
|
||||
/* Initialize the socket address. */
|
||||
if ((addr = __pmSockAddrAlloc()) == NULL)
|
||||
@@ -72,6 +72,9 @@ pmdaRootConnect(const char *path)
|
||||
return sts;
|
||||
}
|
||||
|
||||
+ if ((fdFlags = __pmGetFileDescriptorFlags(fd)) >= 0)
|
||||
+ __pmSetFileDescriptorFlags(fd, fdFlags | FD_CLOEXEC);
|
||||
+
|
||||
/* Check server connection information */
|
||||
if ((sts = __pmdaRecvRootPDUInfo(fd, &version, &features)) < 0) {
|
||||
pmNotifyErr(LOG_ERR,
|
||||
206
SOURCES/pcp-6.3.7-CVE-2026-16527.patch
Normal file
206
SOURCES/pcp-6.3.7-CVE-2026-16527.patch
Normal file
@ -0,0 +1,206 @@
|
||||
From 9dc1484fcab68aa395beec5f13758c2fab4e6033 Mon Sep 17 00:00:00 2001
|
||||
From: Nathan Scott <nathans@redhat.com>
|
||||
Date: Thu, 2 Jul 2026 15:28:34 +1000
|
||||
Subject: [PATCH] pmproxy: fix missing -Q and -S authentication flags (CWE-306)
|
||||
|
||||
The pmproxy -Q (require client certificate) and -S (require
|
||||
authenticated clients) flags existed as case blocks in the option
|
||||
parser but were absent from the short_options string and the longopts
|
||||
table, making them permanently unreachable. An unauthenticated HTTP
|
||||
client could access all REST API endpoints including /store and /derive.
|
||||
|
||||
Fix:
|
||||
- Add Q and S to short_options so pmgetopt_r() delivers them
|
||||
- Add --certreqd and --reqauth entries to the longopts table
|
||||
- Document both flags in the pmproxy(1) man page
|
||||
- Add qa/2100 verifying the flags are accepted and that -S correctly
|
||||
rejects unauthenticated REST API requests with HTTP 403
|
||||
|
||||
Note: -S enforcement in the REST API path already exists in http.c and
|
||||
webapi.c. -Q (CERT_REQD) enforcement is only implemented for the
|
||||
legacy PCP wire protocol path, not the REST API; this is a pre-existing
|
||||
limitation to be addressed separately.
|
||||
|
||||
Reported-by: Francisco Alisson Bezerra, TIM Security Red Team
|
||||
Reported-by: Lucas Gabriel Alves, TIM Security Red Team
|
||||
Reported-by: Massimiliano Brolli, TIM Security Red Team
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||||
---
|
||||
man/man1/pmproxy.1 | 6 +++
|
||||
qa/2100 | 85 +++++++++++++++++++++++++++++++++++++++
|
||||
qa/2100.out | 12 ++++++
|
||||
qa/group | 1 +
|
||||
src/pmproxy/src/pmproxy.c | 4 +-
|
||||
5 files changed, 107 insertions(+), 1 deletion(-)
|
||||
create mode 100755 qa/2100
|
||||
create mode 100644 qa/2100.out
|
||||
|
||||
diff --git a/man/man1/pmproxy.1 b/man/man1/pmproxy.1
|
||||
index f2f831b6e..65d66c5d2 100644
|
||||
--- a/man/man1/pmproxy.1
|
||||
+++ b/man/man1/pmproxy.1
|
||||
@@ -229,6 +229,9 @@ Specify an alternate
|
||||
number to listen on for client connections.
|
||||
The default value is 44322.
|
||||
.TP
|
||||
+\fB\-Q\f1, \fB\-\-certreqd\f1
|
||||
+Require that all client connections provide a trusted client certificate.
|
||||
+.TP
|
||||
\f3\-r\f1 \f2port\f1, \f3\-\-keyport\f1=\f2port\f1
|
||||
Specify an alternate key-value server
|
||||
.I port
|
||||
@@ -243,6 +246,9 @@ The default value is
|
||||
.IR $PCP_RUN_DIR/pmproxy.socket .
|
||||
This option implies \f3pmproxy\f1 is running in \f3timeseries\f1 mode.
|
||||
.TP
|
||||
+\fB\-S\f1, \fB\-\-reqauth\f1
|
||||
+Require that all client connections be authenticated.
|
||||
+.TP
|
||||
\fB\-t\f1, \fB\-\-timeseries\f1
|
||||
Operate in automatic archive timeseries discovery mode.
|
||||
This mode of operation will enable the
|
||||
diff --git a/qa/2100 b/qa/2100
|
||||
new file mode 100755
|
||||
index 000000000..1de957a2f
|
||||
--- /dev/null
|
||||
+++ b/qa/2100
|
||||
@@ -0,0 +1,85 @@
|
||||
+#!/bin/sh
|
||||
+# PCP QA Test No. 2100
|
||||
+# Verify pmproxy -Q and -S authentication flags are accepted
|
||||
+# and that -S (reqauth) enforces authentication on REST API
|
||||
+#
|
||||
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
|
||||
+#
|
||||
+
|
||||
+seq=`basename $0`
|
||||
+echo "QA output created by $seq"
|
||||
+
|
||||
+# get standard environment, filters and checks
|
||||
+. ./common.product
|
||||
+. ./common.filter
|
||||
+. ./common.check
|
||||
+
|
||||
+which curl >/dev/null 2>&1 || _notrun "no curl executable installed"
|
||||
+
|
||||
+_cleanup()
|
||||
+{
|
||||
+ [ -n "$__pid" ] && kill $__pid 2>/dev/null
|
||||
+ wait $__pid 2>/dev/null
|
||||
+ cd $here
|
||||
+ $sudo rm -rf $tmp $tmp.*
|
||||
+}
|
||||
+
|
||||
+status=0 # success is the default!
|
||||
+__pid=""
|
||||
+trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
+
|
||||
+# real QA test starts here
|
||||
+
|
||||
+echo "=== checking -Q and -S appear in usage ==="
|
||||
+pmproxy --help 2>&1 | grep -E '\-[QS]' | sed -e 's/^ *//'
|
||||
+
|
||||
+echo
|
||||
+echo "=== checking -S enforces authentication on REST API ==="
|
||||
+__port=`_find_free_port`
|
||||
+$PCP_BINADM_DIR/pmproxy -S -f -p $__port -l $tmp.log &
|
||||
+__pid=$!
|
||||
+sleep 1
|
||||
+if kill -0 $__pid 2>/dev/null; then
|
||||
+ echo "pmproxy with -S started"
|
||||
+
|
||||
+ # unauthenticated request should be rejected
|
||||
+ __code=`curl -s -o /dev/null -w '%{http_code}' "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null`
|
||||
+ if [ "$__code" = "403" ]; then
|
||||
+ echo "unauthenticated request correctly rejected (HTTP $__code)"
|
||||
+ else
|
||||
+ echo "FAIL: expected HTTP 403, got HTTP $__code"
|
||||
+ fi
|
||||
+
|
||||
+ kill $__pid
|
||||
+ wait $__pid 2>/dev/null
|
||||
+ __pid=""
|
||||
+else
|
||||
+ echo "FAIL: pmproxy with -S did not start"
|
||||
+fi
|
||||
+
|
||||
+echo
|
||||
+echo "=== checking without -S allows unauthenticated access ==="
|
||||
+__port=`_find_free_port`
|
||||
+$PCP_BINADM_DIR/pmproxy -f -p $__port -l $tmp.log2 &
|
||||
+__pid=$!
|
||||
+sleep 1
|
||||
+if kill -0 $__pid 2>/dev/null; then
|
||||
+ echo "pmproxy without -S started"
|
||||
+
|
||||
+ # unauthenticated request should succeed
|
||||
+ __code=`curl -s -o /dev/null -w '%{http_code}' "http://localhost:$__port/pmapi/context?hostspec=localhost" 2>/dev/null`
|
||||
+ if [ "$__code" = "200" ]; then
|
||||
+ echo "unauthenticated request correctly allowed (HTTP $__code)"
|
||||
+ else
|
||||
+ echo "FAIL: expected HTTP 200, got HTTP $__code"
|
||||
+ fi
|
||||
+
|
||||
+ kill $__pid
|
||||
+ wait $__pid 2>/dev/null
|
||||
+ __pid=""
|
||||
+else
|
||||
+ echo "FAIL: pmproxy without -S did not start"
|
||||
+fi
|
||||
+
|
||||
+# success, all done
|
||||
+exit
|
||||
diff --git a/qa/2100.out b/qa/2100.out
|
||||
new file mode 100644
|
||||
index 000000000..5302aea77
|
||||
--- /dev/null
|
||||
+++ b/qa/2100.out
|
||||
@@ -0,0 +1,12 @@
|
||||
+QA output created by 2100
|
||||
+=== checking -Q and -S appear in usage ===
|
||||
+-Q, --certreqd require client certificate authentication
|
||||
+-S, --reqauth require all client connections to be authenticated
|
||||
+
|
||||
+=== checking -S enforces authentication on REST API ===
|
||||
+pmproxy with -S started
|
||||
+unauthenticated request correctly rejected (HTTP 403)
|
||||
+
|
||||
+=== checking without -S allows unauthenticated access ===
|
||||
+pmproxy without -S started
|
||||
+unauthenticated request correctly allowed (HTTP 200)
|
||||
diff --git a/qa/group b/qa/group
|
||||
index 76430211f..a1b2c3d4e 100644
|
||||
--- a/qa/group
|
||||
+++ b/qa/group
|
||||
@@ -2216,6 +2216,7 @@ pmcd.pdu
|
||||
1990 pcp buddyinfo python local
|
||||
1991 pcp netstat python local
|
||||
1992 pmda.uwsgi local
|
||||
+2100 pmproxy local security
|
||||
2104 libpcp local security
|
||||
2105 libpcp pmcd local security pmcd.pdu
|
||||
4751 libpcp threads valgrind local pcp helgrind
|
||||
diff --git a/src/pmproxy/src/pmproxy.c b/src/pmproxy/src/pmproxy.c
|
||||
index 845479c46..ff18396bf 100644
|
||||
--- a/src/pmproxy/src/pmproxy.c
|
||||
+++ b/src/pmproxy/src/pmproxy.c
|
||||
@@ -82,7 +82,9 @@ static pmLongOptions longopts[] = {
|
||||
PMAPI_OPTIONS_HEADER("Connection options"),
|
||||
{ "interface", 1, 'i', "ADDR", "accept connections on this IP address" },
|
||||
{ "port", 1, 'p', "PORT", "accept connections on this port" },
|
||||
+ { "certreqd", 0, 'Q', 0, "require client certificate authentication" },
|
||||
{ "socket", 1, 's', "PATH", "Unix domain socket file [default $PCP_RUN_DIR/pmproxy.socket]" },
|
||||
+ { "reqauth", 0, 'S', 0, "require all client connections to be authenticated" },
|
||||
{ "keyport", 1, 'r', "PORT", "Connect to key server on this TCP/IP port (implies --timeseries)" },
|
||||
{ "keyhost", 1, 'h', "HOST", "Connect to key server on this host name (implies --timeseries)" },
|
||||
{ "redisport", 1, 'r', "PORT", "Backwards-compatibility option, do not use" },
|
||||
@@ -95,7 +97,7 @@ static pmLongOptions longopts[] = {
|
||||
};
|
||||
|
||||
static pmOptions opts = {
|
||||
- .short_options = "Ac:dD:Ffh:i:l:L:p:r:s:tT:U:x:?",
|
||||
+ .short_options = "Ac:dD:Ffh:i:l:L:p:Qr:s:StT:U:x:?",
|
||||
.long_options = longopts,
|
||||
};
|
||||
|
||||
137
SOURCES/pcp-6.3.7-CVE-2026-16529.patch
Normal file
137
SOURCES/pcp-6.3.7-CVE-2026-16529.patch
Normal file
@ -0,0 +1,137 @@
|
||||
From b3f2265227ff5fd32cdd1de99a143ea79214806b Mon Sep 17 00:00:00 2001
|
||||
From: Nathan Scott <nathans@redhat.com>
|
||||
Date: Thu, 2 Jul 2026 16:59:54 +1000
|
||||
Subject: [PATCH] libpcp: fix integer overflow in __pmGetPDU() (CWE-190)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
When php->len is near INT_MAX (e.g. 0x7FFFFFFF), the buffer size
|
||||
computation PDU_CHUNK * (1 + php->len / PDU_CHUNK) overflows signed
|
||||
int, producing a negative value that permanently corrupts the static
|
||||
maxsize variable. Every subsequent __pmFindPDUBuf() call returns NULL,
|
||||
rendering the affected daemon (pmlogger, pmcd) unable to process any
|
||||
further PDUs for the remainder of its lifetime — a persistent denial
|
||||
of service requiring a restart.
|
||||
|
||||
Fix: add an overflow guard (php->len > INT_MAX - PDU_CHUNK) before
|
||||
the multiplication, returning PM_ERR_TOOBIG for absurdly large PDU
|
||||
lengths. This protects the NO_LIMIT code path used by pmcd and
|
||||
pmlogger that is not covered by the existing ceiling check.
|
||||
|
||||
Also add _filter_pmcd() to qa/common.pmcd.pdu to normalize fd=N in
|
||||
pmcd log output, and qa/2105 with a crafted PDU exercising the
|
||||
overflow.
|
||||
|
||||
Reported-by: Francisco Alisson Bezerra, TIM Security Red Team
|
||||
Reported-by: Lucas Gabriel Alves, TIM Security Red Team
|
||||
Reported-by: Massimiliano Brolli, TIM Security Red Team
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||||
---
|
||||
qa/2105 | 17 +++++++++++++++++
|
||||
qa/2105.out | 10 ++++++++++
|
||||
qa/common.pmcd.pdu | 10 +++++++++-
|
||||
qa/group | 1 +
|
||||
src/libpcp/src/pdu.c | 8 ++++++++
|
||||
5 files changed, 45 insertions(+), 1 deletion(-)
|
||||
create mode 100755 qa/2105
|
||||
create mode 100644 qa/2105.out
|
||||
|
||||
diff --git a/qa/2105 b/qa/2105
|
||||
new file mode 100755
|
||||
index 000000000..44b849ad3
|
||||
--- /dev/null
|
||||
+++ b/qa/2105
|
||||
@@ -0,0 +1,17 @@
|
||||
+#!/bin/sh
|
||||
+# PCP QA Test No. 2105
|
||||
+# Verify __pmGetPDU rejects PDU with len near INT_MAX
|
||||
+# (integer overflow in buffer size computation, CWE-190)
|
||||
+#
|
||||
+# Copyright (c) 2026 Red Hat. All Rights Reserved.
|
||||
+#
|
||||
+
|
||||
+seq=`basename $0`
|
||||
+echo "QA output created by $seq"
|
||||
+
|
||||
+pdu_data=pdudata/pdu-getpdu-overflow
|
||||
+grep_pattern="bad PDU len=.*exceeds maximum|PDU len=.*too large"
|
||||
+
|
||||
+# this is one of the generic pmcd PDU exerciser tests ...
|
||||
+#
|
||||
+. ./common.pmcd.pdu
|
||||
diff --git a/qa/2105.out b/qa/2105.out
|
||||
new file mode 100644
|
||||
index 000000000..a304f9d91
|
||||
--- /dev/null
|
||||
+++ b/qa/2105.out
|
||||
@@ -0,0 +1,10 @@
|
||||
+QA output created by 2105
|
||||
+expect error(s) to be logged ...
|
||||
+__pmGetPDU: fd=N type=0x8000 bad PDU len=2147483647 in hdr exceeds maximum client PDU size (65536)
|
||||
+
|
||||
+and no valgrind badness ...
|
||||
+Memcheck, a memory error detector
|
||||
+LEAK SUMMARY:
|
||||
+definitely lost: 0 bytes in 0 blocks
|
||||
+indirectly lost: 0 bytes in 0 blocks
|
||||
+ERROR SUMMARY: 0 errors from 0 contexts ...
|
||||
diff --git a/qa/common.pmcd.pdu b/qa/common.pmcd.pdu
|
||||
index d9e3c6c5c..9882284b5 100644
|
||||
--- a/qa/common.pmcd.pdu
|
||||
+++ b/qa/common.pmcd.pdu
|
||||
@@ -55,6 +55,14 @@ _filter()
|
||||
# end
|
||||
}
|
||||
|
||||
+_filter_pmcd()
|
||||
+{
|
||||
+ sed \
|
||||
+ -e 's/fd=[0-9][0-9]*/fd=N/g' \
|
||||
+ -e 's/^\[.*\] pmcd([0-9]*) [A-Za-z]*: //' \
|
||||
+ # end
|
||||
+}
|
||||
+
|
||||
mkdir $tmp || exit 1
|
||||
cd $tmp
|
||||
grep sampledso $PCP_PMCDCONF_PATH >pmcd.conf
|
||||
@@ -91,7 +99,7 @@ wait
|
||||
[ -s $tmp.err ] && cat $tmp.err
|
||||
|
||||
echo "expect error(s) to be logged ..."
|
||||
-grep -E "$grep_pattern" pmcd.log
|
||||
+grep -E "$grep_pattern" pmcd.log | _filter_pmcd
|
||||
|
||||
echo
|
||||
echo "and no valgrind badness ..."
|
||||
diff --git a/qa/group b/qa/group
|
||||
index e87583d69..76430211f 100644
|
||||
--- a/qa/group
|
||||
+++ b/qa/group
|
||||
@@ -2216,5 +2216,6 @@ pmcd.pdu
|
||||
1990 pcp buddyinfo python local
|
||||
1991 pcp netstat python local
|
||||
1992 pmda.uwsgi local
|
||||
2104 libpcp local security
|
||||
+2105 libpcp pmcd local security pmcd.pdu
|
||||
4751 libpcp threads valgrind local pcp helgrind
|
||||
diff --git a/src/libpcp/src/pdu.c b/src/libpcp/src/pdu.c
|
||||
index 5845932be..0a4ae75b2 100644
|
||||
--- a/src/libpcp/src/pdu.c
|
||||
+++ b/src/libpcp/src/pdu.c
|
||||
@@ -658,6 +658,14 @@ check_read_len:
|
||||
|
||||
PM_LOCK(pdu_lock);
|
||||
if (php->len > maxsize) {
|
||||
+ if (php->len > INT_MAX - PDU_CHUNK) {
|
||||
+ PM_UNLOCK(pdu_lock);
|
||||
+ if (pmDebugOptions.pdu)
|
||||
+ pmNotifyErr(LOG_ERR, "%s: fd=%d PDU len=%d too large",
|
||||
+ __FUNCTION__, fd, php->len);
|
||||
+ __pmUnpinPDUBuf(pdubuf);
|
||||
+ return PM_ERR_TOOBIG;
|
||||
+ }
|
||||
tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK);
|
||||
maxsize = tmpsize;
|
||||
}
|
||||
117
SPECS/pcp.spec
117
SPECS/pcp.spec
@ -1,6 +1,6 @@
|
||||
Name: pcp
|
||||
Version: 6.3.7
|
||||
Release: 8%{?dist}
|
||||
Release: 8.0.1%{?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
|
||||
@ -25,6 +25,39 @@ Patch13: atop-cpu-utilization.patch
|
||||
Patch14: pmda-openmetrics-performance.patch
|
||||
Patch15: pcp-RHEL-133548.patch
|
||||
Patch16: memory-leaks.patch
|
||||
# https://issues.redhat.com/browse/RHEL-213688
|
||||
# https://github.com/performancecopilot/pcp/commit/7e27614006ff6fc4925991edbedaf1eab6b14731
|
||||
Patch17: pcp-6.3.7-CVE-2026-16526.patch
|
||||
# https://github.com/performancecopilot/pcp/commit/ef848fb978d26335f9676933f541c73b58f130a6
|
||||
Patch18: pcp-6.3.7-CVE-2026-16529.patch
|
||||
# https://issues.redhat.com/browse/RHEL-213712
|
||||
# https://github.com/performancecopilot/pcp/commit/d96ba5a716eeff7840138eb08fbab0d11a57f641
|
||||
Patch19: pcp-6.3.7-CVE-2026-16527.patch
|
||||
# https://issues.redhat.com/browse/RHEL-213668
|
||||
# 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}
|
||||
@ -627,7 +660,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
|
||||
Requires: pcp-pmda-openvswitch pcp-pmda-rocestat
|
||||
%endif
|
||||
%if !%{disable_mongodb}
|
||||
Requires: pcp-pmda-mongodb
|
||||
@ -1659,6 +1692,24 @@ 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
|
||||
#
|
||||
@ -2506,6 +2557,9 @@ updated policy package.
|
||||
%prep
|
||||
%autosetup -p1
|
||||
|
||||
# Create binary QA test data for CVE-2026-16529 overflow test (8 bytes)
|
||||
/usr/bin/printf '\x7f\xff\xff\xff\x00\x00\x80\x00' > qa/pdudata/pdu-getpdu-overflow
|
||||
|
||||
%build
|
||||
# the buildsubdir macro gets defined in %%setup and is apparently only available in the next step (i.e. the %%build step)
|
||||
%global __strip %{_builddir}/%{?buildsubdir}/build/rpm/custom-strip
|
||||
@ -2737,6 +2791,7 @@ basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-fil
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/rabbitmq(/|$)' >pcp-pmda-rabbitmq-files
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/redis(/|$)' >pcp-pmda-redis-files
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/resctrl(/|$)|sys-fs-resctrl' >pcp-pmda-resctrl-files
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/rocestat(/|$)' >pcp-pmda-rocestat-files
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/roomtemp(/|$)' >pcp-pmda-roomtemp-files
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/rpm(/|$)' >pcp-pmda-rpm-files
|
||||
basic_manifest | keep '(etc/pcp|pmdas)/rsyslog(/|$)' >pcp-pmda-rsyslog-files
|
||||
@ -2775,7 +2830,7 @@ for pmda_package in \
|
||||
nutcracker nvidia \
|
||||
openmetrics openvswitch oracle \
|
||||
pdns perfevent podman postfix postgresql \
|
||||
rabbitmq redis resctrl roomtemp rpm rsyslog \
|
||||
rabbitmq redis resctrl rocestat roomtemp rpm rsyslog \
|
||||
samba sendmail shping slurm smart snmp \
|
||||
sockets statsd summary systemd \
|
||||
unbound uwsgi \
|
||||
@ -3121,6 +3176,9 @@ exit 0
|
||||
%preun pmda-gluster
|
||||
%{pmda_remove "$1" "gluster"}
|
||||
|
||||
%preun pmda-rocestat
|
||||
%{pmda_remove "$1" "rocestat"}
|
||||
|
||||
%preun pmda-zswap
|
||||
%{pmda_remove "$1" "zswap"}
|
||||
|
||||
@ -3278,6 +3336,26 @@ 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}
|
||||
@ -3474,6 +3552,8 @@ 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
|
||||
@ -3635,6 +3715,37 @@ 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)
|
||||
|
||||
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 6.3.7-8.3
|
||||
- Fix missing pmproxy -Q and -S authentication flags (CVE-2026-16527,
|
||||
RHEL-213712)
|
||||
|
||||
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 6.3.7-8.2
|
||||
- Fix integer overflow in __pmGetPDU() (CVE-2026-16529, RHEL-213737)
|
||||
|
||||
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 6.3.7-8.1
|
||||
- Fix CVE-2026-16526: set FD_CLOEXEC on AF_UNIX sockets (RHEL-213688)
|
||||
|
||||
* Thu Mar 5 2026 Jan Kurik <jkurik@redhat.com> - 6.3.7-8
|
||||
- Backported memory leaks patches from pcp-7.0.3
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user