Skip to content

Commit

Permalink
python: fix pmcc time window handling
Browse files Browse the repository at this point in the history
For reference, see BZ 1671818 - pcp ignores --finish=time option.
PCP python apps that use pmcc and support -u (non-interpolation mode)
ignore the --finish time window option because the calculation relies
on sampling time delta being set (but it is not set in -u mode).

With this fix to pmcc.py, the --samples and --finish options
are both considered when deciding when to terminate the run()
loop. If both --samples and --finish are specified, the run loop
will terminate when either the current timestamp or sample count
reaches the end of the time window. If neither are set, run()
continues to the end of the archive, as normal.

Using sample count OR finish time to terminate the run loop
effectively avoids the problem in the BZ because --finish
no longer relies on the time delta to figure out when to stop.

qa/1588 exercises the fix.

Also note, qa/829 might need a tweak for testing pcp-dmcache(1),
which also uses pmcc.
  • Loading branch information
goodwinos committed May 16, 2019
1 parent 2484809 commit 7aa23e6
Showing 1 changed file with 38 additions and 19 deletions.
57 changes: 38 additions & 19 deletions src/python/pcp/pmcc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
""" Convenience Classes building on the base PMAPI extension module """
#
# Copyright (C) 2013-2016 Red Hat
# Copyright (C) 2013-2016,2019 Red Hat
# Copyright (C) 2009-2012 Michael T. Werner
#
# This file is part of the "pcp" module, the python interfaces for the
Expand Down Expand Up @@ -443,7 +443,10 @@ def mgAdd(self, nameL):
self._pmidArray[x] = c_uint(self[key].pmid)

def mgFetch(self):
""" Fetch the list of Metric values. Save the old value. """
"""
Fetch the list of Metric values. Save the old value.
Return the result timestamp.
"""
try:
self.result = self._ctx.pmFetch(self._pmidArray)
# update the result entries in each metric
Expand All @@ -460,6 +463,8 @@ def mgFetch(self):
print >> stderr, fail
raise SystemExit(1)

return result.timestamp # timeval

def mgDelta(self):
"""
Sample delta - used for rate conversion calculations, which
Expand Down Expand Up @@ -564,8 +569,15 @@ def builder(build, options, argv):
##
# methods

def _tv2float(self, tv):
""" convert timeval to epoch seconds as a float """
if tv is None:
return 0.0
return float(tv.tv_sec) + float(tv.tv_usec) / 1e6

def _computeSamples(self):
""" Calculate the number of samples we are to take.
""" Return the number of samples we are to take and the
finish time, or 0,0 if --finish is not specified.
This is based on command line options --samples but also
must consider --start, --finish and --interval. If none
of these were presented, a zero return means "infinite".
Expand All @@ -574,26 +586,25 @@ def _computeSamples(self):
when counters metrics are present.
"""
if self._options == None:
return 0 # loop until interrupted or PM_ERR_EOL
return 0, None # loop until interrupted or PM_ERR_EOL
extra = 1 # extra sample needed if rate converting
for group in self.keys():
if self[group].nonCounters:
extra = 0
samples = self._options.pmGetOptionSamples()
if samples != None:
return samples + extra
return samples + extra, None
if self._options.pmGetOptionFinishOptarg() == None:
return 0 # loop until interrupted or PM_ERR_EOL
return 0, None # loop until interrupted or PM_ERR_EOL
origin = self._options.pmGetOptionOrigin()
finish = self._options.pmGetOptionFinish()
delta = self._options.pmGetOptionInterval()
if delta == None:
delta = self._default_delta
period = (delta.tv_sec * 1.0e6 + delta.tv_usec) / 1e6
window = float(finish.tv_sec - origin.tv_sec)
window += float((finish.tv_usec - origin.tv_usec) / 1e6)
window /= period
return int(window + 0.5) + extra # roundup to positive number
period = self._tv2float(delta)
window = (self._tv2float(finish) - self._tv2float(origin)) / period
# return samples rounded to positive number and the finish time as a float
return int(window + 0.5) + extra, finish

def _computePauseTime(self):
""" Figure out how long to sleep between samples.
Expand Down Expand Up @@ -644,8 +655,14 @@ def checkMissingMetrics(self, nameL):

def fetch(self):
""" Perform fetch operation on all of the groups. """
fetchtime = None
rmax = 0.0
for group in self.keys():
self[group].mgFetch()
stamp = self[group].mgFetch()
if fetchtime is None or self._tv2float(stamp) > rmax:
fetchtime = stamp
rmax = self._tv2float(stamp)
return fetchtime

def run(self):
""" Using options specification, loop fetching and reporting,
Expand All @@ -655,18 +672,20 @@ def run(self):
in archive mode, but is usually the same as the sampling
interval in live mode.
"""
samples = self._computeSamples()
samples, finish = self._computeSamples()
# print("DEBUG samples=" + str(samples) + " finish=" + str(self._tv2float(finish)))
timer = self._computePauseTime()
try:
self.fetch()
curtime = self.fetch()
while True:
self._counter += 1
if samples == 0 or self._counter <= samples:
self._printer.report(self)
if self._counter == samples:
if samples > 0 and self._counter >= samples:
break
if finish is not None and self._tv2float(curtime) >= self._tv2float(finish):
break
self._printer.report(self)
timer.sleep()
self.fetch()
curtime = self.fetch()
self._counter += 1
except SystemExit as code:
return code
except KeyboardInterrupt:
Expand Down

0 comments on commit 7aa23e6

Please sign in to comment.