Repository: powerapi-ng/powerapi

Issue:
Better Formula API without actor
Currently, formula couldn't be used without a `PusherActor` and managing some internal `State`

Create some methods that encapsulate formula Handlers and State/Pusher management.

For example a method `compute` that take a report and return the power estimation computed from the given report.

Discussion:

Patch:
diff --git a/powerapi/__init__.py b/powerapi/__init__.py
index 69983dd..a99b28c 100644
--- a/powerapi/__init__.py
+++ b/powerapi/__init__.py
@@ -29,4 +29,4 @@ 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.
 """
 
-__version__ = "0.1.1"
+__version__ = "0.1.2"
diff --git a/powerapi/database/mongodb.py b/powerapi/database/mongodb.py
index bf029a4..5b6b224 100644
--- a/powerapi/database/mongodb.py
+++ b/powerapi/database/mongodb.py
@@ -133,9 +133,6 @@ class MongoDB(BaseDB):
         except StopIteration:
             raise StopIteration()
 
-        # Re arrange the json before return it by removing '_id' field
-        json.pop('_id', None)
-
         return self.report_model.from_mongodb(json)
 
     def save(self, json):
diff --git a/powerapi/formula/__init__.py b/powerapi/formula/__init__.py
index 4e306eb..1fe268a 100644
--- a/powerapi/formula/__init__.py
+++ b/powerapi/formula/__init__.py
@@ -30,4 +30,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
 from powerapi.formula.formula_actor import FormulaActor, FormulaState
+from powerapi.formula.dummy.dummy_model import DummyModel
 from powerapi.formula.dummy.dummy_formula_actor import DummyFormulaActor
diff --git a/powerapi/formula/dummy/dummy_formula_actor.py b/powerapi/formula/dummy/dummy_formula_actor.py
index a1462d6..06b6a64 100644
--- a/powerapi/formula/dummy/dummy_formula_actor.py
+++ b/powerapi/formula/dummy/dummy_formula_actor.py
@@ -30,53 +30,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
 import logging
-import time
-from powerapi.formula.formula_actor import FormulaActor
-from powerapi.message import UnknowMessageTypeException, PoisonPillMessage
-from powerapi.report import Report, PowerReport
-from powerapi.handler import Handler, PoisonPillMessageHandler
+import re
+from functools import reduce
 
-
-class DummyHWPCReportHandler(Handler):
-    """
-    A fake handler that simulate data processing
-    """
-
-    def __init__(self, actor_pusher):
-        self.actor_pusher = actor_pusher
-
-    def _process_report(self, report):
-        """
-        Wait 1 second and return a power report containing 42
-
-        :param powerapi.Report report: Received report
-
-        :return: A power report containing consumption estimation
-        :rtype:  powerapi.PowerReport
-        """
-
-        result_msg = PowerReport(report.timestamp, report.sensor,
-                                 report.target, {}, 42)
-        return result_msg
-
-    def handle(self, msg, state):
-        """
-        Process a report and send the result to the pusher actor
-
-        :param powerapi.Report msg:       Received message
-        :param powerapi.State state: Actor state
-
-        :return: New Actor state
-        :rtype:  powerapi.State
-
-        :raises UnknowMessageTypeException: If the msg is not a Report
-        """
-        if not isinstance(msg, Report):
-            raise UnknowMessageTypeException(type(msg))
-
-        result = self._process_report(msg)
-        self.actor_pusher.send_data(result)
-        return state
+from powerapi.formula import FormulaActor, FormulaState, DummyModel
+from powerapi.message import PoisonPillMessage
+from powerapi.report import Report
+from powerapi.handler import PoisonPillMessageHandler, FormulaHandler
+from powerapi.actor import Actor, SocketInterface
 
 
 class DummyFormulaActor(FormulaActor):
@@ -84,14 +45,23 @@ class DummyFormulaActor(FormulaActor):
     A fake Formula that simulate data processing by waiting 1s and send a
     power report containing 42
     """
-
-    def __init__(self, name, actor_pusher, level_logger=logging.WARNING,
-                 timeout=None):
+    def __init__(self, name, pusher_actors,
+                 level_logger=logging.WARNING, timeout=None):
         """
-        :param str name:                            Actor name
-        :param powerapi.PusherActor actor_pusher: Pusher to send results.
+        :param str name: Actor name
+        :param powerapi.PusherActor pusher_actors: Pusher actors whom send results
+        :param int level_logger: Define logger level
+        :param bool timeout: Time in millisecond to wait for a message before called timeout_handler
         """
-        FormulaActor.__init__(self, name, actor_pusher, level_logger, timeout)
+        FormulaActor.__init__(self, name, pusher_actors, level_logger, timeout)
+
+        formula_id = reduce(lambda acc, x: acc + (re.search(r'^\(? ?\'(.*)\'\)?', x).group(1),), name.split(','), ())
+
+        #: (powerapi.State): Basic state of the Formula.
+        self.state = FormulaState(Actor._initial_behaviour,
+                                  SocketInterface(name, timeout),
+                                  self.logger, formula_id, pusher_actors,
+                                  DummyModel())
 
     def setup(self):
         """
@@ -99,5 +69,4 @@ class DummyFormulaActor(FormulaActor):
         """
         FormulaActor.setup(self)
         self.add_handler(PoisonPillMessage, PoisonPillMessageHandler())
-        handler = DummyHWPCReportHandler(self.actor_pusher)
-        self.add_handler(Report, handler)
+        self.add_handler(Report, FormulaHandler())
diff --git a/powerapi/formula/dummy/dummy_model.py b/powerapi/formula/dummy/dummy_model.py
new file mode 100644
index 0000000..34175d0
--- /dev/null
+++ b/powerapi/formula/dummy/dummy_model.py
@@ -0,0 +1,49 @@
+"""
+Copyright (c) 2018, INRIA
+Copyright (c) 2018, 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.report import PowerReport
+from powerapi.model import Model
+
+
+class DummyModel(Model):
+    """
+    DummyModel for testing
+    """
+
+    def estimate(self, report):
+        """
+        Method that estimate the power consumption from an input report
+        :param report: Input Report
+        :return: List of PowerReport
+        """
+        result_msg = PowerReport(report.timestamp, report.sensor,
+                                 report.target, {}, 42)
+        return [result_msg]
diff --git a/powerapi/formula/formula_actor.py b/powerapi/formula/formula_actor.py
index e0bea04..45b0c3c 100644
--- a/powerapi/formula/formula_actor.py
+++ b/powerapi/formula/formula_actor.py
@@ -30,16 +30,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
 import logging
-import re
-from functools import reduce
 
 from powerapi.actor import Actor, State, SocketInterface
 
 
 class FormulaState(State):
-    def __init__(self, behaviour, socket_interface, logger, formula_id):
+    """
+    Formula Actor State
+    """
+
+    def __init__(self, behaviour, socket_interface, logger, formula_id, pusher_actors, model):
         State.__init__(self, behaviour, socket_interface, logger)
         self.formula_id = formula_id
+        self.pusher_actors = pusher_actors
+        self.model = model
 
 
 class FormulaActor(Actor):
@@ -51,11 +55,11 @@ class FormulaActor(Actor):
     result to a Pusher.
     """
 
-    def __init__(self, name, actor_pusher,
+    def __init__(self, name, pusher_actors,
                  level_logger=logging.WARNING, timeout=None):
         """
         :param str name:                            Actor name
-        :param powerapi.PusherActor actor_pusher: Pusher actor whom send
+        :param powerapi.PusherActor pusher_actors:  Pusher actors whom send
                                                     results
         :param int level_logger:                    Define logger level
         :param bool timeout:                        Time in millisecond to wait
@@ -64,24 +68,22 @@ class FormulaActor(Actor):
         """
         Actor.__init__(self, name, level_logger, timeout)
 
-        #: (powerapi.PusherActor): Pusher actor whom send results.
-        self.actor_pusher = actor_pusher
-
-        formula_id = reduce(lambda acc, x: acc + (re.search(r'^\(? ?\'(.*)\'\)?', x).group(1),), name.split(','), ())
-
         #: (powerapi.State): Basic state of the Formula.
         self.state = FormulaState(Actor._initial_behaviour,
                                   SocketInterface(name, timeout),
-                                  self.logger, formula_id)
+                                  self.logger, None, pusher_actors,
+                                  None)
 
     def setup(self):
         """
         Formula basic setup, Connect the formula to the pusher
         """
-        self.actor_pusher.connect_data()
+        for actor_pusher in self.state.pusher_actors:
+            actor_pusher.connect_data()
 
     def terminated_behaviour(self):
         """
         Allow to close actor_pusher socket
         """
-        self.actor_pusher.state.socket_interface.close()
+        for actor_pusher in self.state.pusher_actors:
+            actor_pusher.state.socket_interface.close()
diff --git a/powerapi/handler/__init__.py b/powerapi/handler/__init__.py
index ffb4e7d..3e9dc98 100644
--- a/powerapi/handler/__init__.py
+++ b/powerapi/handler/__init__.py
@@ -32,3 +32,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 from powerapi.handler.handler import Handler, InitHandler, HandlerException
 from powerapi.handler.poison_pill_message_handler import PoisonPillMessageHandler
 from powerapi.handler.start_handler import StartHandler
+from powerapi.handler.formula_handler import FormulaHandler
\ No newline at end of file
diff --git a/powerapi/handler/formula_handler.py b/powerapi/handler/formula_handler.py
new file mode 100644
index 0000000..6462fb6
--- /dev/null
+++ b/powerapi/handler/formula_handler.py
@@ -0,0 +1,63 @@
+"""
+Copyright (c) 2018, INRIA
+Copyright (c) 2018, 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 logging
+from powerapi.handler import Handler
+from powerapi.message import UnknowMessageTypeException
+from powerapi.report import Report
+
+
+class FormulaHandler(Handler):
+    """
+    Basic handler behaviour for a kind of Report
+    """
+
+    def handle(self, msg, state):
+        """
+        Process a report and send the result to the pusher actor
+
+        :param powerapi.Report msg:  Received message
+        :param powerapi.State state: Actor state
+
+        :return: New Actor state
+        :rtype:  powerapi.State
+
+        :raises UnknowMessageTypeException: If the msg is not a Report
+        """
+        if not isinstance(msg, Report):
+            raise UnknowMessageTypeException(type(msg))
+
+        results = state.model.estimate(msg)
+        for actor_pusher in state.pusher_actors:
+            for result in results:
+                actor_pusher.send_data(result)
+        return state
+
diff --git a/powerapi/model/__init__.py b/powerapi/model/__init__.py
new file mode 100644
index 0000000..ecb7079
--- /dev/null
+++ b/powerapi/model/__init__.py
@@ -0,0 +1,32 @@
+"""
+Copyright (c) 2018, INRIA
+Copyright (c) 2018, 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.model.model import Model
diff --git a/powerapi/model/model.py b/powerapi/model/model.py
new file mode 100644
index 0000000..9de81ef
--- /dev/null
+++ b/powerapi/model/model.py
@@ -0,0 +1,44 @@
+"""
+Copyright (c) 2018, INRIA
+Copyright (c) 2018, 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.
+"""
+
+
+class Model:
+    """
+    Model abstract class that allow to estimate a power consumption
+    """
+
+    def estimate(self, report):
+        """
+        Method that estimate the power consumption from an input report
+        :param report: Input Report
+        :return: List of PowerReport
+        """
+        raise NotImplementedError()
diff --git a/powerapi/puller/handlers.py b/powerapi/puller/handlers.py
index 86d53ca..5e624ff 100644
--- a/powerapi/puller/handlers.py
+++ b/powerapi/puller/handlers.py
@@ -33,7 +33,8 @@ import logging
 from powerapi.handler import InitHandler, StartHandler
 from powerapi.database import DBError
 from powerapi.message import ErrorMessage, OKMessage, StartMessage
-from powerapi.message import PoisonPillMessage
+from powerapi.report.report import DeserializationFail
+from powerapi.report_model.report_model import BadInputData
 
 
 class NoReportExtractedException(Exception):
@@ -95,12 +96,11 @@ class TimeoutHandler(InitHandler):
         # report in the database, just pass
         try:
             data = next(state.database_it)
-        except StopIteration:
+            # Deserialization
+            report = state.database.report_model.get_type().deserialize(data)
+        except (StopIteration, BadInputData, DeserializationFail):
             raise NoReportExtractedException()
 
-        # Deserialization
-        report = state.database.report_model.get_type().deserialize(data)
-
         # Filter the report
         dispatchers = state.report_filter.route(report)
         return (report, dispatchers)
diff --git a/powerapi/report/__init__.py b/powerapi/report/__init__.py
index f23164c..8db5a72 100644
--- a/powerapi/report/__init__.py
+++ b/powerapi/report/__init__.py
@@ -29,6 +29,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.report.report import Report
+from powerapi.report.report import Report, DeserializationFail
 from powerapi.report.power_report import PowerReport
 from powerapi.report.hwpc_report import *
diff --git a/powerapi/report/hwpc_report.py b/powerapi/report/hwpc_report.py
index a98285b..c861116 100644
--- a/powerapi/report/hwpc_report.py
+++ b/powerapi/report/hwpc_report.py
@@ -34,7 +34,7 @@ from __future__ import annotations
 from datetime import datetime
 from typing import Dict
 
-from powerapi.report.report import Report
+from powerapi.report.report import Report, DeserializationFail
 
 
 class HWPCReport(Report):
@@ -88,7 +88,12 @@ class HWPCReport(Report):
         :param data: Dictionary containing the report attributes
         :return: The HWPC report initialized with the given data
         """
-        return HWPCReport(data['timestamp'], data['sensor'], data['target'], data['groups'])
+        try:
+            report = HWPCReport(data['timestamp'], data['sensor'], data['target'], data['groups'])
+        except KeyError:
+            raise DeserializationFail()
+
+        return report
 
 
 #############################
diff --git a/powerapi/report/report.py b/powerapi/report/report.py
index b6f64a1..92e7947 100644
--- a/powerapi/report/report.py
+++ b/powerapi/report/report.py
@@ -36,6 +36,13 @@ from typing import Dict
 from powerapi.message import Message
 
 
+class DeserializationFail(Exception):
+    """
+    Exception raised when the
+    in the good format
+    """
+
+
 class Report(Message):
     """
     Report abtract class.
diff --git a/powerapi/report_model/__init__.py b/powerapi/report_model/__init__.py
index af55326..d74e06a 100644
--- a/powerapi/report_model/__init__.py
+++ b/powerapi/report_model/__init__.py
@@ -29,6 +29,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.report_model.report_model import ReportModel
+from powerapi.report_model.report_model import ReportModel, BadInputData
 from powerapi.report_model.report_model import KEYS_COMMON, KEYS_CSV_COMMON
 from powerapi.report_model.hwpc_model import HWPCModel
diff --git a/powerapi/report_model/hwpc_model.py b/powerapi/report_model/hwpc_model.py
index a6f9257..95f1ff2 100644
--- a/powerapi/report_model/hwpc_model.py
+++ b/powerapi/report_model/hwpc_model.py
@@ -29,7 +29,7 @@ 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.report_model import ReportModel, KEYS_CSV_COMMON, KEYS_COMMON
+from powerapi.report_model import ReportModel, KEYS_CSV_COMMON, KEYS_COMMON, BadInputData
 from powerapi.report import HWPCReport
 
 
@@ -51,31 +51,40 @@ class HWPCModel(ReportModel):
         """
         Get HWPCReport from a MongoDB database.
         """
+        # Re arrange the json before return it by removing '_id' field
+        json.pop('_id', None)
+
         return json
 
     def from_csvdb(self, file_name, row):
         """
         Get HWPCReport from a few csv files.
         """
-        final_dict = {key: row[key] for key in KEYS_COMMON}
-        final_dict['groups'] = {}
-
-        # If group doesn't exist, create it
-        if file_name not in final_dict:
-            final_dict['groups'][file_name] = {}
-
-        # If socket doesn't exist, create it
-        if row['socket'] not in final_dict['groups'][file_name]:
-            final_dict['groups'][file_name][row['socket']] = {}
-
-        # If cpu doesn't exist, create it
-        if row['cpu'] not in final_dict['groups'][file_name][row['socket']]:
-            final_dict['groups'][file_name][row['socket']][row['cpu']] = {}
-
-        # Add events
-        for key, value in row.items():
-            if key not in KEYS_CSV_COMMON:
-                final_dict['groups'][file_name][
-                    row['socket']][row['cpu']][key] = int(value)
+        final_dict = {}
+
+        try:
+            final_dict = {key: row[key] for key in KEYS_COMMON}
+            final_dict['groups'] = {}
+
+            # If group doesn't exist, create it
+            if file_name not in final_dict:
+                final_dict['groups'][file_name] = {}
+
+            # If socket doesn't exist, create it
+            if row['socket'] not in final_dict['groups'][file_name]:
+                final_dict['groups'][file_name][row['socket']] = {}
+
+            # If cpu doesn't exist, create it
+            if row['cpu'] not in final_dict['groups'][file_name][row['socket']]:
+                final_dict['groups'][file_name][row['socket']][row['cpu']] = {}
+
+            # Add events
+            for key, value in row.items():
+                if key not in KEYS_CSV_COMMON:
+                    final_dict['groups'][file_name][
+                        row['socket']][row['cpu']][key] = int(value)
+
+        except KeyError:
+            raise BadInputData()
 
         return final_dict
diff --git a/powerapi/report_model/report_model.py b/powerapi/report_model/report_model.py
index 28bb32c..7613fd8 100644
--- a/powerapi/report_model/report_model.py
+++ b/powerapi/report_model/report_model.py
@@ -33,12 +33,19 @@ KEYS_COMMON = ['timestamp', 'sensor', 'target']
 KEYS_CSV_COMMON = KEYS_COMMON + ['socket', 'cpu']
 
 
+class BadInputData(Exception):
+    """
+    Exception raised when the data read in input are not
+    in the good format
+    """
+
+
 class ReportModel:
     """
     ReportModel class.
 
     It define all the function that need to be override if we want
-    to get a report from every kind of db.
+    to format the raw data read in different kind of database.
     """
 
     def get_type(self):


Test patch:
diff --git a/tests/acceptation/test_simple_architecture.py b/tests/acceptation/test_simple_architecture.py
index 5845896..615b5cb 100644
--- a/tests/acceptation/test_simple_architecture.py
+++ b/tests/acceptation/test_simple_architecture.py
@@ -50,7 +50,6 @@ Test if:
     socket in the output database
 """
 import logging
-import math
 import pytest
 import pymongo
 
@@ -68,7 +67,6 @@ from powerapi.dispatcher import DispatcherActor, RouteTable
 
 from tests.mongo_utils import gen_base_db_test
 from tests.mongo_utils import clean_base_db_test
-from tests.utils import is_actor_alive
 
 DB_URI = "mongodb://localhost:27017/"
 LOG_LEVEL = logging.NOTSET
@@ -110,7 +108,7 @@ def test_run(database, supervisor):
 
     # Formula
     formula_factory = (lambda name, verbose:
-                       DummyFormulaActor(name, pusher, level_logger=verbose))
+                       DummyFormulaActor(name, [pusher], level_logger=verbose))
 
     # Dispatcher
     route_table = RouteTable()
diff --git a/tests/unit/report/test_hwpc_report.py b/tests/unit/report/test_hwpc_report.py
new file mode 100644
index 0000000..b686862
--- /dev/null
+++ b/tests/unit/report/test_hwpc_report.py
@@ -0,0 +1,83 @@
+"""
+Copyright (c) 2018, INRIA
+Copyright (c) 2018, 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
+import datetime
+from powerapi.report.report import DeserializationFail
+from powerapi.report.hwpc_report import HWPCReport
+
+
+##############################################################################
+#                                Tests                                       #
+##############################################################################
+
+
+@pytest.mark.parametrize("json_input", [
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target",
+      "groups": {}})
+]
+)
+def test_hwpc_report_deserialize_work(json_input):
+    """
+    Test working input for HWPCReport deserialize
+    :param json_input: Data in input
+    """
+    HWPCReport.deserialize(json_input)
+    assert True
+
+
+@pytest.mark.parametrize("json_input", [
+    ({"sensor": "fake-sensor",
+      "target": "fake-target",
+      "groups": {}}),
+    ({"timestamp": datetime.datetime.now(),
+      "target": "fake-target",
+      "groups": {}}),
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "groups": {}}),
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target"})
+]
+)
+def test_hwpc_report_deserialize_fail(json_input):
+    """
+    Test failure input for HWPCReport deserialize
+    :param json_input: Data in input
+    """
+    try:
+        HWPCReport.deserialize(json_input)
+        assert False
+    except DeserializationFail:
+        assert True
diff --git a/tests/unit/report_model/test_hwpc_report_model.py b/tests/unit/report_model/test_hwpc_report_model.py
new file mode 100644
index 0000000..0af54ed
--- /dev/null
+++ b/tests/unit/report_model/test_hwpc_report_model.py
@@ -0,0 +1,137 @@
+"""
+Copyright (c) 2018, INRIA
+Copyright (c) 2018, 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
+import datetime
+from powerapi.report_model.hwpc_model import HWPCModel, BadInputData
+
+##############################################################################
+#                                Fixtures                                    #
+##############################################################################
+
+
+@pytest.fixture()
+def hwpc_model():
+    """
+    :return: HWPCModel
+    """
+    return HWPCModel()
+
+
+##############################################################################
+#                                Tests                                       #
+##############################################################################
+
+####################
+# MongoDB
+####################
+
+
+@pytest.mark.parametrize("json_input", [
+    ({"_id": "fake-id",
+      "timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target",
+      "groups": {}})
+]
+)
+def test_hwpc_report_from_mongodb_work(hwpc_model, json_input):
+    """
+    Test working input for HWPCReport from_mongodb
+    :param json_input: Data in input
+    """
+    try:
+        hwpc_model.from_mongodb(json_input)
+        assert True
+    except BadInputData:
+        assert False
+
+
+####################
+# CsvDB
+####################
+
+
+@pytest.mark.parametrize("json_input", [
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target",
+      "socket": 0,
+      "cpu": 0,
+      "event1": 1000,
+      "event2": 2000}),
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target",
+      "socket": 0,
+      "cpu": 0}),
+]
+)
+def test_hwpc_report_from_csvdb_work(hwpc_model, json_input):
+    """
+    :param json_input: Data in input
+    """
+    try:
+        hwpc_model.from_mongodb(json_input)
+        assert True
+    except BadInputData:
+        assert False
+
+
+@pytest.mark.parametrize("json_input", [
+    ({"sensor": "fake-sensor",
+      "target": "fake-target"}),
+    ({"timestamp": datetime.datetime.now(),
+      "target": "fake-target"}),
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor"}),
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target"}),
+    ({"timestamp": datetime.datetime.now(),
+      "sensor": "fake-sensor",
+      "target": "fake-target",
+      "socket": 0}),
+     ({"timestamp": datetime.datetime.now(),
+       "sensor": "fake-sensor",
+       "target": "fake-target",
+       "cpu": 0}),
+]
+)
+def test_hwpc_report_from_csvdb_fail(hwpc_model, json_input):
+    """
+    :param json_input: Data in input
+    """
+    try:
+        hwpc_model.from_csvdb("fake-name", json_input)
+        assert False
+    except BadInputData:
+        assert True
\ No newline at end of file