diff --git a/src/common/perf_eval_method_wrapper/Decorator.py b/src/common/perf_eval_method_wrapper/Decorator.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f5c9b952a487e707d8fdc037a36864dba425725
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/Decorator.py
@@ -0,0 +1,72 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import threading
+from enum import Enum
+from typing import Dict, Tuple
+from prometheus_client import Counter, Histogram
+from prometheus_client.metrics import MetricWrapperBase, INF
+
+class MetricTypeEnum(Enum):
+    COUNTER_STARTED    = '{:s}_counter_requests_started'
+    COUNTER_COMPLETED  = '{:s}_counter_requests_completed'
+    COUNTER_FAILED     = '{:s}_counter_requests_failed'
+    HISTOGRAM_DURATION = '{:s}_histogram_duration'
+
+METRIC_TO_CLASS_PARAMS = {
+    MetricTypeEnum.COUNTER_STARTED   : (Counter,   {}),
+    MetricTypeEnum.COUNTER_COMPLETED : (Counter,   {}),
+    MetricTypeEnum.COUNTER_FAILED    : (Counter,   {}),
+    MetricTypeEnum.HISTOGRAM_DURATION: (Histogram, {
+        'buckets': (.005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, INF)
+    })
+}
+
+class MetricsPool:
+    def __init__(self) -> None:
+        self._metrics : Dict[str, MetricWrapperBase] = dict()
+        self._lock = threading.Lock()
+
+    def get_or_create(self, function_name : str, metric_type : MetricTypeEnum, **metric_params) -> MetricWrapperBase:
+        metric_name = str(metric_type.value).format(function_name).upper()
+        with self._lock:
+            if metric_name not in self._metrics:
+                metric_tuple : Tuple[MetricWrapperBase, Dict] = METRIC_TO_CLASS_PARAMS.get(metric_type)
+                metric_class, default_metric_params = metric_tuple
+                if len(metric_params) == 0: metric_params = default_metric_params
+                self._metrics[metric_name] = metric_class(metric_name, '', **metric_params)
+            return self._metrics[metric_name]
+
+def meter_method(metrics_pool : MetricsPool):
+    def outer_wrapper(func):
+        func_name = func.__name__
+        histogram_duration : Histogram = metrics_pool.get_or_create(func_name, MetricTypeEnum.HISTOGRAM_DURATION)
+        counter_started    : Counter   = metrics_pool.get_or_create(func_name, MetricTypeEnum.COUNTER_STARTED)
+        counter_completed  : Counter   = metrics_pool.get_or_create(func_name, MetricTypeEnum.COUNTER_COMPLETED)
+        counter_failed     : Counter   = metrics_pool.get_or_create(func_name, MetricTypeEnum.COUNTER_FAILED)
+
+        @histogram_duration.time()
+        def inner_wrapper(self, *args, **kwargs):
+            counter_started.inc()
+            try:
+                reply = func(self, *args, **kwargs)
+                counter_completed.inc()
+                return reply
+            except KeyboardInterrupt:   # pylint: disable=try-except-raise
+                raise
+            except Exception:           # pylint: disable=broad-except
+                counter_failed.inc()
+
+        return inner_wrapper
+    return outer_wrapper
diff --git a/src/common/perf_eval_method_wrapper/__init__.py b/src/common/perf_eval_method_wrapper/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..70a33251242c51f49140e596b8208a19dd5245f7
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/src/common/perf_eval_method_wrapper/tests/DummyDeviceDriver.py b/src/common/perf_eval_method_wrapper/tests/DummyDeviceDriver.py
new file mode 100644
index 0000000000000000000000000000000000000000..b44c830d98d3860135bde53c7d0ca5ec5d8811ec
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/tests/DummyDeviceDriver.py
@@ -0,0 +1,39 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import random, time
+from common.perf_eval_method_wrapper.Decorator import MetricsPool, meter_method
+
+EXCEPTION_RATIO = 0.05
+
+METRICS_POOL = MetricsPool()
+
+class DummyDeviceDriver:
+    def __init__(self) -> None:
+        pass
+
+    @meter_method(METRICS_POOL)
+    def get_config(self):
+        if random.random() < EXCEPTION_RATIO: raise Exception()
+        time.sleep(random.random())
+
+    @meter_method(METRICS_POOL)
+    def set_config(self):
+        if random.random() < EXCEPTION_RATIO: raise Exception()
+        time.sleep(random.random())
+
+    @meter_method(METRICS_POOL)
+    def del_config(self):
+        if random.random() < EXCEPTION_RATIO: raise Exception()
+        time.sleep(random.random())
diff --git a/src/common/perf_eval_method_wrapper/tests/__init__.py b/src/common/perf_eval_method_wrapper/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..70a33251242c51f49140e596b8208a19dd5245f7
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/tests/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/src/common/perf_eval_method_wrapper/tests/docker_grafana.sh b/src/common/perf_eval_method_wrapper/tests/docker_grafana.sh
new file mode 100644
index 0000000000000000000000000000000000000000..2a1484d5504c69f08b23d652879f4c6bace44548
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/tests/docker_grafana.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+docker create -it --name=prometheus -p 9090:9090 \
+    -v /home/tfs/tfs-ctrl/test-prom-cli/prometheus.yml:/etc/prometheus/prometheus.yml \
+    prom/prometheus
+
+docker create -it --name=grafana -p 3000:3000 \
+    grafana/grafana
diff --git a/src/common/perf_eval_method_wrapper/tests/prometheus.yml b/src/common/perf_eval_method_wrapper/tests/prometheus.yml
new file mode 100644
index 0000000000000000000000000000000000000000..af2849209ab75eef57d41d0489bf695baa6d5fde
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/tests/prometheus.yml
@@ -0,0 +1,23 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+global:
+    scrape_interval: 15s # when Prometheus is pulling data from exporters etc
+    evaluation_interval: 30s # time between each evaluation of Prometheus' alerting rules
+
+scrape_configs:
+- job_name: ddd   # your project name
+  static_configs:
+    - targets:
+        - 172.17.0.1:8000
diff --git a/src/common/perf_eval_method_wrapper/tests/test_unitary.py b/src/common/perf_eval_method_wrapper/tests/test_unitary.py
new file mode 100644
index 0000000000000000000000000000000000000000..e34f750164956c3f206fe35498a5b0726279e4ac
--- /dev/null
+++ b/src/common/perf_eval_method_wrapper/tests/test_unitary.py
@@ -0,0 +1,62 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import grpc, logging, time
+from common.rpc_method_wrapper.Decorator import create_metrics, safe_and_metered_rpc_method
+
+logging.basicConfig(level=logging.DEBUG)
+LOGGER = logging.getLogger(__name__)
+
+def test_database_instantiation():
+    SERVICE_NAME = 'Context'
+    METHOD_NAMES = [
+        'ListContextIds',  'ListContexts',   'GetContext',  'SetContext',  'RemoveContext',  'GetContextEvents',
+        'ListTopologyIds', 'ListTopologies', 'GetTopology', 'SetTopology', 'RemoveTopology', 'GetTopologyEvents',
+        'ListDeviceIds',   'ListDevices',    'GetDevice',   'SetDevice',   'RemoveDevice',   'GetDeviceEvents',
+        'ListLinkIds',     'ListLinks',      'GetLink',     'SetLink',     'RemoveLink',     'GetLinkEvents',
+        'ListServiceIds',  'ListServices',   'GetService',  'SetService',  'RemoveService',  'GetServiceEvents',
+    ]
+    METRICS = create_metrics(SERVICE_NAME, METHOD_NAMES)
+
+    class TestServiceServicerImpl:
+        @safe_and_metered_rpc_method(METRICS, LOGGER)
+        def GetTopology(self, request, grpc_context : grpc.ServicerContext):
+            print('doing funny things')
+            time.sleep(0.1)
+            return 'done'
+
+    tssi = TestServiceServicerImpl()
+    tssi.GetTopology(1, 2)
+
+    for metric_name,metric in METRICS.items():
+        if 'GETTOPOLOGY_' not in metric_name: continue
+        print(metric_name, metric._child_samples()) # pylint: disable=protected-access
+
+
+
+import random
+from prometheus_client import start_http_server
+from DummyDeviceDriver import DummyDeviceDriver
+
+def main():
+    # Start up the server to expose the metrics
+    start_http_server(8000)
+
+    ddd = DummyDeviceDriver()
+    while True:
+        func = random.choice([ddd.get_config, ddd.set_config, ddd.del_config])
+        func()
+
+if __name__ == '__main__':
+    main()