summary refs log tree commit diff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--synapse/metrics/__init__.py11
-rw-r--r--synapse/metrics/metric.py43
-rw-r--r--tests/metrics/test_metric.py27
3 files changed, 73 insertions, 8 deletions
diff --git a/synapse/metrics/__init__.py b/synapse/metrics/__init__.py
index 125845eb30..d5c30bbe41 100644
--- a/synapse/metrics/__init__.py
+++ b/synapse/metrics/__init__.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from .metric import CounterMetric
+from .metric import CounterMetric, CacheCounterMetric
 
 
 # We'll keep all the available metrics in a single toplevel dict, one shared
@@ -43,6 +43,15 @@ class Metrics(object):
 
         return metric
 
+    def register_cachecounter(self, name, *args, **kwargs):
+        full_name = "%s.%s" % (self.name_prefix, name)
+
+        metric = CacheCounterMetric(full_name, *args, **kwargs)
+
+        self._register(metric)
+
+        return metric
+
     def counted(self, func):
         """ A method decorator that registers a counter, to count invocations
         of this method. """
diff --git a/synapse/metrics/metric.py b/synapse/metrics/metric.py
index f5a98763cc..00b149f6f6 100644
--- a/synapse/metrics/metric.py
+++ b/synapse/metrics/metric.py
@@ -14,16 +14,28 @@
 # limitations under the License.
 
 
-class CounterMetric(object):
+class BaseMetric(object):
 
     def __init__(self, name, keys=[]):
         self.name = name
         self.keys = keys # OK not to clone as we never write it
 
+    def _render_key(self, values):
+        # TODO: some kind of value escape
+        return ",".join(["%s=%s" % kv for kv in zip(self.keys, values)])
+
+
+class CounterMetric(BaseMetric):
+    """The simplest kind of metric; one that stores a monotonically-increasing
+    integer that counts events."""
+
+    def __init__(self, *args, **kwargs):
+        super(CounterMetric, self).__init__(*args, **kwargs)
+
         self.counts = {}
 
         # Scalar metrics are never empty
-        if not len(keys):
+        if not len(self.keys):
             self.counts[()] = 0
 
     def inc(self, *values):
@@ -42,13 +54,32 @@ class CounterMetric(object):
     def fetch(self):
         return dict(self.counts)
 
-    def _render_key(self, values):
-        # TODO: some kind of value escape
-        return ",".join(["%s=%s" % kv for kv in zip(self.keys, values)])
-
     def render(self):
         if not len(self.keys):
             return ["%s %d" % (self.name, self.counts[()])]
 
         return ["%s{%s} %d" % (self.name, self._render_key(k), self.counts[k])
                 for k in sorted(self.counts.keys())]
+
+
+class CacheCounterMetric(object):
+    """A combination of two CounterMetrics, one to count cache hits and one to
+    count misses.
+
+    This metric generates standard metric name pairs, so that monitoring rules
+    can easily be applied to measure hit ratio."""
+
+    def __init__(self, name, keys=[]):
+        self.name = name
+
+        self.hits   = CounterMetric(name + ":hits",   keys=keys)
+        self.misses = CounterMetric(name + ":misses", keys=keys)
+
+    def inc_hits(self, *values):
+        self.hits.inc(*values)
+
+    def inc_misses(self, *values):
+        self.misses.inc(*values)
+
+    def render(self):
+        return self.hits.render() + self.misses.render()
diff --git a/tests/metrics/test_metric.py b/tests/metrics/test_metric.py
index a4fd52a9d5..93e8e27e4f 100644
--- a/tests/metrics/test_metric.py
+++ b/tests/metrics/test_metric.py
@@ -15,7 +15,7 @@
 
 from tests import unittest
 
-from synapse.metrics.metric import CounterMetric
+from synapse.metrics.metric import CounterMetric, CacheCounterMetric
 
 
 class CounterMetricTestCase(unittest.TestCase):
@@ -59,3 +59,28 @@ class CounterMetricTestCase(unittest.TestCase):
             "vector{method=GET} 2",
             "vector{method=PUT} 1",
         ])
+
+
+class CacheCounterMetricTestCase(unittest.TestCase):
+
+    def test_cachecounter(self):
+        counter = CacheCounterMetric("cache")
+
+        self.assertEquals(counter.render(), [
+            "cache:hits 0",
+            "cache:misses 0",
+        ])
+
+        counter.inc_misses()
+
+        self.assertEquals(counter.render(), [
+            "cache:hits 0",
+            "cache:misses 1",
+        ])
+
+        counter.inc_hits()
+
+        self.assertEquals(counter.render(), [
+            "cache:hits 1",
+            "cache:misses 1",
+        ])