Initial hack at a TimerMetric; for storing counts + duration accumulators

This commit is contained in:
Paul "LeoNerd" Evans 2015-03-04 19:22:14 +00:00
parent e1a7e3564f
commit 72625f2f4d
2 changed files with 83 additions and 1 deletions

View File

@ -14,6 +14,15 @@
# limitations under the License. # limitations under the License.
from itertools import chain
# TODO(paul): I can't believe Python doesn't have one of these
def map_concat(func, items):
# flatten a list-of-lists
return list(chain.from_iterable(map(func, items)))
class BaseMetric(object): class BaseMetric(object):
def __init__(self, name, keys=[]): def __init__(self, name, keys=[]):
@ -87,6 +96,45 @@ class CallbackMetric(BaseMetric):
return ["%s{%s} %d" % (self.name, self._render_key(k), value[k]) return ["%s{%s} %d" % (self.name, self._render_key(k), value[k])
for k in sorted(value.keys())] for k in sorted(value.keys())]
class TimerMetric(CounterMetric):
"""A combination of an event counter and a time accumulator, which counts
both the number of events and how long each one takes.
TODO(paul): Try to export some heatmap-style stats?
"""
def __init__(self, *args, **kwargs):
super(TimerMetric, self).__init__(*args, **kwargs)
self.times = {}
# Scalar metrics are never empty
if self.is_scalar():
self.times[()] = 0
def inc_time(self, msec, *values):
self.inc(*values)
if values not in self.times:
self.times[values] = msec
else:
self.times[values] += msec
def render(self):
if self.is_scalar():
return ["%s:count %d" % (self.name, self.counts[()]),
"%s:msec %d" % (self.name, self.times[()])]
def render_item(k):
keystr = self._render_key(k)
return ["%s{%s}:count %d" % (self.name, keystr, self.counts[k]),
"%s{%s}:msec %d" % (self.name, keystr, self.times[k])]
return map_concat(render_item, sorted(self.counts.keys()))
class CacheMetric(object): class CacheMetric(object):
"""A combination of two CounterMetrics, one to count cache hits and one to """A combination of two CounterMetrics, one to count cache hits and one to
count misses, and a callback metric to yield the current size. count misses, and a callback metric to yield the current size.

View File

@ -16,7 +16,7 @@
from tests import unittest from tests import unittest
from synapse.metrics.metric import ( from synapse.metrics.metric import (
CounterMetric, CallbackMetric, CacheMetric CounterMetric, CallbackMetric, TimerMetric, CacheMetric
) )
@ -97,6 +97,40 @@ class CallbackMetricTestCase(unittest.TestCase):
]) ])
class TimerMetricTestCase(unittest.TestCase):
def test_scalar(self):
metric = TimerMetric("thing")
self.assertEquals(metric.render(), [
"thing:count 0",
"thing:msec 0",
])
metric.inc_time(500)
self.assertEquals(metric.render(), [
"thing:count 1",
"thing:msec 500",
])
def test_vector(self):
metric = TimerMetric("queries", keys=["verb"])
self.assertEquals(metric.render(), [])
metric.inc_time(300, "SELECT")
metric.inc_time(200, "SELECT")
metric.inc_time(800, "INSERT")
self.assertEquals(metric.render(), [
"queries{verb=INSERT}:count 1",
"queries{verb=INSERT}:msec 800",
"queries{verb=SELECT}:count 2",
"queries{verb=SELECT}:msec 500",
])
class CacheMetricTestCase(unittest.TestCase): class CacheMetricTestCase(unittest.TestCase):
def test_cache(self): def test_cache(self):