Skip to content

Commit

Permalink
Merge pull request #161 from gfieni/refactor/formula-report
Browse files Browse the repository at this point in the history
Rework Formula report
  • Loading branch information
gfieni committed Apr 21, 2023
2 parents 7637f48 + b26af5b commit b6841bb
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 11 deletions.
3 changes: 2 additions & 1 deletion powerapi/cli/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from powerapi.database.influxdb2 import InfluxDB2
from powerapi.exception import PowerAPIException
from powerapi.filter import Filter
from powerapi.report import HWPCReport, PowerReport, ControlReport, ProcfsReport, Report
from powerapi.report import HWPCReport, PowerReport, ControlReport, ProcfsReport, Report, FormulaReport
from powerapi.database import MongoDB, CsvDB, InfluxDB, OpenTSDB, SocketDB, PrometheusDB, DirectPrometheusDB, \
VirtioFSDB, FileDB
from powerapi.puller import PullerActor
Expand Down Expand Up @@ -196,6 +196,7 @@ class DBActorGenerator(SimpleGenerator):

def __init__(self, component_group_name: str):
SimpleGenerator.__init__(self, component_group_name)
self.report_classes['FormulaReport'] = FormulaReport
self.report_classes['ControlReport'] = ControlReport
self.report_classes['ProcfsReport'] = ProcfsReport

Expand Down
58 changes: 48 additions & 10 deletions powerapi/report/formula_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@

from __future__ import annotations

import json
from datetime import datetime
from typing import Dict, Any
from typing import Dict, Any, List

from powerapi.report.report import Report
from powerapi.report.report import Report, CSV_HEADER_COMMON

CSV_HEADER_FORMULA_REPORT = CSV_HEADER_COMMON + ['metadata']


class FormulaReport(Report):
Expand All @@ -43,23 +46,58 @@ class FormulaReport(Report):

def __init__(self, timestamp: datetime, sensor: str, target: str, metadata: Dict[str, Any]):
"""
Initialize a Power report using the given parameters.
Initialize a Formula report using the given parameters.
:param timestamp: Report timestamp
:param sensor: Sensor name
:param target: Target name
:param metadata: Metadata values, can be anything that add useful information
:param metadata: Metadata values, can be anything but should be json serializable
"""
Report.__init__(self, timestamp, sensor, target)
self.metadata = metadata

def __repr__(self) -> str:
return 'FormulaReport(%s, %s, %s, %s)' % (self.timestamp, self.sensor, self.target, self.metadata)
return f'FormulaReport({self.timestamp}, {self.sensor}, {self.target}, {self.metadata})'

@staticmethod
def to_csv_lines(report: FormulaReport, **_) -> (List[str], Dict[str, Any]):
"""
Convert a Formula report into a csv line.
:param report: Formula report that will be converted
:return: Tuple containing the csv header and the csv lines
"""

line = {
'timestamp': int(datetime.timestamp(report.timestamp) * 1000),
'sensor': report.sensor,
'target': report.target,
'metadata': json.dumps(report.metadata)
}

return CSV_HEADER_FORMULA_REPORT, {'FormulaReport': [line]}

@staticmethod
def to_mongodb(report: FormulaReport) -> Dict[str, Any]:
"""
Convert a Formula report into a json document that can be stored into mongodb.
:return: a dictionary, that can be stored into a mongodb
"""
return FormulaReport.to_json(report)

@staticmethod
def deserialize(data: Dict) -> FormulaReport:
def to_influxdb(report: FormulaReport, **_) -> Dict[str, Any]:
"""
Generate a report using the given data.
:param data: Dictionary containing the report attributes
:return: The Formula report initialized with the given data
Convert a Formula report into a dict that can be stored into influxdb.
param report: Formula report that will be converted
:return: a dictionary, that can be stored into influxdb
"""
return FormulaReport(data['timestamp'], data['sensor'], data['target'], data['metadata'])
document = {
'measurement': 'formula_report',
'tags': {
'sensor': report.sensor,
'target': report.target,
},
'time': int(datetime.timestamp(report.timestamp) * 1000),
'fields': report.metadata
}

return document
120 changes: 120 additions & 0 deletions tests/unit/report/test_formula_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright (c) 2023, INRIA
# Copyright (c) 2023, 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 datetime import datetime

from powerapi.report import FormulaReport


def test_formula_report_to_csv_lines_with_empty_metadata():
"""
Test if the report without metadata is correctly converted to csv lines.
"""
ts = datetime.fromtimestamp(0)
report = FormulaReport(ts, 'pytest', 'formula', {})
columns, lines = FormulaReport.to_csv_lines(report)

assert columns == ['timestamp', 'sensor', 'target', 'metadata']
assert len(lines['FormulaReport']) == 1
assert lines['FormulaReport'][0]['timestamp'] == int(ts.timestamp() * 1000) # timestamp in milliseconds
assert lines['FormulaReport'][0]['sensor'] == 'pytest'
assert lines['FormulaReport'][0]['target'] == 'formula'
assert lines['FormulaReport'][0]['metadata'] == '{}' # metadata is serialized as a string


def test_formula_report_to_csv_lines_with_metadata():
"""
Test if the report with metadata is correctly converted to csv lines.
"""
ts = datetime.now()
report = FormulaReport(ts, 'pytest', 'formula', {'string': 'value', 'int': 1, 'float': 1.0, 'bool': True})
columns, lines = FormulaReport.to_csv_lines(report)

assert columns == ['timestamp', 'sensor', 'target', 'metadata']
assert len(lines['FormulaReport']) == 1
assert lines['FormulaReport'][0]['timestamp'] == int(ts.timestamp() * 1000)
assert lines['FormulaReport'][0]['sensor'] == 'pytest'
assert lines['FormulaReport'][0]['target'] == 'formula'
assert lines['FormulaReport'][0]['metadata'] == '{"string": "value", "int": 1, "float": 1.0, "bool": true}'


def test_formula_report_to_mongodb_with_empty_metadata():
"""
Test if the report without metadata is correctly converted to a mongodb document.
"""
ts = datetime.fromtimestamp(0)
report = FormulaReport(ts, 'pytest', 'formula', {})
document = FormulaReport.to_mongodb(report)

assert document['timestamp'] == ts # datetime format is supported
assert document['sensor'] == 'pytest'
assert document['target'] == 'formula'
assert document['metadata'] == {}


def test_formula_report_to_mongodb_with_metadata():
"""
Test if the report with metadata is correctly converted to a mongodb document.
"""
ts = datetime.now()
report = FormulaReport(ts, 'pytest', 'formula', {'string': 'value', 'int': 1, 'float': 1.0, 'bool': True})
document = FormulaReport.to_mongodb(report)

assert document['timestamp'] == ts
assert document['sensor'] == 'pytest'
assert document['target'] == 'formula'
assert document['metadata'] == {'string': 'value', 'int': 1, 'float': 1.0, 'bool': True}


def test_formula_report_to_influxdb_with_empty_metadata():
"""
Test if the report without metadata is correctly converted to an influxdb document.
"""
ts = datetime.fromtimestamp(0)
report = FormulaReport(ts, 'pytest', 'formula', {})
document = FormulaReport.to_influxdb(report)

assert document['measurement'] == 'formula_report'
assert document['tags'] == {'sensor': 'pytest', 'target': 'formula'}
assert document['time'] == int(ts.timestamp() * 1000) # timestamp in milliseconds
assert document['fields'] == {}


def test_formula_report_to_influxdb_with_metadata():
"""
Test if the report with metadata is correctly converted to an influxdb document.
"""
ts = datetime.now()
report = FormulaReport(ts, 'pytest', 'formula', {'string': 'value', 'int': 1, 'float': 1.0, 'bool': True})
document = FormulaReport.to_influxdb(report)

assert document['measurement'] == 'formula_report'
assert document['tags'] == {'sensor': 'pytest', 'target': 'formula'}
assert document['time'] == int(ts.timestamp() * 1000)
assert document['fields'] == {'string': 'value', 'int': 1, 'float': 1.0, 'bool': True}

0 comments on commit b6841bb

Please sign in to comment.