Revert OL modifications
This commit is contained in:
parent
5f0a3da4bf
commit
8e60e87eed
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
44
pcp.spec
44
pcp.spec
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user