On Wed, Jan 27, 2021 at 09:11:05AM +0100, Jan Tluka wrote:
Tue, Jan 26, 2021 at 03:09:17PM CET, olichtne(a)redhat.com wrote:
>From: Ondrej Lichtner <olichtne(a)redhat.com>
>
>Extending the PerfResult object api with the following
>methods/properties:
>
>* start_timestamp
>* end_timestamp
>* time_slice(start, end)
>
>These unify the api for PerfInterval, SequentialPerfResult and
>ParallelPerfResult.
>
>This also means that "timestamp" itself in PerfInterval is renamed to
>"start_timestamp" to avoid duplication.
>
>The "time_slice" method returns an object of the same type
>(PerfInterval, SequentialPerfResult, ParallelPerfResult or other class
>derived from PerfResult) that is "sliced" to only contain measurement
>data from the selected timestamp period restricted by the "start" and
>"end" parameters.
>
>The implementation depends on the specific class:
>* PerfInterval serves as the smalles unit and creates a new PerfInterval
> object that has a shorter *duration* (intersection of current
> start-end and requested start-end), and has the *value*, adjusted by
> the ration of new_duration/old_duration. This ensures that the
> "average" calculated from this PerfInterval is consistent after the
> time_slice
>* PerfList derivatives (SequentialPerfResult and ParallelPerfResult),
> recursively call "time_slice" in all the individual items of the
> sequence.
>
>In case an empty slice would be created (mismatch of start-end
>intervals), an EmptySlice exception is thrown.
>
>This commit also adjusts the PerfRecipe "align_data" related code to use
>this new refactored code.
>
>Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
>---
> .../Perf/Measurements/BaseFlowMeasurement.py | 48 ++++++------
> .../Perf/Measurements/StatCPUMeasurement.py | 14 +---
> lnst/RecipeCommon/Perf/Recipe.py | 2 +-
> lnst/RecipeCommon/Perf/Results.py | 74 ++++++++++++++++++-
> 4 files changed, 96 insertions(+), 42 deletions(-)
>
>diff --git a/lnst/RecipeCommon/Perf/Measurements/BaseFlowMeasurement.py
b/lnst/RecipeCommon/Perf/Measurements/BaseFlowMeasurement.py
>index dcb0d2a..a8e9328 100644
>--- a/lnst/RecipeCommon/Perf/Measurements/BaseFlowMeasurement.py
>+++ b/lnst/RecipeCommon/Perf/Measurements/BaseFlowMeasurement.py
>@@ -165,40 +165,34 @@ def receiver_cpu_stats(self, value):
>
> @property
> def start_timestamp(self):
>- return max([seq_result[0].timestamp for seq_result in
self.generator_results])
>+ return min(
>+ [
>+ self.generator_results.start_timestamp,
>+ self.generator_cpu_stats.start_timestamp,
>+ self.receiver_results.start_timestamp,
>+ self.receiver_cpu_stats.start_timestamp,
>+ ]
>+ )
>
> @property
> def end_timestamp(self):
>- return min([seq_result[-1].timestamp for seq_result in
self.generator_results])
>+ return max(
>+ [
>+ self.generator_results.end_timestamp,
>+ self.generator_cpu_stats.end_timestamp,
>+ self.receiver_results.end_timestamp,
>+ self.receiver_cpu_stats.end_timestamp,
>+ ]
>+ )
>
>- def align_data(self, start, end):
>+ def time_slice(self, start, end):
> result_copy = FlowMeasurementResults(self.measurement, self.flow)
>
>- # NOTE: iperf reports the cpu utilization for the whole test
>- # period, not each second, so the CPU samples cannot be aligned
>- result_copy.generator_cpu_stats = self.generator_cpu_stats
>- result_copy.receiver_cpu_stats = self.receiver_cpu_stats
>-
>- result_copy.generator_results = ParallelPerfResult()
>- result_copy.receiver_results = ParallelPerfResult()
>-
>- for stream in self.generator_results:
>- aligned_intervals = [
>- interval
>- for interval in stream
>- if interval.timestamp >= start and interval.timestamp <=
end
>- ]
>-
>-
result_copy.generator_results.append(SequentialPerfResult(aligned_intervals))
>-
>- for stream in self.receiver_results:
>- aligned_intervals = [
>- interval
>- for interval in stream
>- if interval.timestamp >= start and interval.timestamp <=
end
>- ]
>+ result_copy.generator_cpu_stats = self.generator_cpu_stats.time_slice(start,
end)
>+ result_copy.receiver_cpu_stats = self.receiver_cpu_stats.time_slice(start,
end)
>
>-
result_copy.receiver_results.append(SequentialPerfResult(aligned_intervals))
>+ result_copy.generator_results = self.generator_results.time_slice(start,
end)
>+ result_copy.receiver_results = self.generator_results.time_slice(start,
end)
>
> return result_copy
>
>diff --git a/lnst/RecipeCommon/Perf/Measurements/StatCPUMeasurement.py
b/lnst/RecipeCommon/Perf/Measurements/StatCPUMeasurement.py
>index 49aa262..c5b50fe 100644
>--- a/lnst/RecipeCommon/Perf/Measurements/StatCPUMeasurement.py
>+++ b/lnst/RecipeCommon/Perf/Measurements/StatCPUMeasurement.py
>@@ -28,26 +28,20 @@ def utilization(self):
>
> @property
> def start_timestamp(self):
>- return self._data["user"][0].timestamp
>+ return min([item.start_timestamp for item in self._data.values()])
Although this seems to be a correct way to get the timestamp, the way
how StatCPUMeasurement is implemented does not require this.
The timestamps are the same for all 'user', 'system', 'irq' etc
for one
sample. That's why used the optimization before. I think you can do the
same.
agreed, but if StatCPUMeasurement ever changes this may need to be
updated and we could miss that, I'll do some profiling on this method to
see how much of an optimization it really is and if it's worth it.
-Ondrej
>
> @property
> def end_timestamp(self):
>- return self._data["user"][-1].timestamp
>+ return max([item.end_timestamp for item in self._data.values()])
Same here.
>
>- def align_data(self, start, end):
>+ def time_slice(self, start, end):
> result_copy = StatCPUMeasurementResults(
> self.measurement,
> self.host,
> self.cpu
> )
> for cpu_state, intervals in self._data.items():
>- aligned_interval = [
>- interval
>- for interval in intervals
>- if interval.timestamp >= start and interval.timestamp <=
end
>- ]
>-
>- result_copy._data[cpu_state] = SequentialPerfResult(aligned_interval)
>+ result_copy._data[cpu_state] = intervals.time_slice(start, end)
> return result_copy
>
>
>diff --git a/lnst/RecipeCommon/Perf/Recipe.py b/lnst/RecipeCommon/Perf/Recipe.py
>index 82f9649..07f88fd 100644
>--- a/lnst/RecipeCommon/Perf/Recipe.py
>+++ b/lnst/RecipeCommon/Perf/Recipe.py
>@@ -117,7 +117,7 @@ def time_aligned_results(self) -> "RecipeResults":
> for i, measurement_iteration in enumerate(measurement_results):
> aligned_measurement_results = []
> for result in measurement_iteration:
>- aligned_measurement_result = result.align_data(
>+ aligned_measurement_result = result.time_slice(
> timestamps[i][0], timestamps[i][1]
> )
> aligned_measurement_results.append(
>diff --git a/lnst/RecipeCommon/Perf/Results.py b/lnst/RecipeCommon/Perf/Results.py
>index 61a9ef8..aaca05f 100644
>--- a/lnst/RecipeCommon/Perf/Results.py
>+++ b/lnst/RecipeCommon/Perf/Results.py
>@@ -1,6 +1,9 @@
> from lnst.Common.LnstError import LnstError
> from lnst.Common.Utils import std_deviation
>
>+class EmptySlice(LnstError):
>+ pass
>+
> class PerfStatMixin(object):
> @property
> def average(self):
>@@ -26,6 +29,17 @@ def duration(self):
> def unit(self):
> raise NotImplementedError()
>
>+ @property
>+ def start_timestamp(self):
>+ raise NotImplementedError()
>+
>+ @property
>+ def end_timestamp(self):
>+ raise NotImplementedError()
>+
>+ def time_slice(self, start, end):
>+ raise NotImplementedError()
>+
> class PerfInterval(PerfResult):
> def __init__(self, value, duration, unit, timestamp):
> self._value = value
>@@ -46,9 +60,13 @@ def unit(self):
> return self._unit
>
> @property
>- def timestamp(self):
>+ def start_timestamp(self):
> return self._timestamp
>
>+ @property
>+ def end_timestamp(self):
>+ return self._timestamp + self.duration
>+
> @property
> def std_deviation(self):
> return 0
>@@ -57,6 +75,20 @@ def __str__(self):
> return "{:.2f} {} in {:.2f} seconds".format(
> float(self.value), self.unit, float(self.duration))
>
>+ def time_slice(self, start, end):
>+ if end < self.start_timestamp or start > self.end_timestamp:
>+ raise EmptySlice(
>+ "current start, end {} {}; request start, end {},
{}".format(
>+ self.start_timestamp, self.end_timestamp, start, end,
>+ )
>+ )
>+
>+ new_start = max(self.start_timestamp, start)
>+ new_end = min(self.end_timestamp, end)
>+ new_duration = new_end - new_start
>+ new_value = self.value * (new_duration/self.duration)
>+ return PerfInterval(new_value, new_duration, self.unit, new_start)
>+
> class PerfList(list):
> def __init__(self, iterable=[]):
> for i, item in enumerate(iterable):
>@@ -123,7 +155,23 @@ def __setitem__(self, i, item):
>
> super(PerfList, self).__setitem__(i, item)
>
>-class SequentialPerfResult(PerfResult, PerfList):
>+ def time_slice(self, start, end):
>+ result = self.__class__()
>+ for item in self:
>+ try:
>+ item_slice = item.time_slice(start, end)
>+ result.append(item_slice)
>+ except EmptySlice:
>+ continue
>+ if len(result) == 0:
>+ raise EmptySlice(
>+ "current start, end {} {}; request start, end {},
{}".format(
>+ self.start_timestamp, self.end_timestamp, start, end,
>+ )
>+ )
>+ return result
>+
>+class SequentialPerfResult(PerfList, PerfResult):
> @property
> def value(self):
> return sum([i.value for i in self])
>@@ -139,14 +187,24 @@ def unit(self):
> else:
> return None
>
>-class ParallelPerfResult(PerfResult, PerfList):
>+ @property
>+ def start_timestamp(self):
>+ return self[0].start_timestamp
>+
>+ @property
>+ def end_timestamp(self):
>+ return self[-1].end_timestamp
>+
>+class ParallelPerfResult(PerfList, PerfResult):
> @property
> def value(self):
> return sum([i.value for i in self])
>
> @property
> def duration(self):
>- return max([i.duration for i in self])
>+ min_start = min([item.start_timestamp for item in self])
>+ max_end = max([item.end_timestamp for item in self])
>+ return max_end - min_start
>
> @property
> def unit(self):
>@@ -155,6 +213,14 @@ def unit(self):
> else:
> return None
>
>+ @property
>+ def start_timestamp(self):
>+ return min([i.start_timestamp for i in self])
>+
>+ @property
>+ def end_timestamp(self):
>+ return max([i.end_timestamp for i in self])
>+
> def result_averages_difference(a, b):
> if a is None or b is None:
> return None
>--
>2.30.0
>_______________________________________________
>LNST-developers mailing list -- lnst-developers(a)lists.fedorahosted.org
>To unsubscribe send an email to lnst-developers-leave(a)lists.fedorahosted.org
>Fedora Code of Conduct:
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
>List Guidelines:
https://fedoraproject.org/wiki/Mailing_list_guidelines
>List Archives:
https://lists.fedorahosted.org/archives/list/lnst-developers@lists.fedora...