diff --git a/WORKSPACE b/WORKSPACE
index fbbc290..7bfd85c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -501,3 +501,19 @@ new_git_repository(
commit = "042f812382aa16eada6078594582150fa5dc7235",
remote = "https://github.com/eberlitz/material-steppers.git",
)
+
+# FBN
+new_http_archive(
+ name = "datadog_archive",
+ build_file = "//third_party:datadog.BUILD",
+ sha256 = "86cef95acd73543d18c417f1b0313c0a7274ed8f5ae9cceb46314f4e588085b1",
+ strip_prefix = "datadog-0.22.0",
+ urls = [
+ "https://files.pythonhosted.org/packages/29/45/4f21ad21de22c7abe64f340e6fe1ebc412bb1e8bb580dd963fd70ac86441/datadog-0.22.0.tar.gz",
+ ],
+)
+
+bind(
+ name = "datadog",
+ actual = "@datadog_archive//:datadog",
+)
\ No newline at end of file
diff --git a/third_party/datadog.BUILD b/third_party/datadog.BUILD
new file mode 100644
index 0000000..b4cfdb2
--- /dev/null
+++ b/third_party/datadog.BUILD
@@ -0,0 +1,10 @@
+licenses(["notice"]) # datadog
+
+py_library(
+ name = "datadog",
+ srcs = glob(["**/*.py"]),
+ srcs_version = "PY2AND3",
+ data = glob(["*.egg-info/**"]),
+ visibility = ["//visibility:public"],
+ deps = [],
+)
\ No newline at end of file
diff --git a/upvote/gae/modules/upvote_app/frontend/admin_ui/settingspage/settingspage-controller.js b/upvote/gae/modules/upvote_app/frontend/admin_ui/settingspage/settingspage-controller.js
index 364bc8f..2ba7dd4 100644
--- a/upvote/gae/modules/upvote_app/frontend/admin_ui/settingspage/settingspage-controller.js
+++ b/upvote/gae/modules/upvote_app/frontend/admin_ui/settingspage/settingspage-controller.js
@@ -19,7 +19,7 @@ goog.require('upvote.admin.settings.SettingsService');
goog.require('upvote.shared.Page');
-/** @typedef {{virustotal: string, bit9:string}} */
+/** @typedef {{virustotal: string, bit9:string, datadog:string}} */
upvote.admin.settingspage.ApiKeyStruct;
/**
@@ -77,6 +77,7 @@ upvote.admin.settingspage.SettingsController = class {
this.apiKeys = {
'virustotal': '',
'bit9': '',
+ 'datadog': '',
};
page.title = 'Settings';
diff --git a/upvote/gae/shared/common/BUILD b/upvote/gae/shared/common/BUILD
index 6440bd2..9bfa668 100644
--- a/upvote/gae/shared/common/BUILD
+++ b/upvote/gae/shared/common/BUILD
@@ -81,7 +81,11 @@ py_appengine_library(
py_appengine_library(
name = "monitoring",
srcs = ["monitoring.py"],
- deps = ["//upvote/shared:constants"],
+ deps = [
+ "//upvote/shared:constants",
+ "//upvote/gae/datastore/models:datadog",
+ "//external:datadog"
+ ],
)
py_appengine_library(
diff --git a/upvote/gae/shared/common/monitoring.py b/upvote/gae/shared/common/monitoring.py
index 530c011..7119fa1 100644
--- a/upvote/gae/shared/common/monitoring.py
+++ b/upvote/gae/shared/common/monitoring.py
@@ -17,6 +17,40 @@
import functools
import logging
+from upvote.gae.datastore.models import datadog as datadog_model
+
+import datadog
+
+# datadog metric name rules: https://help.datadoghq.com/hc/en-us/articles/203764705-What-are-valid-metric-names-
+
+_dd_stats = None
+
+
+def _dd_get_stats():
+ global _dd_stats
+
+ if not _dd_stats:
+ dd_api_key = datadog_model.DataDogApiAuth.GetInstance()
+ if not dd_api_key:
+ return None
+
+ datadog.initialize(dd_api_key)
+
+ _dd_stats = datadog.ThreadStats()
+ _dd_stats.start()
+
+ return _dd_stats
+
+
+def _dd_get_format(metric, fields):
+ stat_format = metric.metric_name
+ if not fields:
+ return stat_format
+
+ for (field_name, field_type) in fields:
+ stat_format += u"." + field_name + u"%s"
+ return stat_format
+
def ContainExceptions(func):
@@ -38,11 +72,13 @@ def __init__(self, metric, value_type, fields=None):
self.metric_name = metric.metric_name
self.type_ = value_type
self.fields = fields
+ self._stat_format = _dd_get_format(metric, fields)
@ContainExceptions
def Set(self, value, *args):
- # Implement setting a metric
- pass
+ stats = _dd_get_stats()
+ if stats:
+ stats.gauge(self._stat_format % args, value)
class LatencyMetric(object):
@@ -52,11 +88,13 @@ def __init__(self, metric, fields=None):
self.display_name = metric.display_name
self.metric_name = metric.metric_name
self.fields = fields
+ self._stat_format = _dd_get_format(metric, fields)
@ContainExceptions
def Record(self, value, *args):
- # Implement recording a latency value
- pass
+ stats = _dd_get_stats()
+ if stats:
+ stats.gauge(self._stat_format % args, value)
class Counter(object):
@@ -66,16 +104,17 @@ def __init__(self, metric, fields=None):
self.display_name = metric.display_name
self.metric_name = metric.metric_name
self.fields = fields
+ self._stat_format = _dd_get_format(metric, fields)
@ContainExceptions
def Increment(self, *args):
- # Implement incrementing a metric
- pass
+ stats = _dd_get_stats()
+ if stats:
+ stats.increment(self._stat_format % args)
@ContainExceptions
def IncrementBy(self, inc, *args):
- # Implement incrementing a metric by N
- pass
+ _dd_get_stats().increment(self._stat_format % args, inc)
class RequestCounter(Counter):