Repository: DataDog/dd-agent

Issue:
[go_expvar] add ability to override default metric namespace
By default, the go_expvar check uses the `go_expvar` namespace. However, I'd much rather have these metrics available under the same namespace as my application metrics. To accomplish this, I'm thinking it would just be a `namespace` key within `init_config` that allows you to override the default value.

Looking at the `go_expvar` package it seems fairly simple, however I thought I'd open an issue before trying to submit a patch.



Discussion:

Resolving patch:
diff --git a/checks.d/go_expvar.py b/checks.d/go_expvar.py
--- a/checks.d/go_expvar.py
+++ b/checks.d/go_expvar.py
@@ -24,7 +24,7 @@
     RATE: AgentCheck.rate,
 }
 
-METRIC_NAMESPACE = "go_expvar"
+DEFAULT_METRIC_NAMESPACE = "go_expvar"
 
 
 # See http://golang.org/pkg/runtime/#MemStats
@@ -71,9 +71,10 @@ def _load(self, instance):
         data = self._get_data(url)
         metrics = DEFAULT_METRICS + instance.get("metrics", [])
         max_metrics = instance.get("max_returned_metrics", DEFAULT_MAX_METRICS)
-        return data, tags, metrics, max_metrics, url
+        namespace = instance.get('namespace', DEFAULT_METRIC_NAMESPACE)
+        return data, tags, metrics, max_metrics, url, namespace
 
-    def get_gc_collection_histogram(self, data, tags, url):
+    def get_gc_collection_histogram(self, data, tags, url, namespace):
         num_gc = data.get("memstats", {}).get("NumGC")
         pause_hist = data.get("memstats", {}).get("PauseNs")
         last_gc_count = self._last_gc_count[url]
@@ -91,15 +92,15 @@ def get_gc_collection_histogram(self, data, tags, url):
 
         for value in values:
             self.histogram(
-                self.normalize("memstats.PauseNs", METRIC_NAMESPACE, fix_case=True),
+                self.normalize("memstats.PauseNs", namespace, fix_case=True),
                 value, tags=tags)
 
     def check(self, instance):
-        data, tags, metrics, max_metrics, url = self._load(instance)
-        self.get_gc_collection_histogram(data, tags, url)
-        self.parse_expvar_data(data, tags, metrics, max_metrics)
+        data, tags, metrics, max_metrics, url, namespace = self._load(instance)
+        self.get_gc_collection_histogram(data, tags, url, namespace)
+        self.parse_expvar_data(data, tags, metrics, max_metrics, namespace)
 
-    def parse_expvar_data(self, data, tags, metrics, max_metrics):
+    def parse_expvar_data(self, data, tags, metrics, max_metrics, namespace):
         '''
         Report all the metrics based on the configuration in instance
         If a metric is not well configured or is not present in the payload,
@@ -135,7 +136,7 @@ def parse_expvar_data(self, data, tags, metrics, max_metrics):
                 if tag_by_path:
                     metric_tags.append("path:%s" % actual_path)
 
-                metric_name = alias or self.normalize(actual_path, METRIC_NAMESPACE, fix_case=True)
+                metric_name = alias or self.normalize(actual_path, namespace, fix_case=True)
 
                 try:
                     float(value)


Test patch:
diff --git a/ci/resources/go_expvar/test_expvar.go b/ci/resources/go_expvar/test_expvar.go
--- a/ci/resources/go_expvar/test_expvar.go
+++ b/ci/resources/go_expvar/test_expvar.go
@@ -17,6 +17,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"runtime"
 )
 
 // Two metrics, these are exposed by "magic" :)
@@ -38,6 +39,13 @@ func HelloServer(w http.ResponseWriter, req *http.Request) {
 }
 
 func main() {
+	// In some situations, the CI tests for the go_expvar check would fail due
+	// to the Golang runtime not haivng run GC yet. The reason this is needed
+	// is that get_gc_collection_histogram() function in go_expvar.py
+	// short-circuits if there have been no GCs. This causes the pause_ns
+	// metric to not be present, thus causing tests to fail. So trigger GC.
+	runtime.GC()
+
 	http.HandleFunc("/", HelloServer)
 	http.ListenAndServe(":8079", nil)
 }
diff --git a/tests/checks/mock/test_go_expvar.py b/tests/checks/mock/test_go_expvar.py
--- a/tests/checks/mock/test_go_expvar.py
+++ b/tests/checks/mock/test_go_expvar.py
@@ -20,40 +20,40 @@ class TestGoExpVar(AgentCheckTest):
     CHECK_NAME = 'go_expvar'
 
     CHECK_GAUGES = [
-        'go_expvar.memstats.alloc',
-        'go_expvar.memstats.heap_alloc',
-        'go_expvar.memstats.heap_idle',
-        'go_expvar.memstats.heap_inuse',
-        'go_expvar.memstats.heap_objects',
-        'go_expvar.memstats.heap_released',
-        'go_expvar.memstats.heap_sys',
-        'go_expvar.memstats.total_alloc',
+        'memstats.alloc',
+        'memstats.heap_alloc',
+        'memstats.heap_idle',
+        'memstats.heap_inuse',
+        'memstats.heap_objects',
+        'memstats.heap_released',
+        'memstats.heap_sys',
+        'memstats.total_alloc',
     ]
 
     CHECK_GAUGES_DEFAULT = [
-        'go_expvar.memstats.pause_ns.95percentile',
-        'go_expvar.memstats.pause_ns.avg',
-        'go_expvar.memstats.pause_ns.count',
-        'go_expvar.memstats.pause_ns.max',
-        'go_expvar.memstats.pause_ns.median',
+        'memstats.pause_ns.95percentile',
+        'memstats.pause_ns.avg',
+        'memstats.pause_ns.count',
+        'memstats.pause_ns.max',
+        'memstats.pause_ns.median',
     ]
 
     CHECK_GAUGES_CUSTOM_MOCK = {
-        'go_expvar.gauge1': ['metric_tag1:metric_value1',
-                             'metric_tag2:metric_value2',
-                             'path:random_walk'],
-        'go_expvar.memstats.by_size.1.mallocs': []
+        'gauge1': ['metric_tag1:metric_value1',
+                   'metric_tag2:metric_value2',
+                   'path:random_walk'],
+        'memstats.by_size.1.mallocs': []
     }
 
     CHECK_RATES = [
-        'go_expvar.memstats.frees',
-        'go_expvar.memstats.lookups',
-        'go_expvar.memstats.mallocs',
-        'go_expvar.memstats.num_gc',
-        'go_expvar.memstats.pause_total_ns',
+        'memstats.frees',
+        'memstats.lookups',
+        'memstats.mallocs',
+        'memstats.num_gc',
+        'memstats.pause_total_ns',
     ]
 
-    CHECK_RATES_CUSTOM_MOCK = ['go_expvar.gc.pause']
+    CHECK_RATES_CUSTOM_MOCK = ['gc.pause']
 
     def __init__(self, *args, **kwargs):
         AgentCheckTest.__init__(self, *args, **kwargs)
@@ -123,14 +123,59 @@ def test_go_expvar_mocked(self):
         shared_tags = ['optionaltag1', 'optionaltag2', 'expvar_url:{0}'.format(self._expvar_url)]
 
         for gauge in self.CHECK_GAUGES + self.CHECK_GAUGES_DEFAULT:
-            self.assertMetric(gauge, count=1, tags=shared_tags)
+            self.assertMetric("{0}.{1}".format(self.CHECK_NAME, gauge), count=1, tags=shared_tags)
         for gauge, tags in self.CHECK_GAUGES_CUSTOM_MOCK.iteritems():
-            self.assertMetric(gauge, count=1, tags=shared_tags + tags)
+            self.assertMetric("{0}.{1}".format(self.CHECK_NAME, gauge), count=1, tags=shared_tags + tags)
 
         for rate in self.CHECK_RATES:
-            self.assertMetric(rate, count=1, tags=shared_tags)
+            self.assertMetric("{0}.{1}".format(self.CHECK_NAME, rate), count=1, tags=shared_tags)
         for rate in self.CHECK_RATES_CUSTOM_MOCK:
-            self.assertMetric(rate, count=1, tags=shared_tags + ['path:memstats.PauseTotalNs'])
+            self.assertMetric("{0}.{1}".format(self.CHECK_NAME, rate), count=1, tags=shared_tags + ['path:memstats.PauseTotalNs'])
+
+        self.coverage_report()
+
+    def test_go_expvar_mocked_namespace(self):
+        metric_namespace = "testingapp"
+
+        # adjust mock config to set a namespace value
+        self.mock_config = {
+            "instances": [{
+                "namespace": metric_namespace,
+                "expvar_url": self._expvar_url,
+                "tags": ["optionaltag1", "optionaltag2"],
+                "metrics": [
+                    {
+                        # Contains list traversal and default values
+                        "path": "memstats/BySize/1/Mallocs",
+                    },
+                    {
+                        "path": "memstats/PauseTotalNs",
+                        "alias": "{0}.gc.pause".format(metric_namespace),
+                        "type": "rate"
+                    },
+                    {
+                        "path": "random_walk",
+                        "alias": "{0}.gauge1".format(metric_namespace),
+                        "type": "gauge",
+                        "tags": ["metric_tag1:metric_value1", "metric_tag2:metric_value2"]
+                    }
+                ]
+            }]
+        }
+
+        self._run_check_twice()
+
+        shared_tags = ['optionaltag1', 'optionaltag2', 'expvar_url:{0}'.format(self._expvar_url)]
+
+        for gauge in self.CHECK_GAUGES + self.CHECK_GAUGES_DEFAULT:
+            self.assertMetric("{0}.{1}".format(metric_namespace, gauge), count=1, tags=shared_tags)
+        for gauge, tags in self.CHECK_GAUGES_CUSTOM_MOCK.iteritems():
+            self.assertMetric("{0}.{1}".format(metric_namespace, gauge), count=1, tags=shared_tags + tags)
+
+        for rate in self.CHECK_RATES:
+            self.assertMetric("{0}.{1}".format(metric_namespace, rate), count=1, tags=shared_tags)
+        for rate in self.CHECK_RATES_CUSTOM_MOCK:
+            self.assertMetric("{0}.{1}".format(metric_namespace, rate), count=1, tags=shared_tags + ['path:memstats.PauseTotalNs'])
 
         self.coverage_report()
 
@@ -143,7 +188,7 @@ def test_max_metrics(self):
 
         # Default metrics
         for gauge in self.CHECK_GAUGES_DEFAULT:
-            self.assertMetric(gauge, count=1, tags=shared_tags)
+            self.assertMetric("{0}.{1}".format(self.CHECK_NAME, gauge), count=1, tags=shared_tags)
         # And then check limitation, will fail at the coverage_report if incorrect
         self.assertMetric('go_expvar.memstats.alloc', count=1, tags=shared_tags)