diff --git a/src/powerapi/utils/__init__.py b/src/powerapi/utils/__init__.py index 8c5f2dd1..a616c043 100644 --- a/src/powerapi/utils/__init__.py +++ b/src/powerapi/utils/__init__.py @@ -27,7 +27,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from powerapi.utils.utils import timestamp_to_datetime, datetime_to_timestamp, dict_merge -from powerapi.utils.tree import Tree -from powerapi.utils.stat_buffer import StatBuffer from .json_stream import JsonStream +from .tree import Tree +from .utils import timestamp_to_datetime diff --git a/src/powerapi/utils/cli.py b/src/powerapi/utils/cli.py index 3bb8fa7c..d82b4783 100644 --- a/src/powerapi/utils/cli.py +++ b/src/powerapi/utils/cli.py @@ -73,31 +73,3 @@ def merge_dictionaries(source: dict, destination: dict) -> dict: destination[current_source_key]) return destination - - -def generate_group_configuration_from_environment_variables_dict(environ_vars: dict, prefix: str, - var_names: list) -> dict: - """ - Generate a group configuration by using a dictionary of environment variables. - :param environ_vars: Dictionary for generating the configuration - :param prefix: the prefix of the environment variables in environ_vars (all variable starts vy prefix) - :param var_names: the variables names related to the group configuration (each variable has as suffix a name - in this list) - """ - conf = {} - - for environ_var_name, var_value in environ_vars: - # We remove the prefix : is the result - suffix_var_name = environ_var_name[len(prefix) - len(environ_var_name):].lower() - - for var_name in var_names: - # the var_name has to be at the end of the suffix - var_name_lower_case = var_name.lower() - if suffix_var_name.endswith(var_name_lower_case): - # The group's name is at the begining of the - group_var_name = suffix_var_name[0:suffix_var_name.find(var_name_lower_case) - 1] - if group_var_name not in conf: - conf[group_var_name] = {} - conf[group_var_name][var_name_lower_case] = var_value - break - return conf diff --git a/src/powerapi/utils/stat_buffer.py b/src/powerapi/utils/stat_buffer.py deleted file mode 100644 index ed0e5709..00000000 --- a/src/powerapi/utils/stat_buffer.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2021, INRIA -# Copyright (c) 2021, University of Lille -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from statistics import mean, pstdev -from typing import Dict, List, Tuple, Any - - -def _compute_stats(samples: List[Dict[str, Any]]) -> Tuple[float, float, float, float]: - """ - Compute the mean, std, min, max values from a list samples. - """ - values = [sample['value'] for sample in samples] - return mean(values), pstdev(values), min(values), max(values) - - -class StatBuffer: - """ - Buffer that store timeseries values and compute statistics on it - """ - - def __init__(self, aggregation_periode: int): - """ - :param aggregation_periode: number of second for the value must be aggregated before compute statistics on them - """ - - self.aggregation_periode = aggregation_periode - self.buffer = {} - - def append(self, measure: Dict, key: str): - """ - append a measure to the buffer - - measure must have the following format : - - { - 'tags': Dict, - 'time': int, - 'value': float, - } - """ - if key not in self.buffer: - self.buffer[key] = [] - - self.buffer[key].append(measure) - - def is_available(self, key: str) -> bool: - """ - Return true if they are enough value for the given key to compute a statistics (depend on aggregation periode parameter) - :raise KeyError: if no measure were append with this key before - """ - if key not in self.buffer: - raise KeyError - - first_measure = self.buffer[key][0] - last_measure = self.buffer[key][-1] - - return (last_measure['time'] - first_measure['time']) >= self.aggregation_periode - - def _split_values(self, values: List[Dict]): - time_of_first_measure = values[0]['time'] - - def split(value_in_periode, value_out_periode): - if not value_out_periode: - return value_in_periode, value_out_periode - - if value_out_periode[0]['time'] - time_of_first_measure > self.aggregation_periode: - return value_in_periode, value_out_periode - - val = value_out_periode.pop(0) - value_in_periode.append(val) - return split(value_in_periode, value_out_periode) - - return split([], values) - - def get_stats(self, key: str) -> Dict: - """ - return statistics on the values corresponding to the given key - - :return: a dictionary with this format: - :raise KeyError: if no measure were append with this key before - { - 'mean': float, - 'std': float, - 'min': float, - 'max': float', - 'time': int, # timestamp of the last measure - 'tags': Dict, - } - return None if no statistics are available - """ - if not self.is_available(key): - return None - - values, self.buffer[key] = self._split_values(self.buffer[key]) - - meanv, stdv, minv, maxv = _compute_stats(values) - - return { - 'mean': meanv, - 'std': stdv, - 'min': minv, - 'max': maxv, - 'time': values[-1]['time'], - 'tags': values[-1]['tags'] - } diff --git a/src/powerapi/utils/sync.py b/src/powerapi/utils/sync.py deleted file mode 100644 index 512ed3e0..00000000 --- a/src/powerapi/utils/sync.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright (c) 2021, INRIA -# Copyright (c) 2021, University of Lille -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import datetime -import logging -import sys -from powerapi.exception import PowerAPIException - - -class WrongFormatReport(PowerAPIException): - """ - Exception raised when Sync receive a report which is not of a handled type - """ - def __init__(self, report_type): - PowerAPIException.__init__(self) - self.report_type = report_type - - -class WrongTypeParameter(PowerAPIException): - """ - Exception raised when Sync is instantiate with a parameter of the wrong type - """ - def __init__(self, parameter: str): - PowerAPIException.__init__(self) - self.parameter = parameter - - -class Sync(): - """ - Sync class - - Class that receive asynchronous data and synchronize them using their timestamp - """ - def __init__(self, type1, type2, delay): - """ - type1 : Report -> bool. - Function that return true if the report can be identified as the first type of report - - type2 : Report -> bool. - Function that return true if the report can be identified as the second type of report - - delay : float - maximal delay, in second, that is allowed between two report to pair them - """ - - if not isinstance(delay, datetime.timedelta): - raise WrongTypeParameter("delay") - - self.delay = delay - self.type1 = type1 - self.type2 = type2 - self.type1_buff = [] - self.type2_buff = [] - self.pair_ready = [] - - def insert_report(self, report, main_buff, secondary_buff): - """ - Insert report in the buff and delete obsolete one - If a pair is found store it it the dedicated buff - """ - second_report = main_buff[0] - diff = abs(report.timestamp - second_report.timestamp) - - while diff > self.delay: - if report.timestamp > second_report.timestamp: - main_buff.pop(0) - - if report.timestamp < second_report.timestamp: - return - - if len(main_buff) == 0: - secondary_buff.append(report) - return - - second_report = main_buff[0] - diff = abs(report.timestamp - second_report.timestamp) - - if self.type1(report): - self.pair_ready.append((report, second_report)) # report are in order (type1,type2) - else: - self.pair_ready.append((second_report, report)) - - return - - def add_report(self, report): - """ - Receive a new report. - If there is a report that can be paired with it then it do so - Else the report is stored in the buffer - """ - - if report in self.type2_buff or report in self.type2_buff: - logging.error("Duplicate message") - sys.exit(0) - - if self.type1(report): - if len(self.type2_buff) == 0: - self.type1_buff.append(report) - else: - self.insert_report(report, self.type2_buff, self.type1_buff) - elif self.type2(report): - if len(self.type1_buff) == 0: - self.type2_buff.append(report) - else: - self.insert_report(report, self.type1_buff, self.type2_buff) - else: - raise WrongFormatReport(type(report)) - - def request(self): - """ - Request a pair of report - Return None if there is no pair available - """ - if len(self.pair_ready) > 0: - return self.pair_ready.pop(0) - return None diff --git a/src/powerapi/utils/utils.py b/src/powerapi/utils/utils.py index d0bad20b..d25d4491 100644 --- a/src/powerapi/utils/utils.py +++ b/src/powerapi/utils/utils.py @@ -27,7 +27,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import collections.abc as collections import datetime @@ -39,29 +38,3 @@ def timestamp_to_datetime(timestamp): :rtype: datetime.datetime """ return datetime.datetime.fromtimestamp(timestamp / 1000) - - -def datetime_to_timestamp(date): - """ - Cast a datetime object to a simple timestamp - - :param datetime.datetime date: - :rtype: int - """ - return int(datetime.datetime.timestamp(date) * 1000) - - -def dict_merge(dict1, dict2): - """ - Recursive dict merge, act like dict.update() but update - all level and not only top-level. - - :param dict dict1: - :param dict dict2: - - """ - for key, value in dict2.items(): - if (key in dict1 and isinstance(dict1[key], dict) and isinstance(dict2[key], collections.Mapping)): - dict_merge(dict1[key], dict2[key]) - else: - dict1[key] = value diff --git a/tests/unit/utils/test_stat_buffer.py b/tests/unit/utils/test_stat_buffer.py deleted file mode 100644 index b263f2e3..00000000 --- a/tests/unit/utils/test_stat_buffer.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) 2021, INRIA -# Copyright (c) 2021, University of Lille -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import pytest - -from powerapi.utils import StatBuffer - -M1 = { - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 1, - 'value': 1.0 -} - -M2 = { - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 2, - 'value': 2.0 -} - -M3 = { - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 3, - 'value': 3.0 -} - -M4 = { - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 4, - 'value': 4.0 -} - - -def test_asking_if_stat_is_available_on_a_key_that_was_never_append_must_raise_KeyError(): - buffer = StatBuffer(3) - - buffer.append(M2, 'ab') - with pytest.raises(KeyError): - buffer.is_available('qlksjdq') - - -def test_asking_if_stat_is_available_on_a_stat_buffer_with_aggregation_periode_of_3_while_2_measure_where_append_on_2_seconds_return_false(): - buffer = StatBuffer(3) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - assert not buffer.is_available('ab') - - -def test_asking_if_stat_is_available_on_a_stat_buffer_with_aggregation_periode_of_1_while_2_measure_where_append_on_1_seconds_return_true(): - buffer = StatBuffer(1) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - assert buffer.is_available('ab') - - -def test_asking_if_stat_is_available_on_a_stat_buffer_with_aggregation_periode_of_1_while_2_measure_where_append_on_2_seconds_return_true(): - buffer = StatBuffer(1) - - buffer.append(M1, 'ab') - buffer.append(M3, 'ab') - assert buffer.is_available('ab') - - -def test_get_stats_on_a_stat_buffer_with_aggregation_periode_of_3_while_2_measure_where_append_on_2_seconds_return_None(): - buffer = StatBuffer(3) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - assert buffer.get_stats('ab') is None - - -def test_get_stats_on_a_key_that_was_never_append_must_raise_KeyError(): - buffer = StatBuffer(3) - - buffer.append(M2, 'ab') - with pytest.raises(KeyError): - buffer.get_stats('qlksjdq') - - -def test_get_stats_on_a_stat_buffer_with_aggregation_periode_of_1_while_2_measure_where_append_on_2_seconds_return_good_results(): - buffer = StatBuffer(1) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - assert buffer.get_stats('ab') == { - 'mean': 1.5, - 'std': 0.5, - 'min': 1.0, - 'max': 2.0, - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 2 - } - - -def test_get_stats_on_a_stat_buffer_with_aggregation_periode_of_1_while_3_measure_where_append_on_2_seconds_return_stats_on_two_first_results(): - buffer = StatBuffer(1) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - buffer.append(M3, 'ab') - assert buffer.get_stats('ab') == { - 'mean': 1.5, - 'std': 0.5, - 'min': 1.0, - 'max': 2.0, - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 2 - } - - -def test_get_stats_second_times_on_a_stat_buffer_with_aggregation_periode_of_1_while_3_measure_where_append_on_2_seconds_return_None(): - buffer = StatBuffer(1) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - buffer.append(M3, 'ab') - buffer.get_stats('ab') - assert buffer.get_stats('ab') is None - - -def test_get_stat_buffer_with_aggregation_periode_of_1_while_4_measure_append_good_result_for_two_last_measure(): - buffer = StatBuffer(1) - - buffer.append(M1, 'ab') - buffer.append(M2, 'ab') - buffer.append(M3, 'ab') - buffer.append(M4, 'ab') - buffer.get_stats('ab') - assert buffer.get_stats('ab') == { - 'mean': 3.5, - 'std': 0.5, - 'min': 3.0, - 'max': 4.0, - 'tags': {'t1': 'a', 't2': 'b'}, - 'time': 4 - } diff --git a/tests/unit/utils/test_sync.py b/tests/unit/utils/test_sync.py deleted file mode 100644 index 83a4ea81..00000000 --- a/tests/unit/utils/test_sync.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright (c) 2021, INRIA -# Copyright (c) 2021, University of Lille -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from powerapi.utils.sync import Sync -from powerapi.report import Report, ProcfsReport, PowerReport - -import datetime - - -class Report1(Report): - """ - Type 1 of report - """ - - -class Report2(Report): - """ - Type 2 of report - """ - - -def type1(t): - """ Identificator of type1""" - return isinstance(t, Report1) - - -def type2(t): - """ Identificator of type2""" - return isinstance(t, Report2) - - -def test_request_while_no_pair_available_return_None(): - timestamp = datetime.date(1970, 1, 1) - timedet = datetime.timedelta(1) - - sync = Sync(type1, type2, timedet) - - sync.add_report(Report1(timestamp, "", "")) - sync.add_report(Report1(timestamp + timedet, "", "")) - sync.add_report(Report1(timestamp + 2 * timedet, "", "")) - sync.add_report(Report1(timestamp + 3 * timedet, "", "")) - - assert sync.request() is None - - -def test_request_while_report_missing_return_correct_data(): - timestamp = datetime.date(1970, 1, 1) - timedet = datetime.timedelta(1) - delay = datetime.timedelta(1) - - sync = Sync(type1, type2, delay) - - sync.add_report(Report1(timestamp, "", "")) - sync.add_report(Report1(timestamp + timedet, "", "")) - sync.add_report(Report1(timestamp + 2 * timedet, "", "")) - sync.add_report(Report1(timestamp + 3 * timedet, "", "")) - sync.add_report(Report2(timestamp + 3 * timedet, "", "")) - - (report1, report2) = sync.request() - - assert report1.timestamp == (timestamp + 2 * timedet) - assert report2.timestamp == (timestamp + 3 * timedet) - - -def test_request_correct_use_return_correct_data(): - timestamp = datetime.date(1970, 1, 1) - timedet = datetime.timedelta(1) - sync = Sync(type1, type2, timedet) - - sync.add_report(Report1(timestamp, "", "")) - for t in range(100): - sync.add_report(Report1(timestamp + (t + 1) * timedet, "", "")) - sync.add_report(Report2(timestamp + t * timedet, "", "")) - - r = sync.request() - while r is not None: - report1, report2 = r - assert abs(report1.timestamp - report2.timestamp) <= timedet - r = sync.request() - - -def test_send_timeline_of_reports_receive_right_pair(procfs_timeline, power_timeline): - timedet = datetime.timedelta(250) - sync = Sync(lambda x: isinstance(x, PowerReport), - lambda x: isinstance(x, ProcfsReport), - timedet) - - sum = 0 - - r = power_timeline[0] - sync.add_report(PowerReport.from_json(r)) - r = power_timeline[1] - sync.add_report(PowerReport.from_json(r)) - - for i in range(len(power_timeline) - 2): - r = power_timeline[i + 2] - sync.add_report(PowerReport.from_json(r)) - - r = procfs_timeline[i] - sync.add_report(ProcfsReport.from_json(r)) - - r = sync.request() - assert r is not None - report1, report2 = r - assert abs(report1.timestamp - report2.timestamp) <= timedet - sum += 1 - - r = procfs_timeline[-2] - sync.add_report(ProcfsReport.from_json(r)) - - r = procfs_timeline[-1] - sync.add_report(ProcfsReport.from_json(r)) - - assert sum == len(power_timeline) - 2 - - -def test_send_report_in_special_order_receive_right_pair(procfs_timeline, power_timeline): - timedet = datetime.timedelta(250) - sync = Sync(lambda x: isinstance(x, PowerReport), - lambda x: isinstance(x, ProcfsReport), - timedet) - - sum = 0 - - r = power_timeline[0] - sync.add_report(PowerReport.from_json(r)) - r = power_timeline[1] - sync.add_report(PowerReport.from_json(r)) - - for i in range(len(power_timeline) - 2): - r = power_timeline[i + 2] - sync.add_report(PowerReport.from_json(r)) - - r = procfs_timeline[i] - sync.add_report(ProcfsReport.from_json(r)) - - r = sync.request() - assert r is not None - report1, report2 = r - assert abs(report1.timestamp - report2.timestamp) <= timedet - sum += 1 - - r = procfs_timeline[-2] - sync.add_report(ProcfsReport.from_json(r)) - - r = procfs_timeline[-1] - sync.add_report(ProcfsReport.from_json(r)) - - assert sum == len(power_timeline) - 2