Repository: DataDog/dd-agent

Issue:
Old-style check counters aren't getting cleared
If you sample a counter at `ts1` and `ts2`, the counter will continue to report for every check loop as the same value at `ts2`.

The correct behavior is we should clear all values except the last for a counter on each flush. Therefore if no values come in during the next check run, no value will be flushed. But if a new value _does_ come in, the rate will still be calculated against the last one.



Discussion:

Resolving patch:
diff --git a/checks/__init__.py b/checks/__init__.py
--- a/checks/__init__.py
+++ b/checks/__init__.py
@@ -195,7 +195,7 @@ def _rate(cls, sample1, sample2):
         except Exception, e:
             raise NaN(e)
 
-    def get_sample_with_timestamp(self, metric, tags=None, device_name=None):
+    def get_sample_with_timestamp(self, metric, tags=None, device_name=None, expire=True):
         "Get (timestamp-epoch-style, value)"
 
         # Get the proper tags
@@ -210,10 +210,13 @@ def get_sample_with_timestamp(self, metric, tags=None, device_name=None):
 
         # Not enough value to compute rate
         elif self.is_counter(metric) and len(self._sample_store[metric][key]) < 2:
-            raise UnknownValue()
+           raise UnknownValue()
 
         elif self.is_counter(metric) and len(self._sample_store[metric][key]) >= 2:
-            return self._rate(self._sample_store[metric][key][-2], self._sample_store[metric][key][-1])
+            res = self._rate(self._sample_store[metric][key][-2], self._sample_store[metric][key][-1])
+            if expire:
+                del self._sample_store[metric][key][:-1]
+            return res
 
         elif self.is_gauge(metric) and len(self._sample_store[metric][key]) >= 1:
             return self._sample_store[metric][key][-1]
@@ -221,34 +224,34 @@ def get_sample_with_timestamp(self, metric, tags=None, device_name=None):
         else:
             raise UnknownValue()
 
-    def get_sample(self, metric, tags=None, device_name=None):
+    def get_sample(self, metric, tags=None, device_name=None, expire=True):
         "Return the last value for that metric"
-        x = self.get_sample_with_timestamp(metric, tags, device_name)
+        x = self.get_sample_with_timestamp(metric, tags, device_name, expire)
         assert type(x) == types.TupleType and len(x) == 4, x
         return x[1]
 
-    def get_samples_with_timestamps(self):
+    def get_samples_with_timestamps(self, expire=True):
         "Return all values {metric: (ts, value)} for non-tagged metrics"
         values = {}
         for m in self._sample_store:
             try:
-                values[m] = self.get_sample_with_timestamp(m)
+                values[m] = self.get_sample_with_timestamp(m, expire=expire)
             except:
                 pass
         return values
 
-    def get_samples(self):
+    def get_samples(self, expire=True):
         "Return all values {metric: value} for non-tagged metrics"
         values = {}
         for m in self._sample_store:
             try:
                 # Discard the timestamp
-                values[m] = self.get_sample_with_timestamp(m)[1]
+                values[m] = self.get_sample_with_timestamp(m, expire=expire)[1]
             except:
                 pass
         return values
 
-    def get_metrics(self):
+    def get_metrics(self, expire=True):
         """Get all metrics, including the ones that are tagged.
         This is the preferred method to retrieve metrics
 
@@ -261,7 +264,7 @@ def get_metrics(self):
                 for key in self._sample_store[m]:
                     tags, device_name = key
                     try:
-                        ts, val, hostname, device_name = self.get_sample_with_timestamp(m, tags, device_name)
+                        ts, val, hostname, device_name = self.get_sample_with_timestamp(m, tags, device_name, expire)
                     except UnknownValue:
                         continue
                     attributes = {}


Test patch:
diff --git a/tests/test_common.py b/tests/test_common.py
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -40,11 +40,11 @@ def testEdgeCases(self):
 
     def test_counter(self):
         self.c.save_sample("test-counter", 1.0, 1.0)
-        self.assertRaises(UnknownValue, self.c.get_sample, "test-counter")
+        self.assertRaises(UnknownValue, self.c.get_sample, "test-counter", expire=False)
         self.c.save_sample("test-counter", 2.0, 2.0)
-        self.assertEquals(self.c.get_sample("test-counter"), 1.0)
-        self.assertEquals(self.c.get_sample_with_timestamp("test-counter"), (2.0, 1.0, None, None))
-        self.assertEquals(self.c.get_samples(), {"test-counter": 1.0})
+        self.assertEquals(self.c.get_sample("test-counter", expire=False), 1.0)
+        self.assertEquals(self.c.get_sample_with_timestamp("test-counter", expire=False), (2.0, 1.0, None, None))
+        self.assertEquals(self.c.get_samples(expire=False), {"test-counter": 1.0})
         self.c.save_sample("test-counter", -2.0, 3.0)
         self.assertRaises(UnknownValue, self.c.get_sample_with_timestamp, "test-counter")
 
@@ -76,10 +76,10 @@ def test_samples(self):
         self.c.save_sample("test-metric", 1.0, 0.0)  # value, ts
         self.c.save_sample("test-counter", 1.0, 1.0) # value, ts
         self.c.save_sample("test-counter", 4.0, 2.0) # value, ts
-        assert "test-metric"  in self.c.get_samples_with_timestamps(), self.c.get_samples_with_timestamps()
-        self.assertEquals(self.c.get_samples_with_timestamps()["test-metric"], (0.0, 1.0, None, None))
-        assert "test-counter" in self.c.get_samples_with_timestamps(), self.c.get_samples_with_timestamps()
-        self.assertEquals(self.c.get_samples_with_timestamps()["test-counter"], (2.0, 3.0, None, None))
+        assert "test-metric"  in self.c.get_samples_with_timestamps(expire=False), self.c.get_samples_with_timestamps(expire=False)
+        self.assertEquals(self.c.get_samples_with_timestamps(expire=False)["test-metric"], (0.0, 1.0, None, None))
+        assert "test-counter" in self.c.get_samples_with_timestamps(expire=False), self.c.get_samples_with_timestamps(expire=False)
+        self.assertEquals(self.c.get_samples_with_timestamps(expire=False)["test-counter"], (2.0, 3.0, None, None))
 
     def test_name(self):
         self.assertEquals(self.c.normalize("metric"), "metric")