Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add metric object type #837

Merged
merged 9 commits into from
Jul 8, 2024
Merged
77 changes: 77 additions & 0 deletions datadog/dogstatsd/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from threading import Lock


class MetricAggregator(object):
def __init__(self, name, tags, rate):
self.name = name
self.tags = tags
self.rate = rate

def aggregate(self, value):
raise NotImplementedError("Subclasses should implement this method.")

def flush_unsafe(self):
raise NotImplementedError("Subclasses should implement this method.")


class CountMetric(MetricAggregator):
def __init__(self, name, value, tags, rate):
super(CountMetric, self).__init__(name, tags, rate)
self.value = value

def aggregate(self, v):
self.value += v

def flush_unsafe(self):
return {
"metric_type": "count",
"name": self.name,
"tags": self.tags,
"rate": self.rate,
"ivalue": self.value,
}


class GaugeMetric(MetricAggregator):
def __init__(self, name, value, tags, rate):
super(GaugeMetric, self).__init__(name, tags, rate)
self.value = value

def aggregate(self, v):
self.value = v

def flush_unsafe(self):
return {
gh123man marked this conversation as resolved.
Show resolved Hide resolved
"metric_type": "gauge",
"name": self.name,
"tags": self.tags,
"rate": self.rate,
"fvalue": self.value,
}


class SetMetric(MetricAggregator):
def __init__(self, name, value, tags, rate):
super(SetMetric, self).__init__(name, tags, rate)
self.data = set()
self.data.add(value)
self.lock = Lock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly - maybe we should wait to add synchronization primitives until we actually have multithreaded code running. We can keep this PR focused to just introducing the metric classes and their associated properties.

Copy link
Contributor Author

@andrewqian2001datadog andrewqian2001datadog Jun 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the serialization function needs values like namespace and container_id which is set in the base.py class. I think it would be more convenient to leave it as a function in base.py or move it to aggregator.py? Not sure.

This is what aggregator.py looks like so far

Maybe we can move the serialization function there since it should have access to the DogStatsd client (it does in the existing go code) and its properties? It seems more intuitive that aggregator.py would handle serialization of a metric.


def aggregate(self, v):
with self.lock:
self.data.add(v)

def flush_unsafe(self):
with self.lock:
if not self.data:
return []
return [
{
"metric_type": "set",
"name": self.name,
"tags": self.tags,
"rate": self.rate,
"svalue": value,
}
for value in self.data
]
117 changes: 117 additions & 0 deletions tests/unit/dogstatsd/test_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import unittest

from datadog.dogstatsd.metrics import CountMetric, GaugeMetric, SetMetric

class TestMetrics(unittest.TestCase):

def test_new_count_metric(self):
c = CountMetric("test", 21, ["tag1", "tag2"], 1)
self.assertEqual(c.value, 21)
self.assertEqual(c.name, "test")
self.assertEqual(c.tags, ["tag1", "tag2"])
self.assertEqual(c.rate, 1.0)

def test_count_metric_aggregate(self):
c = CountMetric("test", 10, ["tag1", "tag2"], 1)
c.aggregate(20)
self.assertEqual(c.value, 30)
self.assertEqual(c.name, "test")
self.assertEqual(c.tags, ["tag1", "tag2"])
self.assertEqual(c.rate, 1.0)

def test_flush_unsafe_count_metric(self):
c = CountMetric("test", 10, ["tag1", "tag2"], 1)
m = c.flush_unsafe()
self.assertEqual(m['metric_type'], 'count')
self.assertEqual(m['ivalue'], 10)
self.assertEqual(m['name'], "test")
self.assertEqual(m['tags'], ["tag1", "tag2"])
self.assertEqual(m['rate'], 1)

c.aggregate(20)
m = c.flush_unsafe()
self.assertEqual(m['metric_type'], 'count')
self.assertEqual(m['ivalue'], 30)
self.assertEqual(m['name'], "test")
self.assertEqual(m['tags'], ["tag1", "tag2"])
self.assertEqual(m['rate'], 1.0)

def test_new_gauge_metric(self):
g = GaugeMetric("test", 10, ["tag1", "tag2"], 1)
self.assertEqual(g.value, 10)
self.assertEqual(g.name, "test")
self.assertEqual(g.tags, ["tag1", "tag2"])
self.assertEqual(g.rate, 1)

def test_gauge_metric_aggregate(self):
g = GaugeMetric("test", 10, ["tag1", "tag2"], 1)
g.aggregate(20)
self.assertEqual(g.value, 20)
self.assertEqual(g.name, "test")
self.assertEqual(g.tags, ["tag1", "tag2"])
self.assertEqual(g.rate, 1.0)

def test_flush_unsafe_gauge_metric(self):
g = GaugeMetric("test", 10, ["tag1", "tag2"], 1)
m = g.flush_unsafe()
self.assertEqual(m['metric_type'], 'gauge')
self.assertEqual(m['fvalue'], 10)
self.assertEqual(m['name'], "test")
self.assertEqual(m['tags'], ["tag1", "tag2"])
self.assertEqual(m['rate'], 1)

g.aggregate(20)
m = g.flush_unsafe()
self.assertEqual(m['metric_type'], 'gauge')
self.assertEqual(m['fvalue'], 20)
self.assertEqual(m['name'], "test")
self.assertEqual(m['tags'], ["tag1", "tag2"])
self.assertEqual(m['rate'], 1)

def test_new_set_metric(self):
s = SetMetric("test", "value1", ["tag1", "tag2"], 1)
self.assertEqual(s.data, {"value1"})
self.assertEqual(s.name, "test")
self.assertEqual(s.tags, ["tag1", "tag2"])
self.assertEqual(s.rate, 1)

def test_set_metric_aggregate(self):
s = SetMetric("test", "value1", ["tag1", "tag2"], 1)
s.aggregate("value2")
s.aggregate("value2")
self.assertEqual(s.data, {"value1", "value2"})
self.assertEqual(s.name, "test")
self.assertEqual(s.tags, ["tag1", "tag2"])
self.assertEqual(s.rate, 1)

def test_flush_unsafe_set_metric(self):
s = SetMetric("test", "value1", ["tag1", "tag2"], 1)
m = s.flush_unsafe()

self.assertEqual(len(m), 1)
self.assertEqual(m[0]['metric_type'], 'set')
self.assertEqual(m[0]['svalue'], "value1")
self.assertEqual(m[0]['name'], "test")
self.assertEqual(m[0]['tags'], ["tag1", "tag2"])
self.assertEqual(m[0]['rate'], 1)

s.aggregate("value1")
s.aggregate("value2")
m = s.flush_unsafe()

m = sorted(m, key=lambda x: x['svalue'])

self.assertEqual(len(m), 2)
self.assertEqual(m[0]['metric_type'], 'set')
self.assertEqual(m[0]['svalue'], "value1")
self.assertEqual(m[0]['name'], "test")
self.assertEqual(m[0]['tags'], ["tag1", "tag2"])
self.assertEqual(m[0]['rate'], 1)
self.assertEqual(m[1]['metric_type'], 'set')
self.assertEqual(m[1]['svalue'], "value2")
self.assertEqual(m[1]['name'], "test")
self.assertEqual(m[1]['tags'], ["tag1", "tag2"])
self.assertEqual(m[1]['rate'], 1)

if __name__ == '__main__':
andrewqian2001datadog marked this conversation as resolved.
Show resolved Hide resolved
unittest.main()
Loading