Revert OL modifications

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-19 05:34:09 -04:00
parent 5f0a3da4bf
commit 8e60e87eed
9 changed files with 1 additions and 4698 deletions

View File

@ -1,38 +0,0 @@
From c80dc758eb0eb27fafa0b594d6c2aa1f4c3803fa Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Wed, 3 Dec 2025 20:40:52 +0530
Subject: [PATCH] pmdanfsclient: fix regex to correctly parse NFS op stats
The regex used to match NFS operation statistics in /proc/self/mountstats was
missing a capture group, causing parsing failures when an additional field
was present in newer kernel formats. Updated the regex to include the extra
numeric field so that all opstats lines are parsed correctly.
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/85671f8874d7d4b4e57eb45bfe295de92b95415c
Orabug: 38724847
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pmdas/nfsclient/pmdanfsclient.python | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pmdas/nfsclient/pmdanfsclient.python b/src/pmdas/nfsclient/pmdanfsclient.python
index 786d958..f08b93a 100644
--- a/src/pmdas/nfsclient/pmdanfsclient.python
+++ b/src/pmdas/nfsclient/pmdanfsclient.python
@@ -636,7 +636,7 @@ class NFSCLIENTPMDA(PMDA):
line = STATS.readline()
if line == '':
break
- m = re.match(r'\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$', line)
+ m = re.match(r'\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$', line)
if not m:
break
opname = m.group(1).lower()
--
2.43.7

View File

@ -1,575 +0,0 @@
From ca8fbd2a827e3e7caa72331e003f93523c9f5241 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Tue, 6 Jan 2026 05:53:08 +0000
Subject: [PATCH] pcp-nfsiostat: Introduce PCP implementation of nfsiostat
parser
Cherry-pick-commit: https://github.com/performancecopilot/pcp/commit/c0a0c53b57774dfac0b727e62472fb1ae6065540
Orabug: 38817053
Signed-off-by: Sourav Sharma <sourav.ss.sharma@oracle.com>
---
src/pcp/GNUmakefile | 1 +
src/pcp/nfsiostat/GNUmakefile | 43 ++++
src/pcp/nfsiostat/pcp-nfsiostat.1 | 132 +++++++++++
src/pcp/nfsiostat/pcp-nfsiostat.py | 346 +++++++++++++++++++++++++++++
4 files changed, 522 insertions(+)
create mode 100644 src/pcp/nfsiostat/GNUmakefile
create mode 100644 src/pcp/nfsiostat/pcp-nfsiostat.1
create mode 100644 src/pcp/nfsiostat/pcp-nfsiostat.py
diff --git a/src/pcp/GNUmakefile b/src/pcp/GNUmakefile
index da29269..2b74fc0 100644
--- a/src/pcp/GNUmakefile
+++ b/src/pcp/GNUmakefile
@@ -30,6 +30,7 @@ SUBDIRS = \
mpstat \
netstat \
numastat \
+ nfsiostat \
pidstat \
ps \
python \
diff --git a/src/pcp/nfsiostat/GNUmakefile b/src/pcp/nfsiostat/GNUmakefile
new file mode 100644
index 0000000..d8c5356
--- /dev/null
+++ b/src/pcp/nfsiostat/GNUmakefile
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pcp-nfsiostat
+SCRIPT = $(TARGET).py
+MAN_SECTION = 1
+MAN_PAGES = $(TARGET).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+
+default: $(SCRIPT) $(MAN_PAGES)
+
+include $(BUILDRULES)
+
+install: default
+ifeq "$(HAVE_PYTHON)" "true"
+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BINADM_DIR)/$(TARGET)
+ @$(INSTALL_MAN)
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check:: $(SCRIPT)
+ $(PYLINT) $^
+
+check :: $(MAN_PAGES)
+ $(MANLINT) $^
diff --git a/src/pcp/nfsiostat/pcp-nfsiostat.1 b/src/pcp/nfsiostat/pcp-nfsiostat.1
new file mode 100644
index 0000000..b297285
--- /dev/null
+++ b/src/pcp/nfsiostat/pcp-nfsiostat.1
@@ -0,0 +1,132 @@
+'\"! tbl | mmdoc
+'\"macro stdmacro
+.\"
+.\" Man page for pcp-nfsiostat
+.\" Copyright (c) 2023 Oracle and/or its affiliates.
+.\" DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+.\"
+.\" This program is free software; you can redistribute it and/or modify it
+.\" under the terms of the GNU General Public License as published by the
+.\" Free Software Foundation; either version 2 of the License, or (at your
+.\" option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+.\" or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+.\" for more details.
+.\"
+
+.TH PCP-NFSIOSTAT 1 "PCP" "Performance Co-Pilot"
+
+.SH NAME
+\f3pcp-nfsiostat\f1 \- Emulate iostat for NFS mount points using /proc/self/mountstats
+
+.SH SYNOPSIS
+\fBpcp\fP [\fBpcp options\fP] \fBnfsiostat\fP [\fB-s\fP \fBsamples\fP] [\fB-a\fP \fBarchive\fP] [\fB-Z\fP \fB--timezone\fP] [\fB-z\fP \fB--hostzone\fP] [\fB-V\fP \fBVersion\fP]
+
+.SH DESCRIPTION
+The\fB pcp-nfsiostat \fPcommand reports client-side\fB Network File System (NFS) \fPI/O statistics for each mounted NFS filesystem. For every NFS mountpoint, the output consists of a summary section followed by detailed read and write statistics blocks. These statistics include operation rates, data throughput, latency, retransmissions, queueing delays, and error counts. By default,\fB pcp-nfsiostat \fPmonitors NFS mountpoints on the local host and reports live metrics collected via the \fB PCP NFS PMDA\fP. When an archive is specified, historical metrics are reported instead.
+
+.SH OUTPUT FORMAT
+Statistics are reported per mounted NFS filesystem using the following layout:
+
+.nf
+server:/export mounted on /mount/point:
+
+ ops/s rpc bklog
+ 0.017 0.000
+
+read: ops/s kB/s kB/op retrans avg RTT (ms) avg exe (ms) avg queue (ms) errors
+ ...
+
+write: ops/s kB/s kB/op retrans avg RTT (ms) avg exe (ms) avg queue (ms) errors
+ ...
+.fi
+
+.SH FIELD DESCRIPTIONS
+.TP
+.B ops/s
+Number of NFS operations performed per second.
+
+.TP
+.B rpc bklog
+Average number of RPC requests waiting to be transmitted.
+
+.TP
+.B kB/s
+Kilobytes transferred per second.
+
+.TP
+.B kB/op
+Average number of kilobytes transferred per operation.
+
+.TP
+.B retrans
+Number of RPC retransmissions and the retransmission percentage.
+
+.TP
+.B avg RTT (ms)
+Average round-trip time in milliseconds for RPC requests.
+
+.TP
+.B avg exe (ms)
+Average execution time in milliseconds spent servicing requests on the server.
+
+.TP
+.B avg queue (ms)
+Average time in milliseconds spent waiting in the RPC transmission queue.
+
+.TP
+.B errors
+Number of failed operations and failure percentage.
+
+.SH OPTIONS
+.TP
+.BR \-a ", " \-\-archive " " I archive
+Fetch NFS I/O statistics from the specified PCP archive.
+
+.TP
+.BR \-s ", " \-\-samples " " I samples
+Number of samples to collect before exiting.
+
+.TP
+.BR \-z ", " \-\-hostzone
+Set the reporting timezone to the local timezone of the metrics source.
+
+.TP
+.BR \-Z ", " \-\-timezone " " I tz
+Set the reporting timezone.
+
+.TP
+.BR \-V ", " \-\-version
+Display version information and exit.
+
+.TP
+.BR \-? ", " \-\-help
+Display usage information and exit.
+
+.SH NOTES
+.B pcp-nfsiostat
+reports client-side NFS statistics collected from the kernel and exposed
+via the PCP NFS PMDA. The output format and metrics are similar to those
+reported by the
+.BR nfsiostat (1)
+tool from the nfs-utils package.
+
+.SH PCP ENVIRONMENT
+Environment variables with the prefix \fBPCP_\fP are used to parameterize
+the file and directory names used by PCP.
+On each installation, the
+file \fI/etc/pcp.conf\fP contains the local values for these variables.
+The \fB$PCP_CONF\fP variable may be used to specify an alternative
+configuration file, as described in
+.BR pcp.conf (5).
+
+For environment variables affecting PCP tools, see
+.BR pmGetOptions (3).
+
+.SH SEE ALSO
+.BR PCPIntro (1),
+.BR pcp (1),
+.BR nfsiostat (1),
+.BR environ (7).
diff --git a/src/pcp/nfsiostat/pcp-nfsiostat.py b/src/pcp/nfsiostat/pcp-nfsiostat.py
new file mode 100644
index 0000000..0b0376d
--- /dev/null
+++ b/src/pcp/nfsiostat/pcp-nfsiostat.py
@@ -0,0 +1,346 @@
+#!/usr/bin/pmpython
+#
+# Copyright (c) 2023 Oracle and/or its affiliates.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pylint: disable=bad-whitespace,too-many-lines,bad-continuation
+# pylint: disable=too-many-arguments,too-many-positional-arguments
+# pylint: disable=redefined-outer-name,unnecessary-lambda
+#
+
+import signal
+import sys
+import time
+from pcp import pmapi, pmcc
+from cpmapi import PM_CONTEXT_ARCHIVE
+
+SYS_METRICS= ["kernel.uname.sysname","kernel.uname.release",
+ "kernel.uname.nodename","kernel.uname.machine","hinv.ncpu"]
+NFSIOSTAT_METRICS = ["nfsclient.mountpoint","nfsclient.export","nfsclient.age",
+ "nfsclient.xprt.sends","nfsclient.xprt.backlog_u","nfsclient.ops.read.ops",
+ "nfsclient.ops.read.errors","nfsclient.ops.read.execute","nfsclient.ops.read.rtt",
+ "nfsclient.ops.read.queue","nfsclient.ops.read.bytes_recv","nfsclient.ops.read.bytes_sent",
+ "nfsclient.ops.read.ntrans","nfsclient.ops.write.ops","nfsclient.ops.write.errors",
+ "nfsclient.ops.write.execute","nfsclient.ops.write.rtt","nfsclient.ops.write.queue",
+ "nfsclient.ops.write.bytes_recv","nfsclient.ops.write.bytes_sent","nfsclient.ops.write.ntrans"]
+ALL_METRICS = NFSIOSTAT_METRICS + SYS_METRICS
+
+def adjust_length(name):
+ return name.ljust(25)
+class ReportingMetricRepository:
+
+ def __init__(self,group):
+ self.group=group
+ self.current_cached_values = {}
+
+ def __sorted(self,data):
+ return dict(sorted(data.items(), key=lambda item: item[0].lower()))
+
+ def __fetch_current_value(self,metric):
+ val=dict(map(lambda x: (x[1], x[2]), self.group[metric].netValues))
+ val=self.__sorted(val)
+ return dict(val)
+
+ def current_value(self,metric):
+ if not metric in self.group:
+ return None
+ if self.current_cached_values.get(metric) is None:
+ first_value=self.__fetch_current_value(metric)
+ self.current_cached_values[metric]=first_value
+ return self.current_cached_values[metric]
+
+class NfsioStatUtil:
+ def __init__(self,metrics_repository):
+ self.__metric_repository=metrics_repository
+ self.report=ReportingMetricRepository(self.__metric_repository)
+
+ def mount_point(self):
+ return self.report.current_value('nfsclient.mountpoint')
+
+ def mount_share(self):
+ return self.report.current_value('nfsclient.export')
+
+ def mount_share_keys(self):
+ data = self.report.current_value('nfsclient.export')
+ return data.keys()
+
+ def sample_time(self):
+ return self.report.current_value('nfsclient.age')
+
+ def xprt_sends(self):
+ return self.report.current_value('nfsclient.xprt.sends')
+
+ def xprt_backlog(self):
+ return self.report.current_value('nfsclient.xprt.backlog_u')
+
+ def readops(self):
+ return self.report.current_value('nfsclient.ops.read.ops')
+
+ def readerrors(self):
+ return self.report.current_value('nfsclient.ops.read.errors')
+
+ def readexecute(self):
+ return self.report.current_value('nfsclient.ops.read.execute')
+
+ def readrtt(self):
+ return self.report.current_value('nfsclient.ops.read.rtt')
+
+ def readqueue(self):
+ return self.report.current_value('nfsclient.ops.read.queue')
+
+ def readbytesrecv(self):
+ return self.report.current_value('nfsclient.ops.read.bytes_recv')
+
+ def readbytessent(self):
+ return self.report.current_value('nfsclient.ops.read.bytes_sent')
+
+ def readntrans(self):
+ return self.report.current_value('nfsclient.ops.read.ntrans')
+
+ def writeops(self):
+ return self.report.current_value('nfsclient.ops.write.ops')
+
+ def writeerrors(self):
+ return self.report.current_value('nfsclient.ops.write.errors')
+
+ def writeexecute(self):
+ return self.report.current_value('nfsclient.ops.write.execute')
+
+ def writertt(self):
+ return self.report.current_value('nfsclient.ops.write.rtt')
+
+ def writequeue(self):
+ return self.report.current_value('nfsclient.ops.write.queue')
+
+ def writebytesrecv(self):
+ return self.report.current_value('nfsclient.ops.write.bytes_recv')
+
+ def writebytessent(self):
+ return self.report.current_value('nfsclient.ops.write.bytes_sent')
+
+ def writentrans(self):
+ return self.report.current_value('nfsclient.ops.write.ntrans')
+
+class NfsiostatReport(pmcc.MetricGroupPrinter):
+ def __init__(self,opts,group):
+ self.opts = opts
+ self.group = group
+ self.samples = opts.samples
+ self.context = opts.context
+
+ def __get_ncpu(self, group):
+ return group['hinv.ncpu'].netValues[0][2]
+
+ def __print_machine_info(self, context):
+ timestamp = self.group.pmLocaltime(context.timestamp.tv_sec)
+ # Please check strftime(3) for different formatting options.
+ # Also check TZ and LC_TIME environment variables for more
+ # information on how to override the default formatting of
+ # the date display in the header
+ time_string = time.strftime("%m/%d/%Y %H:%M:%S", timestamp.struct_time())
+ header_string = ''
+ header_string += context['kernel.uname.sysname'].netValues[0][2] + ' '
+ header_string += context['kernel.uname.release'].netValues[0][2] + ' '
+ header_string += '(' + context['kernel.uname.nodename'].netValues[0][2] + ') '
+ header_string += time_string + ' '
+ header_string += context['kernel.uname.machine'].netValues[0][2] + ' '
+ print("%s (%s CPU)" % (header_string, self.__get_ncpu(context)))
+
+ def __print_values(self,timestamp, nfsstatus):
+ n_shares = nfsstatus.mount_share_keys()
+ mountshare = nfsstatus.mount_share()
+ mountpoint = nfsstatus.mount_point()
+ sampletime = nfsstatus.sample_time()
+ sends = nfsstatus.xprt_sends()
+ backlog = nfsstatus.xprt_backlog()
+ readops = nfsstatus.readops()
+ readerrors = nfsstatus.readerrors()
+ readexecute = nfsstatus.readexecute()
+ readrtt = nfsstatus.readrtt()
+ readqueue = nfsstatus.readqueue()
+ readbytesrecv = nfsstatus.readbytesrecv()
+ readbytessent = nfsstatus.readbytessent()
+ readntrans = nfsstatus.readntrans()
+ writeops = nfsstatus.writeops()
+ writeerrors = nfsstatus.writeerrors()
+ writeexecute = nfsstatus.writeexecute()
+ writertt = nfsstatus.writertt()
+ writequeue = nfsstatus.writequeue()
+ writebytesrecv = nfsstatus.writebytesrecv()
+ writebytessent = nfsstatus.writebytessent()
+ writentrans = nfsstatus.writentrans()
+
+ print("%-18s:%s"%("Timestamp", timestamp))
+ print()
+
+ for name in n_shares:
+ # read
+ r_kilobytes = (readbytessent[name] + readbytesrecv[name]) / 1024
+ if sampletime[name] > 0:
+ ops_per_sample = sends[name] / sampletime[name]
+ ops_per_sample_read = readops[name] / sampletime[name]
+ r_kilobytes_per_sample = r_kilobytes / sampletime[name]
+ else:
+ ops_per_sample = 0.0
+ ops_per_sample_read = 0.0
+ r_kilobytes_per_sample = 0.0
+
+ r_retrans = readntrans[name] - readops[name]
+ if readops[name] > 0:
+ r_kilobytes_per_op = r_kilobytes / readops[name]
+ r_retrans_percent = (r_retrans * 100) / readops[name]
+ r_rtt_per_op = readrtt[name] / readops[name]
+ r_exe_per_op = readexecute[name] / readops[name]
+ r_queued_for_per_op = readqueue[name] / readops[name]
+ r_errs_percent = (readerrors[name] * 100) / readops[name]
+ else:
+ r_kilobytes_per_op = 0.0
+ r_retrans_percent = 0.0
+ r_rtt_per_op = 0.0
+ r_exe_per_op = 0.0
+ r_queued_for_per_op = 0.0
+ r_errs_percent = 0.0
+
+ # write
+ w_kilobytes = (writebytessent[name] + writebytesrecv[name]) / 1024
+ if sampletime[name] > 0:
+ ops_per_sample_write = writeops[name] / sampletime[name]
+ w_kilobytes_per_sample = w_kilobytes / sampletime[name]
+ else:
+ ops_per_sample_write = 0.0
+ w_kilobytes_per_sample = 0.0
+
+ w_retrans = writentrans[name] - writeops[name]
+ if writeops[name] > 0:
+ w_kilobytes_per_op = w_kilobytes / writeops[name]
+ w_retrans_percent = (w_retrans * 100) / writeops[name]
+ w_rtt_per_op = writertt[name] / writeops[name]
+ w_exe_per_op = writeexecute[name] / writeops[name]
+ w_queued_for_per_op = writequeue[name] / writeops[name]
+ w_errs_percent = (writeerrors[name] * 100) / writeops[name]
+ else:
+ w_kilobytes_per_op = 0.0
+ w_retrans_percent = 0.0
+ w_rtt_per_op = 0.0
+ w_exe_per_op = 0.0
+ w_queued_for_per_op = 0.0
+ w_errs_percent = 0.0
+
+ print(f"{mountshare[name]} mounted on {mountpoint[name]}:")
+
+ print(f"{'':14}ops/s{'':7}rpc bklog")
+ print(f"{ops_per_sample:19.3f}{backlog[name]:16.3f}")
+ print()
+ print(
+ "read: "
+ "ops/s kB/s kB/op retrans "
+ "avg RTT (ms) avg exe (ms) avg queue (ms) errors"
+ )
+ print(
+ f"{'':19}"
+ f"{ops_per_sample_read:5.3f}"
+ f"{r_kilobytes_per_sample:12.3f}"
+ f"{r_kilobytes_per_op:13.3f} "
+ f"{int(r_retrans):2d} ({r_retrans_percent:2.1f}%)"
+ f"{r_rtt_per_op:15.3f}"
+ f"{r_exe_per_op:15.3f}"
+ f"{r_queued_for_per_op:17.3f} "
+ f"{int(readerrors[name]):4d} ({r_errs_percent:2.1f}%)"
+ )
+
+ print(
+ "write: "
+ "ops/s kB/s kB/op retrans "
+ "avg RTT (ms) avg exe (ms) avg queue (ms) errors"
+ )
+
+ print(
+ f"{'':19}"
+ f"{ops_per_sample_write:5.3f}"
+ f"{w_kilobytes_per_sample:12.3f}"
+ f"{w_kilobytes_per_op:13.3f} "
+ f"{int(w_retrans):2d} ({w_retrans_percent:2.1f}%)"
+ f"{w_rtt_per_op:15.3f}"
+ f"{w_exe_per_op:15.3f}"
+ f"{w_queued_for_per_op:17.3f} "
+ f"{int(writeerrors[name]):4d} ({w_errs_percent:2.1f}%)"
+ )
+ print()
+
+ def print_report(self,group,timestamp, manager_nfsiostat):
+ def __print_nfs_status():
+ nfsstatus = NfsioStatUtil(manager_nfsiostat)
+ if nfsstatus.mount_share():
+ try:
+ self.__print_machine_info(group)
+ self.__print_values(timestamp, nfsstatus)
+ except IndexError:
+ print("Incorrect machine info due to some missing metrics")
+ return
+ else:
+ pass
+
+ if self.context != PM_CONTEXT_ARCHIVE and self.samples is None:
+ __print_nfs_status()
+ sys.exit(0)
+ elif self.context == PM_CONTEXT_ARCHIVE and self.samples is None:
+ __print_nfs_status()
+ elif self.samples >=1:
+ __print_nfs_status()
+ self.samples-=1
+ else:
+ pass
+
+ def report(self, manager):
+ group = manager["sysinfo"]
+ self.samples = self.opts.pmGetOptionSamples()
+ t_s = group.contextCache.pmLocaltime(int(group.timestamp))
+ timestamp = time.strftime(NfsiostatOptions.timefmt, t_s.struct_time())
+ self.print_report(group,timestamp,manager['nfsiostat'])
+
+class NfsiostatOptions(pmapi.pmOptions):
+ timefmt = "%m/%d/%Y %H:%M:%S"
+ def __init__(self):
+ pmapi.pmOptions.__init__(self, "a:s:Z:zV?")
+ self.pmSetLongOptionHeader("General options")
+ self.pmSetLongOptionHostZone()
+ self.pmSetLongOptionTimeZone()
+ self.pmSetLongOptionHelp()
+ self.pmSetLongOptionSamples()
+ self.pmSetLongOptionVersion()
+ self.samples=None
+ self.context=None
+
+if __name__ == '__main__':
+ try:
+ opts = NfsiostatOptions()
+ mngr = pmcc.MetricGroupManager.builder(opts,sys.argv)
+ opts.context=mngr.type
+ missing = mngr.checkMissingMetrics(ALL_METRICS)
+ if missing is not None:
+ sys.stderr.write('Error: not all required metrics are available\nMissing %s\n' % missing)
+ sys.exit(1)
+ mngr["nfsiostat"] = ALL_METRICS
+ mngr["sysinfo"] = SYS_METRICS
+ mngr.printer = NfsiostatReport(opts,mngr)
+ sts = mngr.run()
+ sys.exit(sts)
+ except pmapi.pmErr as error:
+ sys.stderr.write('%s\n' % (error.message()))
+ except pmapi.pmUsageErr as usage:
+ usage.message()
+ sys.exit(1)
+ except IOError:
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ except KeyboardInterrupt:
+ pass
--
2.43.7

View File

@ -1,178 +0,0 @@
From 7bc4a59bfe06f9b988ea21ad6a2decc86c942097 Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Thu, 26 Mar 2026 14:16:48 +0000
Subject: [PATCH OL9 1013/1016] pmlogger_janitor fix not to terminate
unauthorized process
- Fix pmlogger_janitor.sh to verify pid for active pmlogger before kill/removing map files
- Update process type detection to support *BSD ps output syntax
- Refactor pmlogger_check.sh to load janitor env vars more robustly
upstream :- 358b684619d786acc93092824bc24ba69c5d5dbc
f305a22730a614535098c4f85c05de37eadc790e
[Orabug:38598244]
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
src/pmlogger/pmlogger_check.sh | 27 +++++++++++---
src/pmlogger/pmlogger_farm.defaults | 8 ----
src/pmlogger/pmlogger_janitor.sh | 57 ++++++++++++++++++++++++++++-
3 files changed, 76 insertions(+), 16 deletions(-)
diff --git a/src/pmlogger/pmlogger_check.sh b/src/pmlogger/pmlogger_check.sh
index c941df0..27a0a1e 100755
--- a/src/pmlogger/pmlogger_check.sh
+++ b/src/pmlogger/pmlogger_check.sh
@@ -23,9 +23,6 @@
PMLOGGER="$PCP_BINADM_DIR/pmlogger"
PMLOGCONF="$PCP_BINADM_DIR/pmlogconf"
-PMLOGGERENVS="$PCP_SYSCONFIG_DIR/pmlogger"
-PMLOGGERFARMENVS="$PCP_SYSCONFIG_DIR/pmlogger_farm"
-PMLOGGERZEROCONFENVS="$PCP_SHARE_DIR/zeroconf/pmlogger"
# error messages should go to stderr, not the GUI notifiers
#
@@ -703,7 +700,7 @@ _callback_log_control()
if [ "X$primary" = Xy ]
then
# User configuration takes precedence over pcp-zeroconf
- envs=`grep -h ^PMLOGGER "$PMLOGGERZEROCONFENVS" "$PMLOGGERENVS" 2>/dev/null`
+ envs=`grep -h ^PMLOGGER "$PCP_SHARE_DIR/zeroconf/pmlogger" "$PCP_SYSCONFIG_DIR/pmlogger" 2>/dev/null`
args="-P $args"
iam=" primary"
# clean up port-map, just in case
@@ -721,7 +718,7 @@ _callback_log_control()
return
fi
else
- envs=`grep -h ^PMLOGGER "$PMLOGGERFARMENVS" 2>/dev/null`
+ envs=`grep -h ^PMLOGGER "$PCP_SYSCONFIG_DIR/pmlogger_farm" 2>/dev/null`
args="-h $host $args"
iam=""
fi
@@ -896,10 +893,28 @@ fi
# because the legitimate pmloggers, like the primary pmlogger, may
# not be included in the "test" control file(s).
#
+if [ -z "${PMLOGGER_CHECK_SKIP_JANITOR+is_set}" ]
+then
+ check=`grep '^PMLOGGER_CHECK_SKIP_JANITOR=' $PCP_SYSCONFIG_DIR/pmlogger`
+ if [ -n "$check" ]
+ then
+ eval $check
+ fi
+fi
+if [ -z "${PMLOGGER_JANITOR_ARGS+is_set}" ]
+then
+ check=`grep '^PMLOGGER_JANITOR_ARGS=' $PCP_SYSCONFIG_DIR/pmlogger`
+ if [ -n "$check" ]
+ then
+ eval $check
+ fi
+fi
+
if [ "$CONTROL" = "$PCP_PMLOGGERCONTROL_PATH" -a "$PMLOGGER_CHECK_SKIP_JANITOR" != "yes" ]
then
$VERY_VERBOSE && echo "Running: pmlogger_janitor $daily_args"
- $PCP_BINADM_DIR/pmlogger_janitor $daily_args
+ args="$daily_args"
+ $PCP_BINADM_DIR/pmlogger_janitor $args
fi
if [ -f $tmp/err ]
diff --git a/src/pmlogger/pmlogger_farm.defaults b/src/pmlogger/pmlogger_farm.defaults
index 10a0132..af06446 100644
--- a/src/pmlogger/pmlogger_farm.defaults
+++ b/src/pmlogger/pmlogger_farm.defaults
@@ -25,11 +25,3 @@
# setting PMLOGGER_CHECK_SKIP_LOGCONF to yes disables the regeneration
# and checking.
# PMLOGGER_CHECK_SKIP_LOGCONF=yes
-
-# By default pmlogger_check(1) will run pmlogger_janitor to check for
-# pmlogger(1) badness caused by processes and/or files that were once
-# managed from the control files but have become detached from those
-# control files.
-# Setting PMLOGGER_CHECK_SKIP_JANITOR to yes disables pmlogger_janitor
-# and maybe useful for QA or special testing
-# PMLOGGER_CHECK_SKIP_JANITOR=yes
diff --git a/src/pmlogger/pmlogger_janitor.sh b/src/pmlogger/pmlogger_janitor.sh
index bc0360a..7c8ec0c 100755
--- a/src/pmlogger/pmlogger_janitor.sh
+++ b/src/pmlogger/pmlogger_janitor.sh
@@ -348,8 +348,15 @@ _callback_log_control()
if [ -n "$pid" ]
then
- # found matching pmlogger ... cull this one from
- $VERY_VERBOSE && echo "[$filename:$line] match PID $pid, nothing to be done"
+ # found matching pmlogger ... cull this one from $tmp/loggers
+ #
+ if $VERY_VERBOSE
+ then
+ echo "[$filename:$line] match PID $pid, nothing to be done"
+ elif $VERBOSE
+ then
+ echo "Pass 3: PID $pid matches control [$filename:$line], nothing to be done"
+ fi
sed <$tmp/loggers >$tmp/tmp -e "/^$pid /d"
mv $tmp/tmp $tmp/loggers
fi
@@ -365,6 +372,52 @@ then
| while read file
do
pid=`echo "$file" | sed -e "s@$PCP_TMP_DIR/pmlogger/@@"`
+ # sanity checks
+ # 1. does this process exist?
+ # 2. is it really pmlogger?
+ # if "no" to either case, remove this (stale) mapfile
+ # and move on ...
+ #
+ if $PCP_PS_PROG -p "$pid" >$tmp/tmp 2>&1
+ then
+ # ps(1) -p output should be something like this ...
+ # PID TTY TIME CMD
+ # 14298 ? 00:00:00 pmlogger
+ # or this (for *BSD)
+ # PID TT STAT TIME COMMAND
+ # 22839 1 S 0:00.04 /usr/libexec/pcp/bin/pmlogger -N -P ...
+ #
+ if sed -n -e 2p <$tmp/tmp | grep -E -q '( pmlogger$)|(/bin/pmlogger )'
+ then
+ : OK
+ else
+ if $VERBOSE
+ then
+ cat $tmp/tmp
+ echo "Warning: PID $pid is not a pmlogger process, removing $file"
+ fi
+ if $SHOWME
+ then
+ echo "+ rm $file"
+ else
+ rm -f "$file"
+ fi
+ continue
+ fi
+ else
+ if $VERBOSE
+ then
+ echo "Warning: PID $pid has vanished, removing $file"
+ fi
+ if $SHOWME
+ then
+ echo "+ rm $file"
+ else
+ rm -f "$file"
+ fi
+ continue
+ fi
+
# timing window here, file may have gone away between
# find(1) and awk(1), so just ignore any errors ...
#
--
2.43.7

File diff suppressed because it is too large Load Diff

View File

@ -1,604 +0,0 @@
From 09f39fb28a3171300afa5ed15e7f75ac0fbfdabc Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Thu, 26 Mar 2026 16:10:37 +0000
Subject: [PATCH OL9 1015/1016] pmlogger_daily -d/--disk option for archive
space limit
Added -d/--disk option in pmlogger_daily for archive size limit
Doc: Explain $PCP_SPACELIMIT env var for disk usage in PCP archives
Clarify archive retention/purging with space limits and env/CLI opts
utilproc.sh: Refactor _convert_to_kb(), unit checks, env
upstream ref:- 7b7c38c16c6904960ede400efa5225732935b830
[orabug:38757778]
Signed-off-by: sagar sagar <sagar.sagar@oracle.com>
---
man/man1/pmlogger_daily.1 | 85 +++++++++++++++-
src/pmlogger/pmlogger_daily.sh | 176 +++++++++++++++++++++++++++++++--
src/pmlogger/utilproc.sh | 142 +++++++++++++++++++++++++-
3 files changed, 391 insertions(+), 12 deletions(-)
diff --git a/man/man1/pmlogger_daily.1 b/man/man1/pmlogger_daily.1
index c7b30ae..0371f63 100644
--- a/man/man1/pmlogger_daily.1
+++ b/man/man1/pmlogger_daily.1
@@ -20,6 +20,7 @@
.B $PCP_BINADM_DIR/pmlogger_daily
[\f3\-DEfKMNnoPpQrRVzZ?\f1]
[\f3\-c\f1 \f2control\f1]
+[\f3\-d\f1 \f2fssize\f1]
[\f3\-k\f1 \f2time\f1]
[\f3\-l\f1 \f2logfile\f1]
[\f3\-m\f1 \f2addresses\f1]
@@ -120,6 +121,72 @@ Do not perform the conditional
.BR pmlogger_daily_report (1)
processing as described below.
.TP 5
+\fB\-d\fR \fIfssize\fR, \fB\-\-disk\fR=\fIfssize\fR
+This option enforces a maximum total file system space usage per host directory for PCP archive files stored under each
+.IR $PCP_ARCHIVE_DIR/<hostname>
+and
+.IR $PCP_REMOTE_ARCHIVE_DIR/<hostname>
+location. The
+.I fssize
+must be specified as an integer with a suffix
+.B K
+or
+.B k
+for kilobytes,
+.B M
+or
+.B m
+for megabytes,
+.B G
+or
+.B g
+for gigabytes (e.g. 500m or 10G).
+As a special case,
+.I fssize
+may be the keyword
+.B unlimited
+to prevent any file system space limit enforcement (this is the default
+behaviour).
+.RS
+.PP
+Alternatively, the file system space limit may be set by exporting the
+.B $PCP_SPACELIMIT
+environment variable, which is handled analogously to
+.BR $PCP_CULLAFTER .
+If both
+.B $PCP_SPACELIMIT
+and
+.B \-d
+are provided and specify different values then the value from
+the environment variable is used and a warning is issued.
+.PP
+After normal daily log processing (compression, merging, culling by age, etc.)
+completes,
+if the total file system space consumed by any
+.BR pmlogger (1)
+instance exceeds
+.IR fssize ,
+then additional archive files will be purged (i.e. removed),
+oldest first, until
+the file system usage is reduced to be not more than
+.IR fssize .
+This purging operation never deletes archive files with today's date,
+and so always preserves the most recent archives required by active
+.BR pmlogger (1)
+processes.
+.PP
+Enforcement is only performed when
+.B pmlogger_daily
+runs (typically once a day), not continuously.
+As a result, the total file system usage for any
+.BR pmlogger (1)
+instance may exceed a specified maximum between
+.B pmlogger_daily
+executions (for example, as new archives are created
+or when archives are uncompressed for merging or
+prior to asynchronous archive compression).
+.RE
+.TP 5
\fB\-E\fR, \fB\-\-expunge\fR
This option causes
.B pmlogger_daily
@@ -524,7 +591,7 @@ maximizes the diagnostic capabilities for debugging.
.TP 5
\fB\-x\fR \fItime\fR, \fB\-\-compress\-after\fR=\fItime\fR
Archive data files can optionally be compressed after some period
-to conserve disk space.
+to conserve file system space.
This is particularly useful for large numbers of
.BR pmlogger (1)
processes under the control of
@@ -796,7 +863,7 @@ may appear literally in
and will be substituted at execution time to generate the destination
directory name. For example:
.ft CR
-.in +6n
+.in +2n
$PCP_AUTOSAVE_DIR=/gpfs/LOCALHOSTNAME/DATEYYYY/DATEMM-DATEDD
.br
.PP
@@ -957,6 +1024,20 @@ if this file exists, then this is treated as equivalent to using
on the command line and the file will be removed once all rewriting
has been done.
.SH PCP ENVIRONMENT
+.TP 5
+.B $PCP_SPACELIMIT
+If set, specifies a maximum allowed total file system space (in kilobytes, or with optional K/M/G suffix as for the \-d option) for each per-host archive directory under
+.I $PCP_ARCHIVE_DIR
+or
+.I $PCP_REMOTE_ARCHIVE_DIR .
+This value is used by
+.B pmlogger_daily
+when enforcing archive retention limits for file system space, unless the
+.B \-d
+option is provided in which case the command line flag overrides the environment variable and a warning is issued. Enforcement occurs after each run of
+.B pmlogger_daily
+and only applies at that time—not continuously.
+.PP
Environment variables with the prefix \fBPCP_\fP are used to parameterize
the file and directory names used by PCP.
On each installation, the
diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh
index 3ebd17c..be915d6 100755
--- a/src/pmlogger/pmlogger_daily.sh
+++ b/src/pmlogger/pmlogger_daily.sh
@@ -37,6 +37,7 @@ prog=`basename $0`
PROGLOG=$PCP_LOG_DIR/pmlogger/$prog.log
MYPROGLOG=$PROGLOG.$$
USE_SYSLOG=true
+localhost=`hostname || echo localhost`
# optional begin logging to $PCP_LOG_DIR/NOTICES
#
@@ -353,6 +354,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
@@ -404,6 +406,8 @@ DO_DAILY_REPORT=true
NOPROXY=false
PROXYONLY=false
NOERROR=false
+SPACELIMIT_CMDLINE=""
+SPACELIMIT_DEFAULT="unlimited"
ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
[ $? != 0 ] && exit 1
@@ -419,6 +423,24 @@ do
;;
-D) DO_DAILY_REPORT=false
;;
+ -d) SPACELIMIT_CMDLINE="$2"
+ shift
+ if [ -n "$PCP_SPACELIMIT" -a "$PCP_SPACELIMIT" != "$SPACELIMIT_CMDLINE" ]
+ then
+ echo "Warning: -d value ($SPACELIMIT_CMDLINE) ignored because \$PCP_SPACELIMIT ($PCP_SPACELIMIT) set in environment"
+ SPACELIMIT_CMDLINE=""
+ continue
+ fi
+ if [ "$SPACELIMIT_CMDLINE" != unlimited ]
+ then
+ if ! _convert_to_kb "$SPACELIMIT_CMDLINE" >/dev/null
+ then
+ echo "Error: -d value ($SPACELIMIT_CMDLINE) not valid"
+ $NOERROR || status=1
+ exit
+ fi
+ fi
+ ;;
-E) EXPUNGE="-E"
;;
-f) FORCE=true
@@ -781,10 +803,49 @@ fi
if [ ! -f "$CONTROL" ]
then
echo "$prog: Error: cannot find control file ($CONTROL)"
+ echo "... I am here ... `pwd` ... and these files are here ..."
+ ls -l
$NOERROR || status=1
exit
fi
+# 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: $@"
@@ -1062,15 +1123,93 @@ BEGIN { seenslash = 0; inshell = 0; out = "" }
END { print out }'
}
+
+
+# Check disk space allocation for one pmlogger instance, and if more
+# than $SPACELIMIT Kbytes, purge files to try and get the space
+# allocated to be not more than $SPACELIMIT Kbytes.
+#
+# Oldest files are purged first and the most recent archives for today
+# (including those required by active pmlogger (1) processes are
+# never deleted.
+#
+# On entry, $find_dirs is a list of one or more directories holding
+# archives for a pmlogger instance, set in _callback_log_control(),
+# and $host is the host name field from the control line, set in
+# _parse_log_control().
+#
+_do_purge()
+{
+ SPACELIMIT="$PCP_SPACELIMIT"
+ [ -z "$SPACELIMIT" ] && SPACELIMIT="$SPACELIMIT_CMDLINE"
+ [ -z "$SPACELIMIT" ] && SPACELIMIT="$SPACELIMIT_DEFAULT"
+ [ "$SPACELIMIT" = unlimited ] && return
+ if ! _convert_to_kb "$SPACELIMIT" >$tmp/tmp
+ then
+ _warning "skipping purging because of invalid space limit"
+ return
+ fi
+ SPACELIMIT=`cat $tmp/tmp`
+ $VERY_VERBOSE && echo >&2 "SPACELIMIT=$SPACELIMIT"
+ __total_size=`_calculate_total_size $find_dirs`
+ if [ "$__total_size" -le "$SPACELIMIT" ]
+ then
+ $VERBOSE && echo "Info: No purging required for $host, archives ($__total_size Kbytes) <= size limit ($SPACELIMIT Kbytes)"
+ return
+ fi
+ $VERBOSE && echo "Info: archives for $host ($__total_size Kbytes) >= size limit ($SPACELIMIT Kbytes)"
+
+ # algorithm to find archive basenames borrowed from _do_merge()
+ # output is in this format ...
+ # <directory>|<archive>
+ #
+ TODAY=`date +%Y%m%d`
+ find $find_dirs -maxdepth 1 -type f \
+ | sed -n \
+ -e '/\(.*\)\/\([12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.meta.*\)/s//\1|\2/p' \
+ -e '/\(.*\)\/\([12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \
+ -e '/\(.*\)\/\([12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]-[0-9][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \
+ -e '/\(.*\)\/\([0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.meta.*\)/s//\1|\2/p' \
+ -e '/\(.*\)\/\([0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \
+ -e '/\(.*\)\/\([0-9][0-9][0-1][0-9][0-3][0-9]\)\(\.[0-2][0-9].[0-5][0-9]-[0-9][0-9]\)\(\.meta.*\)/s//\1|\2\3/p' \
+ | sort -t'|' -n -k2,2 \
+ | $PCP_AWK_PROG -F'|' '
+$2 == "'$TODAY'" { next }
+$2 ~ /^'$TODAY'/ { next }
+ { print }' >$tmp/purge_candidates
+ if [ ! -s $tmp/purge_candidates ]
+ then
+ $VERBOSE && echo "Info: No candidates to purge."
+ return
+ fi
+
+ sed -e 's/|/ /' <$tmp/purge_candidates \
+ | while read __dir __file
+ do
+ [ "$__dir" != "." ] && __file="$__dir/$__file"
+ __arch_size=`_calculate_archive_size "$__file"`
+ if $SHOWME
+ then
+ echo "+ rm `echo $__file.**`"
+ __total_size=`expr $__total_size - $__arch_size`
+ else
+ if rm "$__file".*
+ then
+ $VERBOSE && echo "Info: purge $__file, reclaims $__arch_size Kbytes"
+ __total_size=`expr $__total_size - $__arch_size`
+ else
+ $VERBOSE && echo "Warning: rm $__file.* failed"
+ fi
+ fi
+ [ "$__total_size" -le "$SPACELIMIT" ] && break
+ done
+}
+
# come here from _parse_log_control() once per valid line in a control
# file ... see utilproc.sh for interface definitions
#
_callback_log_control()
{
- # nothing to do for pmlogger pushing to a remote pmproxy
- #
- $logpush && return
-
if $VERBOSE
then
echo
@@ -1130,6 +1269,10 @@ _callback_log_control()
find_dirs=`echo $orig_dir | _unbackquote`
$VERBOSE && echo "Embedded \`...\`: find_dirs=$find_dirs"
fi
+ # and rewrite LOCALHOSTNAME if it is included
+ #
+ find_dirs=`echo "$find_dirs" | sed -e "s;LOCALHOSTNAME;$localhost;g"`
+ $VERY_VERBOSE && echo "Info: find_dirs=$find_dirs"
# For archive rewriting (to make metadata consistent across
# archives) find the rules as follows:
@@ -1424,6 +1567,14 @@ _callback_log_control()
$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
}
# Paranoid archive saving
@@ -1779,7 +1930,10 @@ _do_compress()
do
# pmlc may race with pmlogger starting up here - a timeout is required
# to avoid pmlc blocking forever and hanging pmlogger_daily. RHBZ#1892326
- [ -z "$PMLOGGER_REQUEST_TIMEOUT" ] && export PMLOGGER_REQUEST_TIMEOUT=2
+ if [ -z "$PMLOGGER_REQUEST_TIMEOUT" ]
+ then
+ PMLOGGER_REQUEST_TIMEOUT=2; export PMLOGGER_REQUEST_TIMEOUT
+ fi
if pmlc "$pid" </dev/null 2>&1 | tee $tmp/out \
| grep "^Connected to .*pmlogger" >/dev/null
then
@@ -2017,6 +2171,7 @@ else
#
if cd "$PCP_REMOTE_ARCHIVE_DIR"
then
+ $VERY_VERBOSE && echo "Info: do_compress: cd to $PCP_REMOTE_ARCHIVE_DIR OK"
# one-trip guard if there is something to be done
#
rm -f $tmp/proxy_sighup
@@ -2077,7 +2232,16 @@ else
_parse_log_control $tmp/control
fi
done
- cd $here
+ if cd $here
+ then
+ $VERY_VERBOSE && echo "Info: do_compress: cd back to $here OK"
+ else
+ echo >&2 "Error: do_compress: failed to cd back to $here"
+ $NOERROR || status=1
+ fi
+ else
+ echo >&2 "Error: do_compress: faild to cd to $PCP_REMOTE_ARCHIVE_DIR"
+ $NOERROR || status=1
fi
fi
diff --git a/src/pmlogger/utilproc.sh b/src/pmlogger/utilproc.sh
index ae9c72f..9e5582c 100644
--- a/src/pmlogger/utilproc.sh
+++ b/src/pmlogger/utilproc.sh
@@ -256,6 +256,7 @@ BEGIN { i = 0 }
[ $? -eq 0 ] && _PWDCMD="$_PWDCMD -P"
fi
_here=`$_PWDCMD`
+ $VERY_VERBOSE && echo >&2 "_parse_log_control: initial pwd=$_here"
if echo "$1" | grep -q -e '\.rpmsave$' -e '\.rpmnew$' -e '\.rpmorig$' -e '\.dpkg-dist$' -e '\.dpkg-old$' -e '\.dpkg-new$'
then
@@ -270,7 +271,10 @@ BEGIN { i = 0 }
| while read host primary socks dir args
do
# start in one place for each iteration (beware of relative paths)
- cd "$_here"
+ if ! cd "$_here"
+ then
+ $VERY_VERBOSE && echo >&2 "_parse_log_control: failed to cd back to $_here"
+ fi
line=`expr $line + 1`
if $VERY_VERBOSE
@@ -350,6 +354,51 @@ s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
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_COMPRESS;'*)
_old_value="$PCP_COMPRESS"
$SHOWME && echo "+ $_cmd"
@@ -537,10 +586,8 @@ s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
# check $dir, cd there, acquire lock
#
$SHOWME && echo "+ cd $dir"
- if cd "$dir"
+ if ! cd "$dir"
then
- :
- else
if $SHOWME
then
echo "+ ... cannot show any more for this control line"
@@ -659,6 +706,10 @@ s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
fi
done
+ if ! cd "$_here"
+ then
+ $VERY_VERBOSE && echo >&2 "_parse_log_control: failed to cd back to $_here at return"
+ fi
}
# Called from _callback_log_control() [in pmlogger_check and pmlogger_janitor]
@@ -859,6 +910,89 @@ END { exit sts }'
fi
}
+# Converts a size string like 10G, 100M, 100K to integer KBytes for limit
+# checks.
+# Usage: _convert_to_kb size_string
+# Outputs: integer KBytes on stdout, returns 0 if OK, 1 if error
+_convert_to_kb()
+{
+ __input="$1"
+ __num=
+ __unit=
+ __kb=
+ if [ -z "$__input" ]
+ then
+ echo "Error: _convert_to_kb(): missing argument" >&2
+ return 1
+ fi
+ __num=`echo "$__input" | sed -E 's/^([0-9]+)\s*([a-zA-Z]*)$/\1/'`
+ case "$__num"
+ in
+ "" )
+ echo "Error: _convert_to_kb(): argument '$__input' does not start with a number" >&2
+ return 1
+ ;;
+ *[!0-9]* )
+ echo "Error: _convert_to_kb(): argument '$__input' has a non-numeric value" >&2
+ return 1
+ ;;
+ 0 )
+ echo "Error: _convert_to_kb(): argument '$__input' resolves to zero (not allowed)" >&2
+ return 1
+ ;;
+ esac
+ __unit=`echo "$__input" | sed -E 's/^([0-9]+)\s*([a-zA-Z]*)$/\2/'`
+ case "$__unit"
+ in
+ G|g|M|m|K|k)
+ ;;
+ '')
+ echo "Error: _convert_to_kb(): missing unit after '$__num'" >&2
+ return 1
+ ;;
+ *)
+ echo "Error: _convert_to_kb(): invalid unit '$__unit'" >&2
+ return 1
+ ;;
+ esac
+
+ MAX_INT32=2147483647
+
+ case "$__unit"
+ in
+ G|g)
+ # Check before multiplying
+ __max=`expr $MAX_INT32 / \( 1024 \* 1024 \)`
+ if [ $__num -gt $__max ]
+ then
+ echo "Error: overflow, $__num Gbytes too large for Kbytes in a 32-bit signed int" >&2
+ return 1
+ fi
+ __kb=`expr $__num \* 1024 \* 1024`
+ ;;
+ M|m)
+ # Check before multiplying
+ __max=`expr $MAX_INT32 / 1024`
+ if [ $__num -gt $__max ]
+ then
+ echo "Error: overflow, $__num Mbytes too large for Kbytes in a 32-bit signed int" >&2
+ return 1
+ fi
+ __kb=`expr $__num \* 1024`
+ ;;
+ K|k)
+ if [ $__num -gt $MAX_INT32 ]
+ then
+ echo "Error: overflow, $__num Kbytes too large for 32-bit signed int" >&2
+ return 1
+ fi
+ __kb=$__num
+ ;;
+ esac
+ echo "$__kb"
+ return 0
+}
+
# current time to the highest precision available from date(1) and
# strftime(3)
#
--
2.43.7

View File

@ -1,332 +0,0 @@
From a6bb22bf407522ef95557de0cebde717de347bde Mon Sep 17 00:00:00 2001
From: Sourav Sharma <sourav.ss.sharma@oracle.com>
Date: Thu, 26 Mar 2026 16:31:22 +0000
Subject: [PATCH OL9 1016/1016] pcp-system-tools: restore backward
compatibility with older pmapi versions
Commit 0a37ed0 introduced support for pmapi version 4, but upgrading to the latest pmapi is not currently desired.
This change updates pcp-iostat, mpstat, pidstat, and ps to fall back to tv_usec when tv_nsec is unavailable
during timestamp delta calculation, ensuring compatibility with older pmapi versions and collectors that only expose tv_usec
- optimized code in pcp-ps for dynamic sorting
- fixed sorting colum indexes in -u case
[Orabug: 38719615]
Signed-off-by: Sagar Sagar <sagar.sagar@oracle.com>
---
src/pcp/iostat/pcp-iostat.py | 15 +++-
src/pcp/mpstat/pcp-mpstat.py | 13 +++-
src/pcp/pidstat/pcp-pidstat.py | 13 +++-
src/pcp/ps/pcp-ps.py | 125 +++++++++++++------------------
src/pcp/tapestat/pcp-tapestat.py | 16 +++-
5 files changed, 97 insertions(+), 85 deletions(-)
diff --git a/src/pcp/iostat/pcp-iostat.py b/src/pcp/iostat/pcp-iostat.py
index fd049a6..79d8c9b 100755
--- a/src/pcp/iostat/pcp-iostat.py
+++ b/src/pcp/iostat/pcp-iostat.py
@@ -56,9 +56,18 @@ class IostatReport(pmcc.MetricGroupPrinter):
Hcount = 0
def timeStampDelta(self, group):
s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec
- n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec
- # n may be negative here, calculation is still correct.
- 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(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 2b0bd59..4e0cdf6 100755
--- a/src/pcp/mpstat/pcp-mpstat.py
+++ b/src/pcp/mpstat/pcp-mpstat.py
@@ -499,8 +499,17 @@ class MpstatReport(pmcc.MetricGroupPrinter):
def timeStampDelta(self, group):
s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec
- n = group.timestamp.tv_nsec - 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(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 32bf925..b7613e1 100755
--- a/src/pcp/pidstat/pcp-pidstat.py
+++ b/src/pcp/pidstat/pcp-pidstat.py
@@ -925,8 +925,17 @@ class PidstatReport(pmcc.MetricGroupPrinter):
def timeStampDelta(self, group):
s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec
- n = group.timestamp.tv_nsec - 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(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 e2992ea..e5abf34 100755
--- a/src/pcp/ps/pcp-ps.py
+++ b/src/pcp/ps/pcp-ps.py
@@ -252,7 +252,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)
@@ -366,6 +366,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
@@ -378,19 +392,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(
@@ -400,48 +410,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:
@@ -457,23 +427,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)
@@ -536,25 +497,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" % (
@@ -590,8 +551,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)
@@ -868,6 +838,11 @@ 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 dd54c70..ac76c68 100755
--- a/src/pcp/tapestat/pcp-tapestat.py
+++ b/src/pcp/tapestat/pcp-tapestat.py
@@ -68,9 +68,19 @@ class TapestatReport(pmcc.MetricGroupPrinter):
Hcount = 0
def timeStampDelta(self, group):
s = group.timestamp.tv_sec - group.prevTimestamp.tv_sec
- n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec
- # n may be negative here, calculation is still correct.
- 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(group.timestamp, 'tv_nsec') and hasattr(group.prevTimestamp, 'tv_nsec'):
+ n = group.timestamp.tv_nsec - group.prevTimestamp.tv_nsec
+ # n may be negative here, calculation is still correct.
+ return s + n / 1000000000.0
+ elif hasattr(group.timestamp, 'tv_usec') and hasattr(group.prevTimestamp, 'tv_usec'):
+ u = group.timestamp.tv_usec - group.prevTimestamp.tv_usec
+ return s + u / 1000000.0
+ # it should not reach here
+ return s
+
def instlist(self, group, name):
return dict(map(lambda x: (x[1], x[2]), group[name].netValues)).keys()
--
2.43.7

View File

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

View File

@ -1,6 +1,6 @@
Name: pcp
Version: 7.0.3
Release: 5.0.1%{?dist}
Release: 5%{?dist}
Summary: System-level performance monitoring and performance management
License: GPL-2.0-or-later AND LGPL-2.1-or-later AND CC-BY-3.0
URL: https://pcp.io
@ -29,14 +29,6 @@ Patch18: pcp-7.0.3-pmproxy-rest-certreqd.patch
Patch19: pcp-7.0.3-pmproxy-logger-auth.patch
Patch20: pcp-7.0.3-pmproxy-logger-meta-network.patch
Patch21: pcp-7.0.3-scanmeta-LogLoadInDom-caller.patch
Patch1011: 1011-orabug38724847-fix-nfsclient-per-op-parsing.patch
Patch1012: 1012-orabug38817053-Introduce-PCP-implementation-of-nfsiostat.patch
Patch1013: 1013-pmlogger_janitor-fix-not-to-terminate-unauthorized-p.patch
Patch1014: 1014-pcp-ps-implement-sort-option-to-allow-sorting-by-cpu.patch
Patch1015: 1015-pmlogger_daily-d-disk-option-for-archive-space-limit.patch
Patch1016: 1016-pcp-system-tools-restore-backward-compatibility-with.patch
Patch1017: 1017-orabug39068870-adds-interval-option-in-nfsiostat.patch
Patch1018: 1018-orabug39096683-introduces-numa-maps-metrics-and-adds-numastat-process-option.patch
%if 0%{?fedora} >= 40 || 0%{?rhel} >= 10
ExcludeArch: %{ix86}
@ -3091,26 +3083,6 @@ done
%endif
%endif
%post pmda-rocestat
PCP_PMDAS_DIR=%{_pmdasdir}
PCP_SYSCONFIG_DIR=%{_sysconfdir}/sysconfig
PCP_PMCDCONF_PATH=%{_confdir}/pmcd/pmcd.conf
# Auto-install rocestat PMDA if not already in pmcd.conf
if ! grep -q "rocestat/pmdarocestat" "$PCP_PMCDCONF_PATH"; then
if [ ! -d /sys/class/infiniband ]; then
if ! lsmod | grep -q '^ib_core'; then
echo "Skipping install for PMDA Rocestat (IB kernel modules are not loaded)" >&2
else
echo "Skipping install for PMDA Rocestat (No IB devices detected)" >&2
fi
else
cd "$PCP_PMDAS_DIR/rocestat" && \
chmod +x Install && \
./Install < /dev/null
fi
fi
%post
PCP_PMNS_DIR=%{_pmnsdir}
PCP_LOG_DIR=%{_logsdir}
@ -3475,20 +3447,6 @@ fi
%files zeroconf -f pcp-zeroconf-files.rpm
%changelog
* Mon Aug 17 2026 EL Errata <el-errata_ww@oracle.com> - 7.0.3-5.0.1
- Fix unterminated backtick in pmlogger_janitor fix not to terminate unauthorized process patch
[Orabug: 39270658]
- Adds interval option support in PCP nfsiostat tool [Orabug: 39068870]
- Introduces proc.numa_maps metrics in linux_proc PMDA [Orabug: 39096683]
- adds numastat process option in the tool
- Added support for size based cleanup for pcp archives [Orabug: 38757778]
- Implement sorting option in pcp ps based on %cpu, %mem [Orabug: 38719615]
- Fixed pmlogger incorrectly attempts to terminate
- unauthorized process [Orabug: 38598244]
- Merges new PCP nfsiostat parser in OL [Orabug: 38817053]
- pmdanfsclient: fix regex to correctly parse NFS op stats [Orabug: 38724847]
- pmda/rocestat: pmda/rocestat: skip installation when IB is absent [Orabug: 38595797]
* Fri Aug 14 2026 Jan Kuřík <jkurik@redhat.com> - 7.0.3-5
- Fix CVE-2026-16530: __pmLogLoadInDom OOB pointer dereference (RHEL-213746)
- Fix CVE-2026-16531: pmproxy logger servlet path traversal (RHEL-213756)