diff --git a/MANIFEST.in b/MANIFEST.in
index 7b741275f0d1fedbd2b153e542ffe0df369d9a7a..cfabbb68818ec823c5b63874a7c751fc79008540 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,4 @@
 include LICENSE
 include README.md
 include requirements.txt
+include CHANGELOG.md
diff --git a/docs/instrumentation/metrics.md b/docs/instrumentation/metrics.md
index d35dcb9374aff165c94e1c6f4781a6b261487d09..2118fccb1ae436c998aaa561793023eb3ebf7534 100644
--- a/docs/instrumentation/metrics.md
+++ b/docs/instrumentation/metrics.md
@@ -1,12 +1,56 @@
 Metrics and instruments are a system to output real-time statistics.
 
-_An instrument_ is a collection of metrics. It has a name (hierarchical, dot-separated),
-that does not have to correspond to particular modules or classes. It can be in one of 3 states:
+Metrics are defined and meant to be used in a similar way
+to Python logging.
+It has a name (hierarchical, dot-separated),
+that does not have to correspond to particular
+modules or classes. It can be in one of 3 states:
 
-* Disabled
-* Runtime
-* Debug
+* DISABLED
+* RUNTIME
+* DEBUG
+* INHERIT
 
-By default, it runs in _runtime_ mode. This means that statistics are collected only from metrics of this
-instrument that are set to at least RUNTIME. If a user wants to dig deeper, it can switch the instrument to 
-DEBUG, which will cause more data to be registered.
+By default, it runs in _runtime_ mode. This means that statistics
+are collected only from metrics of this
+instrument that are set to at least RUNTIME. If a user wants to
+dig deeper, it can switch the instrument to 
+DEBUG, which will cause more data to be registered. If a metric 
+is in state INHERIT, it will inherit the metric level from it's
+parent, traversing the tree if required.
+
+You can switch the metric anytime by calling it's `switch_level`
+method.
+
+You obtain metrics using `getMetric()` as follows:
+
+```python
+metric = getMetric(__name__+'.StringMetric', 'string', RUNTIME, **kwargs)
+```
+
+Where the second argument is a metric type. Following metric types
+are available:
+
+* base - for just a container metric
+* string - for string values
+* int - for int values
+* float - for float values
+* cps - will count given amount of calls to handle() during last
+  time period, as specified by user
+
+Third parameter is optional. If set, all child metrics created 
+during this metric's instantiation will receive such metric level.
+If the metric already exists, it's level will be set to provided
+metric level, if passed.
+
+All child metrics (going from the root metric to 0) will be initialized
+with the value that you just passed. In order to keep them in order,
+an additional parameter passed to `getMetric()`, `metric_level`, if
+specified, will set given level upon returning the even existing
+metric.
+
+If you don't specify it, the metric level for root metric will be
+set to RUNTIME. Same if you specify INHERIT.
+
+If you specify any kwargs, they will be delivered to the last
+metric's in chain constructor.
diff --git a/satella/__init__.py b/satella/__init__.py
index 880e99426d25ac19a7602832e9000e3c641d2c39..1617bb15a5b029f31a46f28daa375faf990a1ef4 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1,3 +1,3 @@
 # coding=UTF-8
-__version__ = '2.1.10a2'
+__version__ = '2.1.10a5'
 
diff --git a/satella/instrumentation/metrics/__init__.py b/satella/instrumentation/metrics/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b8fa09e67667d72b903960eae71d64bc88523cc
--- /dev/null
+++ b/satella/instrumentation/metrics/__init__.py
@@ -0,0 +1,55 @@
+import abc
+import logging
+import typing as tp
+import itertools
+import threading
+logger = logging.getLogger(__name__)
+from .metric_types.base import RUNTIME, DISABLED, DEBUG, INHERIT, Metric
+from .metric_types import METRIC_NAMES_TO_CLASSES
+
+__all__ = ['getMetric', 'DISABLED', 'RUNTIME', 'DEBUG', 'INHERIT']
+
+
+metrics = {}
+metrics_lock = threading.Lock()
+
+
+def getMetric(metric_name: str, metric_type: str = 'base', metric_level: tp.Optional[str] = None, **kwargs):
+    """
+    Obtain a metric of given name.
+    :param metric_name: must be a module name
+    """
+    metric_level_to_set_for_children = metric_level or INHERIT
+    name = metric_name.split('.')
+    with metrics_lock:
+        root_metric = None
+        for name_index, name_part in itertools.chain(enumerate(name), ((len(name), None), )):
+            tentative_name = '.'.join(name[:name_index])
+            if tentative_name not in metrics:
+                if tentative_name == '':
+                    # initialize the root metric
+                    if metric_level is None:
+                        metric_level_to_set_for_root = RUNTIME
+                    elif metric_level_to_set_for_children == INHERIT:
+                        metric_level_to_set_for_root = RUNTIME
+                    else:
+                        metric_level_to_set_for_root = metric_level_to_set_for_children
+                    print(metric_level_to_set_for_root)
+                    metric = Metric('', None, metric_level_to_set_for_root)
+                    metric.level = RUNTIME
+                    root_metric = metric
+                elif metric_name == tentative_name:
+                    metric = METRIC_NAMES_TO_CLASSES[metric_type](tentative_name, root_metric, metric_level, **kwargs)
+                else:
+                    metric = Metric(tentative_name, root_metric, metric_level_to_set_for_children)
+                metrics[tentative_name] = metric
+                if metric != root_metric:  # prevent infinite recursion errors
+                    root_metric.append_child(metric)
+            else:
+                metric = metrics[tentative_name]
+            root_metric = metric
+
+        if metric_level is not None:
+            metric.switch_level(metric_level)
+
+        return metric
diff --git a/satella/instrumentation/metrics/metric_types/__init__.py b/satella/instrumentation/metrics/metric_types/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f5510b6ef352904ff6b66bd5244211552c815a3
--- /dev/null
+++ b/satella/instrumentation/metrics/metric_types/__init__.py
@@ -0,0 +1,16 @@
+import typing as tp
+from .base import Metric
+from .simple import StringMetric, IntegerMetric, FloatMetric
+from .cps import ClicksPerTimeUnitMetric
+
+ALL_METRICS = [
+    Metric,
+    StringMetric,
+    IntegerMetric,
+    FloatMetric,
+    ClicksPerTimeUnitMetric
+]
+
+METRIC_NAMES_TO_CLASSES = {
+    metric.CLASS_NAME: metric for metric in ALL_METRICS
+}
\ No newline at end of file
diff --git a/satella/instrumentation/metrics/metric_types/base.py b/satella/instrumentation/metrics/metric_types/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..2191f2e94d29f64765dcffa2e48e0c5d18dbc6e6
--- /dev/null
+++ b/satella/instrumentation/metrics/metric_types/base.py
@@ -0,0 +1,66 @@
+import typing as tp
+import abc
+from satella.json import JSONAble
+
+DISABLED = 1
+RUNTIME = 2
+DEBUG = 3
+INHERIT = 4
+
+
+class Metric(JSONAble):
+    """
+    A base metric class
+    """
+    CLASS_NAME = 'base'
+
+    def reset(self) -> None:
+        """Delete all child metrics that this metric contains"""
+        from satella.instrumentation import metrics
+        if self.name == '':
+            metrics.metrics = {}
+        else:
+            metrics.metrics = {k: v for k, v in metrics.metrics.items() if not k.startswith(self.name+'.')}
+            del metrics.metrics[self.name]
+        self.children = []
+
+    def __init__(self, name, root_metric: 'Metric' = None, metric_level: str = None, **kwargs):
+        """When reimplementing the method, remember to pass kwargs here!"""
+        self.name = name
+        self.root_metric = root_metric
+        self.level = metric_level or RUNTIME
+        assert not (self.name == '' and self.level == INHERIT), 'Unable to set INHERIT for root metric!'
+        self.children = []
+
+    def __str__(self) -> str:
+        return self.name
+
+    def append_child(self, metric: 'Metric'):
+        self.children.append(metric)
+
+    def can_process_this_level(self, target_level: int) -> bool:
+        metric = self
+        while metric.level == INHERIT:
+            # this is bound to terminate, since it is not possible to set metric_level of INHERIT on root
+            metric = metric.root_metric
+        return metric.level >= target_level
+
+    def switch_level(self, level: int) -> None:
+        assert not (self.name == '' and level == INHERIT), 'Unable to set INHERIT for root metric!'
+        self.level = level
+
+    def to_json(self) -> tp.Union[list, dict, str, int, float, None]:
+        return {
+            child.name[len(self.name)+1 if len(self.name) > 0 else 0:]: child.to_json() for child in self.children
+        }
+
+    def handle(self, level: int, *args, **kwargs) -> None:
+        """Override me!"""
+        raise TypeError('A collection of metrics is not meant to get .handle() called!')
+
+    def debug(self, *args, **kwargs):
+        self.handle(DEBUG, *args, **kwargs)
+
+    def runtime(self, *args, **kwargs):
+        self.handle(RUNTIME, *args, **kwargs)
+
diff --git a/satella/instrumentation/metrics/metric_types/cps.py b/satella/instrumentation/metrics/metric_types/cps.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbe1827991657f662c804af9049666321e07168b
--- /dev/null
+++ b/satella/instrumentation/metrics/metric_types/cps.py
@@ -0,0 +1,41 @@
+import typing as tp
+from .base import Metric
+import time
+import collections
+
+
+class ClicksPerTimeUnitMetric(Metric):
+    CLASS_NAME = 'cps'
+
+    def __init__(self, *args, time_unit_vectors: tp.Optional[tp.List[float]] = None, **kwargs):
+        """
+        :param time_unit_vectors: time units (in seconds) to count the clicks in between.
+            Default - track a single value, amount of calls to .handle() in last second
+        """
+        super().__init__(*args, **kwargs)
+        time_unit_vectors = time_unit_vectors or [1]
+        self.last_clicks = collections.deque()
+        self.cutoff_period = max(time_unit_vectors)
+        self.time_unit_vectors = time_unit_vectors
+
+    def handle(self, level: int, *args, **kwargs) -> None:
+        monotime = time.monotonic()
+        if self.can_process_this_level(level):
+            self.last_clicks.append(time.monotonic())
+            try:
+                while self.last_clicks[0] <= monotime - self.cutoff_period:
+                    self.last_clicks.popleft()
+            except IndexError:
+                pass
+
+    def to_json(self) -> tp.List[int]:
+        count_map = [0] * len(self.time_unit_vectors)
+        monotime = time.monotonic()
+        time_unit_vectors = [monotime-v for v in self.time_unit_vectors]
+
+        for v in self.last_clicks:
+            for index, cutoff in enumerate(time_unit_vectors):
+                if v >= cutoff:
+                    count_map[index] += 1
+
+        return count_map
diff --git a/satella/instrumentation/metrics/metric_types/simple.py b/satella/instrumentation/metrics/metric_types/simple.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a738cadf9f6fe713a89a84ebf4d03bdaddafffa
--- /dev/null
+++ b/satella/instrumentation/metrics/metric_types/simple.py
@@ -0,0 +1,36 @@
+import typing as tp
+from .base import Metric
+
+
+class SimpleMetric(Metric):
+
+    CLASS_NAME = 'string'
+    CONSTRUCTOR = str
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.data = None
+
+    def append_child(self, metric: 'Metric'):
+        raise TypeError('This metric cannot contain children!')
+
+    def handle(self, level, data):
+        if self.can_process_this_level(level):
+            self.data = self.CONSTRUCTOR(data)
+
+    def to_json(self) -> tp.Union[list, dict, str, int, float, None]:
+        return self.data
+
+
+class StringMetric(SimpleMetric):
+    pass
+
+
+class IntegerMetric(SimpleMetric):
+    CLASS_NAME = 'int'
+    CONSTRUCTOR = int
+
+
+class FloatMetric(SimpleMetric):
+    CLASS_NAME = 'float'
+    CONSTRUCTOR = float
diff --git a/satella/instrumentation/metrics/record.py b/satella/instrumentation/metrics/record.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff28131f49717023e63966f8f454841f4d3d02da
--- /dev/null
+++ b/satella/instrumentation/metrics/record.py
@@ -0,0 +1,17 @@
+import typing as tp
+import abc
+from .metric_types.base import RUNTIME, INHERIT
+
+
+class BaseRecord(metaclass=abc.ABCMeta):
+    def __init__(self, level: str = RUNTIME):
+        self.level = level
+
+    def can_be_handled_by(self, metric: 'Metric'):
+        run_level = metric.level
+        while run_level == INHERIT:
+            metric = metric.root_metric
+            run_level = metric.level
+        else:
+            return run_level >= self.level
+        raise ValueError('Invalid metric setup - root metric is in inherit mode!')
diff --git a/setup.cfg b/setup.cfg
index 61afc77149c822262c48bf56cc5cbbd8a516e575..dc321ce6b880d3d1c316e6c65d2d00144602e513 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,11 +1,16 @@
+# coding: utf-8
 [metadata]
 name = satella
-description-file = README.md
+description-file = A set of useful routines and libraries for writing Python long-running services
+long-description = file: README.md
+long-description-content-type = text/markdown; charset=UTF-8
+license_files = LICENSE
 author = Piotr Maślanka
 author_email = piotrm@smok.co
 description = Utilities for writing servers in Python
 url = https://github.com/piotrmaslanka/satella
-
+project-urls =
+    Documentation = https://satella.readthedocs.io/
 classifier = 
     Programming Language :: Python
     Programming Language :: Python :: 3.5
diff --git a/tests/test_instrumentation/test_metrics.py b/tests/test_instrumentation/test_metrics.py
new file mode 100644
index 0000000000000000000000000000000000000000..f62011f5e8a51408c182c1dc642a6c440f379ba4
--- /dev/null
+++ b/tests/test_instrumentation/test_metrics.py
@@ -0,0 +1,86 @@
+import unittest
+import time
+import typing as tp
+from satella.instrumentation.metrics import getMetric, DEBUG, RUNTIME, INHERIT
+
+
+class TestMetric(unittest.TestCase):
+
+    def tearDown(self):
+        getMetric('').reset()
+
+    def test_base_metric(self):
+        metric = getMetric('root.test.StringValue', 'string')
+        metric.runtime('data')
+
+        metric2 = getMetric('root.test.FloatValue', 'float')
+        metric2.runtime(2.0)
+
+        metric3 = getMetric('root.test.IntValue', 'int')
+        metric3.runtime(3)
+
+        root_metric = getMetric('')
+
+        self.assertEquals(root_metric.to_json(), {
+            'root': {
+                'test': {
+                    'StringValue': 'data',
+                    'FloatValue': 2.0,
+                    'IntValue': 3
+                }
+            }
+        })
+
+    def test_base_metric(self):
+        metric2 = getMetric('root.test.FloatValue', 'float', DEBUG)
+        metric2.runtime(2.0)
+        metric2.debug(1.0)
+
+        metric3 = getMetric('root.test.IntValue', 'int')
+        metric3.runtime(3)
+        metric3.debug(2)
+
+        root_metric = getMetric('')
+
+        self.assertEquals(root_metric.to_json(), {
+            'root': {
+                'test': {
+                    'FloatValue': 1.0,
+                    'IntValue': 3
+                }
+            }
+        })
+
+    def testInheritance(self):
+        metric = getMetric('root.test.FloatValue', 'float', INHERIT)
+        metric.runtime(2.0)
+        metric_parent = getMetric('root.test')
+
+        self.assertEquals(getMetric('').to_json(), {
+            'root': {
+                'test': {
+                    'FloatValue': 2.0,
+                }
+            }
+        })
+
+        metric_parent.switch_level(RUNTIME)
+        metric.debug(3.0)
+
+        self.assertEquals(getMetric('').to_json(), {
+            'root': {
+                'test': {
+                    'FloatValue': 2.0,
+                }
+            }
+        })
+
+    def test_cps(self):
+        metric = getMetric('root.CPSValue', 'cps', time_unit_vectors=[1, 2])
+        metric.runtime()
+        self.assertEquals(metric.to_json(), [1, 1])
+        metric.runtime()
+        self.assertEquals(metric.to_json(), [2, 2])
+        time.sleep(1.2)
+        self.assertEquals(metric.to_json(), [0, 2])
+