import Oracle_OSS pcp-5.3.7-22.0.12.el8_10.5

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-19 04:52:59 -04:00
parent eaeb816b4a
commit 7b4c9c7ecc
43 changed files with 26339 additions and 5 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
SOURCES/pcp-5.3.7.src.tar.gz
SOURCES/redhat-bugzilla-2219731-hacluster-metrics.patch
SOURCES/pdu-getpdu-overflow

View File

@ -1,2 +1,2 @@
a0a05bf501b016cb859fb211ae60ce18be2bbd99 SOURCES/pcp-5.3.7.src.tar.gz
bb84c58586e078343c975ed61a8b53d4fd4393e8 SOURCES/redhat-bugzilla-2219731-hacluster-metrics.patch
c3b4ccf034658b6a5c222a52ecf41175d06d0a0a SOURCES/pdu-getpdu-overflow

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
From ff8b1172b43b850f2e858a621f7d79f34083d52e Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Thu, 17 Nov 2022 00:57:13 -0500
Subject: [PATCH] pcp-ps: python2 print issue and -o option fix
For dynamic printing with -o option,end literal in print was being used to
print the output continously but it's not supported in python2 so we have
changed the method.Now we get all the data once in buffer then we print that buffer.
pcp-ps -o option was not working because number of arguments in the extra option
function were incorrect so it was not able to override.
cherry-picked upstream commit:- a5632f0e72fda4ab1e81a971c5b1eda31ab22f34
2641b1713252753a94c7e40cb59f4d3f45e828c1
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
Orabug: 34321683
---
src/pcp/ps/pcp-ps.py | 33 ++++++++++++---------------------
1 file changed, 12 insertions(+), 21 deletions(-)
diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py
index b087951c4..65e4403ff 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -38,25 +38,16 @@ SCHED_POLICY = ['NORMAL', 'FIFO', 'RR', 'BATCH', '', 'IDLE', 'DEADLINE']
class StdoutPrinter:
- def Print(self, args, continuation=None):
- if continuation is not None:
- print(args, end=continuation)
- else:
- print(args)
-
+ def Print(self, args):
+ print(args)
class NoneHandlingPrinterDecorator:
def __init__(self, printer):
self.printer = printer
- def Print(self, args, continuation=None):
- if continuation is None:
- new_args = args.replace('None', '?')
- elif type(args) not in [float, int] or args is not None:
- new_args = str(args).replace('None', '?')
- else:
- new_args = args
- self.printer.Print(new_args, continuation)
+ def Print(self, args):
+ new_args = args.replace('None', '?')
+ self.printer.Print(new_args)
# After fetching non singular metric values, create a mapping of instance id
@@ -429,18 +420,18 @@ class DynamicProcessReporter:
process.rss(), current_process_sname, process.age(), process.total_time(), wchan,
process_name))
elif self.processStatOptions.colum_list is not None:
- print('Timestamp', end="\t")
+ header = "Timestamp" + '\t'
for key in self.processStatOptions.colum_list:
if key in PIDINFO_PAIR:
- self.printer(PIDINFO_PAIR[key][0], '\t\t')
- print('')
+ header += PIDINFO_PAIR[key][0] + '\t\t'
+ print(header)
processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
for process in processes:
- print(timestamp, end="\t")
+ data_to_print = timestamp + '\t'
for key in self.processStatOptions.colum_list:
if key in PIDINFO_PAIR:
- self.printer(PIDINFO_PAIR[key][1](process), '\t\t')
- print('')
+ data_to_print += str(PIDINFO_PAIR[key][1](process)) + '\t\t'
+ print(data_to_print)
class ProcessStatusReporter:
@@ -729,7 +720,7 @@ class ProcessStatOptions(pmapi.pmOptions):
# """Override standard Pcp-ps option to show all process """
return bool(opts in ['p', 'c', 'o', 'P', 'U'])
- def extraOptions(self, opts, optarg):
+ def extraOptions(self, opts, optarg,index):
if opts == 'e':
ProcessStatOptions.show_all_process = True
elif opts == 'c':
--
2.31.1

View File

@ -0,0 +1,82 @@
From 9e13cbc088bb88660a124e4a6b7f333ad9350670 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Tue, 17 Jan 2023 07:14:48 -0500
Subject: [PATCH] pcp-ps:added capabilities to show n sample with archives
we are checking if our current context is archive then don't
limit the output print to one instance of data.
cherry-pick upstream commit:- 5236b96127adde8b4aaf01ad6dc9d4265b102979
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
Orabug: 34849959
---
src/pcp/ps/pcp-ps.py | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py
index 65e4403ff..f05cbe755 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -22,6 +22,8 @@ import sys
import time
from pcp import pmcc
from pcp import pmapi
+from cpmapi import PM_CONTEXT_ARCHIVE
+
import datetime
process_state_info = {}
@@ -378,10 +380,11 @@ class DynamicProcessReporter:
# when the print count is exhausted exit the program gracefully
# we can't use break here because it's being called by the run manager
- if self.processStatOptions.print_count == 0:
- sys.exit(0)
- else:
- self.processStatOptions.print_count -= 1
+ if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE:
+ if self.processStatOptions.print_count == 0:
+ sys.exit(0)
+ else:
+ self.processStatOptions.print_count -= 1
if self.processStatOptions.filterstate is not None:
self.printer("Timestamp" + header_indentation +
@@ -446,11 +449,11 @@ class ProcessStatusReporter:
# when the print count is exhausted exit the program gracefully
# we can't use break here because it's being called by the run manager
-
- if self.processStatOptions.print_count == 0:
- sys.exit(0)
- else:
- self.processStatOptions.print_count -= 1
+ if self.processStatOptions.context is not PM_CONTEXT_ARCHIVE:
+ if self.processStatOptions.print_count == 0:
+ sys.exit(0)
+ else:
+ self.processStatOptions.print_count -= 1
if self.processStatOptions.show_all_process:
self.printer("Timestamp" + header_indentation + "PID\t\t\tTTY\tTIME\t\tCMD")
@@ -666,6 +669,7 @@ class ProcessStatOptions(pmapi.pmOptions):
pid_list = []
ppid_list = []
filtered_process_user = None
+ context = None
def __init__(self):
pmapi.pmOptions.__init__(self, "t:c:e::p:ukVZ:z?:o:P:l:U:k")
@@ -792,7 +796,7 @@ if __name__ == "__main__":
try:
opts = ProcessStatOptions()
manager = pmcc.MetricGroupManager.builder(opts, sys.argv)
-
+ ProcessStatOptions.context = manager.type
if not opts.checkOptions():
raise pmapi.pmUsageErr
missing = manager.checkMissingMetrics(PSSTAT_METRICS)
--
2.31.1

View File

@ -0,0 +1,687 @@
From d2aeae7b1954f4c12a59928d807b0f796bf3ad7a Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Wed, 18 Jan 2023 06:04:43 -0500
Subject: [PATCH] Fixed multiple pcp-ps and pcp-mpstat issue
fixed broken pipe issue in pcp ps and pcp-pidstat utility
fixed mpstat crash issue with archives by adding error handling in current values
cherry-pick upstream commit:-
ac02934682e96a1bfd977c1b89b60d15878eef95
fc82b0bc4a89722094a07ec1acdd37028ca710b6
c8bdc0fa919b3534bd7184cc1aaaac1e3ca01188
94e2e6da2ccea5df70253037ce1106e44c5e2a68
1ce54e647bc8dfab4b313c39db005f16d10b87c4
a0e2447506c1579d1a936d7b1b25c736b560bd07
60d436e929b8964b1eea035a4d69fa17a5108d6c
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
Orabug: 34830203
Orabug: 34891338
Orabug: 34869451
---
src/pcp/mpstat/pcp-mpstat.py | 45 +++-
src/pcp/pidstat/pcp-pidstat.py | 3 +
src/pcp/ps/pcp-ps.py | 238 +++++++++---------
.../test/process_state_util_reporter_test.py | 5 +-
src/pcp/ps/test/process_statusutil_test.py | 50 ++--
5 files changed, 188 insertions(+), 153 deletions(-)
diff --git a/src/pcp/mpstat/pcp-mpstat.py b/src/pcp/mpstat/pcp-mpstat.py
index fefb6aae7..ad73fd6c8 100755
--- a/src/pcp/mpstat/pcp-mpstat.py
+++ b/src/pcp/mpstat/pcp-mpstat.py
@@ -108,7 +108,10 @@ class MetricRepository:
if instance is not None:
return dict(map(lambda x: (x[0].inst, x[2]), self.group[metric].netValues))
else:
- return self.group[metric].netValues[0][2]
+ if self.group[metric].netValues == []:
+ return None
+ else:
+ return self.group[metric].netValues[0][2]
def __fetch_previous_values(self,metric,instance):
if instance is not None:
@@ -140,9 +143,11 @@ class CoreCpuUtil:
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:
+ 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:
@@ -154,9 +159,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -167,9 +174,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -180,9 +189,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -193,9 +204,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -206,9 +219,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -219,9 +234,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -232,9 +249,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -245,9 +264,11 @@ class CoreCpuUtil:
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:
+ 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
@@ -258,9 +279,11 @@ class CoreCpuUtil:
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:
+ 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
diff --git a/src/pcp/pidstat/pcp-pidstat.py b/src/pcp/pidstat/pcp-pidstat.py
index 888527ca8..a086e0f88 100755
--- a/src/pcp/pidstat/pcp-pidstat.py
+++ b/src/pcp/pidstat/pcp-pidstat.py
@@ -21,6 +21,7 @@
import sys
import re
import time
+import signal
from pcp import pmcc
from pcp import pmapi
@@ -1033,5 +1034,7 @@ if __name__ == "__main__":
except pmapi.pmUsageErr as usage:
usage.message()
sys.exit(1)
+ except IOError:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except KeyboardInterrupt:
pass
diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py
index f05cbe755..6e2e9f4ac 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -20,18 +20,17 @@
import sys
import time
+import signal
from pcp import pmcc
from pcp import pmapi
from cpmapi import PM_CONTEXT_ARCHIVE
-import datetime
-
process_state_info = {}
PSSTAT_METRICS = ['kernel.uname.nodename', 'kernel.uname.release', 'kernel.uname.sysname',
'kernel.uname.machine', 'hinv.ncpu', 'proc.psinfo.pid', 'proc.psinfo.guest_time',
'proc.psinfo.utime', 'proc.psinfo.ppid', 'proc.psinfo.rt_priority', 'proc.psinfo.rss',
- 'proc.id.uid_nm', 'proc.psinfo.stime', 'kernel.all.uptime', 'proc.psinfo.sname',
+ 'proc.id.uid_nm', 'proc.psinfo.stime', 'kernel.all.boottime', 'proc.psinfo.sname',
'proc.psinfo.start_time', 'proc.psinfo.vsize', 'proc.psinfo.priority',
'proc.psinfo.nice', 'proc.psinfo.wchan_s', 'proc.psinfo.psargs', 'proc.psinfo.cmd',
'proc.psinfo.ttyname', 'mem.physmem', 'proc.psinfo.policy']
@@ -43,6 +42,7 @@ class StdoutPrinter:
def Print(self, args):
print(args)
+
class NoneHandlingPrinterDecorator:
def __init__(self, printer):
self.printer = printer
@@ -288,17 +288,18 @@ class ProcessStatusUtil:
else:
return c_systime - p_systime
- def age(self):
+ def start(self):
s_time = self.__metric_repository.current_value('proc.psinfo.start_time', self.instance)
group = manager['psstat']
- kernel_uptime = group['kernel.all.uptime'].netValues[0][2]
- timefmt = "%H:%M:%S"
- age = kernel_uptime - (s_time / 1000)
- res = datetime.datetime.now() - datetime.timedelta(seconds=age)
- if res.date() - datetime.datetime.now().date():
- return res.date()
+ kernel_boottime = group['kernel.all.boottime'].netValues[0][2]
+ ts = group.contextCache.pmLocaltime(int(0.5 + kernel_boottime + (s_time / 1000)))
+ if group.timestamp.tv_sec - int(0.5 + kernel_boottime + s_time / 1000) >= 24*60*60:
+ # started one day or more ago, use MmmDD HH:MM
+ return time.strftime("%b%d %H:%M", ts.struct_time())
else:
- return res.strftime(timefmt)
+ # started less than one day ago, use HH:MM:SS
+ return time.strftime("%H:%M:%S", ts.struct_time())
+
def total_time(self):
c_usertime = self.__metric_repository.current_value('proc.psinfo.stime', self.instance)
@@ -338,7 +339,7 @@ class ProcessStatusUtil:
PIDINFO_PAIR = {"%cpu": ('%CPU', ProcessStatusUtil.system_percent),
"%mem": ('%MEM', ProcessStatusUtil.mem),
- "start": ("START\t", ProcessStatusUtil.age),
+ "start": ("START\t", ProcessStatusUtil.start),
"time": ("TIME\t", ProcessStatusUtil.total_time),
"cls": ("CLS", ProcessStatusUtil.policy),
"cmd": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args),
@@ -408,19 +409,19 @@ class DynamicProcessReporter:
self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t%s" %
(timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(),
process.priority(), total_percent, process.system_percent(), process.vsize(),
- process.rss(), current_process_sname, process.age(), process.total_time(), wchan,
+ process.rss(), current_process_sname, process.start(), process.total_time(), wchan,
process_name))
elif len(wchan) >= 15:
self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" %
(timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(),
process.priority(), total_percent, process.system_percent(), process.vsize(),
- process.rss(), current_process_sname, process.age(), process.total_time(), wchan,
+ process.rss(), current_process_sname, process.start(), process.total_time(), wchan,
process_name))
else:
self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t\t%s" %
(timestamp, value_indentation, process.user_name(), process.pid(), process.ppid(),
process.priority(), total_percent, process.system_percent(), process.vsize(),
- process.rss(), current_process_sname, process.age(), process.total_time(), wchan,
+ process.rss(), current_process_sname, process.start(), process.total_time(), wchan,
process_name))
elif self.processStatOptions.colum_list is not None:
header = "Timestamp" + '\t'
@@ -497,7 +498,7 @@ class ProcessStatusReporter:
self.printer("%s%s%s\t\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % (
timestamp, value_indentation, process.user_name(), process.pid(),
process.system_percent(), process.total_percent(), process.vsize(), process.rss(),
- process.tty_name(), process.ppid(), process.total_time(), process.age(),
+ process.tty_name(), process.ppid(), process.total_time(), process.start(),
process.process_name()))
elif self.processStatOptions.user_oriented_format:
@@ -508,7 +509,7 @@ class ProcessStatusReporter:
self.printer("%s%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % (
timestamp, value_indentation, process.user_name(), process.pid(),
process.system_percent(), process.total_percent(), process.vsize(), process.rss(),
- process.tty_name(), process.s_name(), process.total_time(), process.age(),
+ process.tty_name(), process.s_name(), process.total_time(), process.start(),
process.process_name()))
elif self.processStatOptions.command_filter_flag:
@@ -549,106 +550,109 @@ class ProcessstatReport(pmcc.MetricGroupPrinter):
return group['hinv.ncpu'].netValues[0][2]
def report(self, manager):
- group = manager['psstat']
- if group['proc.psinfo.utime'].netPrevValues is None:
- # need two fetches to report rate converted counter metrics
- return
+ try:
+ group = manager['psstat']
+ if group['proc.psinfo.utime'].netPrevValues is None:
+ # need two fetches to report rate converted counter metrics
+ return
- if not group['hinv.ncpu'].netValues or not group['kernel.uname.sysname'].netValues:
- return
+ if not group['hinv.ncpu'].netValues or not group['kernel.uname.sysname'].netValues:
+ return
- try:
- if not self.Machine_info_count:
- self.print_machine_info(group, manager)
- self.Machine_info_count = 1
- except IndexError:
- # missing some metrics
- return
-
- ts = group.contextCache.pmLocaltime(int(group.timestamp))
- timestamp = time.strftime(ProcessStatOptions.timefmt, ts.struct_time())
- interval_in_seconds = self.timeStampDelta(group)
- header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
- value_indentation = ((len(header_indentation) + 9) - len(timestamp)) * " "
-
- metric_repository = ReportingMetricRepository(group)
-
- # Doing this for one single print instance in case there is no count specified
- if ProcessStatOptions.print_count is None:
- ProcessStatOptions.print_count = 1
-
- # =============================================================================================================
- if ProcessStatOptions.show_all_process:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.empty_arg_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.pid_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
- if ProcessStatOptions.ppid_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.command_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.user_oriented_format:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.username_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- # ================================================================
- if ProcessStatOptions.selective_colum_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = DynamicProcessReporter(process_report, process_filter, interval_in_seconds, printdecorator.Print,
- ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
+ try:
+ if not self.Machine_info_count:
+ self.print_machine_info(group, manager)
+ self.Machine_info_count = 1
+ except IndexError:
+ # missing some metrics
+ return
+
+ ts = group.contextCache.pmLocaltime(int(group.timestamp))
+ timestamp = time.strftime(ProcessStatOptions.timefmt, ts.struct_time())
+ interval_in_seconds = self.timeStampDelta(group)
+ header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
+ value_indentation = ((len(header_indentation) + 9) - len(timestamp)) * " "
+
+ metric_repository = ReportingMetricRepository(group)
+
+ # Doing this for one single print instance in case there is no count specified
+ if ProcessStatOptions.print_count is None:
+ ProcessStatOptions.print_count = 1
+
+ # ================================================================
+ if ProcessStatOptions.show_all_process:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+
+ if ProcessStatOptions.empty_arg_flag:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+
+ if ProcessStatOptions.pid_filter_flag:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+ if ProcessStatOptions.ppid_filter_flag:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+
+ if ProcessStatOptions.command_filter_flag:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+
+ if ProcessStatOptions.user_oriented_format:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+
+ if ProcessStatOptions.username_filter_flag:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+
+ # ================================================================
+ if ProcessStatOptions.selective_colum_flag:
+ process_report = ProcessStatus(metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = DynamicProcessReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+ finally:
+ sys.stdout.flush()
class ProcessStatOptions(pmapi.pmOptions):
@@ -724,7 +728,7 @@ class ProcessStatOptions(pmapi.pmOptions):
# """Override standard Pcp-ps option to show all process """
return bool(opts in ['p', 'c', 'o', 'P', 'U'])
- def extraOptions(self, opts, optarg,index):
+ def extraOptions(self, opts, optarg, index):
if opts == 'e':
ProcessStatOptions.show_all_process = True
elif opts == 'c':
@@ -812,6 +816,8 @@ if __name__ == "__main__":
except pmapi.pmUsageErr as usage:
usage.message()
sys.exit(1)
+ except IOError:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except KeyboardInterrupt:
print("Interrupted")
sys.exit(0)
diff --git a/src/pcp/ps/test/process_state_util_reporter_test.py b/src/pcp/ps/test/process_state_util_reporter_test.py
index f473351b5..e7a30364d 100755
--- a/src/pcp/ps/test/process_state_util_reporter_test.py
+++ b/src/pcp/ps/test/process_state_util_reporter_test.py
@@ -15,7 +15,7 @@
#
from mock import Mock
import unittest
-from pcp_ps import ProcessStateReporter
+from pcp_ps import ProcessStatusReporter
class TestProcessStateReporter(unittest.TestCase):
@@ -35,11 +35,12 @@ class TestProcessStateReporter(unittest.TestCase):
def test_print_report_with_user_name(self):
self.options.show_all_process = True
+ self.options.print_count=1
process_stack_util = Mock()
process_filter = Mock()
printer = Mock()
process_filter.filter_processes = Mock(return_value=self.processes)
- reporter = ProcessStateReporter(process_stack_util, process_filter, 1.34, printer, self.options)
+ reporter = ProcessStatusReporter(process_stack_util, process_filter, 1.34, printer, self.options)
reporter.print_report(123, " ", " ")
diff --git a/src/pcp/ps/test/process_statusutil_test.py b/src/pcp/ps/test/process_statusutil_test.py
index 8ef45b451..7d2c9797e 100755
--- a/src/pcp/ps/test/process_statusutil_test.py
+++ b/src/pcp/ps/test/process_statusutil_test.py
@@ -16,7 +16,7 @@
import mock
import unittest
-from pcp_ps import ProcessStatus
+from pcp_ps import ProcessStatusUtil
class TestProcessStackUtil(unittest.TestCase):
@@ -71,88 +71,90 @@ class TestProcessStackUtil(unittest.TestCase):
def test_stack_referenced_size(self):
self.skipTest(reason="Implement when suitable metric is found")
+ #These are blank spaces in assert case been addded
+ #to match the format of function ouput.please don't remove
def test_username(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.user_name()
- self.assertEqual(name, "test")
+ self.assertEqual(name, "test ")
def test_Processname(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.process_name()
- self.assertEqual(name, "test")
+ self.assertEqual(name, "test ")
def test_process_name_with_args(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.process_name_with_args()
- self.assertEqual(name, "test")
+ self.assertEqual(name, "test ")
def test_vszie(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
vsize = process_status_usage.vsize()
self.assertEqual(vsize, 1)
def test_rss(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
vsize = process_status_usage.rss()
self.assertEqual(vsize, 1)
def test_mem(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
vsize = process_status_usage.mem()
self.assertEqual(vsize, 100)
def test_pid(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
pid = process_status_usage.pid()
- self.assertEqual(pid, 1)
+ self.assertEqual(pid,'1 ')
def test_process_name(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.process_name()
- self.assertEqual(name, 'test')
+ self.assertEqual(name, 'test ')
def test_user_id(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
user_id = process_status_usage.user_id()
self.assertEqual(user_id, 1)
def test_s_name(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.s_name()
self.assertEqual(name, 'R')
def test_cpu_number(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.cpu_number()
self.assertEqual(name, 1)
def test_wchan_s(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.wchan_s()
- self.assertEqual(name, 'test')
+ self.assertEqual(name, 'test ')
def test_priority(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.priority()
self.assertEqual(name, 1)
def test_tty_name(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.tty_name()
self.assertEqual(name, 'tty')
def test_start_time(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.start_time()
self.assertEqual(name, 1)
def test_func_state(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.func_state()
self.assertEqual(name, 'N/A')
def test_policy(self):
- process_status_usage = ProcessStatus(1, 1.34, self.__metric_repository)
+ process_status_usage = ProcessStatusUtil(1, 1.34, self.__metric_repository)
name = process_status_usage.policy()
self.assertEqual(name, 'FIFO')
--
2.31.1

View File

@ -0,0 +1,315 @@
From 752bfc9fc16c1e432c3c7534416d2b2b0e3f0713 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Mon, 29 May 2023 12:48:41 +0530
Subject: [PATCH] Fixed PCP python utility issues
Fixed issue where initially created files by pmie failed to read
Fixed broken pipe issue in pcp-mpstat utility
Fixed exception in pcp-ps utility for process name
Orabug: 35434363
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
qa/883 | 4 ++
qa/883.out | 7 ++
qa/915 | 6 +-
qa/archives/GNUmakefile | 20 ++++++
src/libpcp_web/src/query_parser.y | 21 ++++--
src/pcp/mpstat/pcp-mpstat.py | 106 ++++++++++++++++++++----------
src/pcp/ps/pcp-ps.py | 16 +++--
7 files changed, 133 insertions(+), 47 deletions(-)
diff --git a/qa/883 b/qa/883
index 4c798c352..fdc124fcb 100755
--- a/qa/883
+++ b/qa/883
@@ -23,6 +23,7 @@ test -x $pcp_mpstat || _notrun "No pcp-mpstat(1) installed"
pcp_mpstat="$python $pcp_mpstat"
pcp_archive="-z -a archives/pcp-mpstat"
pcp_archive2="-z -a archives/pcp-mpstat2"
+pcp_archive3="-z -a archives/pcp-mpstat3"
# real QA test starts here
export LC_TIME=POSIX
@@ -66,6 +67,9 @@ pcp $pcp_archive2 mpstat -I SCPU -s 2
echo && echo === pcp-mpstat hard interrupt usage without metrics
pcp $pcp_archive2 mpstat -I CPU -s 2
+echo && echo === pcp-mpstat with log-once summary metric archive
+pcp $pcp_archive3 mpstat
+
# success, all done
status=0
exit
diff --git a/qa/883.out b/qa/883.out
index 2a975cd29..be7088cb8 100644
--- a/qa/883.out
+++ b/qa/883.out
@@ -198,3 +198,10 @@ Timestamp CPU
23:52:55 1
23:52:55 2
23:52:55 3
+
+=== pcp-mpstat with log-once summary metric archive
+Linux 4.4.0-31-generic (ram-Lenovo) 08/02/16 x86_64 (4 CPU)
+
+Timestamp CPU %usr %nice %sys %iowait %irq %soft %steal %guest %nice %idle
+23:52:55 all 4.58 0.0 26.38 4.58 0.0 0.0 0.0 0.0 0.0 63.12
+23:52:56 all 4.75 0.0 27.0 4.5 0.0 0.0 0.0 0.0 0.0 63.0
diff --git a/qa/915 b/qa/915
index 3217f9b62..e4bab5fcd 100755
--- a/qa/915
+++ b/qa/915
@@ -131,11 +131,13 @@ echo quit | pmlc -h localhost -P 2>&1 | _filter
echo; echo "== checking bad pmlogger access, expect no control"
echo quit | pmlc -h $interface -P 2>&1 | _filter
-echo; echo "== checking bad pmproxy access, expect no values"
+echo; echo "== checking bad pmproxy access, expect no values" | tee -a $here/$seq.full
pminfo -f -h localhost@$interface pmcd.feature.local 2>&1 | _filter
+cat $PCP_LOG_DIR/pmproxy/pmproxy.log >> $here/$seq.full
-echo; echo "== checking loop pmproxy access, expecting success"
+echo; echo "== checking loop pmproxy access, expecting success" | tee -a $here/$seq.full
pminfo -f -h localhost@localhost pmcd.feature.local 2>&1 | _filter
+cat $PCP_LOG_DIR/pmproxy/pmproxy.log >> $here/$seq.full
# success, all done
status=0
diff --git a/qa/archives/GNUmakefile b/qa/archives/GNUmakefile
index 31d51dd45..005ca231f 100644
--- a/qa/archives/GNUmakefile
+++ b/qa/archives/GNUmakefile
@@ -150,3 +150,23 @@ pcp-mpstat2.0:
pmlogextract -c tmp/config pcp-mpstat pcp-mpstat2
rm -rf tmp
+# variant of the mpstat2 archive with log-once summary metrics
+#
+pcp-mpstat3.0: pcp-mpstat2.0
+ rm -rf tmp
+ mkdir tmp
+ pminfo -a pcp-mpstat2 \
+ | sed \
+ -e '/^kernel.uname./d' \
+ -e '/^hinv.ncpu/d' \
+ > tmp/config-body
+ pminfo -a pcp-mpstat2 \
+ | grep -E '^kernel.uname.|^hinv.ncpu' \
+ > tmp/config-head
+ pmlogextract -c tmp/config-head -s 1 pcp-mpstat2 pcp-mpstat3-head
+ pmlogextract -c tmp/config-body pcp-mpstat2 pcp-mpstat3-body
+ pmlogextract pcp-mpstat3-head pcp-mpstat3-body pcp-mpstat3
+ mv pcp-mpstat3.0 tmp
+ ../src/stripmark tmp/pcp-mpstat3.0 pcp-mpstat3.0
+ rm -rf tmp pcp-mpstat3-head.* pcp-mpstat3-body.*
+
diff --git a/src/libpcp_web/src/query_parser.y b/src/libpcp_web/src/query_parser.y
index 20b92489c..7badd8711 100644
--- a/src/libpcp_web/src/query_parser.y
+++ b/src/libpcp_web/src/query_parser.y
@@ -1118,10 +1118,23 @@ newmetric(char *name)
else
node = newnode(N_EQ);
- node->left = newnode(N_NAME);
- node->left->value = sdsnew("metric.name");
- node->right = newnode(N_STRING);
- node->right->value = sdsnew(name);
+ if (node) {
+ if ((node->left = newnode(N_NAME)) == NULL) {
+ sdsfree(name);
+ free(node);
+ return NULL;
+ }
+ if ((node->right = newnode(N_STRING)) == NULL) {
+ sdsfree(name);
+ free(node->left);
+ free(node);
+ return NULL;
+ }
+ node->left->value = sdsnew("metric.name");
+ node->right->value = name;
+ } else {
+ sdsfree(name);
+ }
return node;
}
diff --git a/src/pcp/mpstat/pcp-mpstat.py b/src/pcp/mpstat/pcp-mpstat.py
index ad73fd6c8..5b761a5a7 100755
--- a/src/pcp/mpstat/pcp-mpstat.py
+++ b/src/pcp/mpstat/pcp-mpstat.py
@@ -15,6 +15,7 @@
# pylint: disable=bad-whitespace,line-too-long
# pylint: disable=redefined-outer-name,unnecessary-lambda
#
+import signal
from pcp import pmapi
from pcp import pmcc
import sys
@@ -588,6 +589,11 @@ class DisplayOptions:
class MpstatReport(pmcc.MetricGroupPrinter):
Machine_info_count = 0
+ ncpu = 1
+ machine = ''
+ release = ''
+ sysname = ''
+ nodename = ''
def __init__(self, cpu_util_reporter, total_interrupt_usage_reporter, soft_interrupt_usage_reporter, hard_interrupt_usage_reporter):
self.cpu_util_reporter = cpu_util_reporter
@@ -601,6 +607,7 @@ class MpstatReport(pmcc.MetricGroupPrinter):
return s + u / 1000000.0
def print_machine_info(self,group, context):
+ self.get_summary_metrics(group)
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
@@ -608,44 +615,71 @@ class MpstatReport(pmcc.MetricGroupPrinter):
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 += self.sysname + ' '
+ header_string += self.release + ' '
+ header_string += '(' + self.nodename + ') '
header_string += time_string + ' '
- header_string += group['kernel.uname.machine'].netValues[0][2] + ' '
- no_cpu = self.get_ncpu(group)
- print("%s (%s CPU)" % (header_string,no_cpu))
-
- def get_ncpu(self,group):
- return group['hinv.ncpu'].netValues[0][2]
+ header_string += self.machine + ' '
+ header_string += '(' + str(self.ncpu) + ' CPU)'
+ print(header_string)
+
+ def get_summary_metrics(self,group):
+ # extract metrics safely which may have 'logged-once' semantics;
+ # we checked earlier so know the metrics exist once at least, so
+ # fallback to previous observed value if not in latest sample -
+ # always try though in case a later sample finds updated values.
+ try:
+ self.ncpu = group['hinv.ncpu'].netValues[0][2]
+ except IndexError:
+ pass
+ try:
+ self.sysname = group['kernel.uname.sysname'].netValues[0][2]
+ except IndexError:
+ pass
+ try:
+ self.machine = group['kernel.uname.machine'].netValues[0][2]
+ except IndexError:
+ pass
+ try:
+ self.release = group['kernel.uname.release'].netValues[0][2]
+ except IndexError:
+ pass
+ try:
+ self.nodename = group['kernel.uname.nodename'].netValues[0][2]
+ except IndexError:
+ pass
def report(self,manager):
- group = manager['mpstat']
- if group['kernel.all.cpu.user'].netPrevValues is None:
- # need two fetches to report rate converted counter metrics
- return
-
- if self.Machine_info_count == 0:
- self.print_machine_info(group, manager)
- self.Machine_info_count = 1
-
- timestamp = group.contextCache.pmCtime(int(group.timestamp)).rstrip().split()
- interval_in_seconds = self.timeStampDelta(group)
- metric_repository = MetricRepository(group)
- display_options = DisplayOptions(MpstatOptions)
-
- if display_options.display_cpu_usage_summary():
- cpu_util = CpuUtil(interval_in_seconds, metric_repository)
- self.cpu_util_reporter.print_report(cpu_util, timestamp[3])
- if display_options.display_total_cpu_usage():
- total_interrupt_usage = TotalInterruptUsage(interval_in_seconds, metric_repository)
- self.total_interrupt_usage_reporter.print_report(total_interrupt_usage, timestamp[3])
- if display_options.display_hard_interrupt_usage():
- hard_interrupt_usage = HardInterruptUsage(interval_in_seconds, metric_repository, interrupts_list)
- self.hard_interrupt_usage_reporter.print_report(hard_interrupt_usage,timestamp[3])
- if display_options.display_soft_interrupt_usage():
- soft_interrupt_usage = SoftInterruptUsage(interval_in_seconds, metric_repository, soft_interrupts_list)
- self.soft_interrupt_usage_reporter.print_report(soft_interrupt_usage, timestamp[3])
+ try:
+ group = manager['mpstat']
+ if group['kernel.all.cpu.user'].netPrevValues is None:
+ # need two fetches to report rate converted counter metrics
+ self.get_summary_metrics(group)
+ return
+
+ if self.Machine_info_count == 0:
+ self.print_machine_info(group, manager)
+ self.Machine_info_count = 1
+
+ timestamp = group.contextCache.pmCtime(int(group.timestamp)).rstrip().split()
+ interval_in_seconds = self.timeStampDelta(group)
+ metric_repository = MetricRepository(group)
+ display_options = DisplayOptions(MpstatOptions)
+
+ if display_options.display_cpu_usage_summary():
+ cpu_util = CpuUtil(interval_in_seconds, metric_repository)
+ self.cpu_util_reporter.print_report(cpu_util, timestamp[3])
+ if display_options.display_total_cpu_usage():
+ total_interrupt_usage = TotalInterruptUsage(interval_in_seconds, metric_repository)
+ self.total_interrupt_usage_reporter.print_report(total_interrupt_usage, timestamp[3])
+ if display_options.display_hard_interrupt_usage():
+ hard_interrupt_usage = HardInterruptUsage(interval_in_seconds, metric_repository, interrupts_list)
+ self.hard_interrupt_usage_reporter.print_report(hard_interrupt_usage,timestamp[3])
+ if display_options.display_soft_interrupt_usage():
+ soft_interrupt_usage = SoftInterruptUsage(interval_in_seconds, metric_repository, soft_interrupts_list)
+ self.soft_interrupt_usage_reporter.print_report(soft_interrupt_usage, timestamp[3])
+ finally:
+ sys.stdout.flush()
@@ -680,5 +714,7 @@ if __name__ == '__main__':
except pmapi.pmUsageErr as usage:
usage.message()
sys.exit(1)
+ except IOError:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except KeyboardInterrupt:
pass
diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py
index 6e2e9f4ac..dbd556556 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -195,12 +195,16 @@ class ProcessStatusUtil:
return data
def process_name(self):
- data = self.__metric_repository.current_value('proc.psinfo.cmd', self.instance)[:20]
- if len(data) < 20:
- whitespace = 20 - len(data)
- res = data.ljust(whitespace + len(data), ' ')
- return res
- else:
+ try:
+ data = self.__metric_repository.current_value('proc.psinfo.cmd', self.instance)[:20]
+ if len(data) < 20:
+ whitespace = 20 - len(data)
+ res = data.ljust(whitespace + len(data), ' ')
+ return res
+ else:
+ return data
+ except TypeError:
+ data = '-'
return data
def process_name_with_args(self):
--
2.39.3

View File

@ -0,0 +1,562 @@
From e6522f0be49a214eb9aacfbc0e948b2c523457ab Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
Date: Mon, 4 Sep 2023 11:19:19 +0000
Subject: [PATCH] pcp-meminfo : initial PCP implementation of meminfo
This patch introduces the pcp-meminfo utility to the pcp.
It gives the output like /proc/meminfo
Signed-off-by: Mohith Kumar Thummaluru <mohith.k.kumar.thummaluru@oracle.com>
upstream commit-links:- 4ec9bbbb264d2766e73512ffafdaf606f17b79a0
125bde660464b40a06a3b0db3fa7acf99bd2b4d4
a5cc36434c767069d2a8e6466d68fd472e56586e
38d06784b7c6d883ce74e9bbc61601304c728791
5d249b060d3f961b242dadd28302497313652ddd
Orabug: 35759707
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
---
build/rpm/pcp.spec.in | 2 +-
build/rpm/redhat.spec | 2 +-
qa/1988 | 39 +++++++
qa/archives/GNUmakefile | 2 +-
qa/archives/mk.meminfo | 68 ++++++++++++
qa/group | 2 +
src/pcp/GNUmakefile | 1 +
src/pcp/meminfo/GNUmakefile | 44 ++++++++
src/pcp/meminfo/pcp-meminfo.1 | 77 +++++++++++++
src/pcp/meminfo/pcp-meminfo.py | 195 +++++++++++++++++++++++++++++++++
10 files changed, 429 insertions(+), 3 deletions(-)
create mode 100755 qa/1988
create mode 100755 qa/archives/mk.meminfo
create mode 100755 src/pcp/meminfo/GNUmakefile
create mode 100755 src/pcp/meminfo/pcp-meminfo.1
create mode 100755 src/pcp/meminfo/pcp-meminfo.py
diff --git a/build/rpm/pcp.spec.in b/build/rpm/pcp.spec.in
index 9e27061d3..95459942c 100755
--- a/build/rpm/pcp.spec.in
+++ b/build/rpm/pcp.spec.in
@@ -2262,7 +2262,7 @@ basic_manifest | grep -E -e 'pmiostat|pmrep|dstat|htop|pcp2csv' \
-e 'pcp-atop|pcp-dmcache|pcp-dstat|pcp-free|pcp-htop' \
-e 'pcp-ipcs|pcp-iostat|pcp-lvmcache|pcp-mpstat' \
-e 'pcp-numastat|pcp-pidstat|pcp-shping|pcp-tapestat' \
- -e 'pcp-uptime|pcp-verify|pcp-ss|pcp-ps' | \
+ -e 'pcp-uptime|pcp-verify|pcp-ss|pcp-ps|pcp-meminfo' | \
cull 'selinux|pmlogconf|pmieconf|pmrepconf' >pcp-system-tools-files
basic_manifest | keep 'sar2pcp' >pcp-import-sar2pcp-files
basic_manifest | keep 'iostat2pcp' >pcp-import-iostat2pcp-files
diff --git a/build/rpm/redhat.spec b/build/rpm/redhat.spec
index adc3a6f7f..5f54a5232 100644
--- a/build/rpm/redhat.spec
+++ b/build/rpm/redhat.spec
@@ -2434,7 +2434,7 @@ basic_manifest | grep -E -e 'pmiostat|pmrep|dstat|htop|pcp2csv' \
-e 'pcp-atop|pcp-dmcache|pcp-dstat|pcp-free|pcp-htop' \
-e 'pcp-ipcs|pcp-iostat|pcp-lvmcache|pcp-mpstat' \
-e 'pcp-numastat|pcp-pidstat|pcp-shping|pcp-tapestat' \
- -e 'pcp-uptime|pcp-verify|pcp-ss|pcp-ps' | \
+ -e 'pcp-uptime|pcp-verify|pcp-ss|pcp-ps|pcp-meminfo' | \
cull 'selinux|pmlogconf|pmieconf|pmrepconf' >pcp-system-tools-files
basic_manifest | keep 'sar2pcp' >pcp-import-sar2pcp-files
diff --git a/qa/1988 b/qa/1988
new file mode 100755
index 000000000..8dd249134
--- /dev/null
+++ b/qa/1988
@@ -0,0 +1,39 @@
+#!/bin/sh
+# PCP QA Test No. 1987
+# Exercise various pcp-meminfo(1) command options.
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+. ./common.python
+
+__version=`$python --version 2>&1 | sed -e 's/Python //'`
+case "$__version"
+in
+ 2.7.5) _notrun "Python $__version reports start seconds off-by one for some processes"
+ ;;
+esac
+
+status=1 # failure is the default!
+$sudo rm -rf $tmp.* $seq.full
+trap "cd $here; rm -rf $tmp.*; exit \$status" 0 1 2 3 15
+
+pcp_meminfo="$PCP_BINADM_DIR/pcp-meminfo"
+test -x $pcp_meminfo || _notrun "No pcp-meminfo(1) installed"
+pcp_meminfo="$python $pcp_meminfo"
+
+# real QA test starts here
+echo && echo pcp-meminfo output : Display default output
+PCP_ARCHIVE="archives/pcp-meminfo" PCP_HOSTZONE=1 PCP_ORIGIN=1 $pcp_meminfo
+
+archive_first="-a archives/pcp-meminfo -z -O +1"
+
+echo && echo pcp-meminfo output : Display output when given specified number of samples
+pcp $archive_first meminfo -s 3
+
+status=0
+exit
diff --git a/qa/archives/GNUmakefile b/qa/archives/GNUmakefile
index 005ca231f..2abf6425f 100644
--- a/qa/archives/GNUmakefile
+++ b/qa/archives/GNUmakefile
@@ -65,7 +65,7 @@ SCRIPTS = mk.diff mk.gap mk.mysql mk.schizo mk.foo+ \
mk.sample_expr mk.tzchange mk.arch-all \
mk.atop mk.hotatop mk.atop-boot mk.atop-nvidia mk.atop-threads \
mk.log-derived mk.vmstat mk.rep mk.procsched \
- mk.ipcs mk.value-test mk.rank-pred mk.cputime \
+ mk.ipcs mk.value-test mk.rank-pred mk.cputime mk.meminfo \
mk.sample-labels mk.pmproxy mk.mmv.help
CONFIGS = config.verify config.shping YWhcCi.toium.config \
diff --git a/qa/archives/mk.meminfo b/qa/archives/mk.meminfo
new file mode 100755
index 000000000..9b50717e8
--- /dev/null
+++ b/qa/archives/mk.meminfo
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# remake the free archive ...
+# this archive is intended to be checked in and not remade, this script is
+# simply a record of how it was created
+#
+
+. /etc/pcp.env
+
+tmp=/var/tmp/$$
+rm -f $tmp.*
+trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
+
+cat <<End-of-File >>$tmp.config
+log mandatory on 1 sec {
+ mem.physmem
+ mem.util.free
+ mem.util.available
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.swapCached
+ mem.util.active
+ mem.util.inactive
+ mem.util.active_anon
+ mem.util.inactive_anon
+ mem.util.active_file
+ mem.util.inactive_file
+ mem.util.unevictable
+ mem.util.mlocked
+ mem.util.swapTotal
+ mem.util.swapFree
+ mem.util.dirty
+ mem.util.writeback
+ mem.util.anonpages
+ mem.util.mapped
+ mem.util.shared
+ mem.util.slab
+ mem.util.slabReclaimable
+ mem.util.slabUnreclaimable
+ mem.util.kernelStack
+ mem.util.pageTables
+ mem.util.NFS_Unstable
+ mem.util.bounce
+ mem.vmstat.nr_writeback_temp
+ mem.util.commitLimit
+ mem.util.committed_AS
+ mem.util.vmallocTotal
+ mem.util.vmallocUsed
+ mem.util.vmallocChunk
+ mem.util.corrupthardware
+ mem.util.anonhugepages
+ mem.vmstat.nr_shmem_hugepages
+ mem.vmstat.nr_shmem_pmdmapped
+ mem.zoneinfo.nr_free_cma
+ mem.util.hugepagesTotal
+ mem.util.hugepagesFree
+ mem.util.hugepagesRsvd
+ mem.util.hugepagesSurp
+ hinv.hugepagesize
+ mem.util.directMap4k
+ mem.util.directMap2M
+ mem.util.directMap1G
+}
+End-of-File
+
+rm -f pcp-meminfo.0 pcp-meminfo.meta pcp-meminfo.index
+
+pmlogger -s 10 -c $tmp.config pcp-meminfo
diff --git a/qa/group b/qa/group
index 63c63b7f7..96b901278 100644
--- a/qa/group
+++ b/qa/group
@@ -367,6 +367,7 @@ pidstat
iostat
tapestat
ipcs
+meminfo
mpstat
# full test search
@@ -1976,8 +1977,9 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+1988 pcp meminfo python local
2101 pmda.sockets local security
2104 libpcp local security
2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/pcp/GNUmakefile b/src/pcp/GNUmakefile
index bd70c1395..8eee01260 100644
--- a/src/pcp/GNUmakefile
+++ b/src/pcp/GNUmakefile
@@ -24,6 +24,7 @@ SUBDIRS = \
htop \
iostat \
ipcs \
+ meminfo \
mpstat \
numastat \
pidstat \
diff --git a/src/pcp/meminfo/GNUmakefile b/src/pcp/meminfo/GNUmakefile
new file mode 100755
index 000000000..98411a7dc
--- /dev/null
+++ b/src/pcp/meminfo/GNUmakefile
@@ -0,0 +1,44 @@
+#
+# 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-meminfo
+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/meminfo/pcp-meminfo.1 b/src/pcp/meminfo/pcp-meminfo.1
new file mode 100755
index 000000000..462dd8dbc
--- /dev/null
+++ b/src/pcp/meminfo/pcp-meminfo.1
@@ -0,0 +1,77 @@
+'\"! tbl | mmdoc
+'\"macro stdmacro
+.\"
+.\" 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-MEMINFO 1 "PCP" "Performance Co-Pilot"
+.SH NAME
+\f3pcp-meminfo\f1 \- Report statistics for System Memory.
+.SH SYNOPSIS
+\f3pcp\f1
+[\f2pcp\ options\f1]
+\f3meminfo\f1
+[\f3\-s\f1 \f2samples\f1]
+[\f3\-a\f1 \f2archive\f1]
+[\f3\-S\f1 \f2start_time\f1]
+[\f3\-T\f1 \f2end_time\f1]
+.SH DESCRIPTION
+The
+.B pcp-meminfo
+command is used for viewing the different kinds of stats related to memory.
+Using various options it helps a user to analyze useful information related to
+the memory availability.
+This information includes total memory, memory available, shared memory, etc.
+By default
+.B pcp-meminfo
+reports live data for the local host.
+.SH OPTIONS
+.TP
+\fB\-a\fP, \fB\-\-archive\fP
+Fetch /proc/meminfo for a specified archive file
+.TP
+\fB\-s\fP, \fB\-\-samples\fP
+Get the meminfo for specified number of samples count
+.TP
+\fB\-S\fP, \fB\-\-START TIME\fP
+Filter the samples from the archive from the given time
+.TP
+\fB\-T\fP, \fB\-\-END TIME\fP
+Filter the samples from the archive till the given time
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version number and exit.
+.TP
+\fB\-?\fR, \fB\-\-help\fR
+Display usage message and exit.
+.SH NOTES
+.B pcp-meminfo
+collects information from
+.BR /proc/meminfo
+and aims to be command line and output compatible with it.
+.SH PCP ENVIRONMENT
+Environment variables with the prefix \fBPCP_\fP are used to parameterize
+the file and directory names used by PCP.
+On each installation, the
+file \fI/etc/pcp.conf\fP contains the local values for these variables.
+The \fB$PCP_CONF\fP variable may be used to specify an alternative
+configuration file, as described in \fBpcp.conf\fP(5).
+.PP
+For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
+.SH SEE ALSO
+.BR PCPIntro (1),
+.BR pcp (1),
+.BR pmParseInterval (3)
+and
+.BR environ (7).
+
diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py
new file mode 100755
index 000000000..57ac05e34
--- /dev/null
+++ b/src/pcp/meminfo/pcp-meminfo.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env 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-arguments,too-many-lines, bad-continuation
+# pylint: disable=redefined-outer-name,unnecessary-lambda
+#
+
+import sys
+import time
+from pcp import pmapi, pmcc
+
+METRICS = ["mem.physmem",
+ "mem.util.free",
+ "mem.util.available",
+ "mem.util.bufmem",
+ "mem.util.cached",
+ "mem.util.swapCached",
+ "mem.util.active",
+ "mem.util.inactive",
+ "mem.util.active_anon",
+ "mem.util.inactive_anon",
+ "mem.util.active_file",
+ "mem.util.inactive_file",
+ "mem.util.unevictable",
+ "mem.util.mlocked",
+ "mem.util.swapTotal",
+ "mem.util.swapFree",
+ "mem.util.dirty",
+ "mem.util.writeback",
+ "mem.util.anonpages",
+ "mem.util.mapped",
+ "mem.util.shared",
+ "mem.util.slab",
+ "mem.util.slabReclaimable",
+ "mem.util.slabUnreclaimable",
+ "mem.util.kernelStack",
+ "mem.util.pageTables",
+ "mem.util.NFS_Unstable",
+ "mem.util.bounce",
+ "mem.vmstat.nr_writeback_temp",
+ "mem.util.commitLimit",
+ "mem.util.committed_AS",
+ "mem.util.vmallocTotal",
+ "mem.util.vmallocUsed",
+ "mem.util.vmallocChunk",
+ "mem.util.corrupthardware",
+ "mem.util.anonhugepages",
+ "mem.vmstat.nr_shmem_hugepages",
+ "mem.vmstat.nr_shmem_pmdmapped",
+ "mem.zoneinfo.nr_free_cma",
+ "mem.util.hugepagesTotal",
+ "mem.util.hugepagesFree",
+ "mem.util.hugepagesRsvd",
+ "mem.util.hugepagesSurp",
+ "hinv.hugepagesize",
+ "mem.util.directMap4k",
+ "mem.util.directMap2M",
+ "mem.util.directMap1G"]
+
+METRICS_DESC = ["MemTotal",
+ "MemFree",
+ "MemAvailable",
+ "Buffers",
+ "Cached",
+ "SwapCached",
+ "Active",
+ "Inactive",
+ "Active(anon)",
+ "Inactive(anon)",
+ "Active(file)",
+ "Inactive(file)",
+ "Unevictable",
+ "Mlocked",
+ "SwapTotal",
+ "SwapFree",
+ "Dirty",
+ "Writeback",
+ "AnonPages",
+ "Mapped",
+ "Shmem",
+ "Slab",
+ "SReclaimable",
+ "SUnreclaim",
+ "KernelStack",
+ "PageTables",
+ "NFS_Unstable",
+ "Bounce",
+ "WritebackTmp",
+ "CommitLimit",
+ "Committed_AS",
+ "VmallocTotal",
+ "VmallocUsed",
+ "VmallocChunk",
+ "HardwareCorrupted",
+ "AnonHugePages",
+ "ShmemHugePages",
+ "ShmemPmdMapped",
+ "CmaFree",
+ "HugePages_Total_NO_kb",
+ "HugePages_Free_NO_kb",
+ "HugePages_Rsvd_NO_kb",
+ "HugePages_Surp_NO_kb",
+ "Hugepagesize",
+ "DirectMap4k",
+ "DirectMap2M",
+ "DirectMap1G"]
+
+class MeminfoReport(pmcc.MetricGroupPrinter):
+ samples = 0
+
+ def __init__(self, samples):
+ self.samples = samples
+
+ def getMetricName(self, idx):
+ metric_name = ""
+ units = ""
+ if METRICS_DESC[idx][-6:] == "_NO_kb":
+ metric_name = METRICS_DESC[idx][:-6]
+ else:
+ metric_name = METRICS_DESC[idx]
+ units = "kB"
+ return metric_name, units
+
+ def report(self, manager):
+ group = manager["meminfo"]
+
+ opts.pmGetOptionSamples()
+
+ t_s = group.contextCache.pmLocaltime(int(group.timestamp))
+ time_string = time.strftime(MeminfoOptions.timefmt, t_s.struct_time())
+ print(time_string)
+
+ idx = 0
+ for metric in METRICS:
+ try:
+ val = group[metric].netValues[0][2]
+ except IndexError:
+ metric_name, units = self.getMetricName(idx)
+ print(F"{metric_name:17} : NA")
+
+ idx += 1
+ continue
+
+ metric_name, units = self.getMetricName(idx)
+ print(F"{metric_name:17} : {val} {units}")
+
+ idx += 1
+ print()
+
+class MeminfoOptions(pmapi.pmOptions):
+ context = None
+ timefmt = "%H:%M:%S"
+ samples = 0
+
+ def __init__(self):
+ pmapi.pmOptions.__init__(self, "a:s:S:T:z:A:t:")
+ self.pmSetLongOptionStart()
+ self.pmSetLongOptionFinish()
+ self.pmSetLongOptionHelp()
+
+if __name__ == '__main__':
+ try:
+ opts = MeminfoOptions()
+ mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
+ MeminfoOptions.context = mngr.type
+
+ missing = mngr.checkMissingMetrics(METRICS)
+ if missing is not None:
+ sys.stderr.write(F"Error:Metric is {missing} missing\n")
+ sys.exit(1)
+
+ mngr["meminfo"] = METRICS
+ mngr.printer = MeminfoReport(opts.samples)
+ sts = mngr.run()
+ sys.exit(sts)
+
+ except pmapi.pmErr as error:
+ sys.stderr.write(F"{error.progname()} {error.message()}")
+ except pmapi.pmUsageErr as usage:
+ usage.message()
+ sys.exit(1)
+ except KeyboardInterrupt:
+ pass
--
2.39.3

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,623 @@
From 80c92de1ac7e589956d974cb6f8c00dfcb999764 Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
Date: Mon, 4 Sep 2023 13:52:49 +0000
Subject: [PATCH] pcp-buddyinfo: initial commit of pcp implementation of
buddyinfo
(cherry picked from commit ebf62cada9887eb521d8269ff9bb9b84345d326a)
Orabug: 35660932
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
---
qa/1990 | 42 ++++++
qa/1990.out | 117 +++++++++++++++++
qa/archives/mk.buddyinfo | 39 ++++++
qa/group | 1 +
src/pcp/GNUmakefile | 1 +
src/pcp/buddyinfo/GNUmakefile | 44 +++++++
src/pcp/buddyinfo/pcp-buddyinfo.1 | 94 ++++++++++++++
src/pcp/buddyinfo/pcp-buddyinfo.py | 197 +++++++++++++++++++++++++++++
8 files changed, 535 insertions(+)
create mode 100755 qa/1990
create mode 100644 qa/1990.out
create mode 100755 qa/archives/mk.buddyinfo
create mode 100644 src/pcp/buddyinfo/GNUmakefile
create mode 100644 src/pcp/buddyinfo/pcp-buddyinfo.1
create mode 100644 src/pcp/buddyinfo/pcp-buddyinfo.py
diff --git a/qa/1990 b/qa/1990
new file mode 100755
index 000000000..47a5f70f9
--- /dev/null
+++ b/qa/1990
@@ -0,0 +1,42 @@
+#!/bin/sh
+# PCP QA Test No. 1990
+# Exercise various pcp-buddyinfo(1) command options.
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+. ./common.python
+
+__version=`$python --version 2>&1 | sed -e 's/Python //'`
+case "$__version"
+in
+ 2.7.5) _notrun "Python $__version reports start seconds off-by one for some processes"
+ ;;
+esac
+
+status=1 # failure is the default!
+$sudo rm -rf $tmp.* $seq.full
+trap "cd $here; rm -rf $tmp.*; exit \$status" 0 1 2 3 15
+
+pcp_buddyinfo="$PCP_BINADM_DIR/pcp-buddyinfo"
+test -x $pcp_buddyinfo || _notrun "No pcp-buddyinfo(1) installed"
+pcp_buddyinfo="$python $pcp_buddyinfo"
+
+# real QA test starts here
+echo && echo pcp-buddyinfo output : Display default output
+PCP_ARCHIVE="archives/pcp-buddyinfo" PCP_HOSTZONE=1 $pcp_buddyinfo
+
+archive_first="-a archives/pcp-buddyinfo -z -O +1"
+
+echo && echo pcp-buddyinfo output : Display output
+pcp $archive_first buddyinfo
+
+echo && echo pcp-buddyinfo output : Display output when given specified number of samples
+pcp $archive_first buddyinfo -s 3
+
+status=0
+exit
diff --git a/qa/1990.out b/qa/1990.out
new file mode 100644
index 000000000..adafa9287
--- /dev/null
+++ b/qa/1990.out
@@ -0,0 +1,117 @@
+QA output created by 1990
+
+pcp-buddyinfo output : Display default output
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:21 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:21 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:21 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:24 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:24 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:24 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:27 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:27 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:27 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:30 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:30 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:30 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:33 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:33 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:33 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:36 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:36 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:36 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:39 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:39 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:39 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:42 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:42 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:42 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:45 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:45 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:45 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+
+pcp-buddyinfo output : Display output
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:19 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:19 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:19 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:22 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:22 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:22 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:25 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:25 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:25 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:28 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:28 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:28 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:31 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:31 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:31 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:34 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:34 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:34 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:37 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:37 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:37 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:40 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:40 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:40 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:43 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:43 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:43 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:46 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:46 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:46 Normal node0 1657 4118 1862 671 220 120 66 29 21 26 3040
+
+pcp-buddyinfo output : Display output when given specified number of samples
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:19 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:19 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:19 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:22 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:22 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:22 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
+Linux 5.4.17-2136.317.5.3.el8uek.x86_64 (localhost.localdomain) 08/02/23 x86_64 (4 CPU)
+TimeStamp Normal Nodes Order0 Order1 Order2 Order3 Order4 Order5 Order6 Order7 Order8 Order9 Order10
+15:53:25 DMA node0 0 0 0 0 0 0 0 0 1 1 3
+15:53:25 DMA32 node0 3 1 1 1 2 3 2 3 3 2 803
+15:53:25 Normal node0 1720 4118 1862 671 220 120 66 29 21 26 3040
diff --git a/qa/archives/mk.buddyinfo b/qa/archives/mk.buddyinfo
new file mode 100755
index 000000000..c9a6707ca
--- /dev/null
+++ b/qa/archives/mk.buddyinfo
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# remake the pcp-buddyinfo archive ...
+# this archive is intended to be checked in and not remade, this script is
+# simply a record of how it was created
+#
+
+. /etc/pcp.env
+
+tmp=/var/tmp/$$
+rm -f $tmp.*
+trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
+
+cat <<End-of-File >>$tmp.config
+log advisory on once{
+ kernel.uname.sysname
+ kernel.uname.release
+ kernel.uname.nodename
+ kernel.uname.machine
+ hinv.ncpu
+}
+log advisory on 10 seconds {
+ mem.buddyinfo.pages
+ mem.buddyinfo.total
+}
+End-of-File
+
+rm -f pcp-buddyinfo.0 pcp-buddyinfo.index pcp-buddyinfo.meta pcp-buddyinfo.*
+
+if pmlogger -s 5 -c $tmp.config pcp-buddyinfo; then
+ xz pcp-buddyinfo.0
+ xz pcp-buddyinfo.index
+ xz pcp-buddyinfo.meta
+else
+ echo "Argh: pmlogger failed ..."
+ cat pmlogger.log
+fi
+
+
diff --git a/qa/group b/qa/group
index 254e6cb42..42863daa4 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,8 +1980,9 @@ x11
1986 pmfind local
1988 pcp meminfo python local
1989 pcp slabinfo python local
+1990 pcp buddyinfo python local
2101 pmda.sockets local security
2104 libpcp local security
2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/src/pcp/GNUmakefile b/src/pcp/GNUmakefile
index d4675a8d9..4f656ce5b 100644
--- a/src/pcp/GNUmakefile
+++ b/src/pcp/GNUmakefile
@@ -18,6 +18,7 @@ include $(TOPDIR)/src/include/builddefs
SUBDIRS = \
atop \
+ buddyinfo \
dmcache \
dstat \
free \
diff --git a/src/pcp/buddyinfo/GNUmakefile b/src/pcp/buddyinfo/GNUmakefile
new file mode 100644
index 000000000..121b358d3
--- /dev/null
+++ b/src/pcp/buddyinfo/GNUmakefile
@@ -0,0 +1,44 @@
+#
+# 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-buddyinfo
+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/buddyinfo/pcp-buddyinfo.1 b/src/pcp/buddyinfo/pcp-buddyinfo.1
new file mode 100644
index 000000000..26c06ba69
--- /dev/null
+++ b/src/pcp/buddyinfo/pcp-buddyinfo.1
@@ -0,0 +1,94 @@
+'\"! tbl | mmdoc
+'\"macro stdmacro
+.\"
+.\" Man page for pcp-buddyinfo
+.\" 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-BUDDYINFO 1 "PCP" "Performance Co-Pilot"
+
+.SH NAME
+\fBpcp-buddyinfo\fP \- Report statistics for buddy algorithm shown in cat /proc/buddyinfo
+
+.SH SYNOPSIS
+\fBpcp\fP [\fBpcp options\fP] \fBbuddyinfo\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 \fBpcp-buddyinfo\fP command is used for viewing different stats related to buddyinfo. It helps users analyze useful information related to the buddy algorithm. The information includes the total number of zones that are currently active, order pages etc. By default, \fBpcp-buddyinfo\fP reports live data for the local host.
+
+The statistics shown are as follows:
+
+.TS
+lfB lfB
+l lx.
+HEADER DESCRIPTION
+_ _
+Normal zones available
+Nodes available nodes
+Order0 available pages of order 0
+Order1 available pages of order 1
+Order2 available pages of order 2
+Order3 available pages of order 3
+Order4 available pages of order 4
+Order5 available pages of order 5
+Order6 available pages of order 6
+Order7 available pages of order 7
+Order8 available pages of order 8
+Order9 available pages of order 9
+Order10 available pages of order 10
+.TE
+
+
+Each column represents the number of pages of a certain order (a certain size) that are available at any given time. For example, for zone DMA (direct memory access), there are 90 of 2^(0*PAGE_SIZE) chunks of memory. Similarly, there are 6 of 2^(1*PAGE_SIZE) chunks, and 2 of 2^(2*PAGE_SIZE) chunks of memory available.
+
+The DMA row references the first 16 MB on a system, the HighMem row references all memory greater than 4 GB on a system, and the Normal row references all memory in between.
+
+.SH OPTIONS
+.TP
+\fB-a\fP, \fB\-\-archive\fP
+Fetch /proc/buddyinfo for a specified archive file
+
+.TP
+\fB-s\fP, \fB\-\-samples\fP
+Get the buddyinfo for the specified number of samples count
+
+.TP
+\fB-z\fP, \fB\-\-hostzone\fP
+Set the reporting timezone to the local time of metrics source
+
+.TP
+\fB-Z\fP, \fB\-\-timezone\fP
+Set the reporting timezone
+
+.TP
+\fB-V\fP, \fB\-\-version\fP
+Display the version number and exit.
+
+.TP
+\fB-?\fP, \fB\-\-help\fP
+Display the usage message and exit.
+
+.SH NOTES
+\fBpcp-buddyinfo\fP collects information from \fI/proc/buddyinfo\fP and aims to be command-line and output compatible with it.
+
+.SH PCP ENVIRONMENT
+Environment variables with the prefix \fBPCP_\fP are used to parameterize the file and directory names used by PCP. On each installation, the file \fI/etc/pcp.conf\fP contains the local values for these variables. The \fB$PCP_CONF\fP variable may be used to specify an alternative configuration file, as described in \fBpcp.conf\fP(5).
+
+For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
+
+.SH SEE ALSO
+.BR PCPIntro(1),
+.BR pcp(1),
+.BR pmParseInterval(3),
+.BR environ(7).
diff --git a/src/pcp/buddyinfo/pcp-buddyinfo.py b/src/pcp/buddyinfo/pcp-buddyinfo.py
new file mode 100644
index 000000000..012fe7cec
--- /dev/null
+++ b/src/pcp/buddyinfo/pcp-buddyinfo.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env 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-arguments,too-many-lines, bad-continuation
+# 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_MECTRICS= ["kernel.uname.sysname","kernel.uname.release",
+ "kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"]
+BUDDYSTAT_METRICS = ["mem.buddyinfo.pages","mem.buddyinfo.total"]
+
+ALL_METRICS = BUDDYSTAT_METRICS + SYS_MECTRICS
+
+def adjust_length(name,size):
+ return name.ljust(size)
+
+class ReportingMetricRepository:
+
+ def __init__(self,group):
+ self.group=group
+ self.current_cached_values = {}
+
+ def __fetch_current_value(self,metric):
+ val=dict(map(lambda x: (x[1], x[2]), self.group[metric].netValues))
+ return dict(val)
+
+ def current_value(self,metric):
+ if metric not 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 BuddyStatUtil:
+ def __init__(self,metrics_repository):
+ self.__metric_repository=metrics_repository
+ self.report=ReportingMetricRepository(self.__metric_repository)
+
+ def buddy_pages(self):
+ return self.report.current_value('mem.buddyinfo.pages')
+
+ def buddy_total_pages(self):
+ return self.report.current_value('mem.buddyinfo.total')
+
+ def names(self):
+ data = self.report.current_value('mem.buddyinfo.pages')
+ return data.keys()
+
+class BuddyinfoReport(pmcc.MetricGroupPrinter):
+ def __init__(self,samples,group,context):
+ self.samples = samples
+ self.group=group
+ self.context=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("%x", 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_header(self,header_indentation,value_indentation):
+ value_indentation+=" "*2
+ print("TimeStamp"+ " "+header_indentation + "Normal" + header_indentation+value_indentation+" " +
+ "Nodes" + header_indentation + "Order0" + value_indentation +
+ "Order1" + value_indentation +"Order2" + value_indentation + "Order3" + value_indentation +
+ "Order4" + value_indentation +"Order5" + value_indentation + "Order6" + value_indentation +
+ "Order7" + value_indentation +"Order8" + value_indentation + "Order9" +
+ value_indentation + "Order10")
+
+ def __print_values(self,timestamp,header_indentation,\
+ value_indentation,buddystatus):
+ names=buddystatus.names()
+ pages=buddystatus.buddy_pages()
+ order_set = set()
+ nodes_set = set()
+ no_of_nodes_set = set()
+ for name in names:
+ part=name.split('::')
+ if len(part)==3:
+ nodes_set.add(part[0])
+ order_set.add(part[1])
+ no_of_nodes_set.add(part[2])
+ def __extract_numeric_part(element):
+ return int(element[5:])
+ for Normal in sorted(nodes_set):
+ for node in no_of_nodes_set:
+ value=""
+ for order in sorted(order_set,key=__extract_numeric_part):
+ nodename = adjust_length(Normal,9) if len(Normal) < 9 else Normal
+ key = f"{Normal}::{order}::{node}"
+ data = str(pages.get(key,0))
+ value += adjust_length(data,8) if len(data) < 8 else data
+ value += value_indentation
+
+ print("%s %s %s %s %s %s %s "%(timestamp,header_indentation,nodename,header_indentation,node,
+ header_indentation,value))
+
+ def print_report(self,group,timestamp,header_indentation,value_indentation,manager_buddyinfo):
+
+ def __print_buddy_status():
+ buddystatus = BuddyStatUtil(manager_buddyinfo)
+ if buddystatus.names():
+ try:
+ self.__print_machine_info(group)
+ self.__print_header(header_indentation, value_indentation)
+ self.__print_values(timestamp, header_indentation, value_indentation, buddystatus)
+ except IndexError:
+ print("Incorrect machine info due to some missing metrics")
+ return
+ else:
+ return
+
+ if self.context != PM_CONTEXT_ARCHIVE and self.samples is None:
+ __print_buddy_status()
+ sys.exit(0)
+ elif self.context == PM_CONTEXT_ARCHIVE and self.samples is None:
+ __print_buddy_status()
+ elif self.samples >=1:
+ __print_buddy_status()
+ self.samples-=1
+
+ def report(self, manager):
+ group = manager["sysinfo"]
+ self.samples=opts.pmGetOptionSamples()
+ t_s = group.contextCache.pmLocaltime(int(group.timestamp))
+ timestamp = time.strftime(BuddyinfoOptions.timefmt, t_s.struct_time())
+ header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
+ value_indentation = ((len(header_indentation) + 2) - len(timestamp)) * " "
+ self.print_report(group,timestamp,header_indentation,value_indentation,manager['buddyinfo'])
+
+class BuddyinfoOptions(pmapi.pmOptions):
+ timefmt = "%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 = BuddyinfoOptions()
+ 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:some metrics are unavailable",missing)
+ sys.exit(1)
+ mngr["buddyinfo"] = BUDDYSTAT_METRICS
+ mngr["sysinfo"] = SYS_MECTRICS
+ mngr["allinfo"]=ALL_METRICS
+ mngr.printer = BuddyinfoReport(opts.samples,mngr,opts.context)
+ 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.39.3

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,455 @@
From 4a2cf79cd483bdb55feab3a2053294495525652d Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Thu, 12 Oct 2023 17:46:07 +0530
Subject: [PATCH] Fixed pcp-zoneinfo to replay ol7 archive on ol8 machine For
more info https://github.com/performancecopilot/pcp/pull/1814
Orabug:35903733
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pcp/zoneinfo/pcp-zoneinfo.py | 315 +++++++++++++++++++------------
1 file changed, 193 insertions(+), 122 deletions(-)
diff --git a/src/pcp/zoneinfo/pcp-zoneinfo.py b/src/pcp/zoneinfo/pcp-zoneinfo.py
index 32fc5ee..e5eabbd 100755
--- a/src/pcp/zoneinfo/pcp-zoneinfo.py
+++ b/src/pcp/zoneinfo/pcp-zoneinfo.py
@@ -21,38 +21,44 @@ import sys
import time
from pcp import pmapi, pmcc
from cpmapi import PM_CONTEXT_ARCHIVE
+from cpmapi import PM_CONTEXT_HOST
-SYS_MECTRICS = ["kernel.uname.sysname","kernel.uname.release",
- "kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"]
-
-ZONESTAT_METRICS = [ "mem.zoneinfo.free","mem.zoneinfo.min","mem.zoneinfo.low","mem.zoneinfo.high",
- "mem.zoneinfo.scanned","mem.zoneinfo.spanned","mem.zoneinfo.present","mem.zoneinfo.managed",
- "mem.zoneinfo.nr_free_pages","mem.zoneinfo.nr_alloc_batch","mem.zoneinfo.nr_inactive_anon",
- "mem.zoneinfo.nr_active_anon","mem.zoneinfo.nr_inactive_file","mem.zoneinfo.nr_active_file",
- "mem.zoneinfo.nr_unevictable","mem.zoneinfo.nr_mlock","mem.zoneinfo.nr_anon_pages",
- "mem.zoneinfo.nr_mapped","mem.zoneinfo.nr_file_pages","mem.zoneinfo.nr_dirty",
- "mem.zoneinfo.nr_writeback","mem.zoneinfo.nr_slab_reclaimable","mem.zoneinfo.nr_slab_unreclaimable",
- "mem.zoneinfo.nr_page_table_pages","mem.zoneinfo.nr_kernel_stack","mem.zoneinfo.nr_unstable",
- "mem.zoneinfo.nr_bounce","mem.zoneinfo.nr_vmscan_write","mem.zoneinfo.nr_vmscan_immediate_reclaim",
- "mem.zoneinfo.nr_writeback_temp","mem.zoneinfo.nr_isolated_anon","mem.zoneinfo.nr_isolated_file",
- "mem.zoneinfo.nr_shmem","mem.zoneinfo.nr_dirtied","mem.zoneinfo.nr_written","mem.zoneinfo.numa_hit",
- "mem.zoneinfo.numa_miss","mem.zoneinfo.numa_foreign","mem.zoneinfo.numa_interleave",
- "mem.zoneinfo.numa_local","mem.zoneinfo.numa_other","mem.zoneinfo.workingset_refault",
- "mem.zoneinfo.workingset_activate","mem.zoneinfo.workingset_nodereclaim",
- "mem.zoneinfo.nr_anon_transparent_hugepages","mem.zoneinfo.nr_free_cma","mem.zoneinfo.cma",
- "mem.zoneinfo.nr_swapcached","mem.zoneinfo.nr_shmem_hugepages","mem.zoneinfo.nr_shmem_pmdmapped",
- "mem.zoneinfo.nr_file_hugepages","mem.zoneinfo.nr_file_pmdmapped",
- "mem.zoneinfo.nr_kernel_misc_reclaimable","mem.zoneinfo.nr_foll_pin_acquired",
- "mem.zoneinfo.nr_foll_pin_released","mem.zoneinfo.workingset_refault_anon",
- "mem.zoneinfo.workingset_refault_file","mem.zoneinfo.workingset_active_anon",
- "mem.zoneinfo.workingset_active_file","mem.zoneinfo.workingset_restore_anon",
- "mem.zoneinfo.workingset_restore_file","mem.zoneinfo.nr_zspages",
- "mem.zoneinfo.nr_zone_inactive_file","mem.zoneinfo.nr_zone_active_file",
- "mem.zoneinfo.nr_zone_inactive_anon","mem.zoneinfo.nr_zone_active_anon",
- "mem.zoneinfo.nr_zone_unevictable","mem.zoneinfo.nr_zone_write_pending",
- "mem.zoneinfo.protection" ]
-
-ALL_METRICS = ZONESTAT_METRICS + SYS_MECTRICS
+# By default we are assuming we are running on the latest pcp version
+# Later will figure out the version and will change the newVersionFlag
+
+newVersionFlag = True
+
+SYS_METRICS = ["kernel.uname.sysname", "kernel.uname.release",
+ "kernel.uname.nodename", "kernel.uname.machine", "hinv.ncpu"]
+
+ZONESTAT_METRICS = ["mem.zoneinfo.managed", "mem.zoneinfo.nr_mlock", "mem.zoneinfo.present",
+ "mem.zoneinfo.scanned", "mem.zoneinfo.nr_unstable", "mem.zoneinfo.nr_page_table_pages",
+ "mem.zoneinfo.nr_shmem", "mem.zoneinfo.nr_free_pages", "mem.zoneinfo.nr_active_file",
+ "mem.zoneinfo.nr_dirty", "mem.zoneinfo.nr_writeback", "mem.zoneinfo.free",
+ "mem.zoneinfo.nr_unevictable", "mem.zoneinfo.nr_alloc_batch", "mem.zoneinfo.low",
+ "mem.zoneinfo.nr_slab_reclaimable", "mem.zoneinfo.nr_kernel_stack", "mem.zoneinfo.numa_interleave",
+ "mem.zoneinfo.workingset_nodereclaim", "mem.zoneinfo.nr_isolated_anon", "mem.zoneinfo.nr_bounce",
+ "mem.zoneinfo.numa_other", "mem.zoneinfo.nr_file_pages", "mem.zoneinfo.numa_hit",
+ "mem.zoneinfo.nr_isolated_file", "mem.zoneinfo.nr_anon_transparent_hugepages",
+ "mem.zoneinfo.nr_inactive_file", "mem.zoneinfo.spanned", "mem.zoneinfo.nr_written",
+ "mem.zoneinfo.numa_foreign", "mem.zoneinfo.nr_vmscan_write", "mem.zoneinfo.nr_free_cma",
+ "mem.zoneinfo.nr_writeback_temp", "mem.zoneinfo.nr_slab_unreclaimable",
+ "mem.zoneinfo.nr_vmscan_immediate_reclaim", "mem.zoneinfo.nr_mapped", "mem.zoneinfo.nr_anon_pages",
+ "mem.zoneinfo.high", "mem.zoneinfo.protection", "mem.zoneinfo.numa_local", "mem.zoneinfo.numa_miss",
+ "mem.zoneinfo.nr_active_anon", "mem.zoneinfo.workingset_refault", "mem.zoneinfo.nr_dirtied",
+ "mem.zoneinfo.workingset_activate", "mem.zoneinfo.nr_inactive_anon", "mem.zoneinfo.min"]
+
+ZONESTAT_NEW_METRICS = ["mem.zoneinfo.nr_foll_pin_acquired", "mem.zoneinfo.nr_foll_pin_released",
+ "mem.zoneinfo.nr_zspages", "mem.zoneinfo.workingset_restore_anon",
+ "mem.zoneinfo.workingset_refault_anon", "mem.zoneinfo.nr_zone_active_anon",
+ "mem.zoneinfo.nr_zone_inactive_file", "mem.zoneinfo.nr_swapcached",
+ "mem.zoneinfo.nr_file_pmdmapped", "mem.zoneinfo.workingset_active_file",
+ "mem.zoneinfo.workingset_refault_file", "mem.zoneinfo.cma",
+ "mem.zoneinfo.workingset_active_anon", "mem.zoneinfo.nr_zone_write_pending",
+ "mem.zoneinfo.nr_shmem_pmdmapped", "mem.zoneinfo.nr_shmem_hugepages",
+ "mem.zoneinfo.nr_kernel_misc_reclaimable", "mem.zoneinfo.nr_zone_active_file",
+ "mem.zoneinfo.nr_file_hugepages", "mem.zoneinfo.nr_zone_unevictable",
+ "mem.zoneinfo.nr_zone_inactive_anon", "mem.zoneinfo.workingset_restore_file"]
ZONEINFO_PER_NODE = {
"nr_inactive_anon" : "mem.zoneinfo.nr_inactive_anon",
@@ -71,104 +77,109 @@ ZONEINFO_PER_NODE = {
"nr_writeback" : "mem.zoneinfo.nr_writeback",
"nr_writeback_temp" : "mem.zoneinfo.nr_writeback_temp",
"nr_shmem" : "mem.zoneinfo.nr_shmem",
- "nr_shmem_hugepages" : "mem.zoneinfo.nr_shmem_hugepages",
- "nr_shmem_pmdmapped" : "mem.zoneinfo.nr_shmem_pmdmapped",
- "nr_file_hugepages" : "mem.zoneinfo.nr_file_hugepages",
- "nr_file_pmdmapped" : "mem.zoneinfo.nr_file_pmdmapped",
"nr_anon_transparent_hugepages" : "mem.zoneinfo.nr_anon_transparent_hugepages",
"nr_unstable" : "mem.zoneinfo.nr_unstable",
"nr_vmscan_write" : "mem.zoneinfo.nr_vmscan_write",
"nr_vmscan_immediate_reclaim" : "mem.zoneinfo.nr_vmscan_immediate_reclaim",
"nr_dirtied" : "mem.zoneinfo.nr_dirtied",
"nr_written" : "mem.zoneinfo.nr_written",
+ "nr_shmem_hugepages" : "mem.zoneinfo.nr_shmem_hugepages",
+ "nr_shmem_pmdmapped" : "mem.zoneinfo.nr_shmem_pmdmapped",
+ "nr_file_hugepages" : "mem.zoneinfo.nr_file_hugepages",
+ "nr_file_pmdmapped" : "mem.zoneinfo.nr_file_pmdmapped",
"nr_kernel_misc_reclaimable" : "mem.zoneinfo.nr_kernel_misc_reclaimable"
}
-ZONEINFO_PAGE_INFO = {
- "pages free" : "mem.zoneinfo.free",
- " min" : "mem.zoneinfo.min",
- " low" : "mem.zoneinfo.low",
- " high" : "mem.zoneinfo.high",
- " spanned" : "mem.zoneinfo.spanned",
- " present" : "mem.zoneinfo.present",
- " managed" : "mem.zoneinfo.managed"
-}
+ZONEINFO_PAGE_INFO = [
+ ("pages free" , "mem.zoneinfo.free"),
+ (" min" , "mem.zoneinfo.min"),
+ (" low" , "mem.zoneinfo.low"),
+ (" high" , "mem.zoneinfo.high"),
+ (" spanned", "mem.zoneinfo.spanned"),
+ (" present", "mem.zoneinfo.present"),
+ (" managed", "mem.zoneinfo.managed")
+]
ZONEINFO_NUMBER_ZONE = {
"nr_free_pages" : "mem.zoneinfo.nr_free_pages",
- "nr_zone_inactive_anon" : "mem.zoneinfo.nr_zone_inactive_anon",
- "nr_zone_active_anon" : "mem.zoneinfo.nr_zone_active_anon",
- "nr_zone_inactive_file" : "mem.zoneinfo.nr_zone_inactive_file",
- "nr_zone_active_file" : "mem.zoneinfo.nr_zone_active_file",
- "nr_zone_unevictable" : "mem.zoneinfo.nr_zone_unevictable",
- "nr_zone_write_pending" : "mem.zoneinfo.nr_zone_write_pending",
"nr_mlock" : "mem.zoneinfo.nr_mlock",
"nr_page_table_pages" : "mem.zoneinfo.nr_page_table_pages",
"nr_kernel_stack" : "mem.zoneinfo.nr_kernel_stack",
"nr_bounce" : "mem.zoneinfo.nr_bounce",
- "nr_zspages" : "mem.zoneinfo.nr_zspages",
"nr_free_cma" : "mem.zoneinfo.nr_free_cma",
"numa_hit" : "mem.zoneinfo.numa_hit",
"numa_miss" : "mem.zoneinfo.numa_miss",
"numa_foreign" : "mem.zoneinfo.numa_foreign",
"numa_interleave" : "mem.zoneinfo.numa_interleave",
+ "numa_other" : "mem.zoneinfo.numa_other",
+ "nr_zone_active_anon" : "mem.zoneinfo.nr_zone_active_anon",
+ "nr_zone_inactive_file" : "mem.zoneinfo.nr_zone_inactive_file",
+ "nr_zone_active_file" : "mem.zoneinfo.nr_zone_active_file",
+ "nr_zone_unevictable" : "mem.zoneinfo.nr_zone_unevictable",
+ "nr_zone_write_pending" : "mem.zoneinfo.nr_zone_write_pending",
+ "nr_zspages" : "mem.zoneinfo.nr_zspages",
"numa_local" : "mem.zoneinfo.numa_local",
- "numa_other" : "mem.zoneinfo.numa_other"
+ "nr_zone_inactive_anon" : "mem.zoneinfo.nr_zone_inactive_anon"
}
+
class ReportingMetricRepository:
- def __init__(self,group):
+ def __init__(self, group):
self.group = group
self.current_cached_values = {}
- def __sorted(self,data):
+ def __sorted(self, data):
return dict(sorted(data.items(), key=lambda item: item[0].lower()))
- def __fetch_current_value(self,metric):
+ 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):
+ def current_value(self, metric):
if metric not 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
+ first_value = self.__fetch_current_value(metric)
+ self.current_cached_values[metric] = first_value
return self.current_cached_values[metric]
+
class ZoneStatUtil:
- def __init__(self,metrics_repository):
- self.__metric_repository=metrics_repository
- self.report=ReportingMetricRepository(self.__metric_repository)
+ def __init__(self, metrics_repository):
+ self.__metric_repository = metrics_repository
+ self.report = ReportingMetricRepository(self.__metric_repository)
def names(self):
data = self.report.current_value('mem.zoneinfo.present')
- return data.keys()
+ return sorted(data.keys())
- def metric_value(self,metric,node):
- data = self.report.current_value(metric)
- data = data.get(node,"0")
- if data != "0":
- data = data/4
- return int(data)
+ def metric_value(self, metric, node):
+ if metric in ALL_METRICS:
+ data = self.report.current_value(metric)
+ data = data.get(node, "0")
+ if data != "0":
+ data = data/4
+ return int(data)
+ return None
- #creating the list of protection values for particular node
- def protection(self,node):
+ # creating the list of protection values for particular node
+ def protection(self, node):
data = self.report.current_value("mem.zoneinfo.protection")
- values = [value // 4 for key, value in data.items() if key.startswith(node)]
- return values
+ return [value // 4 for key, value in data.items() if key.startswith(node)]
- def protection_names(self,node_name):
+ def protection_names(self, node_name):
data = self.report.current_value("mem.zoneinfo.protection")
- filtered_nodes = {key.split("::" + node_name)[0] + "::" + node_name for key in data.keys() if node_name in key}
- if len(filtered_nodes) == 0:
- filtered_nodes = None
- return filtered_nodes
+ return {
+ key.split("::" + node_name)[0] + "::" + node_name
+ for key in data.keys()
+ if node_name in key
+ } or None
+
class ZoneinfoReport(pmcc.MetricGroupPrinter):
- def __init__(self,samples,group,context):
+ def __init__(self, samples, group, context):
self.samples = samples
self.group = group
self.context = context
@@ -191,64 +202,84 @@ class ZoneinfoReport(pmcc.MetricGroupPrinter):
header_string += context['kernel.uname.machine'].netValues[0][2] + ' '
print("%s (%s CPU)" % (header_string, self.__get_ncpu(context)))
- #Function to format the node name as per the /proc/zoneinfo naming convention
- def __format_node_name(self,input_str):
+ # Function to format the node name as per the /proc/zoneinfo naming convention
+ def __format_node_name(self, input_str):
parts = input_str.split("::")
- if len(parts) == 2 and parts[1].startswith("node"):
- node_num = parts[1][4:]
- zone_name = parts[0]
- return "Node {}, zone {}".format(node_num, zone_name)
- else:
+ if len(parts) != 2 or not parts[1].startswith("node"):
return "Invalid input format"
+ node_num = parts[1][4:]
+ return "Node {}, zone {}".format(node_num, parts[0])
- def __print_values(self,timestamp,header_indentation,value_indentation,manager):
+ def __print_old_version_values(self, manager):
+ try:
+ total_nodes = manager.names()
+ total_nodes = sorted(total_nodes, key=lambda
+ x: ((x.split('::')[0] != 'DMA')*2, # Move 'DMA' entries to the front
+ x.split('::')[0]))
+ for node in total_nodes:
+ print(self.__format_node_name(str(node)))
+ print("\tper-node status")
+ # print(ZONEINFO_PER_NODE)
+ for key, value in ZONEINFO_PER_NODE.items():
+ print("\t{} {}".format(key, manager.metric_value(value, node)))
+ for key, value in ZONEINFO_PAGE_INFO:
+ print("\t{} {}".format(key, manager.metric_value(value, node)))
+ print(" " * 14 + "protection " + str([int(i) for i in sorted(manager.protection(node))]))
+ for key, value in ZONEINFO_NUMBER_ZONE.items():
+ print("\t{} {}".format(key, manager.metric_value(value, node)))
+ except IndexError:
+ print("Got some error while printing values for old version zoneinfo")
+
+ def __print_values(self, manager):
total_nodes = manager.names()
- node_names = set(key.split('::')[1] for key in total_nodes)
- #sort the node names in decreasing order
+ node_names = {key.split('::')[1] for key in total_nodes}
+ # sort the node names in decreasing order
node_names = sorted(node_names, key=lambda x: int(x[4:]) if x.startswith('node') else float('inf'))
try:
- #lopping through all the available nodes
+ # lopping through all the available nodes
for node_name in node_names:
- #get all the types available for the current node on which i'm
- node_types=manager.protection_names(node_name)
+ # get all the types available for the current node on which i'm
+ node_types = manager.protection_names(node_name)
if node_types is None:
return
nodes = set(node_types).intersection(total_nodes)
- print("NODE {:>2},".format(node_name[4:]),"per-node status")
- for key,value in ZONEINFO_PER_NODE.items():
- print ("\t",key,manager.metric_value(value,node_name))
+ nodes = sorted(nodes, key=lambda x: ((x.split('::')[0] != 'DMA')*2, # Move 'DMA' entries to the front
+ x.split('::')[0]))
+ print("NODE {:>2}, per-node status".format(node_name[4:]))
+ for key, value in ZONEINFO_PER_NODE.items():
+ print("\t{} {}".format(key, manager.metric_value(value, node_name)))
for node in nodes:
print(self.__format_node_name(node))
- for key,value in ZONEINFO_PAGE_INFO.items():
- print("\t",key,manager.metric_value(value,node))
- print(" "*14,"protection",manager.protection(node))
- for key,value in ZONEINFO_NUMBER_ZONE.items():
- print ("\t",key,manager.metric_value(value,node))
- #finding the remaining type of nodes data which i haven't printed so far
- #these nodes will have only pages information for them so just printing them out
- remaining_nodes = set(node_types) - nodes
+ for key, value in ZONEINFO_PAGE_INFO:
+ print("\t{} {}".format(key, manager.metric_value(value, node)))
+ print(" " * 14 + "protection " + str([int(i) for i in manager.protection(node)]))
+ for key, value in ZONEINFO_NUMBER_ZONE.items():
+ print("\t{} {}".format(key, manager.metric_value(value, node)))
+ # finding the remaining type of nodes data which i haven't printed so far
+ # these nodes will have only pages information for them so just printing them out
+ remaining_nodes = set(node_types) - set(nodes)
if remaining_nodes:
- for node in remaining_nodes:
+ for node in sorted(remaining_nodes):
print(self.__format_node_name(node))
- for key,value in ZONEINFO_PAGE_INFO.items():
- print("\t",key,manager.metric_value(value,node))
- print(" "*14,"protection",manager.protection(node))
+ for key, value in ZONEINFO_PAGE_INFO:
+ print("\t", key, manager.metric_value(value, node))
+ print(" " * 14 + "protection " + str([int(i) for i in manager.protection(node)]))
else:
continue
except IndexError:
print("Got some error while printing values for zoneinfo")
-
-
-
- def print_report(self,group,timestamp,header_indentation,value_indentation,manager_zoneinfo):
+ def print_report(self, group, timestamp, manager_zoneinfo):
def __print_zone_status():
zonestatus = ZoneStatUtil(manager_zoneinfo)
if zonestatus.names():
try:
self.__print_machine_info(group)
- print("TimeStamp = ",timestamp)
- self.__print_values(timestamp, header_indentation, value_indentation, zonestatus)
+ print("TimeStamp = {}".format(timestamp))
+ if newVersionFlag:
+ self.__print_values(zonestatus)
+ else:
+ self.__print_old_version_values(zonestatus)
except IndexError:
print("Incorrect machine info due to some missing metrics")
return
@@ -271,12 +302,12 @@ class ZoneinfoReport(pmcc.MetricGroupPrinter):
self.samples = opts.pmGetOptionSamples()
t_s = group.contextCache.pmLocaltime(int(group.timestamp))
timestamp = time.strftime(ZoneinfoOptions.timefmt, t_s.struct_time())
- header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
- value_indentation = ((len(header_indentation) + 9) - len(timestamp)) * " "
- self.print_report(group,timestamp,header_indentation,value_indentation,manager['zoneinfo'])
+ self.print_report(group, timestamp, manager['zoneinfo'])
+
class ZoneinfoOptions(pmapi.pmOptions):
timefmt = "%H:%M:%S"
+
def __init__(self):
pmapi.pmOptions.__init__(self, "a:s:Z:zV?")
self.pmSetLongOptionHeader("General options")
@@ -288,19 +319,59 @@ class ZoneinfoOptions(pmapi.pmOptions):
self.samples = None
self.context = None
+
+def updateZoneinfoDataToBeFetched():
+ ZoneinfoNumberZoneElementsToBeRemoved = {
+ "nr_zone_active_anon" : "mem.zoneinfo.nr_zone_active_anon",
+ "nr_zone_inactive_file" : "mem.zoneinfo.nr_zone_inactive_file",
+ "nr_zone_active_file" : "mem.zoneinfo.nr_zone_active_file",
+ "nr_zone_unevictable" : "mem.zoneinfo.nr_zone_unevictable",
+ "nr_zone_write_pending" : "mem.zoneinfo.nr_zone_write_pending",
+ "nr_zspages" : "mem.zoneinfo.nr_zspages",
+ "numa_local" : "mem.zoneinfo.numa_local",
+ "nr_zone_inactive_anon" : "mem.zoneinfo.nr_zone_inactive_anon"
+ }
+
+ ZoneinfoPerNodeElementsToBeRemoved = {
+ "nr_shmem_hugepages" : "mem.zoneinfo.nr_shmem_hugepages",
+ "nr_shmem_pmdmapped" : "mem.zoneinfo.nr_shmem_pmdmapped",
+ "nr_file_hugepages" : "mem.zoneinfo.nr_file_hugepages",
+ "nr_file_pmdmapped" : "mem.zoneinfo.nr_file_pmdmapped",
+ "nr_kernel_misc_reclaimable" : "mem.zoneinfo.nr_kernel_misc_reclaimable"
+ }
+
+ for key in ZoneinfoNumberZoneElementsToBeRemoved:
+ del ZONEINFO_NUMBER_ZONE[key]
+
+ for key in ZoneinfoPerNodeElementsToBeRemoved:
+ del ZONEINFO_PER_NODE[key]
+
+
+
if __name__ == '__main__':
try:
opts = ZoneinfoOptions()
- mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
+ mngr = pmcc.MetricGroupManager.builder(opts, sys.argv)
opts.context = mngr.type
- missing = mngr.checkMissingMetrics(ALL_METRICS)
- if missing is not None:
- sys.stderr.write("\nError:some metrics are unavailable ".join(missing) + '\n')
- sys.exit(1)
+ ZONESTAT_METRICS = ZONESTAT_METRICS + ZONESTAT_NEW_METRICS
+
+ if opts.context == PM_CONTEXT_HOST:
+ context = pmapi.pmContext(opts.context)
+ elif opts.context == PM_CONTEXT_ARCHIVE:
+ context = pmapi.pmContext.fromOptions(opts,sys.argv)
+ try:
+ metric_ids =context.pmLookupName(ZONESTAT_METRICS)
+ descs= context.pmLookupDescs(metric_ids)
+ except pmapi.pmErr as error:
+ newVersionFlag = False
+ updateZoneinfoDataToBeFetched()
+ ZONESTAT_METRICS = [x for x in ZONESTAT_METRICS if x not in ZONESTAT_NEW_METRICS]
+
+ ALL_METRICS = ZONESTAT_METRICS + SYS_METRICS
mngr["zoneinfo"] = ZONESTAT_METRICS
- mngr["sysinfo"] = SYS_MECTRICS
+ mngr["sysinfo"] = SYS_METRICS
mngr["allinfo"] = ALL_METRICS
- mngr.printer = ZoneinfoReport(opts.samples,mngr,opts.context)
+ mngr.printer = ZoneinfoReport(opts.samples, mngr, opts.context)
sts = mngr.run()
sys.exit(sts)
except pmapi.pmErr as error:
--
2.39.3

View File

@ -0,0 +1,640 @@
From 0f582f5914cd774cdeb1ce5425760941641ffbc7 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Fri, 25 Oct 2024 16:14:26 +0530
Subject: [PATCH ol8 1012/1015] pmlogcheck: Fixed o(n*n) nested hash table
lookup
upstream ref:- 441b804dee08a7c506ccb4848fe5833d62e79335
Orabug:36995894
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pmlogcheck/GNUmakefile | 7 +-
src/pmlogcheck/logdecompress | 258 +++++++++++++++++++++++++++++++++++
src/pmlogcheck/pass3.c | 121 ++++++++--------
src/pmlogcheck/pmlogcheck.c | 60 +++++++-
4 files changed, 369 insertions(+), 77 deletions(-)
create mode 100755 src/pmlogcheck/logdecompress
diff --git a/src/pmlogcheck/GNUmakefile b/src/pmlogcheck/GNUmakefile
index 8308b40..c7a0d01 100644
--- a/src/pmlogcheck/GNUmakefile
+++ b/src/pmlogcheck/GNUmakefile
@@ -18,14 +18,16 @@ include $(TOPDIR)/src/include/builddefs
CFILES = pmlogcheck.c pass0.c pass1.c pass2.c pass3.c
HFILES = logcheck.h
CMDTARGET = pmlogcheck$(EXECSUFFIX)
+HELPER = logdecompress
LLDLIBS = $(PCPLIB) $(LIB_FOR_MATH)
-default: $(CMDTARGET)
+default: $(CMDTARGET) $(HELPER)
include $(BUILDRULES)
install: $(CMDTARGET)
$(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+ $(INSTALL) -m 755 $(HELPER) $(PCP_SHARE_DIR)/bin/$(HELPER)
default_pcp: default
@@ -34,6 +36,3 @@ install_pcp: install
$(OBJECTS): logcheck.h
$(OBJECTS): $(TOPDIR)/src/include/pcp/libpcp.h
-
-check:: $(CFILES) $(HFILES)
- $(CLINT) $^
diff --git a/src/pmlogcheck/logdecompress b/src/pmlogcheck/logdecompress
new file mode 100755
index 0000000..f78757d
--- /dev/null
+++ b/src/pmlogcheck/logdecompress
@@ -0,0 +1,258 @@
+#!/bin/sh
+#
+# Decompress files of a PCP archive.
+#
+# Usage: logdecompress [-vn] [-d dir] archive
+#
+# Copyright (c) 2024 Ken McDonell, Inc. All Rights Reserved.
+#
+# 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.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+. $PCP_DIR/etc/pcp.env
+
+prog=`basename $0`
+usage="Usage: $prog [-nv] [-d dir] archive"
+
+tmp=/var/tmp/logdecompress.$$
+status=1
+trap "rm -f $tmp.*; exit \$status" 0 1 2 3 15
+
+_decompress()
+{
+ if $showme
+ then
+ echo >&2 "+ $*"
+ elif ! $*
+ then
+ echo >&2 "$* file failed"
+ exit
+ fi
+}
+
+showme=false
+verbose=false
+dir=''
+while getopts 'd:nv\?' c
+do
+ case "$c"
+ in
+ d)
+ if [ -z "$OPTARG" ]
+ then
+ echo >&2 "$prog: -c requires a dir argument"
+ exit
+ fi
+ if [ ! -d "$OPTARG" ]
+ then
+ echo >&2 "$prog: $OPTARG is not an existing directory"
+ exit
+ fi
+ dir="$OPTARG"
+ ;;
+ n)
+ showme=true
+ ;;
+ v)
+ verbose=true
+ ;;
+ \?)
+ echo >&2 "$usage"
+ status=0
+ exit
+ ;;
+ esac
+done
+
+if [ $OPTIND != $# ]
+then
+ echo >&2 "$usage"
+ exit
+fi
+shift `expr $OPTIND - 1`
+
+archbase="`pmlogbasename $1`"
+$verbose && echo >&2 "archbase=$archbase"
+
+# for all the files that seem to be part of this archive,
+# count the files and the number that are compressed
+#
+nfile=0
+ncompress=0
+for file in ${archbase}*
+do
+ [ ! -f "$file" ] && continue
+ if [ "$archbase" = `pmlogbasename "$file"` ]
+ then
+ nfile=`expr $nfile + 1`
+ case $file
+ in
+ *.xz|*.lzma|*.bz2|*.bz|*.gz|*.Z|*.z)
+ ncompress=`expr $ncompress + 1`
+ if [ -z "$dir" ]
+ then
+ outfile=`echo "$file" | sed -e 's/\.[^.]*$//'`
+ else
+ outfile="$dir"/`basename "$file" | sed -e 's/\.[^.]*$//'`
+ fi
+ if [ -f "$outfile" ]
+ then
+ echo >&2 "$prog: $outfile exists and will not be clobbered"
+ exit
+ fi
+ ;;
+ esac
+ fi
+done
+
+if [ "$nfile" -eq 0 ]
+then
+ echo >&2 "No PCP archive files match \"$1\""
+ exit
+fi
+
+if [ "$ncompress" -eq 0 ]
+then
+ $verbose && echo >&2 "No compresssed PCP archive files match \"$1\""
+ echo "$archbase"
+ status=0
+ exit
+fi
+$verbose && echo >&2 "$ncompress of $nfile files in the archive are compressed"
+
+for file in ${archbase}*
+do
+ [ ! -f "$file" ] && continue
+ if [ "$archbase" = `pmlogbasename "$file"` ]
+ then
+ case $file
+ in
+ *.xz)
+ if [ -z "$dir" ]
+ then
+ outfile=`basename "$file" .xz`
+ _decompress xz --decompress "$file"
+ else
+ outfile="$dir"/`basename "$file" .xz`
+ _decompress xz --decompress --stdout "$file" >"$outfile"
+ fi
+ $verbose && echo >&2 "$outfile: decompressed"
+ ;;
+
+ *.lzma)
+ if [ -z "$dir" ]
+ then
+ outfile=`basename "$file" .lzma`
+ _decompress xz --decompress --format=lzma "$file"
+ else
+ outfile="$dir"/`basename "$file" .lzma`
+ _decompress xz --decompress --format=lzma --stdout "$file" >"$outfile"
+ fi
+ $verbose && echo >&2 "$outfile: decompressed"
+ ;;
+
+ *.bz2)
+ if [ -z "$dir" ]
+ then
+ outfile=`basename "$file" .bz2`
+ _decompress bzip2 -d "$file"
+ else
+ outfile="$dir"/`basename "$file" .bz2`
+ _decompress bzip2 -dc "$file" >"$outfile"
+ fi
+ $verbose && echo >&2 "$outfile: decompressed"
+ ;;
+
+ *.bz)
+ if [ -z "$dir" ]
+ then
+ outfile=`basename "$file" .bz`
+ _decompress bzip2 -d "$file"
+ else
+ outfile="$dir"/`basename "$file" .bz`
+ _decompress bzip2 -dc "$file" >"$outfile"
+ fi
+ $verbose && echo >&2 "$outfile: decompressed"
+ ;;
+
+ *.gz)
+ if [ -z "$dir" ]
+ then
+ outfile=`basename "$file" .gz`
+ _decompress gzip -d "$file"
+ else
+ outfile="$dir"/`basename "$file" .gz`
+ _decompress gzip -dc "$file" >"$outfile"
+ fi
+ $verbose && echo >&2 "$outfile: decompressed"
+ ;;
+
+ *.z)
+ if [ -z "$dir" ]
+ then
+ outfile=`basename "$file" .z`
+ _decompress gzip -d "$file"
+ else
+ outfile="$dir"/`basename "$file" .z`
+ _decompress gzip -dc "$file" >"$outfile"
+ fi
+ $verbose && echo >&2 "$outfile: decompressed"
+ ;;
+
+ *.Z)
+ echo >&2 TODO: $file
+ ;;
+
+ *) # not compressed ... do nothing unless -d, and then
+ # try ln(1), failing that cp(1)
+ #
+ if [ -n "$dir" ]
+ then
+ outfile="$dir/`basename $file`"
+ if $showme
+ then
+ echo >&2 "+ ln $file $outfile"
+ echo >&2 "+ if that fails ... cp $file $outfile"
+ else
+ if ! ln "$file" "$outfile" >$tmp.err 2>&1
+ then
+ $verbose && cat >&2 $tmp.err
+ if ! cp "$file" "$outfile" >>$tmp.err 2>&1
+ then
+ cat >&2 $tmp.err
+ echo >&2 "Failed to link or copy $file"
+ exit
+ else
+ $verbose && echo >&2 "$outfile: copied"
+ fi
+ else
+ $verbose && echo >&2 "$outfile: linked"
+ fi
+ fi
+ fi
+ ;;
+ esac
+ fi
+done
+
+if [ -z "$dir" ]
+then
+ echo "$archbase"
+else
+ echo "$dir/$archbase"
+fi
+
+status=0
+exit
diff --git a/src/pmlogcheck/pass3.c b/src/pmlogcheck/pass3.c
index 6f17419..f6b80d3 100644
--- a/src/pmlogcheck/pass3.c
+++ b/src/pmlogcheck/pass3.c
@@ -28,8 +28,7 @@ typedef struct {
pmDesc desc;
int valfmt;
double scale;
- instData **instlist;
- unsigned int listsize;
+ __pmHashCtl insthash;
} checkData;
static __pmHashCtl hashlist; /* hash statistics about each metric */
@@ -101,14 +100,13 @@ print_stamp(FILE *f, struct timeval *stamp)
}
static double
-unwrap(double current, struct timeval *curtime, checkData *checkdata, int index)
+unwrap(double current, struct timeval *curtime, checkData *checkdata, instData *idp)
{
double outval = current;
int wrapflag = 0;
char *str = NULL;
- if ((current - checkdata->instlist[index]->lastval) < 0.0 &&
- checkdata->instlist[index]->lasttime.tv_sec > 0) {
+ if ((current - idp->lastval) < 0.0 && idp->lasttime.tv_sec > 0) {
switch (checkdata->desc.type) {
case PM_TYPE_32:
case PM_TYPE_U32:
@@ -130,16 +128,18 @@ unwrap(double current, struct timeval *curtime, checkData *checkdata, int index)
print_stamp(stderr, curtime);
fprintf(stderr, "]: ");
print_metric(stderr, checkdata->desc.pmid);
- if (pmNameInDomArchive(checkdata->desc.indom, checkdata->instlist[index]->inst, &str) < 0)
+ if (pmNameInDomArchive(checkdata->desc.indom, idp->inst, &str) < 0)
fprintf(stderr, ": %s wrap", typeStr(checkdata->desc.type));
else {
fprintf(stderr, "[%s]: %s wrap", str, typeStr(checkdata->desc.type));
free(str);
}
- fprintf(stderr, "\n\tvalue %.0f at ", checkdata->instlist[index]->lastval);
- print_stamp(stderr, &checkdata->instlist[index]->lasttime);
+ fprintf(stderr, "\n\tvalue %.0f at ", idp->lastval);
+ print_stamp(stderr, &idp->lasttime);
fprintf(stderr, "\n\tvalue %.0f at ", current);
print_stamp(stderr, curtime);
+ if (vflag)
+ fprintf(stderr, "\n\tdifference %.0f", current - idp->lastval);
fputc('\n', stderr);
}
@@ -150,12 +150,13 @@ static void
newHashInst(pmValue *vp,
checkData *checkdata, /* updated by this function */
int valfmt,
- struct timeval *timestamp, /* timestamp for this sample */
- int pos) /* position of this inst in instlist */
+ struct timeval *timestamp /* timestamp for this sample */
+ )
{
int sts;
size_t size;
pmAtomValue av;
+ instData *idp;
if ((sts = pmExtractValue(valfmt, vp, checkdata->desc.type, &av, PM_TYPE_DOUBLE)) < 0) {
fprintf(stderr, "%s.%d:[", l_archname, l_ctxp->c_archctl->ac_vol);
@@ -166,18 +167,19 @@ newHashInst(pmValue *vp,
fprintf(stderr, "%s: possibly corrupt archive?\n", pmGetProgname());
exit(EXIT_FAILURE);
}
- size = (pos+1)*sizeof(instData*);
- checkdata->instlist = (instData**) realloc(checkdata->instlist, size);
- if (!checkdata->instlist)
- pmNoMem("newHashInst.instlist", size, PM_FATAL_ERR);
- size = sizeof(instData);
- checkdata->instlist[pos] = (instData*) malloc(size);
- if (!checkdata->instlist[pos])
- pmNoMem("newHashInst.instlist[pos]", size, PM_FATAL_ERR);
- checkdata->instlist[pos]->inst = vp->inst;
- checkdata->instlist[pos]->lastval = av.d;
- checkdata->instlist[pos]->lasttime = *timestamp;
- checkdata->listsize++;
+ size = sizeof(instData);
+ if ((idp = (instData *)malloc(sizeof(instData))) == NULL) {
+ pmNoMem("newHashInst: instData", size, PM_FATAL_ERR);
+ }
+ idp->inst = vp->inst;
+ idp->lastval = av.d;
+ idp->lasttime = *timestamp;
+ if ((sts = __pmHashAdd(vp->inst, (void *)idp, &checkdata->insthash)) < 0) {
+ fprintf(stderr, "newHashInst: __pmHashAdd(%d, ...) for pmID %s failed: %s\n",
+ vp->inst, pmIDStr(checkdata->desc.pmid), pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+
if (pmDebugOptions.appl1) {
char *name;
@@ -207,6 +209,7 @@ newHashItem(pmValueSet *vsp,
struct timeval *timestamp) /* timestamp for this sample */
{
int j;
+ int sts;
checkdata->desc = *desc;
checkdata->scale = 0.0;
@@ -225,17 +228,21 @@ newHashItem(pmValueSet *vsp,
checkdata->desc.units.dimTime--;
}
- checkdata->listsize = 0;
- checkdata->instlist = NULL;
- for (j = 0; j < vsp->numval; j++) {
- newHashInst(&vsp->vlist[j], checkdata, vsp->valfmt, timestamp, j);
+ memset(&checkdata->insthash, 0, sizeof(checkdata->insthash));
+ if ((sts = __pmHashPreAlloc(vsp->numval, &checkdata->insthash)) < 0) {
+ fprintf(stderr, "newHashItem: __pmHashPreAlloc(%d, ...) for pmID %s failed: %s\n",
+ vsp->numval, pmIDStr(checkdata->desc.pmid), pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ for (j = 0; j < vsp->numval; j++) {
+ newHashInst(&vsp->vlist[j], checkdata, vsp->valfmt, timestamp);
}
}
static void
docheck(pmResult *result)
{
- int i, j, k;
+ int i, j;
int sts;
pmDesc desc;
pmAtomValue av;
@@ -312,13 +319,6 @@ docheck(pmResult *result)
fprintf(stderr, "] ");
print_metric(stderr, vsp->pmid);
fprintf(stderr, ": __pmHashAdd good failed (internal pmlogcheck error)\n");
- /* free memory allocated above on insert failure */
- for (j = 0; j < vsp->numval; j++) {
- if (checkdata->instlist[j] != NULL)
- free(checkdata->instlist[j]);
- }
- if (checkdata->instlist != NULL)
- free(checkdata->instlist);
continue;
}
}
@@ -343,35 +343,19 @@ docheck(pmResult *result)
}
}
for (j = 0; j < vsp->numval; j++) { /* iterate thro result values */
+ __pmHashNode *hnp;
+ instData *idp = NULL;
+
vp = &vsp->vlist[j];
- k = j; /* index into stored inst list, result may differ */
- if ((vsp->numval > 1) || (checkdata->desc.indom != PM_INDOM_NULL)) {
- /* must store values using correct inst - probably in correct order already */
- if ((k < checkdata->listsize) && (checkdata->instlist[k]->inst != vp->inst)) {
- for (k = 0; k < checkdata->listsize; k++) {
- if (vp->inst == checkdata->instlist[k]->inst) {
- break; /* k now correct */
- }
- }
- if (k == checkdata->listsize) { /* no matching inst was found */
- newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp, k);
- continue;
- }
- }
- else if (k >= checkdata->listsize) {
- k = checkdata->listsize;
- newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp, k);
- continue;
- }
- }
- if (k >= checkdata->listsize) { /* only error values observed so far */
- k = checkdata->listsize;
- newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp, k);
+ hnp = __pmHashSearch(vp->inst, &checkdata->insthash);
+ if (hnp == NULL) {
+ /* first time for this inst and this metric */
+ newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp);
continue;
}
-
+ idp = (instData *)hnp->data;
timediff = result->timestamp;
- tsub(&timediff, &(checkdata->instlist[k]->lasttime));
+ tsub(&timediff, &idp->lasttime);
if (timediff.tv_sec < 0 || timediff.tv_usec < 0) {
/* clip negative values at zero */
timediff.tv_sec = 0;
@@ -397,10 +381,10 @@ docheck(pmResult *result)
fprintf(stderr, ": current counter value is %.0f\n", av.d);
}
if (nowrap == 0)
- unwrap(av.d, &(result->timestamp), checkdata, k);
+ unwrap(av.d, &(result->timestamp), checkdata, idp);
}
- checkdata->instlist[k]->lastval = av.d;
- checkdata->instlist[k]->lasttime = result->timestamp;
+ idp->lastval = av.d;
+ idp->lasttime = result->timestamp;
}
}
}
@@ -513,14 +497,17 @@ pass3(__pmContext *ctxp, char *archname, pmOptions *opts)
* at next fetch (mimic interp.c from libpcp)
*/
__pmHashNode *hptr;
- checkData *checkdata;
- int k;
+ /* walk hash list of metrics */
for (hptr = __pmHashWalk(&hashlist, PM_HASH_WALK_START);
hptr != NULL;
hptr = __pmHashWalk(&hashlist, PM_HASH_WALK_NEXT)) {
- checkdata = (checkData *)hptr->data;
- for (k = 0; k < checkdata->listsize; k++) {
- checkdata->instlist[k]->lasttime.tv_sec = 0;
+ checkData *checkdata;
+ __pmHashNode *hnp;
+ checkdata = (checkData *)hptr->data;
+ for (hnp = __pmHashWalk(&checkdata->insthash, PM_HASH_WALK_START);
+ hnp != NULL;
+ hnp = __pmHashWalk(&checkdata->insthash, PM_HASH_WALK_NEXT)) {
+ ((instData *)(hnp->data))->lasttime.tv_sec = 0;
}
}
diff --git a/src/pmlogcheck/pmlogcheck.c b/src/pmlogcheck/pmlogcheck.c
index 9927a79..0f73011 100644
--- a/src/pmlogcheck/pmlogcheck.c
+++ b/src/pmlogcheck/pmlogcheck.c
@@ -170,6 +170,10 @@ main(int argc, char *argv[])
char *archpathname; /* from the command line */
char *archdirname; /* after dirname() */
char archname[MAXPATHLEN]; /* full pathname to base of archive name */
+ struct timespec then_real;
+ struct timespec now_real;
+ struct timespec then_cpu;
+ struct timespec now_cpu;
while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
switch (c) {
@@ -251,9 +255,21 @@ main(int argc, char *argv[])
else {
pmsprintf(path, sizeof(path), "%s%c%s", archdirname, sep, namelist[i]->d_name);
}
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &then_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &then_cpu);
+ }
if (pass0(path) == STS_FATAL)
/* unrepairable or unrepaired error */
sts = STS_FATAL;
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &now_cpu);
+ fprintf(stderr, "pass0(%s) elapsed %.3fs cpu %.6fs\n",
+ namelist[i]->d_name,
+ pmtimespecSub(&now_real, &then_real),
+ pmtimespecSub(&now_cpu, &then_cpu));
+ }
}
if (meta_state == STATE_MISSING) {
fprintf(stderr, "%s%c%s.meta: missing metadata file\n", archdirname, sep, archbasename);
@@ -307,19 +323,51 @@ main(int argc, char *argv[])
strncpy(archname, archbasename, sizeof(archname) - 1);
else
pmsprintf(archname, sizeof(archname), "%s%c%s", archdirname, sep, archbasename);
-
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &then_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &then_cpu);
+ }
sts = pass1(ctxp, archname);
-
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &now_cpu);
+ fprintf(stderr, "pass1(%s) elapsed %.3fs cpu %.6fs\n",
+ archbasename,
+ pmtimespecSub(&now_real, &then_real),
+ pmtimespecSub(&now_cpu, &then_cpu));
+ }
if (index_state == STATE_BAD) {
/* prevent subsequent use of bad temporal index */
ctxp->c_archctl->ac_log->numti = 0;
}
-
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &then_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &then_cpu);
+ }
sts = pass2(ctxp, archname);
-
- if (!mflag)
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &now_cpu);
+ fprintf(stderr, "pass2(%s) elapsed %.3fs cpu %.6fs\n",
+ archbasename,
+ pmtimespecSub(&now_real, &then_real),
+ pmtimespecSub(&now_cpu, &then_cpu));
+ }
+ if (!mflag){
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &then_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &then_cpu);
+ }
sts = pass3(ctxp, archname, &opts);
-
+ if (pmDebugOptions.appl3) {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now_real);
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &now_cpu);
+ fprintf(stderr, "pass3(%s) elapsed %.3fs cpu %.6fs\n",
+ archbasename,
+ pmtimespecSub(&now_real, &then_real),
+ pmtimespecSub(&now_cpu, &then_cpu));
+ }
+ }
if (vflag) {
if (result_count > 0)
fprintf(stderr, "Processed %d pmResult records\n", result_count);
--
2.43.5

View File

@ -0,0 +1,419 @@
From e773134d1cd8ee998ddaac3dd9b9f5039eef4897 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Fri, 25 Oct 2024 16:15:56 +0530
Subject: [PATCH ol8 1013/1015] pcp-ps: added args option to view full process
argument
updated the man page for the changes as well
upstream ref:- https://github.com/performancecopilot/pcp/pull/2094
Orabug: 37062125
Orabug: 37063335
Orabug: 37062126
Orabug: 37064841
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pcp/ps/pcp-ps.1 | 30 +++++--
src/pcp/ps/pcp-ps.py | 188 ++++++++++++++++++-------------------------
2 files changed, 99 insertions(+), 119 deletions(-)
diff --git a/src/pcp/ps/pcp-ps.1 b/src/pcp/ps/pcp-ps.1
index 7ca98db..8c44e67 100755
--- a/src/pcp/ps/pcp-ps.1
+++ b/src/pcp/ps/pcp-ps.1
@@ -69,14 +69,14 @@ Display all the process.
.br
.TS
l l.
-PID Process idenfier.
-TTY The termianl assoicated with the prcoess.
+PID Process identifier.
+TTY The terminal associated with the process.
TIME T{
.ad l
.hy 0
The cumulated CPU time in [DD-]hh:mm:ss format (time=TIME).
T}
-CMD The command name of the task.
+CMD The task name along with its complete arguments.
.TE
.TP
.B \-c [\fIcommand name\fR]
@@ -106,7 +106,7 @@ It is a single argument in the form of a blank-separated or comma-separated list
The argument to -o are following:
.TS
-lfB lfB lfB
+lfB lfB lfB
lfB l lx.
COL HEADER DESCRIPTION
_ _ _
@@ -118,8 +118,14 @@ physical memory on the machine expressed as a percentage
T}
start START time the command started
time TIME accumulated cpu time, user + system
-cls CLS scheduling class of the process
-cmd CMD see \fBargs\fR. (alias args, command).
+cls CLS scheduling class of the process.
+cmd CMD see \fBTask name\fR. (alias args, command).
+args COMMAND T{
+.ad l
+.hy 0
+To display the full program name with its arguments (use at last
+position in -o list to view full command)
+T}
pid PID The process ID
ppid PPID Parent process ID
pri PRI Priority of the process
@@ -129,7 +135,7 @@ rss RSS T{
.hy 0
the non-swapped physical memory that a task has used
T}
-rtprio RTPRIO realtime priority
+rtprio RTPRIO real-time priority
pname Pname Process name
tty TT controlling tty (terminal)
uid UID see \fBeuid\fR
@@ -151,7 +157,7 @@ For example:
.B pcp-ps \-o pid,\:user,\:args
.TS
-lfB lfB lfB
+lfB lfB lfB
lfB l lx.
CODE HEADER DESCRIPTION
_ _ _
@@ -286,3 +292,11 @@ For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
.BR strftime (3)
and
.BR environ (7).
+
+.\" control lines for scripts/man-spell
+.\" +ok+ SCHED_DEADLINE SCHED_BATCH SCHED_OTHER SCHED_FIFO SCHED_IDLE
+.\" +ok+ SCHED_ISO SCHED_RR cputime
+.\" +ok+ RTPRIO rtprio WCHAN PPIDs EUSER wchan Pname pname uname
+.\" +ok+ vsize euser PPID EUID ppid args euid IDL DLN CLS CMD COL PRI RSS
+.\" +ok+ VSZ cls cmd col pri vsz DD FF RR TT hh ss
+.\" +ok+ realtime {not real-time, from (cputime/realtime ratio)}
diff --git a/src/pcp/ps/pcp-ps.py b/src/pcp/ps/pcp-ps.py
index dbd5565..94a428f 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -162,8 +162,9 @@ class ProcessFilter:
class ProcessStatusUtil:
- def __init__(self, instance, delta_time, metrics_repository):
+ def __init__(self, instance, manager, delta_time, metrics_repository):
self.instance = instance
+ self.manager = manager
self.__delta_time = delta_time
self.__metric_repository = metrics_repository
@@ -207,8 +208,11 @@ class ProcessStatusUtil:
data = '-'
return data
- def process_name_with_args(self):
- data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance)[:30]
+ def process_name_with_args(self,flag = False):
+ if flag is True:
+ data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance)
+ else:
+ data = self.__metric_repository.current_value('proc.psinfo.psargs', self.instance)[:30]
if len(data) < 30:
whitespace = 30 - len(data)
res = data.ljust(whitespace + len(data), ' ')
@@ -216,6 +220,8 @@ class ProcessStatusUtil:
else:
return data
+ def process_name_with_args_last(self):
+ return self.process_name_with_args(True)
def vsize(self):
return self.__metric_repository.current_value('proc.psinfo.vsize', self.instance)
@@ -294,10 +300,10 @@ class ProcessStatusUtil:
def start(self):
s_time = self.__metric_repository.current_value('proc.psinfo.start_time', self.instance)
- group = manager['psstat']
+ group = self.manager['psstat']
kernel_boottime = group['kernel.all.boottime'].netValues[0][2]
- ts = group.contextCache.pmLocaltime(int(0.5 + kernel_boottime + (s_time / 1000)))
- if group.timestamp.tv_sec - int(0.5 + kernel_boottime + s_time / 1000) >= 24*60*60:
+ ts = group.contextCache.pmLocaltime(int(kernel_boottime + (s_time / 1000)))
+ if group.timestamp.tv_sec - (kernel_boottime + s_time / 1000) >= 24*60*60:
# started one day or more ago, use MmmDD HH:MM
return time.strftime("%b%d %H:%M", ts.struct_time())
else:
@@ -346,14 +352,16 @@ PIDINFO_PAIR = {"%cpu": ('%CPU', ProcessStatusUtil.system_percent),
"start": ("START\t", ProcessStatusUtil.start),
"time": ("TIME\t", ProcessStatusUtil.total_time),
"cls": ("CLS", ProcessStatusUtil.policy),
- "cmd": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args),
+ "cmd": ("Command\t\t\t", ProcessStatusUtil.process_name),
+ "args": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args),
+ "args_last": ("Command\t\t\t", ProcessStatusUtil.process_name_with_args_last),
"pid": ("PID\t", ProcessStatusUtil.pid),
"ppid": ("PPID\t", ProcessStatusUtil.ppid),
"pri": ("PRI", ProcessStatusUtil.priority),
"state": ("S", ProcessStatusUtil.s_name),
"rss": ("RSS", ProcessStatusUtil.rss),
"rtprio": ("RTPRIO", ProcessStatusUtil.priority),
- "tty": ("TT", ProcessStatusUtil.tty_name),
+ "tty": ("TTY\t", ProcessStatusUtil.tty_name),
"pname": ("Pname\t\t", ProcessStatusUtil.process_name),
"vsize": ("VSZ", ProcessStatusUtil.vsize),
"uname": ("USER\t", ProcessStatusUtil.user_name),
@@ -362,11 +370,13 @@ PIDINFO_PAIR = {"%cpu": ('%CPU', ProcessStatusUtil.system_percent),
class ProcessStatus:
- def __init__(self, metric_repository):
+ def __init__(self, manager, metric_repository):
+ self.__manager = manager
self.__metric_repository = metric_repository
def get_processes(self, delta_time):
- return map((lambda pid: (ProcessStatusUtil(pid, delta_time, self.__metric_repository))), self.__pids())
+ return map(lambda pid:
+ (ProcessStatusUtil(pid, self.__manager, delta_time, self.__metric_repository)), self.__pids())
def __pids(self):
pid_dict = self.__metric_repository.current_values('proc.psinfo.pid')
@@ -381,6 +391,9 @@ class DynamicProcessReporter:
self.printer = printer
self.processStatOptions = processStatOptions
+ def _is_last_and_args(self, key):
+ return (key == "args") and \
+ self.processStatOptions.colum_list.index(key) == len(self.processStatOptions.colum_list) - 1
def print_report(self, timestamp, header_indentation, value_indentation):
# when the print count is exhausted exit the program gracefully
@@ -437,7 +450,9 @@ class DynamicProcessReporter:
for process in processes:
data_to_print = timestamp + '\t'
for key in self.processStatOptions.colum_list:
- if key in PIDINFO_PAIR:
+ if self._is_last_and_args(key):
+ data_to_print += str(PIDINFO_PAIR["args_last"][1](process)) + '\t\t'
+ elif key in PIDINFO_PAIR:
data_to_print += str(PIDINFO_PAIR[key][1](process)) + '\t\t'
print(data_to_print)
@@ -464,7 +479,7 @@ class ProcessStatusReporter:
self.printer("Timestamp" + header_indentation + "PID\t\t\tTTY\tTIME\t\tCMD")
processes = self.process_filter.filter_processes(self.process_report.get_processes(self.delta_time))
for process in processes:
- command = process.process_name()
+ command = process.process_name_with_args(True)
ttyname = process.tty_name()
self.printer("%s%s%s\t\t%s\t%s\t%s" % (timestamp, value_indentation, process.pid(), ttyname,
process.total_time(), command))
@@ -527,134 +542,85 @@ class ProcessStatusReporter:
process.total_time(), command))
-class ProcessstatReport(pmcc.MetricGroupPrinter):
+class ProcessStatReport(pmcc.MetricGroupPrinter):
Machine_info_count = 0
+ group = None
+ def __init__(self, group=None):
+ self.group = group
- def timeStampDelta(self, group):
- s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec
- u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec
+ def timeStampDelta(self):
+ s = self.group.timestamp.tv_sec - self.group.prevTimestamp.tv_sec
+ u = self.group.timestamp.tv_usec - self.group.prevTimestamp.tv_usec
return s + u / 1000000.0
- def print_machine_info(self, group, context):
- timestamp = context.pmLocaltime(group.timestamp.tv_sec)
+ def print_machine_info(self,context):
+ timestamp = context.pmLocaltime(self.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 += self.group['kernel.uname.sysname'].netValues[0][2] + ' '
+ header_string += self.group['kernel.uname.release'].netValues[0][2] + ' '
+ header_string += '(' + self.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)))
+ header_string += self.group['kernel.uname.machine'].netValues[0][2] + ' '
+ print("%s (%s CPU)" % (header_string, self.__get_ncpu(self.group)))
- def get_ncpu(self, group):
+ def __get_ncpu(self, group):
return group['hinv.ncpu'].netValues[0][2]
+ def __print_report(self, manager,timestamp, header_indentation, value_indentation,interval_in_seconds):
+ metric_repository = ReportingMetricRepository(self.group)
+ process_report = ProcessStatus(manager, metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+ def __print_dynamic_report(self, manager,timestamp, header_indentation, value_indentation,interval_in_seconds):
+ metric_repository = ReportingMetricRepository(self.group)
+ process_report = ProcessStatus(manager, metric_repository)
+ process_filter = ProcessFilter(ProcessStatOptions)
+ stdout = StdoutPrinter()
+ printdecorator = NoneHandlingPrinterDecorator(stdout)
+ report = DynamicProcessReporter(process_report, process_filter, interval_in_seconds,
+ printdecorator.Print, ProcessStatOptions)
+ report.print_report(timestamp, header_indentation, value_indentation)
+ def __get_timestamp(self):
+ ts = self.group.contextCache.pmLocaltime(int(self.group.timestamp))
+ timestamp = time.strftime(ProcessStatOptions.timefmt, ts.struct_time())
+ return timestamp
+
def report(self, manager):
try:
- group = manager['psstat']
- if group['proc.psinfo.utime'].netPrevValues is None:
+ if self.group['proc.psinfo.utime'].netPrevValues is None:
# need two fetches to report rate converted counter metrics
return
-
- if not group['hinv.ncpu'].netValues or not group['kernel.uname.sysname'].netValues:
+ if not self.group['hinv.ncpu'].netValues or not self.group['kernel.uname.sysname'].netValues:
return
-
try:
if not self.Machine_info_count:
- self.print_machine_info(group, manager)
+ self.print_machine_info(manager)
self.Machine_info_count = 1
except IndexError:
- # missing some metrics
return
-
- ts = group.contextCache.pmLocaltime(int(group.timestamp))
- timestamp = time.strftime(ProcessStatOptions.timefmt, ts.struct_time())
- interval_in_seconds = self.timeStampDelta(group)
+ timestamp = self.__get_timestamp()
+ interval_in_seconds = self.timeStampDelta()
header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
value_indentation = ((len(header_indentation) + 9) - len(timestamp)) * " "
- metric_repository = ReportingMetricRepository(group)
-
# Doing this for one single print instance in case there is no count specified
if ProcessStatOptions.print_count is None:
ProcessStatOptions.print_count = 1
-
- # ================================================================
- if ProcessStatOptions.show_all_process:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.empty_arg_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.pid_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
- if ProcessStatOptions.ppid_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.command_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.user_oriented_format:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
- if ProcessStatOptions.username_filter_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = ProcessStatusReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
-
# ================================================================
if ProcessStatOptions.selective_colum_flag:
- process_report = ProcessStatus(metric_repository)
- process_filter = ProcessFilter(ProcessStatOptions)
- stdout = StdoutPrinter()
- printdecorator = NoneHandlingPrinterDecorator(stdout)
- report = DynamicProcessReporter(process_report, process_filter, interval_in_seconds,
- printdecorator.Print, ProcessStatOptions)
- report.print_report(timestamp, header_indentation, value_indentation)
+ self.__print_dynamic_report(manager,timestamp, header_indentation,
+ value_indentation, interval_in_seconds)
+ else:
+ self.__print_report(manager,timestamp, header_indentation, value_indentation, interval_in_seconds)
finally:
sys.stdout.flush()
@@ -779,8 +745,8 @@ class ProcessStatOptions(pmapi.pmOptions):
else:
raise ValueError
except ValueError:
- print("Invalid ppid Id List: Either colum name is not correct "
- "or use comma separated colum names without whitespaces")
+ print("Invalid ppid Id List: Either column name is not correct "
+ "or use comma separated column names without whitespaces")
sys.exit(1)
elif opts == 'U':
ProcessStatOptions.username_filter_flag = True
@@ -812,7 +778,7 @@ if __name__ == "__main__":
sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing)
sys.exit(1)
manager['psstat'] = PSSTAT_METRICS
- manager.printer = ProcessstatReport()
+ manager.printer = ProcessStatReport(manager['psstat'])
sts = manager.run()
sys.exit(sts)
except pmapi.pmErr as pmerror:
--
2.43.5

View File

@ -0,0 +1,340 @@
From a2f0e1e303ce482bd5305f2062027fd2ed17e704 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Fri, 25 Oct 2024 16:17:22 +0530
Subject: [PATCH ol8 1014/1015] pcp-buddyinfo: Added timestamp and no
interpolation option
upstream ref:- https://github.com/performancecopilot/pcp/pull/2078
Orabug:36985368
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pcp/buddyinfo/pcp-buddyinfo.1 | 118 ++++++++++++++++++++---------
src/pcp/buddyinfo/pcp-buddyinfo.py | 92 +++++++++++++++-------
2 files changed, 144 insertions(+), 66 deletions(-)
diff --git a/src/pcp/buddyinfo/pcp-buddyinfo.1 b/src/pcp/buddyinfo/pcp-buddyinfo.1
index 26c06ba..7bffccd 100644
--- a/src/pcp/buddyinfo/pcp-buddyinfo.1
+++ b/src/pcp/buddyinfo/pcp-buddyinfo.1
@@ -15,22 +15,33 @@
.\" or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
.\" for more details.
.\"
-
.TH PCP-BUDDYINFO 1 "PCP" "Performance Co-Pilot"
-
.SH NAME
-\fBpcp-buddyinfo\fP \- Report statistics for buddy algorithm shown in cat /proc/buddyinfo
-
+\f3pcp-buddyinfo\f3 \- report Linux kernel buddy algorithm statistics
.SH SYNOPSIS
-\fBpcp\fP [\fBpcp options\fP] \fBbuddyinfo\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]
-
+\f3pcp\f1
+[\f2pcp\ options\f1]
+\f3buddyinfo\f1
+[\f3\-uVz?\f1]
+[\f3\-s\f1 \f2samples\f1]
+[\f3\-a\f1 \f2archive\f1]
+[\f3\-Z\f1 \f2timezone\f1]
.SH DESCRIPTION
-The \fBpcp-buddyinfo\fP command is used for viewing different stats related to buddyinfo. It helps users analyze useful information related to the buddy algorithm. The information includes the total number of zones that are currently active, order pages etc. By default, \fBpcp-buddyinfo\fP reports live data for the local host.
-
+The
+.B pcp-buddyinfo
+command is used for viewing different stats related to buddyinfo.
+It helps users analyze the dynamic behaviour of the buddy algorithm
+used in the Linux kernel virtual memory subsystem.
+The information includes the total number of zones that are currently
+active, counts of different order pages, and so on.
+By default,
+.B pcp-buddyinfo
+reports live data for the local host.
+.PP
The statistics shown are as follows:
.TS
-lfB lfB
+lfB lfB
l lx.
HEADER DESCRIPTION
_ _
@@ -51,44 +62,77 @@ Order10 available pages of order 10
Each column represents the number of pages of a certain order (a certain size) that are available at any given time. For example, for zone DMA (direct memory access), there are 90 of 2^(0*PAGE_SIZE) chunks of memory. Similarly, there are 6 of 2^(1*PAGE_SIZE) chunks, and 2 of 2^(2*PAGE_SIZE) chunks of memory available.
-
+.PP
The DMA row references the first 16 MB on a system, the HighMem row references all memory greater than 4 GB on a system, and the Normal row references all memory in between.
-
.SH OPTIONS
.TP
-\fB-a\fP, \fB\-\-archive\fP
-Fetch /proc/buddyinfo for a specified archive file
-
+\fB\-a\fR \fIfile\fR, \fB\-\-archive\fR=\fIfile\fR
+Fetch buddyinfo metrics from the specified PCP archive.
.TP
-\fB-s\fP, \fB\-\-samples\fP
-Get the buddyinfo for the specified number of samples count
-
+\fB\-s\fR \fIcount\fR, \fB\-\-samples\fR=\fIcount\fR
+Limit reporting to \fIcount\fR samples of buddyinfo statistics.
.TP
-\fB-z\fP, \fB\-\-hostzone\fP
-Set the reporting timezone to the local time of metrics source
-
+\fB\-u\fR, \fB\-\-no-interpol\fR
+disable the default interpolation mode when replaying an archive.
+With the
+.B \-u
+option, interpolated reporting is disabled and each sample is
+reported according to native sampling intervals in the archive.
+In this mode the
+.B \-t
+option is ignored.
+Additionally, non-interpolated replay makes sense only when
+replaying an archive (using the
+.B \-a
+option described above), so the
+.B \-a
+option is mandatory when
+.B \-u
+is specified.
.TP
-\fB-Z\fP, \fB\-\-timezone\fP
-Set the reporting timezone
-
+\fB\-z\fR, \fB\-\-hostzone\fR
+Use the local timezone of the host that is the source of the
+performance metrics, as identified by either the
+.B \-h
+or the
+.B \-a
+options.
+The default is to use the timezone of the local host.
.TP
-\fB-V\fP, \fB\-\-version\fP
-Display the version number and exit.
-
+\fB\-Z\fR \fItimezone\fR, \fB\-\-timezone\fR=\fItimezone\fR
+Use
+.I timezone
+for the date and time.
+.I Timezone
+is in the format of the environment variable
+.B TZ
+as described in
+.BR environ (7).
.TP
-\fB-?\fP, \fB\-\-help\fP
-Display the usage message and exit.
-
+\fB\-V\fR, \fB\-\-version\fR
+Display version number and exit.
+.TP
+\fB\-?\fR, \fB\-\-help\fR
+Display usage message and exit.
.SH NOTES
-\fBpcp-buddyinfo\fP collects information from \fI/proc/buddyinfo\fP and aims to be command-line and output compatible with it.
-
+.B pcp-buddyinfo
+reports information extracted from the \fI/proc/buddyinfo\fP Linux
+kernel procfs file.
+.PP
+If the default interpolation mode is disabled, it is possible that
+some metrics (recorded at different intervals) will be reported as
+having missing values for some samples even if they were recorded.
.SH PCP ENVIRONMENT
Environment variables with the prefix \fBPCP_\fP are used to parameterize the file and directory names used by PCP. On each installation, the file \fI/etc/pcp.conf\fP contains the local values for these variables. The \fB$PCP_CONF\fP variable may be used to specify an alternative configuration file, as described in \fBpcp.conf\fP(5).
-
+.PP
For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
-
.SH SEE ALSO
-.BR PCPIntro(1),
-.BR pcp(1),
-.BR pmParseInterval(3),
-.BR environ(7).
+.BR PCPIntro (1),
+.BR pcp (1),
+.BR pmGetOptions (3),
+.BR pcp.conf (5)
+and
+.BR environ (7).
+
+.\" control lines for scripts/man-spell
+.\" +ok+ PAGE_SIZE buddyinfo {from pcp-buddyinfo} HighMem
diff --git a/src/pcp/buddyinfo/pcp-buddyinfo.py b/src/pcp/buddyinfo/pcp-buddyinfo.py
index 012fe7c..3a7e9c9 100644
--- a/src/pcp/buddyinfo/pcp-buddyinfo.py
+++ b/src/pcp/buddyinfo/pcp-buddyinfo.py
@@ -20,13 +20,13 @@ 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_MECTRICS= ["kernel.uname.sysname","kernel.uname.release",
+SYS_METRICS= ["kernel.uname.sysname","kernel.uname.release",
"kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"]
BUDDYSTAT_METRICS = ["mem.buddyinfo.pages","mem.buddyinfo.total"]
-ALL_METRICS = BUDDYSTAT_METRICS + SYS_MECTRICS
+ALL_METRICS = BUDDYSTAT_METRICS + SYS_METRICS
def adjust_length(name,size):
return name.ljust(size)
@@ -65,28 +65,38 @@ class BuddyStatUtil:
return data.keys()
class BuddyinfoReport(pmcc.MetricGroupPrinter):
- def __init__(self,samples,group,context):
- self.samples = samples
+ def __init__(self,opts,group):
+ self.opts=opts
self.group=group
- self.context=context
+ self.context=opts.context
+ self.samples=opts.samples
+ self.header = "unknown"
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("%x", 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)))
+ if self.header == "unknown":
+ try:
+ 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("%x", 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] + ' '
+ self.header = header_string
+ except IndexError:
+ pass
+ try:
+ print("%s (%s CPU)" % (self.header, self.__get_ncpu(context)))
+ except IndexError:
+ pass
def __print_header(self,header_indentation,value_indentation):
value_indentation+=" "*2
@@ -117,7 +127,7 @@ class BuddyinfoReport(pmcc.MetricGroupPrinter):
value=""
for order in sorted(order_set,key=__extract_numeric_part):
nodename = adjust_length(Normal,9) if len(Normal) < 9 else Normal
- key = f"{Normal}::{order}::{node}"
+ key = "%s::%s::%s" % (Normal, order, node)
data = str(pages.get(key,0))
value += adjust_length(data,8) if len(data) < 8 else data
value += value_indentation
@@ -125,10 +135,10 @@ class BuddyinfoReport(pmcc.MetricGroupPrinter):
print("%s %s %s %s %s %s %s "%(timestamp,header_indentation,nodename,header_indentation,node,
header_indentation,value))
- def print_report(self,group,timestamp,header_indentation,value_indentation,manager_buddyinfo):
+ def print_report(self,group,timestamp,header_indentation,value_indentation):
def __print_buddy_status():
- buddystatus = BuddyStatUtil(manager_buddyinfo)
+ buddystatus = BuddyStatUtil(group)
if buddystatus.names():
try:
self.__print_machine_info(group)
@@ -150,19 +160,28 @@ class BuddyinfoReport(pmcc.MetricGroupPrinter):
self.samples-=1
def report(self, manager):
- group = manager["sysinfo"]
- self.samples=opts.pmGetOptionSamples()
+ group = manager["allinfo"]
+ self.samples=self.opts.pmGetOptionSamples()
t_s = group.contextCache.pmLocaltime(int(group.timestamp))
timestamp = time.strftime(BuddyinfoOptions.timefmt, t_s.struct_time())
header_indentation = " " if len(timestamp) < 9 else (len(timestamp) - 7) * " "
value_indentation = ((len(header_indentation) + 2) - len(timestamp)) * " "
- self.print_report(group,timestamp,header_indentation,value_indentation,manager['buddyinfo'])
+ self.print_report(group,timestamp,header_indentation,value_indentation)
class BuddyinfoOptions(pmapi.pmOptions):
timefmt = "%H:%M:%S"
+ uflag = False
+
+ def extraOptions(self,opt,optarg,index):
+ if opt == 'u':
+ BuddyinfoOptions.uflag = True
+
def __init__(self):
- pmapi.pmOptions.__init__(self, "a:s:Z:zV?")
+ pmapi.pmOptions.__init__(self, "a:s:Z:uzV?")
self.pmSetLongOptionHeader("General options")
+ self.pmSetOptionCallback(self.extraOptions)
+ self.pmSetLongOptionArchive()
+ self.pmSetLongOption("no-interpol", 0, "u", "", "disable interpolation mode with archives")
self.pmSetLongOptionHostZone()
self.pmSetLongOptionTimeZone()
self.pmSetLongOptionHelp()
@@ -170,20 +189,35 @@ class BuddyinfoOptions(pmapi.pmOptions):
self.pmSetLongOptionVersion()
self.samples=None
self.context=None
+ def checkOptions(self, manager):
+ if BuddyinfoOptions.uflag:
+ if manager._options.pmGetOptionInterval(): # pylint: disable=protected-access
+ 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
if __name__ == '__main__':
try:
opts = BuddyinfoOptions()
mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
opts.context=mngr.type
+
+ if not opts.checkOptions(mngr):
+ raise pmapi.pmUsageErr
+ if BuddyinfoOptions.uflag:
+ # -u turns off interpolation
+ mngr.pmSetMode(PM_MODE_FORW, mngr._options.pmGetOptionOrigin(), 0) # pylint: disable=protected-access
+
missing = mngr.checkMissingMetrics(ALL_METRICS)
if missing is not None:
- sys.stderr.write("Error:some metrics are unavailable",missing)
+ sys.stderr.write('Error: not all required metrics are available\nMissing: %s\n' % (missing))
sys.exit(1)
mngr["buddyinfo"] = BUDDYSTAT_METRICS
- mngr["sysinfo"] = SYS_MECTRICS
mngr["allinfo"]=ALL_METRICS
- mngr.printer = BuddyinfoReport(opts.samples,mngr,opts.context)
+ mngr.printer = BuddyinfoReport(opts,mngr)
sts = mngr.run()
sys.exit(sts)
except pmapi.pmErr as error:
--
2.43.5

View File

@ -0,0 +1,152 @@
From ad598761e495c69733973497182917ad24b69916 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Fri, 25 Oct 2024 16:18:35 +0530
Subject: [PATCH ol8 1015/1015] pcp-meminfo: Added timestamp in pcp meinfo
fixed broken pipe issue as well
upstream ref:- 18bea5fe3f8b6f0fded54f4020ed9ad9986012ea
Orabug: 36985368
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pcp/meminfo/pcp-meminfo.py | 73 ++++++++++++++++++++++++++--------
1 file changed, 57 insertions(+), 16 deletions(-)
diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py
index 57ac05e..103aa52 100755
--- a/src/pcp/meminfo/pcp-meminfo.py
+++ b/src/pcp/meminfo/pcp-meminfo.py
@@ -19,7 +19,9 @@
import sys
import time
+import signal
from pcp import pmapi, pmcc
+from cpmapi import PM_CONTEXT_ARCHIVE
METRICS = ["mem.physmem",
"mem.util.free",
@@ -117,11 +119,37 @@ METRICS_DESC = ["MemTotal",
"DirectMap2M",
"DirectMap1G"]
-class MeminfoReport(pmcc.MetricGroupPrinter):
- samples = 0
+SYS_METRICS = ["kernel.uname.sysname",
+ "kernel.uname.release",
+ "kernel.uname.nodename",
+ "kernel.uname.machine",
+ "hinv.ncpu"]
+
+ALL_METRICS = METRICS + SYS_METRICS
- def __init__(self, samples):
- self.samples = samples
+class MeminfoReport(pmcc.MetricGroupPrinter):
+ def __init__(self, opts):
+ self.opts = opts
+ self.Machine_info_count = 0
+
+ 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 getMetricName(self, idx):
metric_name = ""
@@ -134,13 +162,22 @@ class MeminfoReport(pmcc.MetricGroupPrinter):
return metric_name, units
def report(self, manager):
+ group = manager["sysinfo"]
+ try:
+ if not self.Machine_info_count:
+ self.__print_machine_info(group, manager)
+ self.Machine_info_count = 1
+ except IndexError:
+ # missing some metrics
+ return
+
group = manager["meminfo"]
- opts.pmGetOptionSamples()
+ self.opts.pmGetOptionSamples()
t_s = group.contextCache.pmLocaltime(int(group.timestamp))
time_string = time.strftime(MeminfoOptions.timefmt, t_s.struct_time())
- print(time_string)
+ print("Timestamp".ljust(18) + ": " + time_string)
idx = 0
for metric in METRICS:
@@ -148,21 +185,23 @@ class MeminfoReport(pmcc.MetricGroupPrinter):
val = group[metric].netValues[0][2]
except IndexError:
metric_name, units = self.getMetricName(idx)
- print(F"{metric_name:17} : NA")
+ print("%-17s : NA"%(metric_name))
idx += 1
continue
metric_name, units = self.getMetricName(idx)
- print(F"{metric_name:17} : {val} {units}")
+ print("%-17s : %s %s"%(metric_name, val, units))
idx += 1
- print()
+ print("")
+
+ if MeminfoOptions.context is not PM_CONTEXT_ARCHIVE and self.opts.pmGetOptionSamples() is None:
+ sys.exit(0)
class MeminfoOptions(pmapi.pmOptions):
context = None
- timefmt = "%H:%M:%S"
- samples = 0
+ timefmt = "%m/%d/%Y %H:%M:%S"
def __init__(self):
pmapi.pmOptions.__init__(self, "a:s:S:T:z:A:t:")
@@ -176,18 +215,20 @@ if __name__ == '__main__':
mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
MeminfoOptions.context = mngr.type
- missing = mngr.checkMissingMetrics(METRICS)
+ missing = mngr.checkMissingMetrics(ALL_METRICS)
if missing is not None:
- sys.stderr.write(F"Error:Metric is {missing} missing\n")
+ sys.stderr.write('Error: not all required metrics are available\nMissing: %s\n' % (missing))
sys.exit(1)
mngr["meminfo"] = METRICS
- mngr.printer = MeminfoReport(opts.samples)
+ mngr["sysinfo"] = SYS_METRICS
+ mngr.printer = MeminfoReport(opts)
sts = mngr.run()
sys.exit(sts)
-
+ except IOError:
+ signal.signal(signal.SIGPIPE,signal.SIG_DFL)
except pmapi.pmErr as error:
- sys.stderr.write(F"{error.progname()} {error.message()}")
+ sys.stderr.write("%s %s\n"%(error.progname(), error.message()))
except pmapi.pmUsageErr as usage:
usage.message()
sys.exit(1)
--
2.43.5

View File

@ -0,0 +1,46 @@
From be84a0cc9d880e2e9cd91e9e983b22ffec6bd126 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Mon, 3 Mar 2025 13:31:31 +0530
Subject: [PATCH OL8 1016/1016] pmstat: avoid corner-case infinite loop When -a
is in play, if the archive contains only one pmResult for the metrics of
interest to pmstat, pmstat goes into an infinite loop.
Simple fix in pmstat.c.
upstream ref:- 8ed9735e93f2f0e4dbe038e36939f40e5e87b5ae
Orabug: 37638447
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pmstat/pmstat.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/pmstat/pmstat.c b/src/pmstat/pmstat.c
index 57ec954..0901df9 100644
--- a/src/pmstat/pmstat.c
+++ b/src/pmstat/pmstat.c
@@ -67,6 +67,7 @@ struct statsrc {
pmLongOptions longopts[] = {
PMAPI_GENERAL_OPTIONS,
+ PMOPT_DEBUG,
PMAPI_OPTIONS_HEADER("Alternate sources"),
PMOPT_HOSTSFILE,
PMOPT_LOCALPMDA,
@@ -577,10 +578,12 @@ main(int argc, char *argv[])
} else if ((opts.context == PM_CONTEXT_ARCHIVE) &&
(sts == PM_ERR_EOL) && (!s->fetched)) {
/*
- * We are yet to see something from this archive - so
- * don't discard it just yet.
+ * We are yet to see something from this archive and
+ * we're at the end of the archive.
*/
puts(" No data in the archive");
+ printf(" pmFetchGroup: %s\n", pmErrStr(sts));
+ exit(0);
} else {
int valid = 0;
--
2.43.5

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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: 38526356
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/meminfo/pcp-meminfo.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py
index 103aa52..bae8931 100755
--- a/src/pcp/meminfo/pcp-meminfo.py
+++ b/src/pcp/meminfo/pcp-meminfo.py
@@ -57,10 +57,14 @@ METRICS = ["mem.physmem",
"mem.util.vmallocTotal",
"mem.util.vmallocUsed",
"mem.util.vmallocChunk",
+ "mem.util.percpu",
"mem.util.corrupthardware",
"mem.util.anonhugepages",
"mem.vmstat.nr_shmem_hugepages",
"mem.vmstat.nr_shmem_pmdmapped",
+ "mem.util.filehugepages",
+ "mem.util.filepmdmapped",
+ "mem.util.cmatotal",
"mem.zoneinfo.nr_free_cma",
"mem.util.hugepagesTotal",
"mem.util.hugepagesFree",
@@ -105,10 +109,14 @@ METRICS_DESC = ["MemTotal",
"VmallocTotal",
"VmallocUsed",
"VmallocChunk",
+ "Percpu",
"HardwareCorrupted",
"AnonHugePages",
"ShmemHugePages",
"ShmemPmdMapped",
+ "FileHugePages",
+ "FilePmdMapped",
+ "CmaTotal",
"CmaFree",
"HugePages_Total_NO_kb",
"HugePages_Free_NO_kb",
--
2.43.7

View File

@ -0,0 +1,302 @@
From b80dfd69c7872a7d1c90d2e9617bdf312c47f587 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Fri, 10 Oct 2025 17:26:37 +0530
Subject: [PATCH] pmdalinux: additional mem.util metrics from recent Linux kernel versions
Ten additional Linux kernel /proc/meminfo memory metrics relating to new
aspects of hugepages, the contiguous and percpu memory allocators, zswap,
shadow call stacks and unaccepted memory.
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/1c559713ebc5c02759de59013066c3d29e4395e4
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pmdas/linux/help | 23 ++++++-
src/pmdas/linux/pmda.c | 114 ++++++++++++++++++++++++++++++++-
src/pmdas/linux/proc_meminfo.c | 11 ++++
src/pmdas/linux/proc_meminfo.h | 12 ++++
src/pmdas/linux/root_linux | 11 ++++
5 files changed, 168 insertions(+), 3 deletions(-)
diff --git a/src/pmdas/linux/help b/src/pmdas/linux/help
index 640a21a..3700809 100644
--- a/src/pmdas/linux/help
+++ b/src/pmdas/linux/help
@@ -937,8 +937,29 @@ you will get real memory on demand, and just as much as you use.
@ mem.util.hugepagesFreeBytes amount of memory in free hugepages
@ mem.util.hugepagesRsvdBytes amount of memory in reserved hugepages
@ mem.util.hugepagesSurpBytes amount of memory in surplus hugepages
-
User memory (Kbytes) in pages not backed by files, e.g. from malloc()
+@ mem.util.shmemhugepages amount of shared memory allocated with hugepages
+@ mem.util.shmempmdmapped shared memory mapped into userspace with hugepages
+@ mem.util.filehugepages page cache (file) pages allocated with hugepages
+@ mem.util.filepmdmapped page cache mapped into userspace with hugepages
+@ mem.util.cmatotal total contiguous memory allocator memory
+@ mem.util.cmafree free contiguous memory allocator memory
+@ mem.util.unaccepted amount of unaccepted memory
+When this mechanism is in use, a virtual machine can be launched with
+its memory in an unaccepted state. Such a system will not be able to
+make use of the memory provided until that memory has been explicitly
+accepted. On these systems, the bootloader will typically pre-accept
+enough memory to allow the guest kernel to boot then that kernel must
+take responsibility for accepting the rest before using it.
+@ mem.util.zswap total size of the zswap memory pool
+@ mem.util.zswapped current memory used from the zswap memory pool
+@ mem.util.shadowcallstack memory used for shadown call stacks
+Shadow stacks are a modern security feature allowing for detection of
+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.numa.util.total per-node total memory
@ mem.numa.util.free per-node free memory
@ mem.numa.util.used per-node used memory
diff --git a/src/pmdas/linux/pmda.c b/src/pmdas/linux/pmda.c
index 428a222..23d8033 100644
--- a/src/pmdas/linux/pmda.c
+++ b/src/pmdas/linux/pmda.c
@@ -1094,7 +1094,7 @@ static pmdaMetric metrictab[] = {
{ PMDA_PMID(CLUSTER_MEMINFO,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
-/* mem.util.mmap_copy */
+/* mem.util.anonhugepages */
{ NULL,
{ PMDA_PMID(CLUSTER_MEMINFO,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
@@ -1134,6 +1134,61 @@ static pmdaMetric metrictab[] = {
{ PMDA_PMID(CLUSTER_MEMINFO,63), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* mem.util.shmemhugepages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,64), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.shmempmdmapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,65), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.filehugepages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,66), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.filepmdmapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,67), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.cmatotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,68), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.cmafree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,69), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.unaccepted */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,70), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.zswap */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,71), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.zswapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,72), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.shadowcallstack */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,73), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.percpu */
+ { NULL,
+ { 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.numa.util.total */
{ NULL,
{ PMDA_PMID(CLUSTER_NUMA_MEMINFO,0), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
@@ -8201,7 +8256,7 @@ linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
return 0; /* no values available */
atom->ull = proc_meminfo.HardwareCorrupted;
break;
- case 56: /* mem.util.anonhugepages (in pages) */
+ case 56: /* mem.util.anonhugepages (in kbytes) */
if (!MEMINFO_VALID_VALUE(proc_meminfo.AnonHugePages))
return 0; /* no values available */
atom->ull = proc_meminfo.AnonHugePages;
@@ -8245,6 +8300,61 @@ linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
atom->ull = proc_meminfo.HugepagesSurp *
(proc_meminfo.Hugepagesize << 10);
break;
+ case 64: /* mem.util.shmemhugepages (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.ShmemHugePages))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.ShmemHugePages;
+ break;
+ case 65: /* mem.util.shmempmdmapped (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.ShmemPmdMapped))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.ShmemPmdMapped;
+ break;
+ case 66: /* mem.util.filehugepages (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.FileHugePages))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.FileHugePages;
+ break;
+ case 67: /* mem.util.filepmdmapped (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.FilePmdMapped))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.FilePmdMapped;
+ break;
+ case 68: /* mem.util.cmatotal (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.CmaTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.CmaTotal;
+ break;
+ case 69: /* mem.util.cmafree (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.CmaFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.CmaFree;
+ break;
+ case 70: /* mem.util.unaccepted (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Unaccepted))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Unaccepted;
+ break;
+ case 71: /* mem.util.zswap (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Zswap))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Zswap;
+ break;
+ case 72: /* mem.util.zswapped (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Zswapped))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Zswapped;
+ break;
+ case 73: /* mem.util.shadowcallstack (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.ShadowCallStack))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.ShadowCallStack;
+ break;
+ case 74: /* mem.util.percpu (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Percpu))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Percpu;
+ break;
default:
return PM_ERR_PMID;
}
diff --git a/src/pmdas/linux/proc_meminfo.c b/src/pmdas/linux/proc_meminfo.c
index f7599e2..dff30bc 100644
--- a/src/pmdas/linux/proc_meminfo.c
+++ b/src/pmdas/linux/proc_meminfo.c
@@ -47,6 +47,8 @@ static struct {
{ "MmapCopy", &moff.MmapCopy },
{ "SwapTotal", &moff.SwapTotal },
{ "SwapFree", &moff.SwapFree },
+ { "Zswap", &moff.Zswap },
+ { "Zswapped", &moff.Zswapped },
{ "Dirty", &moff.Dirty },
{ "Writeback", &moff.Writeback },
{ "AnonPages", &moff.AnonPages },
@@ -56,6 +58,7 @@ static struct {
{ "SReclaimable", &moff.SlabReclaimable },
{ "SUnreclaim", &moff.SlabUnreclaimable },
{ "KernelStack", &moff.KernelStack },
+ { "ShadowCallStack", &moff.ShadowCallStack },
{ "PageTables", &moff.PageTables },
{ "Quicklists", &moff.Quicklists },
{ "NFS_Unstable", &moff.NFS_Unstable },
@@ -66,8 +69,16 @@ static struct {
{ "VmallocTotal", &moff.VmallocTotal },
{ "VmallocUsed", &moff.VmallocUsed },
{ "VmallocChunk", &moff.VmallocChunk },
+ { "Percpu", &moff.Percpu },
{ "HardwareCorrupted", &moff.HardwareCorrupted },
{ "AnonHugePages", &moff.AnonHugePages },
+ { "ShmemHugePages", &moff.ShmemHugePages },
+ { "ShmemPmdMapped", &moff.ShmemPmdMapped },
+ { "FileHugePages", &moff.FileHugePages },
+ { "FilePmdMapped", &moff.FilePmdMapped },
+ { "CmaTotal", &moff.CmaTotal },
+ { "CmaFree", &moff.CmaFree },
+ { "Unaccepted", &moff.Unaccepted },
/* vendor kernel patches, some outdated now */
{ "MemShared", &moff.MemShared },
{ "ReverseMaps", &moff.ReverseMaps },
diff --git a/src/pmdas/linux/proc_meminfo.h b/src/pmdas/linux/proc_meminfo.h
index c353606..afcf837 100644
--- a/src/pmdas/linux/proc_meminfo.h
+++ b/src/pmdas/linux/proc_meminfo.h
@@ -45,6 +45,8 @@ typedef struct {
int64_t SwapTotal;
int64_t SwapFree;
int64_t SwapUsed; /* computed */
+ int64_t Zswap;
+ int64_t Zswapped;
int64_t Dirty;
int64_t Writeback;
int64_t Mapped;
@@ -53,6 +55,7 @@ typedef struct {
int64_t SlabReclaimable;
int64_t SlabUnreclaimable;
int64_t KernelStack;
+ int64_t ShadowCallStack;
int64_t CommitLimit;
int64_t Committed_AS;
int64_t PageTables;
@@ -65,8 +68,17 @@ typedef struct {
int64_t VmallocTotal;
int64_t VmallocUsed;
int64_t VmallocChunk;
+ int64_t Percpu;
int64_t HardwareCorrupted;
int64_t AnonHugePages;
+ int64_t ShmemHugePages;
+ int64_t ShmemPmdMapped;
+ int64_t FileHugePages;
+ int64_t FilePmdMapped;
+ int64_t CmaTotal;
+ int64_t CmaFree;
+ int64_t Unaccepted;
+ /* vendor patches (old) */
int64_t HugepagesTotal;
int64_t HugepagesFree;
int64_t HugepagesRsvd;
diff --git a/src/pmdas/linux/root_linux b/src/pmdas/linux/root_linux
index 58ef225..18f70c7 100644
--- a/src/pmdas/linux/root_linux
+++ b/src/pmdas/linux/root_linux
@@ -695,6 +695,17 @@ mem.util {
hugepagesFreeBytes 60:1:61
hugepagesRsvdBytes 60:1:62
hugepagesSurpBytes 60:1:63
+ shmemhugepages 60:1:64
+ shmempmdmapped 60:1:65
+ filehugepages 60:1:66
+ filepmdmapped 60:1:67
+ cmatotal 60:1:68
+ cmafree 60:1:69
+ unaccepted 60:1:70
+ zswap 60:1:71
+ zswapped 60:1:72
+ shadowcallstack 60:1:73
+ percpu 60:1:74
}
mem.numa {
--
2.43.7

View File

@ -0,0 +1,171 @@
From 02568f90a0cb92feb95d5b7f65810cbc99bfed4e Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Fri, 10 Oct 2025 17:35:40 +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: 38526356
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 | 22 +++++++++++++++++++++-
src/pmdas/linux/proc_meminfo.c | 2 ++
src/pmdas/linux/proc_meminfo.h | 2 ++
src/pmdas/linux/root_linux | 2 ++
6 files changed, 34 insertions(+), 1 deletion(-)
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 3700809..531b136 100644
--- a/src/pmdas/linux/help
+++ b/src/pmdas/linux/help
@@ -959,6 +959,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 23d8033..37a65f1 100644
--- a/src/pmdas/linux/pmda.c
+++ b/src/pmdas/linux/pmda.c
@@ -1189,6 +1189,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,
@@ -8354,7 +8364,17 @@ linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
if (!MEMINFO_VALID_VALUE(proc_meminfo.Percpu))
return 0; /* no values available */
atom->ull = proc_meminfo.Percpu;
- break;
+ 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 dff30bc..4891610 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 afcf837..56512d1 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 18f70c7..0a3a92a 100644
--- a/src/pmdas/linux/root_linux
+++ b/src/pmdas/linux/root_linux
@@ -706,6 +706,8 @@ mem.util {
zswapped 60:1:72
shadowcallstack 60:1:73
percpu 60:1:74
+ kreclaimable 60:1:75
+ hugetlb 60:1:76
}
mem.numa {
--
2.43.7

View File

@ -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: 38526338
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/a02f695771e7534a90391fe243badb47b7b2371d
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/mpstat/pcp-mpstat.py | 184 ++++++++---------------------------
1 file changed, 42 insertions(+), 142 deletions(-)
diff --git a/src/pcp/mpstat/pcp-mpstat.py b/src/pcp/mpstat/pcp-mpstat.py
index 89f308ac39..fed1b91ae1 100755
--- a/src/pcp/mpstat/pcp-mpstat.py
+++ b/src/pcp/mpstat/pcp-mpstat.py
@@ -22,14 +22,14 @@
import time
MPSTAT_METRICS = ['kernel.uname.nodename', 'kernel.uname.release', 'kernel.uname.sysname',
'kernel.uname.machine', 'hinv.map.cpu_num', 'hinv.ncpu', 'hinv.cpu.online',
- 'kernel.all.cpu.user',
- 'kernel.all.cpu.nice', 'kernel.all.cpu.sys', 'kernel.all.cpu.wait.total',
- 'kernel.all.cpu.irq.hard', 'kernel.all.cpu.irq.soft', 'kernel.all.cpu.steal',
- 'kernel.all.cpu.guest', 'kernel.all.cpu.guest_nice', 'kernel.all.cpu.idle',
- 'kernel.percpu.cpu.user', 'kernel.percpu.cpu.nice', 'kernel.percpu.cpu.sys',
- 'kernel.percpu.cpu.wait.total', 'kernel.percpu.cpu.irq.hard', 'kernel.percpu.cpu.irq.soft',
- 'kernel.percpu.cpu.steal', 'kernel.percpu.cpu.guest','kernel.percpu.cpu.guest_nice',
- 'kernel.percpu.cpu.idle', 'kernel.all.intr', 'kernel.percpu.intr']
+ 'kernel.all.cpu.vuser', 'kernel.all.cpu.vnice', 'kernel.all.cpu.sys',
+ 'kernel.all.cpu.wait.total', 'kernel.all.cpu.irq.hard', 'kernel.all.cpu.irq.soft',
+ 'kernel.all.cpu.steal', 'kernel.all.cpu.guest', 'kernel.all.cpu.guest_nice',
+ 'kernel.all.cpu.idle','kernel.percpu.cpu.vuser','kernel.percpu.cpu.vnice',
+ 'kernel.percpu.cpu.sys','kernel.percpu.cpu.wait.total', 'kernel.percpu.cpu.irq.hard',
+ 'kernel.percpu.cpu.irq.soft','kernel.percpu.cpu.steal', 'kernel.percpu.cpu.guest',
+ 'kernel.percpu.cpu.guest_nice','kernel.percpu.cpu.idle', 'kernel.all.intr', 'kernel.percpu.intr']
+
interrupts_list = []
soft_interrupts_list = []
@@ -126,170 +126,70 @@ def __fetch_previous_values(self,metric,instance):
class CoreCpuUtil:
def __init__(self, instance, delta_time, metric_repository):
- self.delta_time = delta_time
self.instance = instance
+ self.delta_time = delta_time
self.metric_repository = metric_repository
+ self._total_cpus = None # Cache for performance
def total_cpus(self):
- return self.metric_repository.current_value('hinv.ncpu', None)
+ if self._total_cpus is None:
+ self._total_cpus = self.metric_repository.current_value('hinv.ncpu', None)
+ return self._total_cpus
+
def cpu_number(self):
return self.instance
- def cpu_online(self):
- return self.metric_repository.current_value('hinv.cpu.online', self.instance)
-
def user_time(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.user'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
-
- else:
- return None
+ return self._compute_metric('cpu.vuser')
def nice_time(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.nice'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.vnice')
def sys_time(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.sys'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.sys')
def iowait_time(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.wait.total'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.wait.total')
def irq_hard(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.irq.hard'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.irq.hard')
def irq_soft(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.irq.soft'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.irq.soft')
def steal(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.steal'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.steal')
def guest_time(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.guest'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.guest')
def guest_nice(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.guest_nice'
- p_time = self.metric_repository.previous_value(metric, self.instance)
- c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
- return None
- return float("%.2f"%(value))
- else:
- return None
+ return self._compute_metric('cpu.guest_nice')
def idle_time(self):
- metric = 'kernel.' + self.__all_or_percpu() + '.cpu.idle'
+ return self._compute_metric('cpu.idle')
+
+ def _compute_metric(self, metric_suffix):
+ metric = f'kernel.{self._all_or_percpu()}.{metric_suffix}'
p_time = self.metric_repository.previous_value(metric, self.instance)
c_time = self.metric_repository.current_value(metric, self.instance)
- if p_time is not None and c_time is not None:
- value = (100*(c_time - p_time))/(1000*self.delta_time)
- if self.instance is None and self.total_cpus() is not None:
- return float("%.2f"%(value/self.total_cpus()))
- else:
- if self.total_cpus() is None:
+
+ if p_time is None or c_time is None or self.delta_time == 0:
+ return None
+
+ try:
+ value = (100 * (c_time - p_time)) / (1000 * self.delta_time)
+ if self.instance is None:
+ total = self.total_cpus()
+ if total:
+ value /= total
+ else:
return None
- return float("%.2f"%(value))
- else:
+ return min (round(value, 2),100)
+ except (ZeroDivisionError, TypeError):
return None
- def __all_or_percpu(self):
+ def _all_or_percpu(self):
return 'all' if self.instance is None else 'percpu'
class CpuUtil:
@@ -652,7 +552,7 @@ def get_summary_metrics(self,group):
def report(self,manager):
try:
group = manager['mpstat']
- if group['kernel.all.cpu.user'].netPrevValues is None:
+ if group['kernel.all.cpu.vuser'].netPrevValues is None:
# need two fetches to report rate converted counter metrics
self.get_summary_metrics(group)
return

View File

@ -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: 37655753
Cherry-pick-commit: https://github.com/performancecopilot/pcp/pull/2200/commits/64cfffa8777d349d58aac07684abe0d834695e22
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/iostat/pcp-iostat.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/pcp/iostat/pcp-iostat.py b/src/pcp/iostat/pcp-iostat.py
index 909f2ac70d..ae503ed83f 100755
--- a/src/pcp/iostat/pcp-iostat.py
+++ b/src/pcp/iostat/pcp-iostat.py
@@ -461,5 +461,7 @@ def __init__(self):
except pmapi.pmUsageErr as usage:
usage.message()
sys.exit(1)
+ except IOError:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except KeyboardInterrupt:
pass

View File

@ -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: 38435551
Cherry-pick-commit: https://github.com/orasagar/pcp/commit/8e6a5ac18c06f813bad74494134ad5ac2202684e
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pmdas/linux/help | 7 ++++
src/pmdas/linux/numa_meminfo.c | 50 +++++++++++++++++------------
src/pmdas/linux/pmda.c | 58 +++++++++++++++++++++++++++++++++-
src/pmdas/linux/root_linux | 7 ++++
4 files changed, 100 insertions(+), 22 deletions(-)
diff --git a/src/pmdas/linux/help b/src/pmdas/linux/help
index c608d10ba5..482e099d66 100644
--- a/src/pmdas/linux/help
+++ b/src/pmdas/linux/help
@@ -1030,6 +1030,13 @@ pressure.
@ mem.numa.util.hugepagesTotal per-node total count of hugepages
@ mem.numa.util.hugepagesFree per-node count of free hugepages
@ mem.numa.util.hugepagesSurp per-node count of surplus hugepages
+@ mem.numa.util.swapCached per-node amount of memory in swap cache
+@ mem.numa.util.kreclaimable per-node kernel reclaimable memory
+@ mem.numa.util.anonhugepages per-node amount of memory in anonymous huge pages
+@ mem.numa.util.shmemhugepages per-node amount of shared memory allocated with huge
+@ mem.numa.util.shmempmdmapped per-node shared memory mapped into userspace with hugepages
+@ mem.numa.util.filehugepages per-node page cache (file) pages allocated with hugepages
+@ mem.numa.util.filepmdmapped per-node page cache mapped into userspace with hugepages
@ mem.numa.alloc.hit per-node count of times a task wanted alloc on local node and succeeded
@ mem.numa.alloc.miss per-node count of times a task wanted alloc on local node but got another node
@ mem.numa.alloc.foreign count of times a task on another node alloced on that node, but got this node
diff --git a/src/pmdas/linux/numa_meminfo.c b/src/pmdas/linux/numa_meminfo.c
index 9024962b2e..461bed0965 100644
--- a/src/pmdas/linux/numa_meminfo.c
+++ b/src/pmdas/linux/numa_meminfo.c
@@ -24,42 +24,50 @@
/* sysfs file for numa meminfo */
static struct linux_table numa_meminfo_table[] = {
- { field: "MemTotal:", maxval: 0x0 },
- { field: "MemFree:", maxval: 0x0 },
- { field: "MemUsed:", maxval: 0x0 },
- { field: "Active:", maxval: 0x0 },
- { field: "Inactive:", maxval: 0x0 },
+ { field: "MemTotal:", maxval: 0x0 },
+ { field: "MemFree:", maxval: 0x0 },
+ { field: "MemUsed:", maxval: 0x0 },
+ { field: "SwapCached:", maxval: 0x0 },
+ { field: "Active:", maxval: 0x0 },
+ { field: "Inactive:", maxval: 0x0 },
{ field: "Active(anon):", maxval: 0x0 },
{ field: "Inactive(anon):", maxval: 0x0 },
{ field: "Active(file):", maxval: 0x0 },
{ field: "Inactive(file):", maxval: 0x0 },
- { field: "HighTotal:", maxval: 0x0 },
- { field: "HighFree:", maxval: 0x0 },
- { field: "LowTotal:", maxval: 0x0 },
- { field: "LowFree:", maxval: 0x0 },
+ { field: "HighTotal:", maxval: 0x0 },
+ { field: "HighFree:", maxval: 0x0 },
+ { field: "LowTotal:", maxval: 0x0 },
+ { field: "LowFree:", maxval: 0x0 },
{ field: "Unevictable:", maxval: 0x0 },
- { field: "Mlocked:", maxval: 0x0 },
- { field: "Dirty:", maxval: 0x0 },
- { field: "Writeback:", maxval: 0x0 },
- { field: "FilePages:", maxval: 0x0 },
- { field: "Mapped:", maxval: 0x0 },
- { field: "AnonPages:", maxval: 0x0 },
- { field: "Shmem:", maxval: 0x0 },
+ { field: "Mlocked:", maxval: 0x0 },
+ { field: "Dirty:", maxval: 0x0 },
+ { field: "Writeback:", maxval: 0x0 },
+ { field: "FilePages:", maxval: 0x0 },
+ { field: "AnonPages:", maxval: 0x0 },
+ { field: "Shmem:", maxval: 0x0 },
{ field: "KernelStack:", maxval: 0x0 },
- { field: "PageTables:", maxval: 0x0 },
+ { field: "PageTables:", maxval: 0x0 },
+ { field: "SecPageTables:", maxval: 0x0 },
{ field: "NFS_Unstable:", maxval: 0x0 },
- { field: "Bounce:", maxval: 0x0 },
+ { field: "Bounce:", maxval: 0x0 },
{ field: "WritebackTmp:", maxval: 0x0 },
- { field: "Slab:", maxval: 0x0 },
+ { field: "KReclaimable:", maxval: 0x0 },
+ { field: "Slab:", maxval: 0x0 },
{ field: "SReclaimable:", maxval: 0x0 },
- { field: "SUnreclaim:", maxval: 0x0 },
+ { field: "SUnreclaim:", maxval: 0x0 },
+ { field: "AnonHugePages:", maxval: 0x0 },
+ { field: "ShmemHugePages:", maxval: 0x0 },
+ { field: "ShmemPmdMapped:", maxval: 0x0 },
+ { field: "FileHugePages:", maxval: 0x0 },
+ { field: "FilePmdMapped:", maxval: 0x0 },
+ { field: "Mapped:", maxval: 0x0 },
{ field: "HugePages_Total:", maxval: 0x0 },
{ field: "HugePages_Free:", maxval: 0x0 },
{ field: "HugePages_Surp:", maxval: 0x0 },
{ field: NULL }
};
-/* sysfs file for numastat */
+/* sysfs file for numastat */
static struct linux_table numa_memstat_table[] = {
{ field: "numa_hit", maxval: ULONGLONG_MAX },
{ field: "numa_miss", maxval: ULONGLONG_MAX },
diff --git a/src/pmdas/linux/pmda.c b/src/pmdas/linux/pmda.c
index 1026caf860..b30496b4b8 100644
--- a/src/pmdas/linux/pmda.c
+++ b/src/pmdas/linux/pmda.c
@@ -1416,6 +1416,42 @@ static pmdaMetric metrictab[] = {
{ PMDA_PMID(CLUSTER_NUMA_MEMINFO,41), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* mem.numa.util.swapCached */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,42), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.kreclaimable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,43), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.anonhugepages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,44), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.shmemhugepages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,45), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.shemempmdmapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,46), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.filehugepages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,47), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.filepmdmapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,48), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+
/* swap.length */
{ NULL,
{ PMDA_PMID(CLUSTER_MEMINFO,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
@@ -9952,7 +9988,27 @@ linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
sts = linux_table_lookup("HugePages_Surp:", np->meminfo, &atom->ull);
atom->ull *= (proc_meminfo.Hugepagesize << 10);
break;
-
+ case 42: /* mem.numa.util.swapCached */
+ sts = linux_table_lookup("SwapCached:", np->meminfo, &atom->ull);
+ break;
+ case 43: /* mem.numa.util.kreclaimable */
+ sts = linux_table_lookup("KReclaimable:", np->meminfo, &atom->ull);
+ break;
+ case 44: /* mem.numa.util.anonhugepages */
+ sts = linux_table_lookup("AnonHugePages:", np->meminfo, &atom->ull);
+ break;
+ case 45: /* mem.numa.util.shmemhugepages */
+ sts = linux_table_lookup("ShmemHugePages:", np->meminfo, &atom->ull);
+ break;
+ case 46: /* mem.numa.util.shmempmdmapped */
+ sts = linux_table_lookup("ShmemPmdMapped:", np->meminfo, &atom->ull);
+ break;
+ case 47: /* mem.numa.util.filehugepages */
+ sts = linux_table_lookup("FileHugePages:", np->meminfo, &atom->ull);
+ break;
+ case 48: /* mem.numa.util.filepmdmapped */
+ sts = linux_table_lookup("FilePmdMapped:", np->meminfo, &atom->ull);
+ break;
default:
return PM_ERR_PMID;
}
diff --git a/src/pmdas/linux/root_linux b/src/pmdas/linux/root_linux
index 1add9a43cf..054b77b67d 100644
--- a/src/pmdas/linux/root_linux
+++ b/src/pmdas/linux/root_linux
@@ -793,6 +793,13 @@ mem.numa.util {
hugepagesTotalBytes 60:36:39
hugepagesFreeBytes 60:36:40
hugepagesSurpBytes 60:36:41
+ swapCached 60:36:42
+ kreclaimable 60:36:43
+ anonhugepages 60:36:44
+ shmemhugepages 60:36:45
+ shmempmdmapped 60:36:46
+ filehugepages 60:36:47
+ filepmdmapped 60:36:48
}
mem.numa.alloc {

View File

@ -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: 38380185
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/e4d513fd503cd3f0e7a2ccab0e0f069acf872427
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/numastat/pcp-numastat.1 | 8 +-
src/pcp/numastat/pcp-numastat.py | 414 +++++++++++++++------
2 files changed, 422 insertions(+), 1 deletions(-)
diff --git a/src/pcp/numastat/pcp-numastat.1 b/src/pcp/numastat/pcp-numastat.1
index d9165ea86e..65910a4877 100644
--- a/src/pcp/numastat/pcp-numastat.1
+++ b/src/pcp/numastat/pcp-numastat.1
@@ -18,7 +18,7 @@
\f3pcp-numastat\f1 \- report on NUMA memory allocation
.SH SYNOPSIS
\f3pcp\f1 [\f2pcp\ options\f1] \f3numastat\f1
-[\f3\-Vw?\f1]
+[\fB-V\fR] [\fB-w\fR \fIwidth\fR] [\fB-m\fR][-n\fR] [\fB-?\fR]
.SH DESCRIPTION
.B pcp-numastat
displays NUMA allocation statistics from the kernel memory
@@ -69,6 +69,12 @@ Display the current version of the command.
Limit display to
.IR width .
.TP
+\fB-m\fR, \fB--meminfo\fR
+Display meminfo-like system-wide memory usage.
+.TP
+\fB-n\fR, \fB--numastat\fR
+Display the numastat statistics info.
+.TP
\fB\-?\fR, \fB\-\-help\fR
Display usage message and exit.
.SH NOTES
diff --git a/src/pcp/numastat/pcp-numastat.py b/src/pcp/numastat/pcp-numastat.py
index 46c2fcb97d..2a044c9bbd 100755
--- a/src/pcp/numastat/pcp-numastat.py
+++ b/src/pcp/numastat/pcp-numastat.py
@@ -17,150 +17,328 @@
""" Display NUMA memory allocation statistucs """
import os
+import signal
import sys
+import time
+
from pcp import pmapi
-from cpmapi import PM_TYPE_U64, PM_CONTEXT_ARCHIVE
+from pcp import pmcc
+from cpmapi import PM_CONTEXT_ARCHIVE
if sys.version >= '3':
long = int # python2 to python3 portability (no long() in python3)
xrange = range # more back-compat (xrange() is range() in python3)
-class NUMAStat(object):
- """ Gives a short summary of per-node NUMA memory information.
+NUMA_METRICS = [
+ "mem.numa.alloc.hit",
+ "mem.numa.alloc.miss",
+ "mem.numa.alloc.foreign",
+ "mem.numa.alloc.interleave_hit",
+ "mem.numa.alloc.local_node",
+ "mem.numa.alloc.other_node",
+]
- Knows about some of the default PCP arguments - can function
- using remote hosts or historical data, using the timezone of
- the metric source, at an offset within an archive, and so on.
- """
+MEM_METRICS = [
+ "mem.numa.util.total",
+ "mem.numa.util.free",
+ "mem.numa.util.used",
+ "mem.numa.util.active",
+ "mem.numa.util.inactive",
+ "mem.numa.util.active_anon",
+ "mem.numa.util.inactive_anon",
+ "mem.numa.util.active_file",
+ "mem.numa.util.inactive_file",
+ "mem.numa.util.unevictable",
+ "mem.numa.util.mlocked",
+ "mem.numa.util.dirty",
+ "mem.numa.util.writeback",
+ "mem.numa.util.filePages",
+ "mem.numa.util.mapped",
+ "mem.numa.util.anonpages",
+ "mem.numa.util.shmem",
+ "mem.numa.util.kernelStack",
+ "mem.numa.util.pageTables",
+ "mem.numa.util.NFS_Unstable",
+ "mem.numa.util.bounce",
+ "mem.numa.util.writebackTmp",
+ "mem.numa.util.filehugepages",
+ "mem.numa.util.filepmdmapped",
+ "mem.numa.util.slab",
+ "mem.numa.util.slabReclaimable",
+ "mem.numa.util.slabUnreclaimable",
+ "mem.numa.util.anonhugepages",
+ "mem.numa.util.shmemhugepages",
+ "mem.numa.util.shmempmdmapped",
+ "mem.numa.util.hugepagesTotal",
+ "mem.numa.util.hugepagesFree",
+ "mem.numa.util.hugepagesSurp",
+ "mem.numa.util.swapCached",
+ "mem.numa.util.kreclaimable",
+]
- def __init__(self):
- """ Construct object - prepare for command line handling """
- self.opts = self.options()
- self.context = None
- self.width = 0
+SYS_METRICS = [
+ 'kernel.uname.nodename',
+ 'kernel.uname.release',
+ 'kernel.uname.sysname',
+ 'kernel.uname.machine',
+ 'hinv.ncpu',
+]
+
+ALL_METRICS = NUMA_METRICS + MEM_METRICS
- def resize(self):
+def prefix(metric):
+ last_part = metric.split('.')[-1]
+ result = last_part[0].upper() + last_part[1:]
+ return result
+
+class MetricRepository:
+ def __init__(self, group):
+ self.group = group
+ self.current_cached_values = {}
+ self.previous_cached_values = {}
+
+ def _fetch_current_values(self, metric, instance):
+ if instance is not None:
+ return dict(
+ map(lambda x: (x[0].inst, x[2]), self.group[metric].netValues)
+ )
+ else:
+ if self.group[metric].netValues == []:
+ return None
+ else:
+ return self.group[metric].netValues[0][2]
+ def current_values(self, metric_name):
+ if self.group.get(metric_name, None) is None:
+ return None
+ if self.current_cached_values.get(metric_name, None) is None:
+ self.current_cached_values[
+ metric_name
+ ] = self._fetch_current_values(metric_name, True)
+ return self.current_cached_values.get(metric_name, None)
+
+class NUMAStat:
+
+ def __init__(self, group):
+ self.group = group
+ self.repo = MetricRepository(group)
+
+ def resize(self, width):
""" Find a suitable display width limit """
- if self.width == 0:
+ if width == 0:
if not sys.stdout.isatty():
- self.width = 1000000000 # mimic numastat(1) here
+ width = 1000000000 # mimic numastat(1) here
else:
- # popen() is SAFE, command is a literal string
+ # popen() is SAFE, command is a literal string
(_, width) = os.popen('stty size', 'r').read().split()
- self.width = int(width)
- self.width = int(os.getenv('NUMASTAT_WIDTH', str(self.width)))
- self.width = max(self.width, 32)
+ width = int(width)
+ width = int(os.getenv('NUMASTAT_WIDTH', str(width)))
+ return max(width, 32)
- def option(self, opt, optarg, index):
- """ Perform setup for an individual command line option """
- if opt == 'w':
- self.width = int(optarg)
+ def __format_table(self, width, nodes, data):
+ null_output = False
+ if not nodes:
+ null_output = True
+ nodes = [(0, 'Node ')]
- def options(self):
- """ Setup default command line argument option handling """
- opts = pmapi.pmOptions()
- opts.pmSetOptionCallback(self.option)
- opts.pmSetShortOptions("w:V?")
- opts.pmSetLongOptionHeader("Options")
- opts.pmSetLongOption("width", 1, 'w', "N", "limit the display width")
- opts.pmSetLongOptionVersion()
- opts.pmSetLongOptionHelp()
- return opts
-
- def extract(self, descs, insts, result):
- """ Extract the set of metric values from a given pmResult """
- values = [[]]
- for metrics in xrange(len(descs)):
- values.append([])
- for nodes in xrange(len(insts)):
- if result.contents.get_numval(metrics) > 0:
- atom = self.context.pmExtractValue(
- result.contents.get_valfmt(metrics),
- result.contents.get_vlist(metrics, nodes),
- descs[metrics].contents.type, PM_TYPE_U64)
- values[metrics].append(long(atom.ull))
- else:
- values[metrics].append(long(0))
- return values
-
- def execute(self):
- """ Using a PMAPI context (could be either host or archive),
- fetch and report per-node values related to NUMA memory.
- """
- metrics = ('mem.numa.alloc.hit', 'mem.numa.alloc.miss',
- 'mem.numa.alloc.foreign', 'mem.numa.alloc.interleave_hit',
- 'mem.numa.alloc.local_node', 'mem.numa.alloc.other_node')
-
- pmids = self.context.pmLookupName(metrics)
- descs = self.context.pmLookupDescs(pmids)
- if self.context.type == PM_CONTEXT_ARCHIVE:
- (insts, nodes) = self.context.pmGetInDomArchive(descs[0])
+ if "numastat" in data:
+ metrics = NUMA_METRICS
+ title = "NUMA memory allocation statistics (pages)"
else:
- (insts, nodes) = self.context.pmGetInDom(descs[0])
- result = self.context.pmFetch(pmids)
- values = self.extract(descs, insts, result)
- self.context.pmFreeResult(result)
- self.report(metrics, nodes, values)
-
- def report(self, metrics, nodes, values):
- """ Given per-node metric names and values, dump 'em like numastat(1)
- Nodes is a list of strings, values is a list of lists of values.
- """
- columns = len(nodes) * 16
- if columns == 0:
- print("No NUMA nodes found, exiting")
- sys.exit(1)
- self.resize()
- maxnodes = int((self.width - 16) / 16)
+ metrics = MEM_METRICS
+ title = "Per-node system memory usage (KB)"
+
+ total_w = max(42, int(width))
+ print(title[:total_w])
+
+ width = self.resize(width)
+ maxnodes = int((width - 16) / 16)
if maxnodes > len(nodes): # just an initial header suffices
- header = '%-16s' % ''
- for node in nodes:
- header += '%16s' % node
+ header = '%30s' % ''
+ for _, node in nodes:
+ header += '%-12s' % node
print(header)
- for index in xrange(len(metrics)):
- title = self.prefix(metrics[index])
- self.metric(title, nodes, values[index], maxnodes)
-
- def metric(self, prefix, nodes, values, maxnodes):
- """ Given one metric and its per-node values, produce one or more
- lines of output with the values, each line node-name prefixed
- and with a new node header for each.
- """
- done = 0
- while done < len(nodes):
- header = '%-16s' % ''
- window = '%-16s' % prefix
- for index in xrange(maxnodes):
- current = done + index
- if current >= len(nodes):
- break
- header += '%16s' % (nodes[current])
- window += '%16d' % (values[current])
- if done > maxnodes or maxnodes <= len(nodes):
- print('%s\n%s' % (header, window))
- else:
- print('%s' % window)
- done += maxnodes
- def prefix(self, metric):
- """ Transform the PCP metric names into the reported sub-headings """
- title = metric[15:]
- if '_' not in title:
- title = 'numa_' + title
- return title
+ for m in metrics:
+ if not null_output:
+ vals = self.repo.current_values(m)
+ done = 0 # reset for each metric
+
+ # Loop through nodes in chunks of 'maxnodes'
+ while done < len(nodes):
+ header = '%-30s' % ''
+ window = '%-20s : ' % prefix(m)
+
+ # Slice the range we'll print in this batch
+ chunk = nodes[done:done + maxnodes]
+
+ for i, ( _, name) in enumerate(chunk):
+ header += '%-12s' % name
+ if not null_output:
+ window += '%12s' % vals[done + i]
+ else:
+ window += '%12s' % "NA"
+
+ # Print header once per row group (not every metric)
+ if done > maxnodes or maxnodes <= len(nodes):
+ print('%s\n%s' % (header, window))
+ else:
+ print('%s' % window)
+ done += maxnodes
+ print()
+
+ def print_mem(self, width, nodes, data):
+ self.__format_table(width, nodes, data)
+
+ def print_numa(self, width, nodes, data):
+ self.__format_table(width, nodes, data)
+
+class NumaStatOption(pmapi.pmOptions):
+ context = None
+ timefmt = "%m/%d/%Y %H:%M:%S"
+ width = 0
+ mem_out = False
+ numa_out = False
+
+ def override(self,opt):
+ """ Override standard PCP options to match numastat(1) """
+ if opt == 'n':
+ return True
+ return False
+
+ def __init__(self):
+ pmapi.pmOptions.__init__(self)
+ self.pmSetShortOptions("w:mV?:n")
+ self.pmSetOptionCallback(self.extraOptions)
+ self.pmSetOverrideCallback(self.override)
+ self.pmSetLongOptionHeader("Numastat options")
+ self.pmSetLongOption("width", 1, 'w', "n", "limit the display width")
+ # Map long options to our non-conflicting short letters
+ self.pmSetLongOption("meminfo", 0, 'm', "", "show meminfo-like system-wide memory usage")
+ self.pmSetLongOption("numastat", 0, 'n', "", "show the numastat statistics info")
+ self.pmSetLongOptionVersion()
+ self.pmSetLongOptionHelp()
+
+ def extraOptions(self, opt, optarg, index):
+ if opt == 'w':
+ self.width = int(optarg)
+ elif opt == "m":
+ self.mem_out = True
+ elif opt == "n":
+ self.numa_out = True
+ elif opt == "V":
+ pass
+ else:
+ raise pmapi.pmUsageErr()
+ return True
+
+ def checkoptions(self):
+ if (not self.mem_out) and (not self.numa_out) and (self.width == 0):
+ self.numa_out = True
+ if self.width < 0:
+ return False
+ return True
- def connect(self):
- """ Establish a PMAPI context to archive, host or local, via args """
- self.context = pmapi.pmContext.fromOptions(self.opts, sys.argv)
+class NumaStatReport(pmcc.MetricGroupPrinter):
+ machine_info_count = 0
+
+ def __init__(self, options):
+ self.options = options
+ self.timestamp = None
+
+ def __get_timestamp(self, group):
+ ts = group.contextCache.pmLocaltime(int(group.timestamp))
+ self.timestamp = time.strftime(NumaStatOption.timefmt, ts.struct_time())
+ return self.timestamp
+
+ def __get_ncpu(self, group):
+ return group['hinv.ncpu'].netValues[0][2]
+
+ def print_machine_info(self,group, context):
+ timestamp = context.pmLocaltime(group.timestamp.tv_sec)
+ # Please check strftime(3) for different formatting options.
+ # Also check TZ and LC_TIME environment variables for more
+ # information on how to override the default formatting of
+ # the date display in the header
+ time_string = time.strftime("%x", timestamp.struct_time())
+ header_string = ''
+ header_string += group['kernel.uname.sysname'].netValues[0][2] + ' '
+ header_string += group['kernel.uname.release'].netValues[0][2] + ' '
+ header_string += '(' + group['kernel.uname.nodename'].netValues[0][2] + ') '
+ header_string += time_string + ' '
+ header_string += group['kernel.uname.machine'].netValues[0][2] + ' '
+ print("%s (%s CPU)" % (header_string, self.__get_ncpu(group)))
+
+ def __discover_nodes(self, group, name):
+ # Build list of online nodes (instance id, instance name)
+ nodes = []
+ try:
+ for ent in group[name].netValues:
+ inst_id = ent[0].inst
+ inst_name = ent[1] # usually "node0", "node1", ...
+ online = int(ent[2]) != 0
+ if online:
+ nodes.append((inst_id, inst_name))
+ except Exception:
+ pass
+ # Sort by instance id (node number)
+ nodes.sort(key=lambda t: t[0])
+ return nodes
+
+ def report(self, manager):
+ # Print in a stable order
+ group = manager["sys_info"]
+ try:
+ if not self.machine_info_count:
+ self.print_machine_info(group, manager)
+ self.machine_info_count = 1
+ except IndexError:
+ return
+
+ output_numa = (
+ self.options.numa_out
+ or (not self.options.mem_out and not self.options.numa_out)
+ )
+ output_mem = self.options.mem_out
+ group = manager["numastat"]
+ nodes = self.__discover_nodes(group, "mem.numa.util.total")
+ timestamp = self.__get_timestamp(group)
+ print("%-20s : %s"%("Timestamp", timestamp))
+ if output_mem:
+ NUMAStat(group).print_mem(self.options.width, nodes, "meminfo")
+ if output_numa:
+ NUMAStat(group).print_numa(self.options.width, nodes, "numastat")
+
+ if (
+ NumaStatOption.context is not PM_CONTEXT_ARCHIVE
+ and self.options.pmGetOptionSamples() is None
+ ):
+ sys.exit(0)
if __name__ == '__main__':
try:
- NUMASTAT = NUMAStat()
- NUMASTAT.connect()
- NUMASTAT.execute()
+ opts = NumaStatOption()
+ mngr = pmcc.MetricGroupManager.builder(opts, sys.argv)
+ if not opts.checkoptions():
+ print("Invalid options from command line")
+ raise pmapi.pmUsageErr()
+ NumaStatOption.context = mngr.type
+
+ missing = mngr.checkMissingMetrics(ALL_METRICS)
+ if missing is not None:
+ sys.stderr.write('Error: not all required metrics are available\nMissing: %s\n' % (missing))
+ sys.exit(1)
+
+ mngr["numastat"] = ALL_METRICS
+ mngr["sys_info"] = SYS_METRICS
+ mngr.printer = NumaStatReport(opts)
+ sts = mngr.run()
+ sys.exit(sts)
+ except IOError:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except pmapi.pmErr as error:
- print("%s: %s" % (error.progname(), error.message()))
+ sys.stderr.write("%s %s\n" % (error.progname(), error.message()))
except pmapi.pmUsageErr as usage:
usage.message()
+ sys.exit(1)
except KeyboardInterrupt:
pass

View File

@ -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: 38531609
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/meminfo/pcp-meminfo.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/pcp/meminfo/pcp-meminfo.py b/src/pcp/meminfo/pcp-meminfo.py
index 8492859..f956910 100755
--- a/src/pcp/meminfo/pcp-meminfo.py
+++ b/src/pcp/meminfo/pcp-meminfo.py
@@ -168,6 +168,9 @@ class MeminfoReport(pmcc.MetricGroupPrinter):
units = ""
if METRICS_DESC[idx][-6:] == "_NO_kb":
metric_name = METRICS_DESC[idx][:-6]
+ elif METRICS_DESC[idx] == "Hugepagesize":
+ metric_name = METRICS_DESC[idx]
+ units = "B"
else:
metric_name = METRICS_DESC[idx]
units = "kB"
@@ -203,6 +206,9 @@ class MeminfoReport(pmcc.MetricGroupPrinter):
continue
metric_name, units = self.getMetricName(idx)
+ if units == "B":
+ val = int(val / 1024)
+ units = "kB"
print("%-17s : %s %s"%(metric_name, val, units))
idx += 1
--
2.43.7

View File

@ -0,0 +1,32 @@
From 82d8675ebfc244f73314f6a19e171443019a6fbb Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Fri, 31 Oct 2025 14:02:13 +0530
Subject: [OL8 1027/1027] xz default compression changed to level 3
to reduce the size of the compressed pcp archives
Orabug: 38592558
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pmlogger/pmlogger_daily.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh
index db66b1d..27b1550 100755
--- a/src/pmlogger/pmlogger_daily.sh
+++ b/src/pmlogger/pmlogger_daily.sh
@@ -187,10 +187,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"
+ COMPRESS_DEFAULT="xz -3 --block-size=10MiB"
else
COMPRESS_DEFAULT=xz
fi
--
2.43.7

View File

@ -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: 38724884
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pmdas/nfsclient/pmdanfsclient.python | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pmdas/nfsclient/pmdanfsclient.python b/src/pmdas/nfsclient/pmdanfsclient.python
index 786d958..f08b93a 100644
--- a/src/pmdas/nfsclient/pmdanfsclient.python
+++ b/src/pmdas/nfsclient/pmdanfsclient.python
@@ -636,7 +636,7 @@ class NFSCLIENTPMDA(PMDA):
line = STATS.readline()
if line == '':
break
- m = re.match(r'\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$', line)
+ m = re.match(r'\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$', line)
if not m:
break
opname = m.group(1).lower()
--
2.43.7

View File

@ -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: 38817091
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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,366 @@
From a60882f460d54c039c905aff24962e107ee59c93 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Tue, 3 Mar 2026 16:13:45 +0530
Subject: [PATCH OL8 1031/1032] pmlogger_daily: add disk usage limit option and
purge logic
Introduce -d/--disk option to set max disk usage per pmlogger instance.
Implement logic to check total archive size and purge oldest files when limit is exceeded.
upstream ref:- 7b7c38c16c6904960ede400efa5225732935b830
[Orabug: 38991685]
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pmlogger/pmlogger_daily.sh | 193 ++++++++++++++++++++++++++++++++-
src/pmlogger/utilproc.sh | 83 ++++++++++++++
2 files changed, 274 insertions(+), 2 deletions(-)
diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh
index 27b1550..53e5eab 100755
--- a/src/pmlogger/pmlogger_daily.sh
+++ b/src/pmlogger/pmlogger_daily.sh
@@ -261,6 +261,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
@@ -302,6 +303,8 @@ MFLAG=false
EXPUNGE=""
FORCE=false
KILL=pmsignal
+SPACELIMIT_CMDLINE=""
+SPACELIMIT_DEFAULT="unlimited"
ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
[ $? != 0 ] && exit 1
@@ -315,6 +318,24 @@ do
CONTROLDIR="$2.d"
shift
;;
+ -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
@@ -448,7 +469,7 @@ do
COMPRESSREGEX_CMDLINE=""
continue
fi
- ;;
+ ;;
--) shift
break
;;
@@ -610,6 +631,43 @@ _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: $@"
@@ -713,6 +771,86 @@ _get_primary_logger_pid()
echo "$pid"
}
+# 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
+}
+
# mails out any entries for the previous 24hrs from the PCP notices file
#
if [ ! -z "$MAILME" ]
@@ -999,7 +1137,50 @@ s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
fi
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
+ ;;
'export PCP_COMPRESSREGEX;'*)
old_value="$PCP_COMPRESSREGEX"
$SHOWME && echo "+ $cmd"
@@ -1671,6 +1852,14 @@ p
$VERY_VERBOSE && echo >&2 "Warning: no trace files found to cull"
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 168d040..8c77b37 100644
--- a/src/pmlogger/utilproc.sh
+++ b/src/pmlogger/utilproc.sh
@@ -152,3 +152,86 @@ END { exit sts }'
fi
return $?
}
+
+# 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
+}
\ No newline at end of file
--
2.43.7

View File

@ -0,0 +1,328 @@
From 28b612d8743b36429df222a4baa5cb48dcaf8bc7 Mon Sep 17 00:00:00 2001
From: sagar sagar <sagar.sagar@oracle.com>
Date: Tue, 3 Mar 2026 16:15:10 +0530
Subject: [PATCH OL8 1032/1032] 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: 38991685]
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 6b7d32d..80799a2 100755
--- a/src/pcp/iostat/pcp-iostat.py
+++ b/src/pcp/iostat/pcp-iostat.py
@@ -55,9 +55,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 09a8182..9ffee11 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 a086e0f..a479cc4 100755
--- a/src/pcp/pidstat/pcp-pidstat.py
+++ b/src/pcp/pidstat/pcp-pidstat.py
@@ -907,8 +907,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 5b80589..1c27cf1 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -248,7 +248,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)
@@ -358,6 +358,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
@@ -370,19 +384,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(
@@ -392,47 +402,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:
@@ -449,23 +420,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)
@@ -530,25 +492,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" % (
@@ -584,8 +546,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)
@@ -854,6 +825,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 4f01c56..42c1a7a 100644
--- a/src/pcp/tapestat/pcp-tapestat.py
+++ b/src/pcp/tapestat/pcp-tapestat.py
@@ -67,9 +67,18 @@ class TapestatReport(pmcc.MetricGroupPrinter):
Hcount = 0
def timeStampDelta(self, group):
s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec
- u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec
- # u may be negative here, calculation is still correct.
- return s + u / 1000000.0
+ # pmapi timestamps may provide sub-second resolution via tv_nsec (nanoseconds)
+ # or tv_usec (microseconds) depending on the collector. Prefer nanoseconds
+ # when available, but gracefully fall back to microseconds to avoid
+ if hasattr(group.timestamp, 'tv_nsec') and hasattr(group.prevTimestamp, 'tv_nsec'):
+ n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec
+ # n may be negative here, calculation is still correct.
+ return s + n / 1000000000.0
+ elif hasattr(group.timestamp, 'tv_usec') and hasattr(group.prevTimestamp, 'tv_usec'):
+ u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec
+ return s + u / 1000000.0
+ # it should not reach here
+ return s
def instlist(self, group, name):
return dict(map(lambda x: (x[1], x[2]), group[name].netValues)).keys()
--
2.43.7

View File

@ -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: 38902336
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/nfsiostat/pcp-nfsiostat.py | 269 ++++++++++++++++-------------
1 file changed, 145 insertions(+), 124 deletions(-)
diff --git a/src/pcp/nfsiostat/pcp-nfsiostat.py b/src/pcp/nfsiostat/pcp-nfsiostat.py
index 0b0376d..e19fa41 100644
--- a/src/pcp/nfsiostat/pcp-nfsiostat.py
+++ b/src/pcp/nfsiostat/pcp-nfsiostat.py
@@ -22,7 +22,7 @@ import signal
import sys
import time
from pcp import pmapi, pmcc
-from cpmapi import PM_CONTEXT_ARCHIVE
+from cpmapi import PM_CONTEXT_ARCHIVE, PM_MODE_FORW
SYS_METRICS= ["kernel.uname.sysname","kernel.uname.release",
"kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"]
@@ -40,98 +40,54 @@ def adjust_length(name):
class ReportingMetricRepository:
def __init__(self,group):
- self.group=group
- self.current_cached_values = {}
-
- def __sorted(self,data):
- return dict(sorted(data.items(), key=lambda item: item[0].lower()))
-
- def __fetch_current_value(self,metric):
- val=dict(map(lambda x: (x[1], x[2]), self.group[metric].netValues))
- val=self.__sorted(val)
- return dict(val)
-
- def current_value(self,metric):
- if not metric in self.group:
- return None
- if self.current_cached_values.get(metric) is None:
- first_value=self.__fetch_current_value(metric)
- self.current_cached_values[metric]=first_value
- return self.current_cached_values[metric]
+ self.group = group
+ self._current_cache = {}
+ self._previous_cache = {}
+
+ def _fetch_values(self, metric, use_previous=False):
+ """Fetch values - always returns a dictionary."""
+ if metric not in self.group:
+ return {}
+ attr = "netPrevValues" if use_previous else "netValues"
+ values = getattr(self.group[metric], attr, [])
+ return {x[0].inst: x[2] for x in values} if values else {}
+
+ def _get_values_dict(self, metric, use_previous=False):
+ """Get cached dictionary of all values for a metric."""
+ cache = self._previous_cache if use_previous else self._current_cache
+ if metric not in cache:
+ cache[metric] = self._fetch_values(metric, use_previous)
+ return cache[metric]
+
+ def previous_value(self, metric, instance=None):
+ """Get previous value. Returns single value if instance given, else returns dict."""
+ values_dict = self._get_values_dict(metric, use_previous=True)
+ if instance is not None:
+ return values_dict.get(instance)
+ return values_dict
+
+ def current_value(self, metric, instance=None):
+ """Get current value. Returns single value if instance given, else returns dict."""
+ values_dict = self._get_values_dict(metric, use_previous=False)
+ if instance is not None:
+ return values_dict.get(instance)
+ return values_dict
+
+ def previous_value(self, metric, instance=None):
+ """Get previous value. Returns single value if instance given, else returns dict."""
+ values_dict = self._get_values_dict(metric, use_previous=True)
+ if instance is not None:
+ return values_dict.get(instance)
+ return values_dict
class NfsioStatUtil:
def __init__(self,metrics_repository):
self.__metric_repository=metrics_repository
self.report=ReportingMetricRepository(self.__metric_repository)
- def mount_point(self):
- return self.report.current_value('nfsclient.mountpoint')
-
- def mount_share(self):
- return self.report.current_value('nfsclient.export')
-
- def mount_share_keys(self):
- data = self.report.current_value('nfsclient.export')
- return data.keys()
-
- def sample_time(self):
- return self.report.current_value('nfsclient.age')
-
- def xprt_sends(self):
- return self.report.current_value('nfsclient.xprt.sends')
-
- def xprt_backlog(self):
- return self.report.current_value('nfsclient.xprt.backlog_u')
-
- def readops(self):
- return self.report.current_value('nfsclient.ops.read.ops')
-
- def readerrors(self):
- return self.report.current_value('nfsclient.ops.read.errors')
-
- def readexecute(self):
- return self.report.current_value('nfsclient.ops.read.execute')
-
- def readrtt(self):
- return self.report.current_value('nfsclient.ops.read.rtt')
-
- def readqueue(self):
- return self.report.current_value('nfsclient.ops.read.queue')
-
- def readbytesrecv(self):
- return self.report.current_value('nfsclient.ops.read.bytes_recv')
-
- def readbytessent(self):
- return self.report.current_value('nfsclient.ops.read.bytes_sent')
-
- def readntrans(self):
- return self.report.current_value('nfsclient.ops.read.ntrans')
-
- def writeops(self):
- return self.report.current_value('nfsclient.ops.write.ops')
-
- def writeerrors(self):
- return self.report.current_value('nfsclient.ops.write.errors')
-
- def writeexecute(self):
- return self.report.current_value('nfsclient.ops.write.execute')
-
- def writertt(self):
- return self.report.current_value('nfsclient.ops.write.rtt')
-
- def writequeue(self):
- return self.report.current_value('nfsclient.ops.write.queue')
-
- def writebytesrecv(self):
- return self.report.current_value('nfsclient.ops.write.bytes_recv')
-
- def writebytessent(self):
- return self.report.current_value('nfsclient.ops.write.bytes_sent')
-
- def writentrans(self):
- return self.report.current_value('nfsclient.ops.write.ntrans')
-
class NfsiostatReport(pmcc.MetricGroupPrinter):
+ machine_info_count = 0
+
def __init__(self,opts,group):
self.opts = opts
self.group = group
@@ -156,34 +112,67 @@ class NfsiostatReport(pmcc.MetricGroupPrinter):
header_string += context['kernel.uname.machine'].netValues[0][2] + ' '
print("%s (%s CPU)" % (header_string, self.__get_ncpu(context)))
- def __print_values(self,timestamp, nfsstatus):
- n_shares = nfsstatus.mount_share_keys()
- mountshare = nfsstatus.mount_share()
- mountpoint = nfsstatus.mount_point()
- sampletime = nfsstatus.sample_time()
- sends = nfsstatus.xprt_sends()
- backlog = nfsstatus.xprt_backlog()
- readops = nfsstatus.readops()
- readerrors = nfsstatus.readerrors()
- readexecute = nfsstatus.readexecute()
- readrtt = nfsstatus.readrtt()
- readqueue = nfsstatus.readqueue()
- readbytesrecv = nfsstatus.readbytesrecv()
- readbytessent = nfsstatus.readbytessent()
- readntrans = nfsstatus.readntrans()
- writeops = nfsstatus.writeops()
- writeerrors = nfsstatus.writeerrors()
- writeexecute = nfsstatus.writeexecute()
- writertt = nfsstatus.writertt()
- writequeue = nfsstatus.writequeue()
- writebytesrecv = nfsstatus.writebytesrecv()
- writebytessent = nfsstatus.writebytessent()
- writentrans = nfsstatus.writentrans()
+ # --------------------------------------------------------
+
+ def __collect(self, nfs):
+ return {
+ metric: nfs.report.current_value(metric)
+ for metric in NFSIOSTAT_METRICS
+ }
+
+ # --------------------------------------------------------
+
+ def __delta(self, new: dict, nfs):
+ delta = {}
+ old = {
+ metric: nfs.report.previous_value(metric)
+ for metric in NFSIOSTAT_METRICS
+ }
+
+ for metric in new:
+ delta[metric] = {}
+
+ for inst in new[metric]:
+ new_val = new[metric][inst]
+ old_val = old.get(metric, {}).get(inst, 0)
+
+ # If value is numeric → subtract
+ if isinstance(new_val, (int, float)):
+ delta[metric][inst] = new_val - old_val
+ else:
+ # If string → just copy (no subtraction)
+ delta[metric][inst] = new_val
+
+ return delta
+
+ def __print_values(self,timestamp, delta):
+
+ sampletime = delta["nfsclient.age"]
+ readops = delta["nfsclient.ops.read.ops"]
+ writeops = delta["nfsclient.ops.write.ops"]
+ readbytesrecv = delta["nfsclient.ops.read.bytes_recv"]
+ writebytesrecv = delta["nfsclient.ops.write.bytes_recv"]
+ mountpoint = delta["nfsclient.mountpoint"]
+ mountshare = delta["nfsclient.export"]
+ sends = delta["nfsclient.xprt.sends"]
+ backlog = delta["nfsclient.xprt.backlog_u"]
+ readerrors = delta["nfsclient.ops.read.errors"]
+ readexecute = delta["nfsclient.ops.read.execute"]
+ readrtt = delta["nfsclient.ops.read.rtt"]
+ readqueue = delta["nfsclient.ops.read.queue"]
+ readbytessent = delta["nfsclient.ops.read.bytes_sent"]
+ readntrans = delta["nfsclient.ops.read.ntrans"]
+ writeerrors = delta["nfsclient.ops.write.errors"]
+ writeexecute = delta["nfsclient.ops.write.execute"]
+ writertt = delta["nfsclient.ops.write.rtt"]
+ writequeue = delta["nfsclient.ops.write.queue"]
+ writebytessent = delta["nfsclient.ops.write.bytes_sent"]
+ writentrans = delta["nfsclient.ops.write.ntrans"]
print("%-18s:%s"%("Timestamp", timestamp))
print()
- for name in n_shares:
+ for name in mountshare:
# read
r_kilobytes = (readbytessent[name] + readbytesrecv[name]) / 1024
if sampletime[name] > 0:
@@ -277,13 +266,24 @@ class NfsiostatReport(pmcc.MetricGroupPrinter):
)
print()
- def print_report(self,group,timestamp, manager_nfsiostat):
+ def get_timestamp(self, group):
+ t_s = group.contextCache.pmLocaltime(int(group.timestamp))
+ timestamp = time.strftime(NfsiostatOptions.timefmt, t_s.struct_time())
+ return timestamp
+
+ def print_report(self,group, manager_nfsiostat, mgr):
def __print_nfs_status():
- nfsstatus = NfsioStatUtil(manager_nfsiostat)
- if nfsstatus.mount_share():
+ timestamp = self.get_timestamp(group)
+ nfs = NfsioStatUtil(manager_nfsiostat)
+ if nfs.report.current_value("nfsclient.export"):
try:
- self.__print_machine_info(group)
- self.__print_values(timestamp, nfsstatus)
+ if self.machine_info_count == 0:
+ self.__print_machine_info(group)
+ self.machine_info_count = 1
+ current = self.__collect(nfs)
+ diff_dict = self.__delta(current, nfs)
+ self. __print_values(timestamp, diff_dict)
+
except IndexError:
print("Incorrect machine info due to some missing metrics")
return
@@ -292,7 +292,6 @@ class NfsiostatReport(pmcc.MetricGroupPrinter):
if self.context != PM_CONTEXT_ARCHIVE and self.samples is None:
__print_nfs_status()
- sys.exit(0)
elif self.context == PM_CONTEXT_ARCHIVE and self.samples is None:
__print_nfs_status()
elif self.samples >=1:
@@ -302,30 +301,52 @@ class NfsiostatReport(pmcc.MetricGroupPrinter):
pass
def report(self, manager):
- group = manager["sysinfo"]
self.samples = self.opts.pmGetOptionSamples()
- t_s = group.contextCache.pmLocaltime(int(group.timestamp))
- timestamp = time.strftime(NfsiostatOptions.timefmt, t_s.struct_time())
- self.print_report(group,timestamp,manager['nfsiostat'])
+ self.print_report(manager["sysinfo"] ,manager['nfsiostat'], manager)
class NfsiostatOptions(pmapi.pmOptions):
timefmt = "%m/%d/%Y %H:%M:%S"
+ uflag = False
+ def checkOptions(self, manager):
+ if NfsiostatOptions.uflag:
+ if manager._options.pmGetOptionInterval():
+ print("Error: -t incompatible with -u")
+ return False
+ if manager.type != PM_CONTEXT_ARCHIVE:
+ print("Error: -u can only be specified with -a archive")
+ return False
+ return True
+
+ def extraOptions(self, opt, optarg, index):
+ if opt == "u":
+ NfsiostatOptions.uflag = True
+
def __init__(self):
- pmapi.pmOptions.__init__(self, "a:s:Z:zV?")
+ pmapi.pmOptions.__init__(self, "a:s:Z:t:uzV?")
+ self.pmSetOptionCallback(self.extraOptions)
self.pmSetLongOptionHeader("General options")
self.pmSetLongOptionHostZone()
self.pmSetLongOptionTimeZone()
- self.pmSetLongOptionHelp()
+ self.pmSetLongOptionArchive()
self.pmSetLongOptionSamples()
+ self.pmSetLongOptionInterval()
+ self.pmSetLongOption("no-interpolation", 0, "u", "", "disable interpolation mode with archives")
+ self.pmSetLongOptionHelp()
self.pmSetLongOptionVersion()
- self.samples=None
- self.context=None
+ self.context = None
+ self.samples = None
if __name__ == '__main__':
try:
opts = NfsiostatOptions()
mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
opts.context=mngr.type
+ if not opts.checkOptions(mngr):
+ raise pmapi.pmUsageErr
+
+ if NfsiostatOptions.uflag:
+ # -u turns off interpolation
+ mngr.pmSetMode(PM_MODE_FORW, mngr._options.pmGetOptionOrigin(), None)
missing = mngr.checkMissingMetrics(ALL_METRICS)
if missing is not None:
sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing)
--
2.43.7

View File

@ -0,0 +1,776 @@
From 18ad74bfbec889c1a68a5ba055248b3aeb8a5280 Mon Sep 17 00:00:00 2001
From: Sagar Sagar <sagar.sagar@oracle.com>
Date: Mon, 11 May 2026 12:51:12 +0000
Subject: [PATCH OL8 Patch1035 1/1] pmrep: add unit information option for CSV
headers
Add --csv-unit-info to include metric unit strings in CSV header fields,
using metric(unit) and metric[instance](unit) formatting. Keep stdout
unit header behavior separate by clarifying --no-unit-info as stdout-only.
Centralize CSV header sanitization, update instance formatting in CSV
headers, and refresh pmrep documentation for the new option, pcp2csv,
debug options, and derived metric handling.
Orabug: 39064482
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
---
src/pmrep/pmrep.1 | 225 +++++++++++++++++++++++++++++++++++----------
src/pmrep/pmrep.py | 92 ++++++++++--------
2 files changed, 231 insertions(+), 86 deletions(-)
diff --git a/src/pmrep/pmrep.1 b/src/pmrep/pmrep.1
index bafecbb..3d3a11d 100644
--- a/src/pmrep/pmrep.1
+++ b/src/pmrep/pmrep.1
@@ -1,6 +1,6 @@
-'\"macro stdmacro
+'\" t
.\"
-.\" Copyright (C) 2015-2021 Marko Myllynen <myllynen@redhat.com>
+.\" Copyright (C) 2015-2026 Marko Myllynen <myllynen@redhat.com>
.\" Copyright (c) 2016-2018 Red Hat.
.\"
.\" This program is free software; you can redistribute it and/or modify it
@@ -16,7 +16,7 @@
.\"
.TH PMREP 1 "PCP" "Performance Co-Pilot"
.SH NAME
-\f3pmrep\f1 \- performance metrics reporter
+\f3pmrep\f1, \f3pcp2csv\f1 \- performance metrics reporter
.SH SYNOPSIS
\fBpmrep\fP
[\fB\-12357CdgGHIjkLmnprRuUvVxz?\fP]
@@ -29,6 +29,8 @@
[\fB\-b\fP|\fB\-B\fP \fIspace-scale\fP]
[\fB\-c\fP \fIconfig\fP]
[\fB\-\-container\fP \fIcontainer\fP]
+[\fB\-\-csv\-unit\-info\fP]
+[\f3\-D\f1 \f2debug\f1]
[\fB\-\-daemonize\fP]
[\fB\-e\fP \fIderived\fP]
[\fB\-E\fP \fIlines\fP]
@@ -84,7 +86,7 @@ collects selected metric values through the facilities of the
Performance Co-Pilot (PCP), see
.BR PCPIntro (1).
The metrics to be reported are specified on the command line,
-in a configuration file, or both.
+in configuration files, or both.
Metrics can be automatically converted and scaled using the PCP facilities,
either by default or by per-metric scaling specifications.
In addition to the existing metrics, derived metrics can be defined using
@@ -95,7 +97,7 @@ A wide range of metricsets (see below) is included by default, providing
reports on per-process details, NUMA performance, mimicking other tools
like
.BR sar(1)
-and more, see the \fBpmrep\fP configuration files under
+and more, see the \fBpmrep\fP configuration files in
.I $PCP_SYSCONF_DIR/pmrep
(typically \fI/etc/pcp/pmrep\fP) for details.
Tab completion for options, metrics, and metricsets
@@ -113,7 +115,7 @@ The
.B \-a
option causes
.B pmrep
-to use the specified set of archive logs rather than connecting to a PMCD.
+to use the specified set of archives rather than connecting to a PMCD.
The
.B \-a
and
@@ -141,7 +143,7 @@ If a metricspec specifies a non-leaf node in the
Performance Metrics Name Space (PMNS), then
.B pmrep
will recursively descend the PMNS and report on all leaf nodes
-(i.e., metrics) for that metricspec.
+(i.e. metrics) for that metricspec.
Use
.BR pminfo (1)
to list all the metrics (PMNS lead nodes) and their descriptions.
@@ -151,11 +153,13 @@ A
has three different forms.
First, on the command line it can start with a colon (``:'') to indicate a
.I metricset
-to be read from a
+to be read from
.B pmrep
-configuration file (see
+configuration files (see
+.B \-c
+and
.BR pmrep.conf (5)),
-which can then consist of any number of metrics.
+which may then consist of any number of metrics.
Second, a
.I metricspec
starting with non-colon specifies a PMNS node as described above,
@@ -165,7 +169,7 @@ This so-called
of a metricspec is defined as follows:
.PP
.in 0.5i
-.ft CW
+.ft CR
.nf
metric[,label[,instances[,unit/scale[,type[,width[,precision[,limit]]]]]]]
.fi
@@ -175,7 +179,7 @@ metric[,label[,instances[,unit/scale[,type[,width[,precision[,limit]]]]]]]
A valid PMNS node
.RI ( metric )
is mandatory.
-It can be followed by a text
+It may be followed by a text
.I label
used with
.I stdout
@@ -240,7 +244,7 @@ are optional, they must always be provided in the order specified above,
thus the commas.
.PP
.in 1.5i
-.ft CW
+.ft CR
.nf
kernel.all.sysfork,forks,,,,8
.fi
@@ -261,6 +265,10 @@ Configuration file options override the corresponding
environment variables (if any).
Command line options override the corresponding configuration
file options (if any).
+.PP
+.B pcp2csv
+is an alias for
+.BR pmrep .
.SH OPTIONS
The available command line options are:
.TP 5
@@ -271,7 +279,7 @@ but this option \fIwill\fP override per-metric specifications.
.TP
\fB\-1\fR, \fB\-\-dynamic\-header\fR
Print a new dynamically adjusted header every time changes in
-availability of metric and instance values occur.
+the availability of metric and instance values occur.
By default a static header that never changes is printed once.
See also
.BR \-4 ,
@@ -308,12 +316,37 @@ Valid values for
.I action
are \fBupdate\fP (refresh metrics being sampled),
\fBignore\fP (do nothing \- the default behaviour)
-and \fBabort\fP (exit the program if such an event happens).
+and \fBabort\fP (exit the program if such an event occurs).
\fIupdate\fP implies \fB\-\-dynamic\-header\fP.
.TP
\fB\-5\fR, \fB\-\-ignore\-unknown\fR
-Silently ignore any metric name that cannot be resolved.
-At least one metric must be found for the tool to start.
+A metric that
+.B pmrep
+is asked to report may be ``unknown'' for a number of reasons:
+the metric is not in the PMNS of the requested source of metrics (\c
+.BR pmcd (1)
+for live metrics from a host, or a PCP archive for historical metrics),
+or one or more operand metrics in a derived metric definition is not available,
+or a derived metric definition contains syntax errors.
+By default,
+.B pmrep
+will report errors and exit if
+.B any
+requested metric is unknown.
+The
+.B \-5
+option changes the default behaviour and
+.B pmrep
+will silently ignore any metric that is unknown, provided
+at least one known metric remains to be reported.
+.RS
+.PP
+If an unknown metric is associated with a derived metric definition,
+refer to
+.BR pmRegisterDerivedMetric (3)
+for a deeper explanation of derived metrics and the options available
+for diagnosing issues with their definition and evaluation.
+.RE
.TP
\fB\-6\fR, \fB\-\-sort\-metric\fR=\fIsort-metric\fR
Specify a sort reference metric to sort output by values with
@@ -355,7 +388,7 @@ but this option \fIwill\fP override per-metric specifications.
.TP
\fB\-a\fR \fIarchive\fR, \fB\-\-archive\fR=\fIarchive\fR
Performance metric values are retrieved from the set of Performance
-Co-Pilot (PCP) archive log files identified by the
+Co-Pilot (PCP) archive files identified by the
.I archive
argument, which is a comma-separated list of names,
each of which may be the base name of an archive or the name of
@@ -402,7 +435,7 @@ but this option \fIwill\fP override per-metric specifications.
Specify the
.I config
file or directory to use.
-In case \fIconfig\fP is a directory all files under it ending
+In case \fIconfig\fP is a directory all files in it ending
\fB.conf\fP will be included.
The default is the first found of:
.IR ./pmrep.conf ,
@@ -420,6 +453,14 @@ Fetch performance metrics from the specified
either local or remote (see
.BR \-h ).
.TP
+\fB\-\-csv\-unit\-info\fR
+Append unit strings to metric names in the CSV header using the form
+.I "metric(unit)"
+or
+.IR "metric[inst](unit)" .
+See also
+.BR \-k .
+.TP
\fB\-C\fR, \fB\-\-check\fR
Exit before reporting any values, but after parsing the configuration
and metrics and printing possible headers.
@@ -432,7 +473,7 @@ to effect a pause, rather than
the default behaviour of replaying at full speed.
.TP
.B \-\-daemonize
-Daemonize on startup.
+Daemonise on startup.
.TP
\fB\-e\fR \fIderived\fR, \fB\-\-derived\fR=\fIderived\fR
Specify
@@ -441,12 +482,25 @@ performance metrics.
If
.I derived
starts with a slash (``/'') or with a dot (``.'') it will be
-interpreted as a derived metrics configuration file, otherwise it will
+interpreted as a PCP derived metrics configuration file, otherwise it will
be interpreted as comma- or semicolon-separated derived metric expressions.
-For details see
+For complete description of derived metrics and PCP derived metrics
+configuration files see
.BR pmLoadDerivedConfig (3)
and
.BR pmRegisterDerived (3).
+Alternatively, using
+.BR pmrep.conf (5)
+configuration syntax allows defining derived metrics as part of metricsets.
+.RS
+.PP
+In case of issues with derived metrics, review the aforementioned manual
+pages in detail and ensure all the required metrics are available,
+especially when using archives.
+Use
+.B \-Dderive
+to see additional debug information about parsing and evaluating derived metrics.
+.RE
.TP
\fB\-E\fR \fIlines\fR, \fB\-\-repeat\-header\fR=\fIlines\fR
Repeat the header every
@@ -469,7 +523,7 @@ method which is mostly the same as that described in
.BR strftime (3).
An empty
.I format
-string (i.e., "") will remove the timestamps from the output.
+string (i.e. "") will remove the timestamps from the output.
Defaults to
.B %H:%M:%S
when using the
@@ -550,7 +604,7 @@ Multiple
options are allowed as an alternative way of specifying more than
one non-metric-specific instance filters.
.PP
-An individual instance filter may be one of the following:
+An individual instance filter can be one of the following:
.TP 10
.I name
Full instance name.
@@ -598,16 +652,30 @@ $ pmrep \-i '1 minute','5 minute' 'kernel.all.load,,.*'
.RE
.TP
\fB\-I\fR, \fB\-\-ignore\-incompat\fR
-Ignore incompatible metrics.
-By default incompatible metrics (that is,
-their type is unsupported or they cannot be scaled as requested)
-will cause
+Similar to unknown metrics described above for the
+.B \-5
+option, a metric that
+.B pmrep
+is asked to report may be considered ``incompatible'' for a number of reasons:
+a metric type that is not supported by
+.B pmrep
+(e.g. PM_TYPE_AGGREGATE or PM_TYPE_EVENT),
+or a derived metric definition that contains semantic errors,
+or the metric is known in the PMNS but the metric's metadata is not available.
+By default,
+.B pmrep
+will report errors and exit if
+.B any
+requested metric is incompatible.
+The
+.B \-I
+option changes the default behaviour and
.B pmrep
-to terminate with an error message.
-With this option all incompatible metrics are silently omitted
-from reporting.
+will silently ignore any metric that is incompatible, provided
+at least one compatible metric remains to be reported.
This may be especially useful when requesting
-non-leaf nodes of the PMNS tree for reporting.
+non-leaf nodes of the PMNS tree for reporting when the
+associated metrics have different metadata.
.TP
\fB\-\-include\-texts\fR
When writing a PCP archive,
@@ -640,6 +708,8 @@ and
\fB\-k\fR, \fB\-\-extended\-csv\fR
Write extended CSV output, similar to
.BR sadf (1).
+See also
+.BR \-\-csv\-unit\-info .
.TP
\fB\-K\fR \fIspec\fR, \fB\-\-spec\-local\fR=\fIspec\fR
When fetching metrics from a local context (see
@@ -667,9 +737,14 @@ The default for
.I stdout
is two spaces (`` '') and comma (``,'') for
.IR csv .
-In case of CSV output or stdout output with non-whitespace delimiter,
-any instances of the delimiter in string values will be replaced by
+When using a non-whitespace delimiter,
+all instances of the delimiter in string values will be replaced by
the underscore (``_'') character.
+Note that many default metricsets specify a delimiter (that may
+not be a comma) so it might be
+necessary to use this option with metricsets to explicitly
+set the delimiter as comma for CSV output, i.e. \c.
+.B \-\-delimiter=,
.TP
\fB\-L\fR, \fB\-\-local\-PMDA\fR
Use a local context to collect metrics from DSO PMDAs on the local host
@@ -677,8 +752,8 @@ without PMCD.
See also
.BR \-K .
.TP
-\fB-m\fR, \fB\-\-include\-labels\fR
-Include metric labels in the output.
+\fB\-m\fR, \fB\-\-include\-labels\fR
+Include PCP metric labels in the output.
.TP
\fB\-n\fR, \fB\-\-invert\-filter\fR
Perform ranking before live filtering.
@@ -917,7 +992,7 @@ to disable).
.RE
.TP
\fB\-U\fR, \fB\-\-no\-unit\-info\fR
-Omit unit information from headers.
+Omit unit information from \fIstdout\fP headers.
.TP
\fB\-v\fR, \fB\-\-omit\-flat\fR
Report only set-valued metrics with instances (e.g. disk.dev.read) and
@@ -1012,6 +1087,8 @@ the metric values, no external utilities are needed.
The referenced colon-starting
.I metricsets
are part of the default \fBpmrep\fR configuration.
+With bash and zsh tab completes available options, metrics, and after a
+colon metricsets.
.PP
Display network interface metrics on the local host:
.RS +4
@@ -1037,7 +1114,7 @@ Display the slab total usage (in MB) of two specific slab instances:
.RS +4
.ft B
.nf
-$ pmrep mem.slabinfo.slabs.total_size,,'kmalloc-4k|xfs_inode',MB
+$ pmrep mem.slabinfo.slabs.total_size,,'kmalloc\-4k|xfs_inode',MB
.fi
.ft P
.RE
@@ -1045,8 +1122,7 @@ $ pmrep mem.slabinfo.slabs.total_size,,'kmalloc-4k|xfs_inode',MB
Display timestamped
.BR vmstat (8)
like information using megabytes instead of kilobytes and also include
-the number of inodes used (tab completes available metrics and
-after a colon metricsets with bash and zsh):
+the number of inodes used:
.RS +4
.ft B
.nf
@@ -1087,7 +1163,7 @@ containing details about I/O requests by current
.RS +4
.ft B
.nf
-$ pmrep \-gp \-i pmlogger :proc-io
+$ pmrep \-gp \-i pmlogger :proc\-io
.fi
.ft P
.RE
@@ -1124,7 +1200,7 @@ on one minute interval at every full minute in a background process:
.ft B
.nf
$ pmrep \-\-daemonize \-A 1m \-t 1m \-i '.*java.*' \-j \-o archive \-F ./a \\
- :proc-info :proc-cpu :proc-mem :proc-io
+ :proc\-info :proc\-cpu :proc\-mem :proc\-io
.fi
.ft P
.RE
@@ -1154,7 +1230,7 @@ $ pmrep \-o archive \-F ./a \-J 3 \-N proc.memory.rss proc.memory proc.io
.I pmrep.conf
\fBpmrep\fP configuration file (see \fB\-c\fP)
.TP
-.I \f(CW$PCP_SYSCONF_DIR\fP/pmrep/*.conf
+.I \f(CR$PCP_SYSCONF_DIR\fP/pmrep/*.conf
system provided default \fBpmrep\fP configuration files
.SH PCP ENVIRONMENT
Environment variables with the prefix \fBPCP_\fP are used to parameterize
@@ -1165,11 +1241,51 @@ The \fB$PCP_CONF\fP variable may be used to specify an alternative
configuration file, as described in \fBpcp.conf\fP(5).
.PP
For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
+.PP
+Of particular note,
+.B PCP_DISCRETE_ONCE
+can be set to ensure that discrete metric values are reported only once,
+unless they change at some point.
+.SH DEBUGGING OPTIONS
+The
+.B \-D
+or
+.B \-\-debug
+option enables the output of additional diagnostics on
+.I stderr
+to help triage problems, although the information is sometimes cryptic and
+primarily intended to provide guidance for developers rather end-users.
+.I debug
+is a comma separated list of debugging options; use
+.BR pmdbg (1)
+with the
+.B \-l
+option to obtain
+a list of the available debugging options and their meaning.
+.PP
+Debugging options specific to
+.B pmrep
+are as follows:
+.TS
+box;
+lf(B) | lf(B)
+lf(B) | lxf(R) .
+Option Description
+_
+appl1 T{
+.ad l
+dump keywords for configurations and specifications
+T}
+_
+derive T{
+.ad l
+dump details as each derived metric is parsed
+T}
+.TE
.SH SEE ALSO
-.BR mkaf (1),
.BR PCPIntro (1),
+.BR mkaf (1),
.BR pcp (1),
-.BR pcp-atop (1),
.BR pcp2elasticsearch (1),
.BR pcp2graphite (1),
.BR pcp2influxdb (1),
@@ -1178,13 +1294,14 @@ For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
.BR pcp2xlsx (1),
.BR pcp2xml (1),
.BR pcp2zabbix (1),
+.BR pcp\-atop (1),
.BR pmcd (1),
.BR pmchart (1),
.BR pmdiff (1),
-.BR pmdumplog (1),
.BR pmdumptext (1),
.BR pminfo (1),
.BR pmiostat (1),
+.BR pmlogdump (1),
.BR pmlogextract (1),
.BR pmlogsummary (1),
.BR pmprobe (1),
@@ -1192,16 +1309,28 @@ For environment variables affecting PCP tools, see \fBpmGetOptions\fP(3).
.BR pmval (1),
.BR sadf (1),
.BR sar (1),
+.BR PMAPI (3),
.BR pmGetOptions (3),
-.BR pmSpecLocalPMDA (3),
.BR pmLoadDerivedConfig (3),
.BR pmParseUnitsStr (3),
.BR pmRegisterDerived (3),
+.BR pmSpecLocalPMDA (3),
.BR strftime (3),
.BR LOGARCHIVE (5),
-.BR pcp.conf (5),
.BR PMNS (5),
+.BR pcp.conf (5),
.BR pmrep.conf (5),
.BR environ (7)
and
.BR vmstat (8).
+
+.\" control lines for scripts/man-spell
+.\" +ok+ CdgGHIjkLmnprRuUvVxz EST NUMA XFS csv datetime
+.\" +ok+ eth incompat influxdb inodes java
+.\" +ok+ kmalloc metricset metricsets sda slabinfo
+.\" +ok+ total_bytes total_size vfs vmstat wlan
+.\" +ok+ xfs xfs_inode zsh
+.\" +ok+ ds {from ds389} gp gUJ {all from command line -xxx args}
+.\" +ok+ sp {from .sp in troff macro} un {from (un)available)}
+.\" +ok+ Dderive {from -Dderive}
+.\" +ok+ PCP_DISCRETE_ONCE {from pmrep-only env var}
diff --git a/src/pmrep/pmrep.py b/src/pmrep/pmrep.py
index 69d1c9c..a7b0dde 100755
--- a/src/pmrep/pmrep.py
+++ b/src/pmrep/pmrep.py
@@ -1,6 +1,6 @@
#!/usr/bin/env pmpython
#
-# Copyright (C) 2015-2021 Marko Myllynen <myllynen@redhat.com>
+# Copyright (C) 2015-2026 Marko Myllynen <myllynen@redhat.com>
#
# 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
@@ -17,7 +17,7 @@
# pylint: disable=too-many-boolean-expressions, too-many-statements
# pylint: disable=too-many-instance-attributes, too-many-locals
# pylint: disable=too-many-branches, too-many-nested-blocks
-# pylint: disable=broad-except, too-many-arguments
+# pylint: disable=broad-except, too-many-arguments, too-many-positional-arguments
# pylint: disable=too-many-lines, too-many-public-methods
""" Performance Metrics Reporter """
@@ -38,17 +38,14 @@ import os
# PCP Python PMAPI
from pcp import pmapi, pmi, pmconfig
-from cpmapi import PM_CONTEXT_ARCHIVE, PM_CONTEXT_LOCAL
-from cpmapi import PM_INDOM_NULL, PM_IN_NULL, PM_DEBUG_APPL1, PM_TIME_SEC
+from cpmapi import PM_CONTEXT_ARCHIVE, PM_CONTEXT_LOCAL, PM_DEBUG_APPL1
+from cpmapi import PM_INDOM_NULL, PM_IN_NULL, PM_TIME_SEC
from cpmapi import PM_SEM_DISCRETE, PM_TYPE_STRING
from cpmapi import PM_TEXT_PMID, PM_TEXT_INDOM, PM_TEXT_ONELINE, PM_TEXT_HELP
from cpmapi import PM_LABEL_INDOM, PM_LABEL_INSTANCES
from cpmapi import PM_LABEL_DOMAIN, PM_LABEL_CLUSTER, PM_LABEL_ITEM
from cpmi import PMI_ERR_DUPINSTNAME, PMI_ERR_DUPTEXT
-if sys.version_info[0] >= 3:
- long = int # pylint: disable=redefined-builtin
-
# Default config
DEFAULT_CONFIG = ["./pmrep.conf", "$HOME/.pmrep.conf", "$HOME/.pcp/pmrep.conf", "$PCP_SYSCONF_DIR/pmrep/pmrep.conf", "$PCP_SYSCONF_DIR/pmrep"]
@@ -80,7 +77,7 @@ class PMReporter(object):
self.keys = ('source', 'output', 'derived', 'header', 'globals',
'samples', 'interval', 'type', 'precision', 'daemonize',
'timestamp', 'unitinfo', 'colxrow', 'separate_header', 'fixed_header',
- 'delay', 'width', 'delimiter', 'extcsv', 'width_force',
+ 'delay', 'width', 'delimiter', 'extcsv', 'width_force', 'csv_unitinfo',
'extheader', 'repeat_header', 'timefmt', 'interpol',
'dynamic_header', 'overall_rank', 'overall_rank_alt', 'sort_metric',
'count_scale', 'space_scale', 'time_scale', 'version',
@@ -106,7 +103,7 @@ class PMReporter(object):
self.globals = 1
self.timestamp = 0
self.samples = None # forever
- self.interval = pmapi.timeval(1) # 1 sec
+ self.interval = pmapi.timespec(1) # 1 sec
self.opts.pmSetOptionInterval(str(1)) # 1 sec
self.delay = 0
self.type = 0
@@ -159,6 +156,7 @@ class PMReporter(object):
self.localtz = None
self.prev_ts = None
self.runtime = -1
+ self.csv_unitinfo = 0
self.found_insts = []
self.prev_insts = None
self.static_header = 1
@@ -206,7 +204,8 @@ class PMReporter(object):
opts.pmSetLongOptionHeader("Reporting options")
opts.pmSetLongOption("no-header", 0, "H", "", "omit headers")
- opts.pmSetLongOption("no-unit-info", 0, "U", "", "omit unit info from headers")
+ opts.pmSetLongOption("no-unit-info", 0, "U", "", "omit unit info from stdout headers")
+ opts.pmSetLongOption("csv-unit-info", 0, "", "", "include unit info in CSV headers")
opts.pmSetLongOption("no-inst-info", 0, "", "", "omit instance info from headers")
opts.pmSetLongOption("no-globals", 0, "G", "", "omit global metrics")
opts.pmSetLongOption("timestamps", 0, "p", "", "print timestamps")
@@ -272,6 +271,8 @@ class PMReporter(object):
self.daemonize = 1
elif opt == 'include-texts':
self.include_texts = 1
+ elif opt == 'csv-unit-info':
+ self.csv_unitinfo = 1
elif opt == 'no-inst-info':
self.instinfo = 0
elif opt == 'K':
@@ -413,9 +414,7 @@ class PMReporter(object):
self.pmconfig.validate_common_options()
- if self.output != OUTPUT_ARCHIVE and \
- self.output != OUTPUT_CSV and \
- self.output != OUTPUT_STDOUT:
+ if self.output not in (OUTPUT_ARCHIVE, OUTPUT_CSV, OUTPUT_STDOUT):
sys.stderr.write("Error while parsing options: Invalid output target specified.\n")
sys.exit(1)
@@ -489,8 +488,8 @@ class PMReporter(object):
""" Fetch and report """
# Debug
if self.context.pmDebug(PM_DEBUG_APPL1):
- sys.stdout.write("Known config file keywords: " + str(self.keys) + "\n")
- sys.stdout.write("Known metric spec keywords: " + str(self.pmconfig.metricspec) + "\n")
+ sys.stderr.write("Known config file keywords: " + str(self.keys) + "\n")
+ sys.stderr.write("Known metric spec keywords: " + str(self.pmconfig.metricspec) + "\n")
# Set delay mode, interpolation
if self.context.type != PM_CONTEXT_ARCHIVE:
@@ -750,13 +749,22 @@ class PMReporter(object):
samples = self.samples
else:
duration = float(self.opts.pmGetOptionFinish()) - origin
+ # Avoid "too large value" with time.asctime(time.localtime(endtime))
+ now = datetime.now()
+ try:
+ next_year = now.replace(year=now.year + 1)
+ except ValueError:
+ # Handle Feb 29
+ next_year = now.replace(year=now.year + 1, month=2, day=28)
+ year_secs = (next_year - now).total_seconds()
+ duration = min(duration, year_secs)
samples = int(duration / float(self.interval) + 1)
samples = max(0, samples)
duration = (samples - 1) * float(self.interval)
duration = max(0, duration)
endtime = origin + duration
- instances = sum([len(x[0]) for x in self.pmconfig.insts])
+ instances = sum(len(x[0]) for x in self.pmconfig.insts)
insts_txt = "instances" if instances != 1 else "instance"
if not self.static_header:
if self.context.type == PM_CONTEXT_ARCHIVE:
@@ -786,9 +794,9 @@ class PMReporter(object):
samples = "N/A"
comm = "#" if self.output == OUTPUT_CSV else ""
+ source = self.source + " (archive)" if self.context.type == PM_CONTEXT_ARCHIVE else "pmcd (live)"
self.writer.write(comm + "\n")
- if self.context.type == PM_CONTEXT_ARCHIVE:
- self.writer.write(comm + " archive: " + self.source + "\n")
+ self.writer.write(comm + " source: " + source + "\n")
self.writer.write(comm + " host: " + host + "\n")
self.writer.write(comm + " timezone: " + timezone + "\n")
self.writer.write(comm + " start: " + time.asctime(time.localtime(origin)) + "\n")
@@ -812,12 +820,12 @@ class PMReporter(object):
""" Helper to get number of instances of current results """
if self.static_header:
if self.colxrow is None:
- c = len(str(sum([len(i[0]) for i in self.pmconfig.insts])))
+ c = len(str(sum(len(i[0]) for i in self.pmconfig.insts)))
else:
c = len(str(len(self.metrics)))
else:
if self.colxrow is None:
- c = len(str(sum([len(results[i]) for i in results])))
+ c = len(str(sum(len(results[i]) for i in results)))
else:
c = len(str(len(results)))
return c
@@ -927,19 +935,21 @@ class PMReporter(object):
for i, metric in enumerate(self.metrics):
for j, n in self.get_results_iter(i, metric, results):
name = metric
- if not self.dynamic_header:
- if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL:
- # Always mark metrics with instance domain
- name += "-"
- if self.pmconfig.insts[i][1][j]:
- # Append instance name when present
- name += self.pmconfig.insts[i][1][j]
- else:
- if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL:
- name += "-" + n[1]
- if self.delimiter:
- name = name.replace(self.delimiter, " ")
- name = name.replace("\n", " ").replace("\"", " ")
+ if self.instinfo:
+ if not self.dynamic_header:
+ if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL:
+ # Always mark metrics with instance domain
+ name += "["
+ if self.pmconfig.insts[i][1][j]:
+ # Append instance name when present
+ name += self.pmconfig.insts[i][1][j]
+ name += "]"
+ else:
+ if self.pmconfig.descs[i].contents.indom != PM_INDOM_NULL:
+ name += "[" + n[1] + "]"
+ if self.csv_unitinfo and self.metrics[metric][2][0] is not None:
+ name += "(" + self.metrics[metric][2][0] + ")"
+ name = self.sanitize_csv_header_item(name)
self.writer.write(self.delimiter + "\"" + name + "\"")
if self.include_labels:
ins = j if not self.dynamic_header else n[0]
@@ -947,7 +957,7 @@ class PMReporter(object):
if self.delimiter:
repl = ";" if self.delimiter == "," else ","
labels = labels.replace(self.delimiter, repl)
- labels = labels.replace("\n", " ").replace("\"", " ")
+ labels = self.sanitize_csv_header_item(labels)
self.writer.write(self.delimiter + "\"" + labels + "\"")
self.writer.write("\n")
@@ -1132,14 +1142,14 @@ class PMReporter(object):
continue
try:
self.pmi.pmiPutValue(metric, name, str(value))
- except pmi.pmiErr as pmierror:
+ except pmi.pmiErr:
pass
data = 1
self.prev_res = results # pylint: disable=attribute-defined-outside-init
# Flush
if data:
- self.pmi.pmiWrite(int(self.pmfg_ts().strftime('%s')), self.pmfg_ts().microsecond)
+ self.pmi.pmiWrite(int(self.pmfg_ts().timestamp()), self.pmfg_ts().microsecond)
def dynamic_header_update(self, results, line=None):
""" Update dynamic header as needed """
@@ -1187,6 +1197,12 @@ class PMReporter(object):
value = value.replace(self.delimiter, " ")
return value
+ def sanitize_csv_header_item(self, item):
+ """ Sanitize CSV header item """
+ if self.delimiter:
+ item = item.replace(self.delimiter, " ")
+ return item.replace("\n", " ").replace("\"", " ")
+
def write_csv(self, timestamp):
""" Write results in CSV format """
if timestamp is None:
@@ -1255,7 +1271,7 @@ class PMReporter(object):
def format_stdout_value(self, value, width, precision, fmt, k):
""" Format value for stdout output """
- if isinstance(value, (int, long)):
+ if isinstance(value, int):
if len(str(value)) > width:
value = pmconfig.TRUNC
else:
@@ -1542,7 +1558,7 @@ class PMReporter(object):
self.writer.flush()
except IOError as ioerror:
if ioerror.errno != errno.EPIPE:
- raise error
+ raise ioerror
try:
self.writer.close()
except Exception:
--
2.43.7

View File

@ -0,0 +1,749 @@
diff -Naurp pcp-5.3.7.orig/qa/1897.out pcp-5.3.7/qa/1897.out
--- pcp-5.3.7.orig/qa/1897.out 2023-07-10 16:51:15.049365731 +1000
+++ pcp-5.3.7/qa/1897.out 2023-07-10 16:52:18.069511419 +1000
@@ -376,12 +376,12 @@ is given as label metadata values for th
inst [2 or "node-2:test_clone_state"] value 1
inst [3 or "node-2:test_site"] value 1
-ha_cluster.pacemaker.nodes.status.dc PMID: 155.3.8 [Whether the node status is given as disconnected]
+ha_cluster.pacemaker.nodes.status.dc PMID: 155.3.8 [Whether the node status is given as the DC]
Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
Semantics: instant Units: count
Help:
-Whether the disconnected status is reported by the node in the cluster, a
-value of 1 confirms the node status as disconnected.
+Whether the DC status is reported by the node in the cluster, a
+value of 1 confirms the node status as the designated coordinator.
inst [0 or "node-1"] value 0
inst [1 or "node-2"] value 1
@@ -680,6 +680,632 @@ ha_cluster.drbd.split_brain
=== std err ===
=== filtered valgrind report ===
Memcheck, a memory error detector
+Command: pminfo -L -K clear -K add,155,PCP_PMDAS_DIR/hacluster/pmda_hacluster.so,hacluster_init -f ha_cluster.pacemaker.config_last_change ha_cluster.corosync.quorate ha_cluster.sbd.devices.path ha_cluster.drbd.split_brain
+LEAK SUMMARY:
+definitely lost: 0 bytes in 0 blocks
+indirectly lost: 0 bytes in 0 blocks
+ERROR SUMMARY: 0 errors from 0 contexts ...
+
+== done
+
+== Checking metric descriptors and values - hacluster-root-002.tgz
+
+ha_cluster.corosync.member_votes.local PMID: 155.6.1 [Votes that are local to this node]
+ Data Type: 32-bit unsigned int InDom: 155.5 0x26c00005
+ Semantics: instant Units: count
+Help:
+The quorum votes which are local to this node in the cluster.
+ inst [0 or "rhel9-node1"] value 1
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.corosync.member_votes.node_id PMID: 155.6.2 [Node ID]
+ Data Type: 64-bit unsigned int InDom: 155.5 0x26c00005
+ Semantics: instant Units: count
+Help:
+The full corosync ID for the nodes in the cluster.
+ inst [0 or "rhel9-node1"] value 1
+ inst [1 or "rhel9-node2"] value 2
+
+ha_cluster.corosync.member_votes.votes PMID: 155.6.0 [Quorum votes per node]
+ Data Type: 32-bit unsigned int InDom: 155.5 0x26c00005
+ Semantics: instant Units: count
+Help:
+The number of quorum votes allocated to each node in the cluster.
+ inst [0 or "rhel9-node1"] value 1
+ inst [1 or "rhel9-node2"] value 1
+
+ha_cluster.corosync.quorate PMID: 155.7.0 [Value given for quorate]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+The value represents whether or not the cluster is quorate.
+ value 1
+
+ha_cluster.corosync.quorum_votes.expected_votes PMID: 155.7.1 [Expected vote count]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+The number of expected quorum votes for the cluster.
+ value 2
+
+ha_cluster.corosync.quorum_votes.highest_expected PMID: 155.7.2 [Highest expected vote count]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+The highest number of expected quorum votes for the cluster.
+ value 2
+
+ha_cluster.corosync.quorum_votes.quorum PMID: 155.7.4 [Whether cluster is quorate]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+The value reported for the quorate for the cluster.
+ value 1
+
+ha_cluster.corosync.quorum_votes.total_votes PMID: 155.7.3 [Total number of votes]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+The total number of quorum votes for the cluster.
+ value 2
+
+ha_cluster.corosync.ring_errors PMID: 155.7.5 [Number of faulty rings]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+The total number of Corosync rings which are faulty.
+ value 0
+
+ha_cluster.corosync.rings.address PMID: 155.8.1 [IP Address for ring]
+ Data Type: string InDom: 155.6 0x26c00006
+ Semantics: instant Units: count
+Help:
+The IP address locally linked to this ring.
+ inst [0 or "0"] value "192.168.122.148"
+
+ha_cluster.corosync.rings.all PMID: 155.15.0 [Corosync rings information]
+ Data Type: 32-bit unsigned int InDom: 155.13 0x26c0000d
+ Semantics: instant Units: count
+Help:
+Value is 1 if a ring exists. The details of the corresponding ring
+is given as label metadata values for this metric.
+ inst [0 or "0"] value 1
+
+ha_cluster.corosync.rings.node_id PMID: 155.8.2 [ID of the local node]
+ Data Type: 64-bit unsigned int InDom: 155.6 0x26c00006
+ Semantics: instant Units: count
+Help:
+The internal Corosync node ID to the node matched to the ring.
+ inst [0 or "0"] value 1
+
+ha_cluster.corosync.rings.number PMID: 155.8.3 [Ring number]
+ Data Type: 32-bit unsigned int InDom: 155.6 0x26c00006
+ Semantics: instant Units: count
+Help:
+The number for the Corosync ring.
+ inst [0 or "0"] value 0
+
+ha_cluster.corosync.rings.ring_id PMID: 155.8.4 [Ring ID]
+ Data Type: string InDom: 155.6 0x26c00006
+ Semantics: instant Units: count
+Help:
+The internal Corosync ring ID, corresponds to the first node to join.
+ inst [0 or "0"] value "1.9"
+
+ha_cluster.corosync.rings.status PMID: 155.8.0 [Corosync ring status]
+ Data Type: 32-bit unsigned int InDom: 155.6 0x26c00006
+ Semantics: instant Units: count
+Help:
+The status for each Corosync ring in the cluster, a value of 1 is faulty and
+0 is healthy.
+ inst [0 or "0"] value 0
+
+ha_cluster.drbd.al_writes PMID: 155.10.6 [Writes to the activity log]
+ Data Type: 64-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+Amount of writes to the activity log area of metadata by the DRBD
+resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.bm_writes PMID: 155.10.7 [Writes to bitmap area]
+ Data Type: 64-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+Amount of writes to the bitmap area of metadata by the DRBD resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.connections.all PMID: 155.18.0 [DRBD Peer disk information]
+ Data Type: 32-bit unsigned int InDom: 155.16 0x26c00010
+ Semantics: instant Units: count
+Help:
+Value is 1 if a drbd peer connection exists. The details of the corresponding DRBD peer
+connection is given as label metadata values for this metric.
+No value(s) available!
+
+ha_cluster.drbd.connections.peer_disk_state PMID: 155.11.4 [Peer disk state]
+ Data Type: string InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The reported peer disk state for the connection.
+No value(s) available!
+
+ha_cluster.drbd.connections.peer_node_id PMID: 155.11.1 [The ID the connection is for]
+ Data Type: string InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The given peer node ID for which the connection is for.
+No value(s) available!
+
+ha_cluster.drbd.connections.peer_role PMID: 155.11.2 [Role of the connection]
+ Data Type: string InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The reported role type for the connection.
+No value(s) available!
+
+ha_cluster.drbd.connections.resource PMID: 155.11.0 [Resource that the connection is for]
+ Data Type: string InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The given resource that the DRBD connection is for each resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.connections.volume PMID: 155.11.3 [Volume number]
+ Data Type: 32-bit unsigned int InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The reported volume for the connection.
+No value(s) available!
+
+ha_cluster.drbd.connections_pending PMID: 155.11.8 [Number of connections pending]
+ Data Type: 32-bit unsigned int InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The number of requests sent which have not yet been received by DRBD for the
+resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.connections_received PMID: 155.11.6 [Volume of data received]
+ Data Type: 64-bit unsigned int InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The volume of data received via the network connection in KiB by DRBD for the
+resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.connections_sent PMID: 155.11.7 [Volume of data sent]
+ Data Type: 64-bit unsigned int InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The volume of data sent via the network connection in KiB by DRBD for the
+resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.connections_sync PMID: 155.11.5 [Connections in sync]
+ Data Type: float InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The number of DRBD connections in sync for each resource:volume, this value is
+between 0 to 100.
+No value(s) available!
+
+ha_cluster.drbd.connections_unacked PMID: 155.11.9 [Number of connection not acknowledged]
+ Data Type: 32-bit unsigned int InDom: 155.9 0x26c00009
+ Semantics: instant Units: count
+Help:
+The number of requests sent which have not yet been acknowledged by DRBD for
+the resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.lower_pending PMID: 155.10.9 [Number of open requests]
+ Data Type: 64-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The number of open requests to the local I/O subsystem by DRBD for the
+resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.quorum PMID: 155.10.10 [Quorum status of DRBD resource:volume]
+ Data Type: 32-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The Quorum status of the DRBD resource according to resource:volume, 1 is
+quorate and 0 is non-quorate.
+No value(s) available!
+
+ha_cluster.drbd.read PMID: 155.10.5 [Amount read by DRBD resource]
+ Data Type: 32-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+Amount in KiB read by the DRBD resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.resources.all PMID: 155.17.0 [DRBD resource information]
+ Data Type: 32-bit unsigned int InDom: 155.15 0x26c0000f
+ Semantics: instant Units: count
+Help:
+Value is 1 if a drbd resource exists. The details of the corresponding drbd resource
+is given as label metadata values for this metric.
+No value(s) available!
+
+ha_cluster.drbd.resources.disk_state PMID: 155.10.3 [Disk state]
+ Data Type: string InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The current reported disk state of for the resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.resources.resource PMID: 155.10.0 [Name of the resource]
+ Data Type: string InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The name given for the DRBD resource for each resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.resources.role PMID: 155.10.1 [Role of the resource]
+ Data Type: string InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The reported role for the DRBD resource for each resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.resources.volume PMID: 155.10.2 [Volume of the resource]
+ Data Type: string InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The volume number of the resource for each resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.split_brain PMID: 155.10.11 [Signal for split brain detection.]
+ Data Type: 32-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+This metric signals if there has been a split brain occurring in DRBD for the
+resource:volume, value is 1 is a split brain has been detected.
+No value(s) available!
+
+ha_cluster.drbd.upper_pending PMID: 155.10.8 [Block I/O requests pending]
+ Data Type: 64-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+The number of the block I/O requests forwarded but not yet answered by DRBD
+for the resource:volume.
+No value(s) available!
+
+ha_cluster.drbd.written PMID: 155.10.4 [Amount written to DRBD resource]
+ Data Type: 32-bit unsigned int InDom: 155.8 0x26c00008
+ Semantics: instant Units: count
+Help:
+Amount in KiB written to the DRBD resource:volume.
+No value(s) available!
+
+ha_cluster.pacemaker.config_last_change PMID: 155.0.0 [Unix timestamp corresponding to last Pacemaker configuration change]
+ Data Type: 64-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+Unix timestamp in seconds corresponding to the last time that the Pacemaker
+configuration was changed on the system.
+ value 1687428694
+
+ha_cluster.pacemaker.fail_count PMID: 155.1.0 [The number of fail count recorded]
+ Data Type: 64-bit unsigned int InDom: 155.0 0x26c00000
+ Semantics: instant Units: count
+Help:
+The number of fail count per node and resource ID, the actual maximum value
+depends on Pacemaker internals.
+No value(s) available!
+
+ha_cluster.pacemaker.location_constraints.all PMID: 155.12.0 [Location constraint information]
+ Data Type: 32-bit unsigned int InDom: 155.10 0x26c0000a
+ Semantics: instant Units: count
+Help:
+Value is 1 if a location constraint exists. The details of the location constraint
+is given as label metadata values for this metric.
+No value(s) available!
+
+ha_cluster.pacemaker.location_constraints.node PMID: 155.2.0 [Node of the location constraint]
+ Data Type: string InDom: 155.1 0x26c00001
+ Semantics: instant Units: count
+Help:
+The node that the location constraint applies to in the cluster.
+No value(s) available!
+
+ha_cluster.pacemaker.location_constraints.resource PMID: 155.2.1 [Resource of the location constraint]
+ Data Type: string InDom: 155.1 0x26c00001
+ Semantics: instant Units: count
+Help:
+The resource that the location constraint applies to in the cluster.
+No value(s) available!
+
+ha_cluster.pacemaker.location_constraints.role PMID: 155.2.2 [Resource role of the location constraint]
+ Data Type: string InDom: 155.1 0x26c00001
+ Semantics: instant Units: count
+Help:
+The resource role that the location constraint applies to, if any.
+No value(s) available!
+
+ha_cluster.pacemaker.location_constraints.score PMID: 155.2.3 [Score of the location constraint]
+ Data Type: string InDom: 155.1 0x26c00001
+ Semantics: instant Units: count
+Help:
+The score given to the location constraint by Pacemaker, the value depends on
+Pacemaker internals.
+No value(s) available!
+
+ha_cluster.pacemaker.migration_threshold PMID: 155.1.1 [Migration threshold per node and resource ID]
+ Data Type: 64-bit unsigned int InDom: 155.0 0x26c00000
+ Semantics: instant Units: count
+Help:
+The value for the migration threshold per node and resource ID set by the
+Pacemaker cluster.
+No value(s) available!
+
+ha_cluster.pacemaker.node_attributes PMID: 155.4.0 [Metadata used by Resource Agents]
+ Data Type: string InDom: 155.3 0x26c00003
+ Semantics: instant Units: count
+Help:
+The raw values for the cluster metadata attributes and their value per node as
+used by the Resource Agents.
+No value(s) available!
+
+ha_cluster.pacemaker.node_attributes_all PMID: 155.13.0 [Metadata information]
+ Data Type: 32-bit unsigned int InDom: 155.11 0x26c0000b
+ Semantics: instant Units: count
+Help:
+Value is 1 if a node metadata exists. The details of the node metadata
+is given as label metadata values for this metric.
+No value(s) available!
+
+ha_cluster.pacemaker.nodes.status.dc PMID: 155.3.8 [Whether the node status is given as the DC]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the DC status is reported by the node in the cluster, a
+value of 1 confirms the node status as the designated coordinator.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 1
+
+ha_cluster.pacemaker.nodes.status.expected_up PMID: 155.3.7 [Whether the node status is given as expected_up]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the expected_up status is reported by the node in the cluster, a
+value of 1 confirms the node status as expected_up.
+ inst [0 or "rhel9-node1"] value 1
+ inst [1 or "rhel9-node2"] value 1
+
+ha_cluster.pacemaker.nodes.status.maintenance PMID: 155.3.3 [Whether the node status is given as maintenance]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the maintenance status is reported by the node in the cluster, a
+value of 1 confirms the node status as online.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.pacemaker.nodes.status.online PMID: 155.3.0 [Whether the node status is given as online]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the online status is reported by the node in the cluster, a value of
+1 confirms the node status as online.
+ inst [0 or "rhel9-node1"] value 1
+ inst [1 or "rhel9-node2"] value 1
+
+ha_cluster.pacemaker.nodes.status.pending PMID: 155.3.4 [Whether the node status is given as pending]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the pending status is reported by the node in the cluster, a value of
+1 confirms the node status as pending.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.pacemaker.nodes.status.shutdown PMID: 155.3.6 [Whether the node status is given as shutdown]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the shutdown status is reported by the node in the cluster, a value
+of 1 confirms the node status as shutdown.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.pacemaker.nodes.status.standby PMID: 155.3.1 [Whether the node status is given as standby]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the standby status is reported by the node in the cluster, a value of
+1 confirms the node status as standby.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.pacemaker.nodes.status.standby_on_fail PMID: 155.3.2 [Whether the node status is given as standby_on_fail]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the standby_on_fail status is reported by the node in the cluster,
+a value of 1 confirms the node status as standby_on_fail.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.pacemaker.nodes.status.unclean PMID: 155.3.5 [Whether the node status is given as unclean]
+ Data Type: 32-bit unsigned int InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+Whether the unclean status is reported by the node in the cluster, a value of
+1 confirms the node status as unclean.
+ inst [0 or "rhel9-node1"] value 0
+ inst [1 or "rhel9-node2"] value 0
+
+ha_cluster.pacemaker.nodes.type PMID: 155.3.9 [The type given to the node]
+ Data Type: string InDom: 155.2 0x26c00002
+ Semantics: instant Units: count
+Help:
+The membership type given to the node in the Pacemaker cluster.
+ inst [0 or "rhel9-node1"] value "member"
+ inst [1 or "rhel9-node2"] value "member"
+
+ha_cluster.pacemaker.resources.agent PMID: 155.5.0 [The name of the resource agent for this resource]
+ Data Type: string InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+The name given for the resource agent for the given resource instance in the
+cluster.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.all PMID: 155.14.0 [Pacemaker resources information]
+ Data Type: 32-bit unsigned int InDom: 155.12 0x26c0000c
+ Semantics: instant Units: count
+Help:
+Value is 1 if a resources exists. The details of the resource
+is given as label metadata values for this metric.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.clone PMID: 155.5.1 [The name of the clone given for this resource]
+ Data Type: string InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+The name given for the clone for the given resource instance in the cluster,
+if any.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.group PMID: 155.5.2 [The name of the group given for this resource]
+ Data Type: string InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+The name given for the group for the given resource instance in the cluster,
+if any.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.managed PMID: 155.5.3 [Value is either true or false]
+ Data Type: 32-bit unsigned int InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+The value of whether the resource instance in the cluster is managed or not.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.role PMID: 155.5.4 [The given role state]
+ Data Type: string InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+The value of the given role state for the resource instance in the cluster.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.status.active PMID: 155.5.5 [Whether the resources status is given as active]
+ Data Type: 32-bit unsigned int InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+Whether the active status is reported by the resource in the cluster, a
+value of 1 confirms the resource status as active.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.status.blocked PMID: 155.5.7 [Whether the resources status is given as blocked]
+ Data Type: 32-bit unsigned int InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+Whether the blocked status is reported by the resource in the cluster, a
+value of 1 confirms the resource status as blocked.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.status.failed PMID: 155.5.8 [Whether the resources status is given as failed]
+ Data Type: 32-bit unsigned int InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+Whether the failed status is reported by the resource in the cluster, a
+value of 1 confirms the resource status as failed.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.status.failure_ignored PMID: 155.5.9 [Whether the resources status is given as failure_ignored]
+ Data Type: 32-bit unsigned int InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+Whether the failure_ignored status is reported by the resource in the
+cluster, a value of 1 confirms the resource status as failure_ignored.
+No value(s) available!
+
+ha_cluster.pacemaker.resources.status.orphaned PMID: 155.5.6 [Whether the resources status is given as orphaned]
+ Data Type: 32-bit unsigned int InDom: 155.4 0x26c00004
+ Semantics: instant Units: count
+Help:
+Whether the orphaned status is reported by the resource in the cluster, a
+value of 1 confirms the resource status as orphaned.
+No value(s) available!
+
+ha_cluster.pacemaker.stonith_enabled PMID: 155.0.1 [Whether stonith is enabled in the cluster]
+ Data Type: 32-bit unsigned int InDom: PM_INDOM_NULL 0xffffffff
+ Semantics: instant Units: count
+Help:
+Value returns as to whether stonith is enabled or disabled for the cluster.
+ value 0
+
+ha_cluster.sbd.all PMID: 155.16.0 [SBD device information]
+ Data Type: 32-bit unsigned int InDom: 155.14 0x26c0000e
+ Semantics: instant Units: count
+Help:
+Value is 1 if a sbd device exists. The details of the corresponding SBD device
+is given as label metadata values for this metric.
+No value(s) available!
+
+ha_cluster.sbd.devices.path PMID: 155.9.0 [Path of SBD device]
+ Data Type: string InDom: 155.7 0x26c00007
+ Semantics: instant Units: count
+Help:
+The full path given to each SBD device.
+No value(s) available!
+
+ha_cluster.sbd.devices.status PMID: 155.9.1 [Status of the SBD device]
+ Data Type: string InDom: 155.7 0x26c00007
+ Semantics: instant Units: count
+Help:
+The current status given for each of the SBD devices, the value is one of
+healthy or unhealthy.
+No value(s) available!
+
+ha_cluster.sbd.timeouts.allocate PMID: 155.9.3 [allocate timeout value]
+ Data Type: 32-bit unsigned int InDom: 155.7 0x26c00007
+ Semantics: instant Units: count
+Help:
+The value threshold for allocate timeouts for the given SBD device.
+No value(s) available!
+
+ha_cluster.sbd.timeouts.loop PMID: 155.9.4 [loop timeout value]
+ Data Type: 32-bit unsigned int InDom: 155.7 0x26c00007
+ Semantics: instant Units: count
+Help:
+The value threshold for loop timeouts for the given SBD device.
+No value(s) available!
+
+ha_cluster.sbd.timeouts.mgswait PMID: 155.9.2 [mgswait timeout value]
+ Data Type: 32-bit unsigned int InDom: 155.7 0x26c00007
+ Semantics: instant Units: count
+Help:
+The value threshold for msgwait timeouts for the given SBD device.
+No value(s) available!
+
+ha_cluster.sbd.timeouts.watchdog PMID: 155.9.5 [watchdog timeout value]
+ Data Type: 32-bit unsigned int InDom: 155.7 0x26c00007
+ Semantics: instant Units: count
+Help:
+The value threshold for watchdog timeouts for the given SBD device.
+No value(s) available!
+== Checking with valgrind - hacluster-root-002.tgz
+=== std out ===
+
+ha_cluster.pacemaker.config_last_change
+ value 1687428694
+
+ha_cluster.corosync.quorate
+ value 1
+
+ha_cluster.sbd.devices.path
+No value(s) available!
+
+ha_cluster.drbd.split_brain
+No value(s) available!
+=== std err ===
+=== filtered valgrind report ===
+Memcheck, a memory error detector
Command: pminfo -L -K clear -K add,155,PCP_PMDAS_DIR/hacluster/pmda_hacluster.so,hacluster_init -f ha_cluster.pacemaker.config_last_change ha_cluster.corosync.quorate ha_cluster.sbd.devices.path ha_cluster.drbd.split_brain
LEAK SUMMARY:
definitely lost: 0 bytes in 0 blocks
diff -Naurp pcp-5.3.7.orig/qa/hacluster/GNUmakefile pcp-5.3.7/qa/hacluster/GNUmakefile
--- pcp-5.3.7.orig/qa/hacluster/GNUmakefile 2023-07-10 16:51:15.069365777 +1000
+++ pcp-5.3.7/qa/hacluster/GNUmakefile 2023-07-10 16:52:18.069511419 +1000
@@ -8,7 +8,7 @@ default setup default_pcp:
install install_pcp: $(ROOTFILES)
$(INSTALL) -m 755 -d $(TESTDIR)
- $(INSTALL) -m 755 -f $(ROOTFILES) $(TESTDIR)/$(ROOTFILES)
+ $(INSTALL) -m 644 -f $(ROOTFILES) $(TESTDIR)
$(INSTALL) -m 644 -f GNUmakefile.install $(TESTDIR)/GNUmakefile
include $(BUILDRULES)
diff -Naurp pcp-5.3.7.orig/qa/hacluster/hacluster-root-002.tgz pcp-5.3.7/qa/hacluster/hacluster-root-002.tgz
--- pcp-5.3.7.orig/qa/hacluster/hacluster-root-002.tgz 1970-01-01 10:00:00.000000000 +1000
+++ pcp-5.3.7/qa/hacluster/hacluster-root-002.tgz 2023-07-10 16:53:58.649743937 +1000
@@ -0,0 +1,15 @@
+í[[sÛ6Îkô+°ôC“©© -Ùµ*팻[o¤<E280BA>½íôI‘ Äš$X´¢™þøý@R”,ÇM×IÓ-Ïdl Î çN¥ß÷zO>0ôýœŒúÍß< ‡ÃQà <ìôO¼“'lô¡#(”æ9cO2qËSuÿ¾‡žÿE¡<45>û_r?†<44>,á=îÿ¨oîß;ñíý ؾ?šó ‰Ò®Z>*<2A>wÞÿp8Ôþ?êáþG}ïø ë?"÷ÂßüþþÑ+TÞGiO¤·lÎÕ²Óñ¹f“ÉÅë¯;Xóód
+®‹\Ì”ÐSç¨;8îz»åqp-ÜU¤—S'ã¾Hø<48>ÈÝ£î©ÃD&}¬ž8,-YÑN5uú366+Ÿã3ˆ¸1WÚ]å‘Ö"<22>:×Ëý»H™ç±A<Œ<>†Ìë{G³x\™Gó¥ˆOÝTbP?óãH¤`³²æúA¡DŽ#Rj‡-ù­p-d^$Sgß-Š(˜:žóÏc_¦a´(r®#™Ò
+­AvÝ.ÐõœYËLäzM
+b„†dšƒÒ9Ï\™åTçp2½Íx”ß¿×5®¸ö—<C3B6>\@‰<Sgg7P`5ä±Nï}°CÞ[+ü]¡n®”x½î ;rO»ÐñÌsùQ8žœÃÓáûÑ*•äFi˜ãšóÂ'[ªèÞ÷´äÁ—¹TëÔÿc$‰Ä.!»V¢‡Mêj÷ûPZ¦°{W¤| ¢rgù<67>ßQ*K u3Gþº"qw½¤-R™7ˆLzûì³´æÞ®9OÈ<4F>T}>æÈ³,ᦷõînôölôª<C3B4>“^ý$J¹/TõŒ<>ðQªë¥\ù³@„¼ˆõ†«Dh>ãZçѼÀݺó"Š·BéVgîñ¶<C3B1>ÝJGþM”
+¥*õî}Tj¸)wo‡—JÎ]¾¡éÝ`2AÖÑ…jè}F+ïR#CÈô}+xÌ—‡­2<C2AD>ÁŸù±çÅ¢Œ<>´gPjª"mÜúI<>ÌaìL¼Í„¯EP¯ÔÙ3¢»šöš:<3A>pc<õÅî&+›ÛÜ»}Õ·[¦VYiÿ`k­T{™qšÞÓÛC~s5û$¨ŸÆyrW,Îv­0a½iÀV«û/n¿£|7x<37><tqƒ?ûâÿÓÅMz•÷M¨âýg‡*<2A>û“`§þ/s­ë‡ -eü8}ÀCýŸwìmêÿ£cÔÿÇ£ã~[ÿ x þÿNú<f&Á_ž³Á!3î™É\³TèÎw—¯¾¥'}VYç)‚üé” NQ8xø=ü¢óÔúÛ¸óô)¡Š‚ñ&¼ ÆOc"²”Jï{î<>Ÿ"{¦&Þý<C39E>õÁ=þoËËG
+øÿf²ñÿõÿ'GÃaëÿðÿŒ ˜êµãÞ<C3A3>Î9kÃeÛý»ç<C2BB>_˜þ½Bˆvä6
+DŽS•ÍÍn¥Öî:¯¨SØÂè™5„šfè載ÒÅÎZ÷ÔÐØæèg¡:<3A>Ècdº(k,Fl^¼Î7Ñb‰v±®¿ÆX»²ÞdöY!›üë|óÅŽPF¦<13>ô×2?cÖé¼4…<34>ZFÙïa<61>½2Ó"&QýñC n#_°W¨Ð:Mnîþùê kT­ì™‰ÊÏ7‡¼ym|þËÁNüG)<29>ÈGÿ>ÿ½Ñqcþ{Lõßмÿiãÿ‡‡ç¿Ö"X9Ü ÚQi=÷ ˜Z«Y˜Ë¤1 f:/4ÅEš"`;Œf½å@˜ºpä·ì¹b4öNÙçýÁ¸ß·mãDIÂóuÙÌ™Ãô:Û3œøEžSïøH1B™1°m»÷ °5Ódû†.vCîY5-¶“è­f5Ú­ñÞ¤!!ÓaÝɈ£ñQßN´·ŽøKž.öÙ7Ùw§Ýûfã½ÆÄBͪyÒ[ZP¢!!ë±[ÕKïÛÖwX)3Ù4æH7öïúÊc9Åd;ÑJU°•$hñýjüZivw¼YÏ5YBóAàI}á&<26>dƒKËÌåq\<5C>õTó!O:•…v<:¦¸ãÀD"èJ¯ÝP¤>,Ó DÌ×fGe{½†ñ5ç£vè¹oØcç@v¾SIDóŽ`¾nðd>Ï _ÅõrC¼z-iÖêÏEêÇ×ÇÇôòe <0B>»ßûžF- ÈÕæPUÀÀD+#§©7ll tZsÙÖéÊ™Sï]ŠØxÎ_UöÃïÕCc¸m‡Ëv—¯{fÆdcè'9gÚÎÿj|ýþ1º{òÿhˆÚ Êÿ'Ç'ÈÿhÿmþÿppÀ®aÍcxbóîàó¹}u1fŽÓ9è°«¯Îgç?^¾¸`
z ½Ìöø[RBbèRàO&ƒQ[[b‰PŠ/Ð|0ø9mL(˼Ë.ÃÛš6'ïÀÉSÄ Á2®—‡§ó¥Ã8W"ãèpxÚ°4<>ZˆµP©ÜaMÄqþ§¥Ë9±`ó<> .RÅ›…}?CˆÀ÷g/.^ž}{ñfJH6Db¸ö3¯øZšô|‹¢}b5g4¤ÈÌ4£¤eítٕК¤×ˆÄ™cÐQµ<51><10>sMzc¹2ÊÄ$
+Y¤ÙŠ+dJMuÎm$ ¯¥1aðZñÜ+f“0ðD©YJ â WR yu}öæúåëóiÉ÷Ž2YϪFä[B·µjR'ã!ˆe³²<14>ôfÓHí$j±BH,P\
+l°k>Áá9òˆðÉdò5 ¹Fèeeh23H…xlp)ÉB”Kd1ôC4ŒØ]<'}“1RJoÄP?¦r…Rd!”N,[r%jNð<4E>ÌÐ÷9vèí#¥
+a*@våºÀRÂý%½4Æh<C386>ù8„œ"IвÒasÉèAhÒ¯È/Õe^;cáV#ìJ¢,B±>X,QæånãÖñÌ5ä%©<10>KWäI;/E‡8À¸°¤?èÍh#Œe<C592>kÅCs•ÀC8+ÚDuŒµªÖÆŽ²²œbŸD"U…j¬e~r33\Ïþ %£F__á
+VÜ:¼¶ÎAj¡1
+éƒQý”Às`ƒHÂÜ‰ï ”cµþ¢äßb<C39F>­ "_,µùb‡QvÀ3½q½"«*@Ü?d¡ZÂ8 ­¡Sè1ý V„¸%*¬»ìÙµ=wEh®„oýw «Lç°žV²­XÖpœ,8“ð¤Cøƒß <*ˆM„ÔÖŒõæˆ "¯\ôü⻳Ÿ­£Náw<C3A1>w…ìbr¯úzˆuÑòC­Ë`m㯵Js&-âøîÃV;û) D± âé§³ëßœ¿þEáé6Ñ ûÇÈ0õ B™~eø‡æjò«Ê}A:¤ì<C2A4>ñÔ¼<C394>_”o¿uR·@9ƒ^ÕÇôS7œ2T5"ºH¥q=&¦Pi†hî%õ×_ñÓ 0V™ hDã…O)„šÖˆB<0ÁøVd 7 §CUi°îJÝ)‡¦ÅµË2´Qt´”ž©çTjœ‰Gª±Ž( ÒaŽ®^\]r„r)8 ƈÇù_¨ôæ@h”µ@dpPi<50>R¸»ÇùjuW¾a¾¾d#†L]´z7Œ¾å€F3 ƒ%âÈ8H3•<[æp}ùòâõ®§£w©"ª/m4·UÆ™o»E‹Ä[á4}EÀJ7†<37>f”ø57aÞEá<45>qôy@WÆMÂÈrI7Fº#û<>á£"µ‰AÛÇ€Y½®µùÊÕ•’\nx%ª¾DwèÅ ØÅçy”š2<14>Q3?Y†ŸeûÍϹZEý&ðkÍâ²¶Û92%£Et[ªÀ¦8D³µb%ÓeÔjš¼&ñ04&Pª5ŽnkxÈjº‡U6E„6;Ò&â² n#^pLÅRd}†2>ITP˜±•ˆcS…Ô£ÃV© Þpg²× úÊ€<C38A>
+<2B>1Æži¨ªa ‘ŠiBH"X>¯L­´°ÙÙëËׯ¦MCzÀæJ; ó<>
+dj@aây£d))½þþújÚi”šŸZïö°Ýÿù<0çQ'À~ÿ½aýýï>õ#oäµýßÇ€濟âÈ¢…G„;óŸG~÷ci¼‡ÿÿÿ1l¿ÿÿ õÿZh¡…Zh¡…Zh¡…Zh¡…Zh¡…Zh¡…ÿ/ø/ƒ¸VHP
\ No newline at end of file
diff -Naurp pcp-5.3.7.orig/src/pmdas/hacluster/help pcp-5.3.7/src/pmdas/hacluster/help
--- pcp-5.3.7.orig/src/pmdas/hacluster/help 2023-07-10 16:51:15.069365777 +1000
+++ pcp-5.3.7/src/pmdas/hacluster/help 2023-07-10 16:52:18.069511419 +1000
@@ -103,9 +103,9 @@ of 1 confirms the node status as shutdow
Whether the expected_up status is reported by the node in the cluster, a
value of 1 confirms the node status as expected_up.
-@ ha_cluster.pacemaker.nodes.status.dc Whether the node status is given as disconnected
-Whether the disconnected status is reported by the node in the cluster, a
-value of 1 confirms the node status as disconnected.
+@ ha_cluster.pacemaker.nodes.status.dc Whether the node status is given as the DC
+Whether the DC status is reported by the node in the cluster, a
+value of 1 confirms the node status as the designated coordinator.
@ ha_cluster.pacemaker.resources.agent The name of the resource agent for this resource
The name given for the resource agent for the given resource instance in the
diff -Naurp pcp-5.3.7.orig/src/pmdas/hacluster/pacemaker.c pcp-5.3.7/src/pmdas/hacluster/pacemaker.c
--- pcp-5.3.7.orig/src/pmdas/hacluster/pacemaker.c 2023-07-10 16:51:15.069365777 +1000
+++ pcp-5.3.7/src/pmdas/hacluster/pacemaker.c 2023-07-10 16:52:18.069511419 +1000
@@ -534,18 +534,33 @@ hacluster_refresh_pacemaker_nodes(const
/* Collect our node names */
if (found_nodes && strstr(buffer, node_name)) {
- sscanf(buffer, "%*s %*s %*s online=\"%9[^\"]\" standby=\"%9[^\"]\" standby_onfail=\"%9[^\"]\" maintenance=\"%9[^\"]\" pending=\"%9[^\"]\" unclean=\"%9[^\"]\" shutdown=\"%9[^\"]\" expected_up=\"%9[^\"]\" is_dc =\"%9[^\"]\" %*s type=\"%9[^\"]\"",
- online,
- standby,
- standby_on_fail,
- maintenance,
- pending,
- unclean,
- shutdown,
- expected_up,
- dc,
- nodes->type
- );
+ if(strstr(buffer, "feature_set")) {
+ sscanf(buffer, "%*s %*s %*s online=\"%9[^\"]\" standby=\"%9[^\"]\" standby_onfail=\"%9[^\"]\" maintenance=\"%9[^\"]\" pending=\"%9[^\"]\" unclean=\"%9[^\"]\" %*s %*s shutdown=\"%9[^\"]\" expected_up=\"%9[^\"]\" is_dc =\"%9[^\"]\" %*s type=\"%9[^\"]\"",
+ online,
+ standby,
+ standby_on_fail,
+ maintenance,
+ pending,
+ unclean,
+ shutdown,
+ expected_up,
+ dc,
+ nodes->type
+ );
+ } else {
+ sscanf(buffer, "%*s %*s %*s online=\"%9[^\"]\" standby=\"%9[^\"]\" standby_onfail=\"%9[^\"]\" maintenance=\"%9[^\"]\" pending=\"%9[^\"]\" unclean=\"%9[^\"]\" shutdown=\"%9[^\"]\" expected_up=\"%9[^\"]\" is_dc =\"%9[^\"]\" %*s type=\"%9[^\"]\"",
+ online,
+ standby,
+ standby_on_fail,
+ maintenance,
+ pending,
+ unclean,
+ shutdown,
+ expected_up,
+ dc,
+ nodes->type
+ );
+ }
nodes->online = bool_convert(online);
nodes->standby = bool_convert(standby);

View File

@ -0,0 +1,270 @@
From 37171c2042b007037730eb3c61719548c43b5142 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,69 @@
+#!/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
+
+# 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 cf7a93e56..updated 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,7 +1980,8 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+2101 pmda.sockets local security
2104 libpcp local security
2100 pmproxy 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);
}

View File

@ -0,0 +1,221 @@
From 3af40048474ed643d3904497312e710ee378329c 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 | 11 ++++++++++-
qa/src/check_cloexec.c | 38 +++++++++++++++++++++++++++++++++++++
src/libpcp/src/auxconnect.c | 6 +++++-
src/libpcp_pmda/src/root.c | 5 ++++-
7 files changed, 91 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 a8b52b68e..cf7a93e56 100644
--- a/qa/group
+++ b/qa/group
@@ -1982,5 +1982,6 @@ x11
1985 pmfind local valgrind
1986 pmfind local
+2104 libpcp local security
2100 pmproxy local security
2105 libpcp pmcd local security pmcd.pdu
4751 libpcp threads valgrind local pcp helgrind
diff --git a/qa/src/GNUlocaldefs b/qa/src/GNUlocaldefs
index c5abe0bd3..5238b7a52 100644
--- a/qa/src/GNUlocaldefs
+++ b/qa/src/GNUlocaldefs
@@ -51,7 +51,9 @@ CFILES = disk_test.c exercise.c context_test.c chkoptfetch.c \
timeshift.c checkstructs.c bcc_profile.c sha1int2ext.c \
getdomainname.c profilecrash.c store_and_fetch.c test_service_notify.c \
ctx_derive.c pmstrn.c pmfstring.c pmfg-derived.c mmv_help.c sizeof.c \
- stampconv.c
+ stampconv.c \
+ check_cloexec.c
+
ifeq ($(shell test -f ../localconfig && echo 1), 1)
include ../localconfig
@@ -533,6 +535,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
@@ -814,6 +821,8 @@ xlog.o: libpcp.h
xmktime.o: libpcp.h
xxx.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 2afd53cb7..ee6e93fa0 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,

View File

@ -0,0 +1,205 @@
From ccbe2d6cb72c2f3f8969564e59f864e588b3d54e 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 90ec61be8..30758a114 100644
--- a/man/man1/pmproxy.1
+++ b/man/man1/pmproxy.1
@@ -271,6 +271,9 @@ be unreadable by any user other than the user running the
.B pmproxy
process).
.TP
+\fB\-Q\f1, \fB\-\-certreqd\f1
+Require that all client connections provide a trusted client certificate.
+.TP
\f3\-r\f1 \f2port\f1, \f3\-\-redisport\f1=\f2port\f1
Specify an alternate Redis
.I port
@@ -285,6 +288,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 742d3b54b..a8b52b68e 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,5 +1980,6 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+2100 pmproxy 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 d975ef37a..43766c20f 100644
--- a/src/pmproxy/src/pmproxy.c
+++ b/src/pmproxy/src/pmproxy.c
@@ -86,7 +86,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" },
{ "redisport", 1, 'r', "PORT", "Connect to Redis instance on this TCP/IP port (implies --timeseries)" },
{ "redishost", 1, 'h', "HOST", "Connect to Redis instance on this host name (implies --timeseries)" },
PMAPI_OPTIONS_HEADER("Diagnostic options"),
@@ -96,7 +98,7 @@ static pmLongOptions longopts[] = {
};
static pmOptions opts = {
- .short_options = "Ac:C:dD:Ffh:i:l:L:M:p:P:r:s:tU:x:?",
+ .short_options = "Ac:C:dD:Ffh:i:l:L:M:p:P:Qr:s:StU:x:?",
.long_options = longopts,
};

View File

@ -0,0 +1,223 @@
From 7800c577023232c994113c42336b84a2138cb561 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 | 108 +++++++++++++++++++++++++++++++++++++++++++
qa/group | 1 +
src/libpcp/src/pdu.c | 8 ++++
5 files changed, 144 insertions(+)
create mode 100755 qa/2105
create mode 100644 qa/2105.out
create mode 100644 qa/common.pmcd.pdu
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
new file mode 100644
index 000000000..8fa832aa8
--- /dev/null
+++ b/qa/common.pmcd.pdu
@@ -0,0 +1,108 @@
+# This is really a full QA test for stress testing pmcd's protocol
+# handling ... set the shell variables
+# $pdu_data
+# ascii pdu specifications suitable for src/pdu-gadget or binary
+# protocol data stream, these typically contain malformed PDUs
+# (see the qa/pdudata directory for examples) ... this needs
+# to be a relative path
+# $grep_pattern
+# a grep -E pattern for scanning pmcd.log once the test has been
+# run
+# and then source this file
+
+[ -z "$pdu_data" ] && echo "Botch! \$pdu_data not set" && exit 1
+[ -z "$grep_pattern" ] && echo "Botch! \$grep_pattern not set" && exit 1
+
+# get standard environment, filters and checks
+. ./common.product
+. ./common.filter
+. ./common.check
+
+[ ! -f "$here/$pdu_data" ] && echo "Botch! PDU data file $here/$pdu_data not found" && exit 1
+
+
+which nc >/dev/null 2>&1 || _notrun "no nc executable installed"
+# Need nc option to terminate when EOF encountered on stdin,
+# but unfortunately there are multiple versions of nc(1) ...
+nc --help >$tmp.out 2>&1
+if grep -q '.-[^ ]*N' $tmp.out
+then
+ __nc_opt=-N
+elif grep -q '.--send-only' $tmp.out
+then
+ __nc_opt="--send-only"
+else
+ cat $tmp.out >>$here/$seq.full
+ _notrun 'nc --help is not helping ... see $seq.full'
+fi
+_check_valgrind
+
+_cleanup()
+{
+ cat pmcd.log >>$here/$seq.full
+ cd $here
+ $sudo rm -rf $tmp $tmp.*
+}
+
+status=0 # success is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_filter()
+{
+ sed \
+ -e '/^Command: /d' \
+ # 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
+$PCP_AWK_PROG <$PCP_PMCDCONF_PATH >>pmcd.conf '
+$1 == "[access]" { print ""; want = 1 }
+want == 1 { print }'
+cat pmcd.conf >>$here/$seq.full
+__port=`_find_free_port`
+echo "__port=$__port" >>$here/$seq.full
+
+# real QA test starts here
+valgrind --leak-check=full $PCP_BINADM_DIR/pmcd -f -Dpdu,appl3,attr -c ./pmcd.conf -s ./pmcd.socket -p $__port >out 2>err &
+valgrind_pid=$!
+sleep 2
+__pmcd_pid=`$PCP_PS_PROG $PCP_PS_ALL_FLAGS | grep '[p]mcd -f -Dpdu' | $PCP_AWK_PROG '{ print $2 }'`
+echo "__pmcd_pid=$__pmcd_pid" >>$here/$seq.full
+case "`file $here/$pdu_data`"
+in
+ *'ASCII text'*)
+ # assume ascii PDU specification as input to pdu-gadget
+ $here/src/pdu-gadget -p $__port <$here/$pdu_data 2>$tmp.err
+ ;;
+ *)
+ # assume binary data, e.g. SUSE reproducers
+ rm -f $tmp.err
+ nc $__nc_opt <$here/$pdu_data localhost $__port 2>&1 \
+ ;;
+esac \
+| od -X >>$here/$seq.full
+sleep 2
+kill -TERM $__pmcd_pid
+wait
+
+[ -s $tmp.err ] && cat $tmp.err
+
+echo "expect error(s) to be logged ..."
+grep -E "$grep_pattern" pmcd.log | _filter_pmcd
+
+echo
+echo "and no valgrind badness ..."
+cat out err | _filter_valgrind | _filter
+
+# success, all done
+exit
diff --git a/qa/group b/qa/group
index 86265eb1f..742d3b54b 100644
--- a/qa/group
+++ b/qa/group
@@ -1980,4 +1980,5 @@ x11
1984 pmlogconf pmda.redis local
1985 pmfind local valgrind
1986 pmfind local
+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 4afe5719c..5b934513a 100644
--- a/src/libpcp/src/pdu.c
+++ b/src/libpcp/src/pdu.c
@@ -585,6 +585,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",
+ "__pmGetPDU", fd, php->len);
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_TOOBIG;
+ }
tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK);
maxsize = tmpsize;
}

View File

@ -1,12 +1,13 @@
Name: pcp
Version: 5.3.7
Release: 22%{?dist}
Release: 22.0.12%{?dist}.5
Summary: System-level performance monitoring and performance management
License: GPLv2+ and LGPLv2+ and CC-BY
URL: https://pcp.io
%global artifactory https://performancecopilot.jfrog.io/artifactory
Source0: %{artifactory}/pcp-source-release/pcp-%{version}.src.tar.gz
Source1: pdu-getpdu-overflow
Patch0: redhat-bugzilla-2003956-pmdabcc-update-kernel-version-check-due-to-backporting.patch
Patch1: redhat-bugzilla-1981886-pmdasockets-backporting.patch
Patch2: redhat-bugzilla-2059461-pmie-systemd-fixup.patch
@ -33,6 +34,69 @@ Patch22: redhat-issues-RHEL-57796-pmcd-pmstore-corruption.patch
Patch23: redhat-issues-RHEL-57799-pmpost-symlink-handling.patch
Patch24: redhat-issues-RHEL-34586-pmproxy-pmcd-fd-leak.patch
Patch25: redhat-issues-RHEL-57788-pmdahacluster-update.patch
# https://issues.redhat.com/browse/RHEL-213729
# https://github.com/performancecopilot/pcp/commit/ef848fb978d26335f9676933f541c73b58f130a6
Patch26: redhat-issues-RHEL-213729-pdu-integer-overflow.patch
Patch27: redhat-issues-RHEL-213720-pmproxy-auth-flags.patch
# https://issues.redhat.com/browse/RHEL-213686
# https://github.com/performancecopilot/pcp/commit/7e27614006ff6fc4925991edbedaf1eab6b14731
Patch28: redhat-issues-RHEL-213686-CVE-2026-16526.patch
# https://issues.redhat.com/browse/RHEL-213662
# https://github.com/performancecopilot/pcp/commit/c5cbeceb7d3c2af357c04065cdd911efdc270de0
Patch29: redhat-issues-RHEL-213662-CVE-2026-16524.patch
#oracle patch
Patch1001: 1001-pcp-ps-utilty-to-view-ps-like-output-for-pcp.patch
Patch1002: 1002-pcp-ps-python2-print-issue-and-o-option-fix.patch
Patch1003: 1003-pcp-ps-added-capabilities-to-show-n-sample-with-arch.patch
Patch1004: 1004-Fixed-multiple-pcp-ps-and-pcp-mpstat-issue.patch
Patch1005: 1005-Fixed-PCP-python-utility-issues.patch
#pcp-meminfo
Patch1006: 1006-pcp-meminfo-initial-PCP-implementation-of-meminfo.patch
#pcp-slabinfo
Patch1007: 1007-pcp-slabinfo-initial-PCP-implementation-of-slabinfo.patch
#pcp-buddyinfo
Patch1008: 1008-pcp-buddyinfo-initial-commit-of-pcp-implementation.patch
#pcp-netstat
Patch1009: 1009-pcp-netstat-Initial-implemenation-of-pcp-netstat-tool.patch
#pcp-zoneinfo
Patch1010: 1010-pcp-zoneinfo-Initial-implemenation.patch
Patch1011: 1011-pcp-zoneinfo-ol7-replay-fix.patch
#pmlogcheck
Patch1012: 1012-pmlogcheck-Fixed-o-n-n-nested-hash-table-lookup.patch
#pcp-ps
Patch1013: 1013-pcp-ps-added-args-option-to-view-full-process-argume.patch
#pcp-buddyinfo
Patch1014: 1014-pcp-buddyinfo-Added-timestamp-and-no-interpolation.patch
#pcp-meminfo
Patch1015: 1015-pcp-meminfo-Added-timestamp-in-pcp-meinfo.patch
#pmstat
Patch1016: 1016-pmstat-avoid-corner-case-infinite-loop.patch
#rocestat-pmda
Patch1017: 1017-Merge-branch-mohith-kumar-thummaluru-rocestat-pmda.patch
#rocestat-client
Patch1018: 1018-Merge-branch-pcp-rocestat-of-https-github.com-mohith.patch
Patch1019: 1019-pcp-meminfo-additional-metrics-added-for-mem.util-to.patch
Patch1020: 1020-additional-mem.util-metrics-recent-kernel.patch
Patch1021: 1021-meminfo-added-kreclaimable-and-hugtlb-metrics.patch
Patch1022: 1022-fix-mpstat-showing-inconsistent-values.patch
Patch1023: 1023-fix-broken-pipe-error-iostat.patch
Patch1024: 1024-add-missing-numastat-metrics.patch
Patch1025: 1025-add-numastat-support-for-mn-options.patch
Patch1026: 1026-Fixes-higepagesize-metric-value-from-bytes-to-KB.patch
#pmlogger_daily
Patch1027: 1027-xz-default-compression-changed-to-level-3.patch
Patch1028: 1028-orabug38724884-fix-nfsclient-per-op-parsing.patch
Patch1029: 1029-orabug38817091-Introduce-PCP-implementation-of-nfsiostat.patch
Patch1030: 1030-pcp-ps-implement-sort-option-to-allow-sorting-by-cpu.patch
Patch1031: 1031-pmlogger_daily-add-disk-usage-limit-option-and-purge.patch
Patch1032: 1032-pcp-system-tools-restore-backward-compatibility-with.patch
Patch1033: 1033-orabug38902336-adds-interval-option-in-nfsiostat.patch
Patch1034: 1034-orabug38509848-introduces-numa-maps-metrics-and-adds-numastat-process-option.patch
Patch1035: 1035-pmrep-add-unit-information-option-for-CSV-headers.patch
# The additional linker flags break out-of-tree PMDAs.
# https://bugzilla.redhat.com/show_bug.cgi?id=2043092
@ -575,7 +639,7 @@ Requires: pcp-pmda-bpftrace
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
Requires: pcp-pmda-openvswitch pcp-pmda-mongodb
Requires: pcp-pmda-openvswitch pcp-pmda-mongodb pcp-pmda-rocestat
%endif
%if !%{disable_mssql}
Requires: pcp-pmda-mssql
@ -1533,6 +1597,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
#
@ -2304,6 +2386,7 @@ updated policy package.
%prep
%autosetup -p1
install -D -m 644 %{SOURCE1} 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)
@ -2531,6 +2614,7 @@ basic_manifest | keep '(etc/pcp|pmdas)/postfix(/|$)' >pcp-pmda-postfix-files
basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-files
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)/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
@ -2567,7 +2651,7 @@ for pmda_package in \
nutcracker nvidia \
openmetrics openvswitch oracle \
pdns perfevent podman postfix postgresql \
rabbitmq redis roomtemp rpm rsyslog \
rabbitmq redis rocestat roomtemp rpm rsyslog \
samba sendmail shping slurm smart snmp \
sockets statsd summary systemd \
unbound \
@ -2897,6 +2981,9 @@ exit 0
%preun pmda-gluster
%{pmda_remove "$1" "gluster"}
%preun pmda-rocestat
%{pmda_remove "$1" "rocestat"}
%preun pmda-zswap
%{pmda_remove "$1" "zswap"}
@ -3043,6 +3130,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}
@ -3236,6 +3343,8 @@ fi
%if !%{disable_python2} || !%{disable_python3}
%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
@ -3375,6 +3484,56 @@ fi
%files zeroconf -f pcp-zeroconf-files.rpm
%changelog
* Mon Aug 17 2026 EL Errata <el-errata_ww@oracle.com> - 5.3.7-22.0.12.el8_10.5
- Implemented units header in csv output using pmrep[Orabug: 39064482]
- Adds interval option support in PCP nfsiostat tool [Orabug: 38902336]
- Introduces proc.numa_maps metrics in linux_proc PMDA [Orabug: 38509848]
- adds numastat process option in the tool
- Added support for size based cleanup for pcp archives [Orabug: 38991685]
- Added --sort option in pcp ps utility [Orabug: 38991685]
- Merges new PCP nfsiostat parser in OL [Orabug: 38817091]
- pmdanfsclient: fix regex to correctly parse NFS op stats [Orabug: 38724884]
- pmda/rocestat: skip installation when IB is absent [Orabug: 38742706]
- pmlogger_daily default xz compression changed to level 3 [Orabug: 38592558]
- Backports various pcp bugs and enhancements [Orabug: 35778072]
- [Orabug: 36932629] [Orabug: 38526407] [Orabug: 38526462]
- [Orabug: 38526444] [Orabug: 38531609]
- Add support for rocestat pmda [Orabug: 38109218]
- Add support for rocestat client [Orabug: 38109360]
- Fixed pmstat infinte loop issue in archive replay [Orabug: 37638447]
- Fixed pmlogcheck time consuming issue [Orabug: 36995894]
- Added pcp-ps arg option to view full command argument [Orabug: 37062125]
- Added no interpolation option in pcp-buddyinfo [Orabug: 36985368]
- Added timestamp and fixed broken pipe in pcp-meminfo [Orabug: 36985368]
- pcp-zoneinfo fix to replay ol7 archives [Orabug: 35903733]
- Backporting of python tool pcp-meminfo [Orabug: 35759707]
- Backporting of python tool pcp-slabinfo [Orabug: 35560940]
- Backporting of python tool pcp-buddyinfo [Orabug: 35660932]
- Backporting of python tool pcp-netstat [Orabug: 34324779]
- Backporting of python tool pcp-zoneinfo [Orabug: 35660927]
- Fixed multiple pcp python utiltites issues[Orabug: 35434363]
- Fixed broken pipe issue in pcp ps utlity[Orabug: 34830203]
- Fixed pcp mpstat utiltiy crash issue [Orabug: 34891338]
- Pcp mpstat utiltiy initial archive file read error fix [Orabug: 34869451]
- Fix pcp-ps to show n sample with archives[Orabug: 34849959]
- Pcp ps Utility -o option and print issue fix [Orabug: 34321683]
- Pcp ps utilty has been added [Orabug: 34321683]
* Fri Aug 14 2026 Jan Kurik <jkurik@redhat.com> - 5.3.7-22.5
- Fix qa/2101 and qa/common.pmcd.pdu for PCP 5.3.7 testsuite compatibility
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.4
- Fix CVE-2026-16524: command injection in linux_sockets PMDA (RHEL-213662)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.3
- Fix CVE-2026-16526: set FD_CLOEXEC on AF_UNIX sockets (RHEL-213686)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.2
- Fix missing pmproxy -Q and -S authentication flags (CVE-2026-16527)
* Thu Jul 30 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 5.3.7-22.1
- Fix integer overflow in __pmGetPDU PDU handling (RHEL-213729)
* Mon Sep 09 2024 Nathan Scott <nathans@redhat.com> - 5.3.7-22
- Fix buffer sizing checks in pmstore PDU handling (RHEL-57796)
- Guard against symlink attacks in pmpost program (RHEL-57799)